Skip to content

Composites

Composites group related resources into reusable factories. See also the core Composite Resources guide.

lambda-api.ts
import { Role, Function, Permission, Code, Environment, Sub, Role_Policy } from "@intentius/chant-lexicon-aws";
import { Composite } from "@intentius/chant";
import { lambdaTrustPolicy, lambdaBasicExecutionArn } from "./defaults";
export interface LambdaApiProps {
name: string | ReturnType<typeof Sub>;
runtime: string;
handler: string;
code: InstanceType<typeof Code> | { ZipFile: string };
timeout?: number;
memorySize?: number;
environment?: InstanceType<typeof Environment> | { Variables: Record<string, unknown> };
policies?: InstanceType<typeof Role_Policy>[];
}
export const LambdaApi = Composite<LambdaApiProps>((props) => {
const role = new Role({
AssumeRolePolicyDocument: lambdaTrustPolicy,
ManagedPolicyArns: [lambdaBasicExecutionArn],
Policies: props.policies,
});
const func = new Function({
FunctionName: props.name,
Runtime: props.runtime,
Handler: props.handler,
Code: props.code,
Role: role.Arn,
Timeout: props.timeout,
MemorySize: props.memorySize,
Environment: props.environment,
});
const permission = new Permission({
FunctionName: func.Arn,
Action: "lambda:InvokeFunction",
Principal: "apigateway.amazonaws.com",
});
return { role, func, permission };
}, "LambdaApi");
export function SecureApi(props: Partial<LambdaApiProps>) {
return LambdaApi({
name: props.name!,
runtime: props.runtime ?? "nodejs20.x",
handler: props.handler ?? "index.handler",
code: props.code!,
timeout: props.timeout ?? 10,
memorySize: props.memorySize ?? 256,
environment: props.environment,
policies: props.policies,
});
}
export function HighMemoryApi(props: Partial<LambdaApiProps>) {
return LambdaApi({
name: props.name!,
runtime: props.runtime ?? "nodejs20.x",
handler: props.handler ?? "index.handler",
code: props.code!,
timeout: props.timeout ?? 25,
memorySize: props.memorySize ?? 1024,
environment: props.environment,
policies: props.policies,
});
}

Instantiate and export:

health-api.ts
import { Sub, AWS, Ref } from "@intentius/chant-lexicon-aws";
import { SecureApi } from "./lambda-api";
import { environment } from "./params";
export const healthApi = SecureApi({
name: Sub`${AWS.StackName}-${Ref(environment)}-health`,
runtime: "nodejs20.x",
handler: "index.handler",
code: {
ZipFile: `exports.handler = async () => ({
statusCode: 200,
body: JSON.stringify({ status: 'healthy' })
});`,
},
});

During build, composites expand to flat CloudFormation resources: healthApiRole, healthApiFunc, healthApiPermission.

The AWS lexicon ships ready-to-use composites for common patterns. Import them from @intentius/chant-lexicon-aws:

builtin-composites.ts
import { Sub, AWS, Ref } from "@intentius/chant-lexicon-aws";
import { LambdaNode, LambdaApi, LambdaScheduled, S3Actions, VpcDefault, FargateAlb, RdsInstance } from "@intentius/chant-lexicon-aws";
import { Role_Policy, Parameter } from "@intentius/chant-lexicon-aws";
// LambdaNode: Role + Function with nodejs20.x defaults
export const worker = LambdaNode({
name: Sub`${AWS.StackName}-worker`,
Code: { ZipFile: `exports.handler = async () => ({ statusCode: 200 });` },
});
// LambdaApi: Role + Function + Permission for API Gateway
export const api = LambdaApi({
name: Sub`${AWS.StackName}-api`,
Runtime: "nodejs20.x",
Handler: "index.handler",
Code: { ZipFile: `exports.handler = async () => ({ statusCode: 200 });` },
Timeout: 10,
sourceArn: Sub`arn:aws:execute-api:${AWS.Region}:${AWS.AccountId}:*/prod/*`,
});
// LambdaScheduled: Role + Function + EventBridge Rule + Permission
export const cron = LambdaScheduled({
name: Sub`${AWS.StackName}-cron`,
Runtime: "python3.12",
Handler: "handler.handler",
Code: { ZipFile: `def handler(event, context): return {"statusCode": 200}` },
schedule: "rate(5 minutes)",
});
// VpcDefault: Production-ready VPC (17 resources)
export const network = VpcDefault({});
// FargateAlb: Fargate service behind an ALB (11 resources)
export const web = FargateAlb({
image: "nginx:latest",
vpcId: network.vpc.VpcId,
publicSubnetIds: [network.publicSubnet1.SubnetId, network.publicSubnet2.SubnetId],
privateSubnetIds: [network.privateSubnet1.SubnetId, network.privateSubnet2.SubnetId],
});
// RdsInstance: DBSubnetGroup + SecurityGroup + DBInstance (3-4 resources)
export const dbPassword = new Parameter("AWS::SSM::Parameter::Value<String>", {
description: "SSM path to the database password",
defaultValue: "/myapp/dev/db-password",
});
export const rdsDatabase = RdsInstance({
engine: "postgres",
vpcId: network.vpc.VpcId,
subnetIds: [network.privateSubnet1.SubnetId, network.privateSubnet2.SubnetId],
masterPassword: Ref(dbPassword) as unknown as string,
databaseName: "myapp",
});
// Composites accept extra IAM policies
export const reader = LambdaNode({
name: Sub`${AWS.StackName}-reader`,
Code: { ZipFile: `exports.handler = async () => ({});` },
Policies: [
new Role_Policy({
PolicyName: "S3Read",
PolicyDocument: {
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: S3Actions.ReadOnly,
Resource: "*",
}],
},
}),
],
});
CompositeMembersDescription
LambdaFunctionrole, funcIAM Role + Lambda Function. Auto-attaches AWSLambdaBasicExecutionRole; adds AWSLambdaVPCAccessExecutionRole when VpcConfig is provided.
LambdaNoderole, funcLambdaFunction preset with Runtime: "nodejs20.x" and Handler: "index.handler"
LambdaPythonrole, funcLambdaFunction preset with Runtime: "python3.12" and Handler: "handler.handler"
LambdaApirole, func, permissionLambdaFunction + Lambda Permission for API Gateway invocation
LambdaScheduledrole, func, rule, permissionLambdaFunction + EventBridge Rule + Lambda Permission
LambdaSqsqueue, role, funcSQS Queue + Lambda + EventSourceMapping. Auto-attaches SQS receive policy.
LambdaEventBridgerule, role, func, permissionEventBridge Rule + Lambda. Supports schedule and/or eventPattern.
LambdaDynamoDBtable, role, funcDynamoDB Table + Lambda. Auto-attaches DynamoDB policy and injects TABLE_NAME env var.
LambdaS3bucket, role, funcS3 Bucket (encrypted, public access blocked) + Lambda. Auto-attaches S3 policy and injects BUCKET_NAME env var.
LambdaSnstopic, role, func, subscription, permissionSNS Topic + Lambda via Subscription. Auto-attaches invoke permission for SNS.
VpcDefaultvpc, igw, igwAttachment, publicSubnet1, publicSubnet2, privateSubnet1, privateSubnet2, publicRouteTable, publicRoute, publicRta1, publicRta2, privateRouteTable, privateRta1, privateRta2, natEip, natGateway, privateRouteProduction-ready VPC: 2 public + 2 private subnets across 2 AZs, internet gateway, single NAT gateway.
FargateAlbcluster, executionRole, taskRole, logGroup, taskDef, albSg, taskSg, alb, targetGroup, listener, serviceFargate service behind an ALB. Accepts VPC outputs as props.
AlbSharedcluster, executionRole, albSg, alb, listenerShared ALB infrastructure (ECS cluster, execution role, ALB, listener with 404 default). Created once, consumed by multiple FargateService instances.
FargateServicetaskRole, logGroup, taskDef, taskSg, targetGroup, rule, servicePer-service Fargate resources with listener rule routing. Wire to an AlbShared instance for multi-service ALB patterns.
RdsInstancesubnetGroup, sg, db (+ parameterGroup if configured)RDS instance (postgres, mysql, mariadb) in private subnets. Creates DB subnet group, security group, and optionally a parameter group. Engine-specific defaults for port, username, and version. Encrypted by default.

All built-in composites accept ManagedPolicyArns and Policies for adding IAM permissions to the auto-created role.

Typed IAM action constants for common AWS services. Use them in policy documents instead of hand-typing action strings:

action-constants.ts
import { Role, Role_Policy, Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
import { S3Actions } from "@intentius/chant-lexicon-aws";
export const s3DataBucket = new Bucket({
BucketName: Sub`${AWS.StackName}-data`,
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true,
},
});
export const s3ReaderRole = new Role({
AssumeRolePolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
Action: "sts:AssumeRole",
},
],
},
Policies: [
new Role_Policy({
PolicyName: "S3Read",
PolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Action: S3Actions.ReadOnly,
Resource: s3DataBucket.Arn,
},
],
},
}),
],
});

Available constants:

ConstantKey groups
S3ActionsReadOnly, WriteOnly, ReadWrite, Full, GetObject, PutObject, DeleteObject, ListObjects
LambdaActionsInvoke, ReadOnly, Full
DynamoDBActionsReadOnly, WriteOnly, ReadWrite, Full, GetItem, PutItem, Query, Scan
SQSActionsSendMessage, ReceiveMessage, Full
SNSActionsPublish, Subscribe, Full
IAMActionsPassRole
ECRActionsPull, Full
LogsActionsWrite, Full
ECSActionsRunTask, Service, Full

Broad groups like ReadWrite are always supersets of their narrow counterparts (ReadOnly + WriteOnly). All values are as const arrays for full type safety.

Wrap a composite with pre-applied defaults. Defaulted props become optional:

with-defaults.ts
import { Sub, AWS, Function as LambdaFunction } from "@intentius/chant-lexicon-aws";
import { Composite, withDefaults } from "@intentius/chant";
interface LambdaApiProps {
name: string;
runtime: string;
handler: string;
timeout: number;
memorySize: number;
code: { ZipFile: string };
}
const LambdaApi = Composite<LambdaApiProps>((props) => ({
fn: new LambdaFunction({
FunctionName: props.name,
Runtime: props.runtime,
Handler: props.handler,
Timeout: props.timeout,
MemorySize: props.memorySize,
Code: props.code,
}),
}), "LambdaApi");
const SecureApi = withDefaults(LambdaApi, {
runtime: "nodejs20.x",
handler: "index.handler",
timeout: 10,
memorySize: 256,
});
export const healthApi = SecureApi({
name: Sub`${AWS.StackName}-health`,
code: { ZipFile: `exports.handler = async () => ({ statusCode: 200 });` },
});
// Composable — stack defaults on top of defaults
export const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 });

withDefaults preserves the original composite’s identity — same _id and compositeName, no new registry entry.

withDefaults also accepts a function that receives the caller’s props and returns defaults. This enables conditional logic without generating extra resources:

computed-defaults.ts
import { Composite, withDefaults } from "@intentius/chant";
import { Function as LambdaFunction } from "@intentius/chant-lexicon-aws";
interface ApiProps {
name: string;
runtime: string;
handler: string;
timeout: number;
memorySize: number;
code: { ZipFile: string };
}
const Api = Composite<ApiProps>((props) => ({
fn: new LambdaFunction({
FunctionName: props.name,
Runtime: props.runtime as any,
Handler: props.handler,
Timeout: props.timeout,
MemorySize: props.memorySize,
Code: props.code,
}),
}), "Api");
// Function-based defaults can compute values from caller props
const SmartApi = withDefaults(Api, (props) => ({
runtime: "nodejs20.x",
handler: "index.handler",
// Scale timeout with memory — higher memory → longer timeout
timeout: (props.memorySize ?? 128) >= 1024 ? 60 : 10,
memorySize: 256,
}));
export const fast = SmartApi({
name: "fast-api",
code: { ZipFile: `exports.handler = async () => ({ statusCode: 200 });` },
});
// memorySize >= 1024 triggers the longer timeout
export const heavy = SmartApi({
name: "heavy-api",
memorySize: 2048,
code: { ZipFile: `exports.handler = async () => ({ statusCode: 200 });` },
});

Merge order: computed defaults are applied first, then user-provided props override them.

Attach properties that merge into every member during expansion:

propagate.ts
import { propagate } from "@intentius/chant";
import { healthApi } from "./with-defaults";
export const propagatedApi = propagate(
healthApi,
{ Tags: [{ Key: "env", Value: "prod" }] },
);

Merge semantics:

  • Scalars — member-specific value wins over shared
  • Arrays (e.g. tags) — shared values prepended, member values appended
  • undefined — stripped from shared props, never overwrites

When you need to split resources into a separate CloudFormation template, the lexicon supports nested stacks via nestedStack(). See the Nested Stacks page for details.