Function
Source:
src/AWS/Lambda/Function.ts
An AWS Lambda host resource that combines code bundling, IAM role provisioning, and runtime binding collection.
Function is the canonical runtime host for AWS. It can either bundle a
TypeScript entry module into a zip artifact or build a user-authored
Dockerfile into a Lambda container image. In both modes Alchemy creates the
execution role and applies bindings; image mode additionally owns the
private ECR repository and Lambda pull policy.
Zip-packaged functions can be defined in two ways:
- Async — plain handler export, no Effect runtime in the bundle.
- Effect — Effect implementation with typed bindings and event sources.
See Effect handlers vs async handlers for plain handler patterns, or the Lambda guide for the full Effect-based approach with bindings, event sources, and sinks.
Async Functions
Section titled “Async Functions”Point main at a file that exports a standard Lambda handler. No
Effect runtime is included in the bundle. Useful when migrating
existing Lambda functions or when you don’t need Effect.
Defining an async Lambda in your stack
import * as AWS from "alchemy/AWS";
const func = yield* AWS.Lambda.Function("ApiFunction", { main: "./src/handler.ts", functionUrl: true,});Function using ARM64
const func = yield* AWS.Lambda.Function("ArmFunction", { main: "./src/handler.ts", architecture: "arm64",});Function with a native package (Sharp)
const func = yield* AWS.Lambda.Function("ImageProcessor", { main: "./src/handler.ts", architecture: "arm64", build: { install: ["sharp"], },});Writing the async handler
export const handler = async (event: any) => { return { statusCode: 200, body: JSON.stringify({ message: "Hello from Lambda!" }), };};Container Image Functions
Section titled “Container Image Functions”Set image instead of main to deploy an existing private ECR image or to
build and publish a local Docker context. Image sources must be literal
because Lambda’s pre-create phase needs the deployable image before normal
Output resolution.
Existing ECR image with runtime overrides
Section titled “Existing ECR image with runtime overrides”const func = yield* AWS.Lambda.Function("Worker", { image: { uri: "123456789012.dkr.ecr.us-east-1.amazonaws.com/worker@sha256:...", command: ["app.handler"], entryPoint: ["/lambda-entrypoint.sh"], workingDirectory: "/var/task", }, architecture: "x86_64",});Tagged URIs are resolved through ECR on each plan. If a tag is repointed to a new digest, Alchemy updates the Lambda function even though the URI string is unchanged. External repositories are never modified or deleted.
Build a Lambda container image
Section titled “Build a Lambda container image”const func = yield* AWS.Lambda.Function("JavaFunction", { image: { context: "./lambda", dockerfile: "Dockerfile", buildArgs: { APP_ENV: "production", }, }, architecture: "arm64", functionUrl: false,});The Dockerfile owns the runtime and handler. Alchemy does not generate a
Node.js adapter or otherwise impose a language. For example,
./lambda/Dockerfile can use AWS’s Java base image:
FROM public.ecr.aws/lambda/java:21COPY target/function.jar ${LAMBDA_TASK_ROOT}/lib/CMD ["com.example.Handler::handleRequest"]Effect Functions
Section titled “Effect Functions”Pass the Effect implementation as the third argument. Bindings attach IAM permissions and environment variables at deploy time, while the runtime execution context collects listeners and exports.
export default class ApiFunction extends AWS.Lambda.Function<ApiFunction>()( "ApiFunction", { main: import.meta.url, functionUrl: true }, Effect.gen(function* () { // init: bind resources const getItem = yield* AWS.DynamoDB.GetItem(table);
return { // runtime: use them fetch: Effect.gen(function* () { const request = yield* HttpServerRequest; const url = new URL(request.url); const id = url.searchParams.get("id"); const result = yield* getItem({ Key: { pk: { S: id! } } }); return yield* HttpServerResponse.json(result.Item); }), }; }),) {}Configuration
Section titled “Configuration”Function with URL
const func = yield* AWS.Lambda.Function("ApiFunction", { main: "./src/handler.ts", functionUrl: true,});Function URL with IAM auth
const func = yield* AWS.Lambda.Function("ApiFunction", { main: "./src/handler.ts", functionUrl: { authType: "AWS_IAM", },});Function in a VPC
const func = yield* AWS.Lambda.Function("VpcFunction", { main: "./src/handler.ts", vpc: { subnetIds: ["subnet-abc123", "subnet-def456"], securityGroupIds: ["sg-xyz789"], },});Async invocation retries and failure destination
const func = yield* AWS.Lambda.Function("AsyncFunction", { main: "./src/handler.ts", eventInvokeConfig: { maximumRetryAttempts: 0, maximumEventAge: "1 minute", destinationConfig: { OnFailure: { Destination: queue.queueArn, }, }, },});Bundling & Tree-shaking
Section titled “Bundling & Tree-shaking”main is bundled with rolldown at deploy time. Top-level calls in the
effect, @effect/*, alchemy, @alchemy.run/*, and
@distilled.cloud/* packages receive #__PURE__ annotations by
default, so anything the function doesn’t use from those packages is
tree-shaken out of the bundle. Any other package — including your own
app — is left untouched unless you list it explicitly.
Treat additional packages as pure
Pass package names (or picomatch globs) via build.pure.packages to
annotate them in addition to the defaults. Listing a package that also
declares "sideEffects": false (or []) in its package.json opts it
into full annotation — top-level calls whose result is discarded are
deleted under minification when unused — so only list packages whose
modules really are free of meaningful top-level side effects.
const func = yield* AWS.Lambda.Function("ApiFunction", { main: "./src/handler.ts", build: { pure: { packages: ["my-lib", "@my-scope/*"] }, },});Disable pure annotations
const func = yield* AWS.Lambda.Function("ApiFunction", { main: "./src/handler.ts", build: { pure: false },});EFS File Systems
Section titled “EFS File Systems”Mount an EFS access point into the function’s /mnt/… file system. The
function must be attached to a VPC that can reach an EFS mount target for
the file system.
Mount an EFS access point via props
const accessPoint = yield* AWS.EFS.AccessPoint("FilesAccess", { fileSystemId: fileSystem.fileSystemId, posixUser: { uid: 1000, gid: 1000 },});
const func = yield* AWS.Lambda.Function("FilesFunction", { main: "./src/handler.ts", vpc: { subnetIds, securityGroupIds }, fileSystemConfigs: [ // pass the AccessPoint resource itself (or its ARN via `arn`) { accessPoint, localMountPath: "/mnt/files" }, ],});Mount via the host-agnostic EFS.mount binding
EFS.mount wires the same mount config plus least-privilege IAM through
the binding channel and works on both Lambda and ECS hosts.
export default class FilesFunction extends AWS.Lambda.Function<FilesFunction>()( "FilesFunction", { main: import.meta.url, vpc: { subnetIds, securityGroupIds } }, Effect.gen(function* () { const files = yield* AWS.EFS.mount(accessPoint, { path: "/mnt/files" }); return Effect.fn(function* (event: unknown) { return { mountedAt: files.path }; }); }).pipe(Effect.provide(AWS.EFS.MountLive)),) {}S3 Bindings
Section titled “S3 Bindings”Bind S3 operations in the init phase to give the function IAM permissions and inject the bucket name as an environment variable.
// initconst getObject = yield* S3.GetObject(bucket);const putObject = yield* S3.PutObject(bucket);
return { fetch: Effect.gen(function* () { // runtime yield* putObject({ Key: "hello.txt", Body: "Hello!" }); const obj = yield* getObject({ Key: "hello.txt" }); return HttpServerResponse.text("OK"); }),};DynamoDB Bindings
Section titled “DynamoDB Bindings”Bind DynamoDB operations in the init phase to grant table-scoped IAM permissions.
// initconst getItem = yield* AWS.DynamoDB.GetItem(table);const putItem = yield* AWS.DynamoDB.PutItem(table);
return { fetch: Effect.gen(function* () { // runtime yield* putItem({ Item: { pk: { S: "user#1" }, name: { S: "Alice" } } }); const result = yield* getItem({ Key: { pk: { S: "user#1" } } }); return yield* HttpServerResponse.json(result.Item); }),};SQS Bindings
Section titled “SQS Bindings”Bind SQS operations in the init phase to send messages to a queue.
// initconst sendMessage = yield* SQS.SendMessage(queue);
return { fetch: Effect.gen(function* () { // runtime yield* sendMessage({ MessageBody: JSON.stringify({ orderId: "123" }), }); return HttpServerResponse.text("Queued"); }),};SNS Bindings
Section titled “SNS Bindings”Bind SNS operations in the init phase to publish messages to a topic.
// initconst publish = yield* AWS.SNS.Publish(topic);
return { fetch: Effect.gen(function* () { // runtime yield* publish({ Message: JSON.stringify({ event: "order.created" }), Subject: "OrderCreated", }); return HttpServerResponse.text("Published"); }),};Kinesis Bindings
Section titled “Kinesis Bindings”Bind Kinesis operations in the init phase to put records into a stream.
// initconst putRecord = yield* AWS.Kinesis.PutRecord(stream);
return { fetch: Effect.gen(function* () { // runtime yield* putRecord({ PartitionKey: "order-123", Data: new TextEncoder().encode(JSON.stringify({ orderId: "123" })), }); return HttpServerResponse.text("Sent"); }),};Event Sources
Section titled “Event Sources”Lambda functions can be triggered by event sources like SQS queues, DynamoDB streams, S3 notifications, SNS topics, and Kinesis streams.
Process SQS messages
yield* SQS.consumeQueueMessages(queue, Effect.fn(function* (message) { yield* Effect.log(`Received: ${message.body}`); }),);Process DynamoDB stream changes
yield* AWS.DynamoDB.consumeTableChanges(table, { StreamViewType: "NEW_AND_OLD_IMAGES",}, Effect.fn(function* (record) { yield* Effect.log(`Change: ${record.eventName}`); }),);Process S3 notifications
yield* AWS.S3.consumeBucketEvents(bucket, { events: ["s3:ObjectCreated:*"],}, (stream) => stream.pipe( Stream.runForEach((event) => Effect.log(`New object: ${event.key}`), ), ),);