Skip to content

AWS.Lambda reference

Source: src/AWS/Lambda/AppConfigDeploymentEventSource.ts

Lambda runtime implementation for AWS.AppConfig.consumeDeploymentEvents(...).

This layer does two things:

  1. At deploy time it provisions an AppConfig Extension whose actions invoke the current Lambda function at the subscribed deployment action points, the IAM role AppConfig assumes to perform the invocation, and an ExtensionAssociation attaching the extension to the target application or environment.
  2. At runtime it narrows incoming invocations to AppConfig deployment notifications for the bound target and forwards them into the supplied handler as a typed DeploymentEventRecord stream.

AppConfigDeploymentEventSource: Consuming Deployment Events

Section titled “AppConfigDeploymentEventSource: Consuming Deployment Events”
yield* AppConfig.consumeDeploymentEvents(
env,
{ events: ["ON_DEPLOYMENT_COMPLETE"] },
(events) =>
events.pipe(
Stream.runForEach((event) =>
Effect.log(`deployment ${event.DeploymentNumber} completed`),
),
),
);

Source: src/AWS/Lambda/BucketEventSource.ts

Connects an S3 bucket notification stream to the current Lambda function.

This layer listens for bucket notifications routed through the Lambda runtime and exposes them as an Effect.Stream, while the companion policy configures the invoke permission and bucket notification binding during deployment.

yield* AWS.Lambda.BucketEventSource(
bucket,
{ events: ["s3:ObjectCreated:*"] },
(events) => Stream.runForEach(events, (event) => Effect.log(event.key)),
);

Source: src/AWS/Lambda/CreateAuthToken.ts

Runtime binding for CreateMicrovmAuthToken.

Bind it to a MicrovmImage to get a callable that mints a short-lived token for a running MicroVM. Send it on the MicroVM endpoint in the X-aws-proxy-auth header.

const createAuthToken = yield* AWS.Lambda.CreateAuthToken(Sandbox);
const { authToken } = yield* createAuthToken({
microvmIdentifier: vm.microvmId,
expirationInMinutes: 5,
allowedPorts: [{ port: 5000 }],
});

Source: src/AWS/Lambda/CreateShellAuthToken.ts

Runtime binding for CreateMicrovmShellAuthToken.

Bind it to a MicrovmImage to get a callable that mints a short-lived token for interactive shell access to a running MicroVM (the MicroVM must have been run with the shell ingress connector attached).

const createShellAuthToken = yield* AWS.Lambda.CreateShellAuthToken(Sandbox);
const { authToken } = yield* createShellAuthToken({
microvmIdentifier: vm.microvmId,
expirationInMinutes: 5,
});

Source: src/AWS/Lambda/DeleteMicrovmImageVersion.ts

Runtime binding for DeleteMicrovmImageVersion.

Bind it to a MicrovmImage to delete a specific image version (the imageIdentifier is injected). Idempotent.

Source: src/AWS/Lambda/DurableFunction.ts

An AWS Lambda Durable Function — a code-first, replay-based orchestrator that IS a durable Lambda Function. AWS.Lambda.DurableFunction is a wrapper of Function: it owns the underlying Lambda function, configures its DurableConfig at CreateFunction (durability is a create-time property — a DurableFunction is always durable), registers the durable-execution listener on the owned entrypoint, self-binds the checkpoint-protocol IAM (lambda:CheckpointDurableExecution, lambda:GetDurableExecutionState) onto the execution role, and vendors the open-source @aws/durable-execution-sdk-js into the artifact (install it in your project: npm i @aws/durable-execution-sdk-js).

Executions progress by checkpoint + replay: a Durable.sleep or Durable.waitForCallback suspends the execution with zero compute billed until Lambda re-invokes the same function version to resume, and completed Durable.steps replay from the checkpoint log without re-executing.

Every invocation of a durable function arrives as the durable-execution envelope, so a DurableFunction has no HTTP surface (functionUrl is disabled) — it does one thing: run durable orchestrations. Reusing a logical id between a plain Function and a DurableFunction replaces the physical function (DurableConfig cannot be flipped in place).

DurableFunction: Defining a Durable Function

Section titled “DurableFunction: Defining a Durable Function”

Class form with steps and a durable sleep

export class OrderFlow extends AWS.Lambda.DurableFunction<OrderFlow>()(
"OrderFlow",
{
main: import.meta.url,
executionTimeout: "1 hour",
retentionPeriod: "7 days",
},
Effect.gen(function* () {
// init: resolve typed binding clients (IAM lands on this function's role)
const putItem = yield* AWS.DynamoDB.PutItem(table);
return Effect.fn(function* (input: { orderId: string }) {
const reserved = yield* AWS.Lambda.Durable.step(
"reserve",
putItem({ Item: { pk: { S: input.orderId } } }).pipe(Effect.orDie),
{ retry: { limit: 3, delay: "5 seconds" } },
);
yield* AWS.Lambda.Durable.sleep("cooldown", "10 minutes");
return { orderId: input.orderId, reserved };
});
}),
) {}

Tag + default export (entrypoint form)

// order-flow.ts — `main` points at this module
export class OrderFlow extends AWS.Lambda.DurableFunction<OrderFlow>()(
"OrderFlow",
) {}
export default OrderFlow.make(
{ main: import.meta.url, executionTimeout: "1 hour" },
Effect.gen(function* () {
return Effect.fn(function* (input: { orderId: string }) {
return yield* AWS.Lambda.Durable.step("work", doWork(input));
});
}),
);

Inline effect form

const flow = yield* AWS.Lambda.DurableFunction(
"OrderFlow",
{ main: "./src/order-flow.ts" },
Effect.gen(function* () {
return Effect.fn(function* (input: { orderId: string }) {
return yield* AWS.Lambda.Durable.step("work", doWork(input));
});
}),
);

DurableFunction: Starting and Monitoring Executions

Section titled “DurableFunction: Starting and Monitoring Executions”

Starting an execution

const orders = yield* OrderFlow;
const ref = yield* orders.start({
name: "order-123", // idempotent start
params: { orderId: "123" },
qualifier: "live",
});

Publish and promote for production

const orders = yield* OrderFlow;
const version = yield* AWS.Lambda.Version("OrderFlowVersion", {
function: orders.function,
});
yield* AWS.Lambda.Alias("OrderFlowLive", {
version,
aliasName: "live",
});
const ref = yield* orders.start({
name: "order-123",
params: { orderId: "123" },
qualifier: "live",
});

Checking status

const execution = yield* orders.get(ref.executionArn!);
// execution.Status: "RUNNING" | "SUCCEEDED" | "FAILED" | ...
const approval = yield* AWS.Lambda.Durable.waitForCallback<{ ok: boolean }>(
"approve",
(callbackId) => storeCallbackId(callbackId),
{ timeout: "1 day" },
);

Source: src/AWS/Lambda/EventBridgeEventSource.ts

Lambda runtime implementation for AWS.EventBridge.consumeBusEvents(...).

This layer does two things:

  1. It delegates to EventSourcePolicy so deployment creates an EventBridge rule targeting the current Lambda function.
  2. At runtime it filters incoming Lambda events against the original event pattern and forwards matching events into the supplied Stream.

EventSource: Subscribing To The Default Bus

Section titled “EventSource: Subscribing To The Default Bus”
yield* AWS.EventBridge.consumeBusEvents(
{
source: ["app.user"],
"detail-type": ["UserCreated"],
},
(events) =>
Stream.runForEach(events, (event) =>
Effect.log(`new user: ${event.detail.userId}`),
),
);
const bus = yield* AWS.EventBridge.EventBus("OrdersBus", {
name: "orders",
});
yield* AWS.EventBridge.consumeBusEvents(
bus,
{
source: ["app.orders"],
"detail-type": ["OrderPaid"],
},
(events) =>
Stream.runForEach(events, (event) =>
Effect.log(`paid order: ${event.detail.orderId}`),
),
);
yield* AWS.EventBridge.consumeBusEvents(
"InvoiceEvents",
{
source: ["app.billing"],
"detail-type": ["InvoiceIssued"],
},
{
description: "Deliver invoice events into this Lambda function",
},
(events) =>
Stream.runForEach(events, (event) =>
Effect.log(`invoice: ${event.detail.invoiceId}`),
),
);
type UserCreated = {
userId: string;
email: string;
};
yield* AWS.EventBridge.consumeBusEvents(
{
source: ["app.user"],
"detail-type": ["UserCreated"],
},
(events) =>
Stream.runForEach(
events as Stream.Stream<AWS.EventBridge.EventRecord<UserCreated>>,
(event) => Effect.log(`welcome ${event.detail.email}`),
),
);

Source: src/AWS/Lambda/EventSourceMapping.ts

Connects an event source — an SQS queue, Kinesis stream, DynamoDB stream, Amazon MQ broker, or Kafka topic — to a Lambda function so that records are polled from the source and delivered to the function in batches.

Most stacks create mappings indirectly through the higher-level event-source helpers (SQS.consumeQueueMessages(queue, ...), Kinesis.consumeStreamRecords(stream, ...), DynamoDB.consumeTableChanges(table, ...)), which wire up the matching IAM permissions automatically. Use this resource directly when you need full control over batching, starting position, retry behavior, or filtering.

SQS is the simplest source: no startingPosition is needed because there is no stream cursor. Lambda long-polls the queue and invokes the function with up to batchSize messages, and functionName plus eventSourceArn are the only required props.

import * as AWS from "alchemy/AWS";
const queue = yield* AWS.SQS.Queue("Jobs", {});
const worker = yield* AWS.Lambda.Function("Worker", {
main: "./src/worker.ts",
});
const mapping = yield* AWS.Lambda.EventSourceMapping("JobsToWorker", {
functionName: worker.functionName,
eventSourceArn: queue.queueArn,
batchSize: 10,
maximumBatchingWindow: "5 seconds",
});

This delivers up to 10 messages per invocation, waiting up to 5 seconds to fill a batch before invoking. Increasing the batching window trades latency for fewer, larger invocations — useful for amortizing cold starts or downstream write costs on bursty queues.

EventSourceMapping: Streaming from Kinesis & DynamoDB

Section titled “EventSourceMapping: Streaming from Kinesis & DynamoDB”

Stream sources (Kinesis and DynamoDB Streams) deliver records in shard order and therefore require a startingPosition that tells Lambda where in the shard to begin reading. These sources also unlock the stream-only tuning knobs covered in the next sections.

Process a Kinesis stream from the latest records

import * as AWS from "alchemy/AWS";
const stream = yield* AWS.Kinesis.Stream("Events", {});
const consumer = yield* AWS.Lambda.Function("Consumer", {
main: "./src/consumer.ts",
});
const mapping = yield* AWS.Lambda.EventSourceMapping("EventsToConsumer", {
functionName: consumer.functionName,
eventSourceArn: stream.streamArn,
startingPosition: "LATEST",
batchSize: 100,
});

startingPosition: "LATEST" skips any backlog and only processes records written after the mapping is created — the right choice for live event pipelines where replaying history would be wasteful or incorrect.

Replay a DynamoDB stream from the beginning

import * as AWS from "alchemy/AWS";
const table = yield* AWS.DynamoDB.Table("Orders", {
partitionKey: { name: "id", type: "S" },
});
const handler = yield* AWS.Lambda.Function("OrdersStream", {
main: "./src/orders.ts",
});
const mapping = yield* AWS.Lambda.EventSourceMapping("OrdersToHandler", {
functionName: handler.functionName,
eventSourceArn: table.latestStreamArn!,
startingPosition: "TRIM_HORIZON",
});

TRIM_HORIZON starts at the oldest record still in the stream, so the function processes the full available history before catching up to new writes — use it when every change matters (e.g. building a projection).

Start reading from a specific timestamp

const mapping = yield* AWS.Lambda.EventSourceMapping("EventsFromTime", {
functionName: consumer.functionName,
eventSourceArn: stream.streamArn,
startingPosition: "AT_TIMESTAMP",
startingPositionTimestamp: new Date("2026-01-01T00:00:00Z"),
});

AT_TIMESTAMP (Kinesis only) begins at the first record on or after startingPositionTimestamp, letting you reprocess a known time range without replaying the entire stream.

For stream sources, throughput is governed by how records are batched and how many batches run in parallel per shard. These knobs let you balance end-to-end latency against invocation count and downstream load.

const mapping = yield* AWS.Lambda.EventSourceMapping("HighThroughput", {
functionName: consumer.functionName,
eventSourceArn: stream.streamArn,
startingPosition: "LATEST",
batchSize: 500,
maximumBatchingWindow: "10 seconds",
parallelizationFactor: 5,
tumblingWindow: "30 seconds",
});

parallelizationFactor runs up to 5 concurrent batches per shard (records with the same partition key still stay in order), while tumblingWindow aggregates results across sequential batches for windowed stream processing. Raising batchSize/maximumBatchingWindow favors fewer, larger invocations.

EventSourceMapping: Error Handling & Retries

Section titled “EventSourceMapping: Error Handling & Retries”

For stream sources a single poison-pill record can block a shard forever. These props bound retries, split failing batches, expire stale records, and route failures elsewhere instead of stalling the stream.

Bisect on error, cap retries, and expire old records

const dlq = yield* AWS.SQS.Queue("StreamFailures", {});
const mapping = yield* AWS.Lambda.EventSourceMapping("ResilientStream", {
functionName: consumer.functionName,
eventSourceArn: stream.streamArn,
startingPosition: "LATEST",
bisectBatchOnFunctionError: true,
maximumRetryAttempts: 3,
maximumRecordAge: "1 hour",
destinationConfig: {
OnFailure: { Destination: dlq.queueArn },
},
});

On a function error, bisectBatchOnFunctionError splits the batch in two and retries each half to isolate the bad record; after maximumRetryAttempts (or once a record is older than maximumRecordAge) the record is discarded and its metadata is sent to the destinationConfig.OnFailure target so it is never silently lost.

Report partial batch failures

const mapping = yield* AWS.Lambda.EventSourceMapping("PartialFailures", {
functionName: handler.functionName,
eventSourceArn: table.latestStreamArn!,
startingPosition: "TRIM_HORIZON",
functionResponseTypes: ["ReportBatchItemFailures"],
});

functionResponseTypes: ["ReportBatchItemFailures"] lets the function return only the IDs of records it failed to process, so Lambda retries just those instead of the whole batch — avoiding redundant reprocessing of records that already succeeded.

Attach filterCriteria so the function is only invoked for records matching an event pattern. Filtering happens before invocation, so it cuts both cost and unnecessary cold starts. Encrypt the patterns with kmsKeyArn when they contain sensitive values.

const mapping = yield* AWS.Lambda.EventSourceMapping("OrdersOnly", {
functionName: worker.functionName,
eventSourceArn: queue.queueArn,
filterCriteria: {
Filters: [{ Pattern: JSON.stringify({ body: { type: ["order"] } }) }],
},
kmsKeyArn:
"arn:aws:kms:us-east-1:111122223333:key/abcd1234-...",
});

Each Pattern is a JSON event-pattern string; messages that don’t match are dropped without invoking the function. The optional kmsKeyArn encrypts the stored filter criteria with your own KMS key instead of an AWS-managed one.

The enabled flag controls whether Lambda actively polls the source without deleting the mapping, so you can pause and resume delivery in place.

const mapping = yield* AWS.Lambda.EventSourceMapping("PausedConsumer", {
functionName: consumer.functionName,
eventSourceArn: stream.streamArn,
startingPosition: "LATEST",
enabled: false,
});

With enabled: false the mapping exists but pulls no records — flip it back to true to resume. This is handy for maintenance windows or for staging a consumer before turning on traffic.

EventSourceMapping: Scaling & Provisioned Pollers

Section titled “EventSourceMapping: Scaling & Provisioned Pollers”

Cap concurrency for SQS sources with scalingConfig, or reserve dedicated polling capacity (for Kafka/MSK and SQS) with provisionedPollerConfig to keep latency predictable under load.

const mapping = yield* AWS.Lambda.EventSourceMapping("BoundedConsumer", {
functionName: worker.functionName,
eventSourceArn: queue.queueArn,
scalingConfig: { MaximumConcurrency: 10 },
provisionedPollerConfig: {
MinimumPollers: 1,
MaximumPollers: 20,
},
});

scalingConfig.MaximumConcurrency caps how many function instances Lambda runs for this queue (protecting downstream systems), while provisionedPollerConfig keeps a pool of dedicated event pollers warm so throughput doesn’t lag behind sudden spikes.

EventSourceMapping: Kafka, MQ & DocumentDB Sources

Section titled “EventSourceMapping: Kafka, MQ & DocumentDB Sources”

Beyond AWS-native streams, an event source mapping can poll Amazon MSK, self-managed Apache Kafka, Amazon MQ brokers, and Amazon DocumentDB change streams. These sources use topics/queues to select what to consume, sourceAccessConfigurations for VPC and authentication wiring, and source-specific config props.

Consume a self-managed Kafka topic

const mapping = yield* AWS.Lambda.EventSourceMapping("KafkaConsumer", {
functionName: consumer.functionName,
eventSourceArn: stream.streamArn,
topics: ["orders"],
selfManagedEventSource: {
Endpoints: { KAFKA_BOOTSTRAP_SERVERS: ["broker1:9092", "broker2:9092"] },
},
selfManagedKafkaEventSourceConfig: { ConsumerGroupId: "orders-consumer" },
sourceAccessConfigurations: [
{ Type: "SASL_SCRAM_512_AUTH", URI: "arn:aws:secretsmanager:...:secret:kafka" },
],
loggingConfig: { LogFormat: "JSON" },
});

topics names the Kafka topic(s) to read; selfManagedEventSource.Endpoints points at the brokers; sourceAccessConfigurations supplies the SASL/VPC credentials; and selfManagedKafkaEventSourceConfig.ConsumerGroupId pins the consumer group. For Amazon MSK use amazonManagedKafkaEventSourceConfig instead.

Consume an Amazon MQ queue and a DocumentDB change stream

const mqMapping = yield* AWS.Lambda.EventSourceMapping("MqConsumer", {
functionName: worker.functionName,
eventSourceArn: stream.streamArn,
queues: ["orders-queue"],
sourceAccessConfigurations: [
{ Type: "BASIC_AUTH", URI: "arn:aws:secretsmanager:...:secret:mq" },
],
});
const docDbMapping = yield* AWS.Lambda.EventSourceMapping("DocDbConsumer", {
functionName: worker.functionName,
eventSourceArn: stream.streamArn,
documentDBEventSourceConfig: {
DatabaseName: "shop",
CollectionName: "orders",
FullDocument: "UpdateLookup",
},
});

For Amazon MQ, queues names the broker destination to consume and sourceAccessConfigurations carries the broker credentials; for DocumentDB, documentDBEventSourceConfig selects the database/collection and whether full documents are delivered on updates.

Opt into per-mapping CloudWatch metrics with metricsConfig and brand the mapping with your own tags (Alchemy also applies its internal ownership tags automatically).

const mapping = yield* AWS.Lambda.EventSourceMapping("ObservedConsumer", {
functionName: worker.functionName,
eventSourceArn: queue.queueArn,
metricsConfig: { Metrics: ["EventCount"] },
tags: { team: "payments", env: "prod" },
});

metricsConfig.Metrics turns on the named CloudWatch metrics (e.g. EventCount) for this mapping, and tags attaches arbitrary key/value pairs for cost allocation and discovery.

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.

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

alchemy.run.ts
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

src/handler.ts
export const handler = async (event: any) => {
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello from Lambda!" }),
};
};

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.

Function: Existing ECR image with runtime overrides
Section titled “Function: 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.

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:21
COPY target/function.jar ${LAMBDA_TASK_ROOT}/lib/
CMD ["com.example.Handler::handleRequest"]

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);
}),
};
}),
) {}

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,
},
},
},
});

main is bundled with rolldown at deploy time. Unused code is tree-shaken. effect, alchemy, and @distilled.cloud are marked pure so unused parts prune more aggressively. Your app is not marked pure.

Mark additional packages as pure

Only list packages with no top-level side effects.

const func = yield* AWS.Lambda.Function("ApiFunction", {
main: "./src/handler.ts",
build: {
pure: { packages: ["my-lib", "@my-scope/*"] },
},
});

Turn it off

const func = yield* AWS.Lambda.Function("ApiFunction", {
main: "./src/handler.ts",
build: { pure: false },
});

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)),
) {}

Bind S3 operations in the init phase to give the function IAM permissions and inject the bucket name as an environment variable.

// init
const 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");
}),
};

Bind DynamoDB operations in the init phase to grant table-scoped IAM permissions.

// init
const 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);
}),
};

Bind SQS operations in the init phase to send messages to a queue.

// init
const sendMessage = yield* SQS.SendMessage(queue);
return {
fetch: Effect.gen(function* () {
// runtime
yield* sendMessage({
MessageBody: JSON.stringify({ orderId: "123" }),
});
return HttpServerResponse.text("Queued");
}),
};

Bind SNS operations in the init phase to publish messages to a topic.

// init
const 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");
}),
};

Bind Kinesis operations in the init phase to put records into a stream.

// init
const 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");
}),
};

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}`),
),
),
);

Source: src/AWS/Lambda/GetAccountSettings.ts

Runtime binding for lambda:GetAccountSettings.

An account-level binding — call it with no arguments to get a callable that reads the region’s Lambda quotas (AccountLimit) and current usage (AccountUsage). Provide the GetAccountSettingsHttp layer on the Function to satisfy the binding.

const getAccountSettings = yield* AWS.Lambda.GetAccountSettings();
const settings = yield* getAccountSettings();
const concurrency = settings.AccountLimit?.ConcurrentExecutions;

Source: src/AWS/Lambda/GetFunction.ts

Runtime binding for lambda:GetFunction.

Reads the bound Function’s configuration, code location, and tags — useful for introspection and operational tooling at runtime. Provide the GetFunctionHttp layer on the Function to satisfy the binding.

const getFunction = yield* AWS.Lambda.GetFunction(target);
const response = yield* getFunction();
const memory = response.Configuration?.MemorySize;

Source: src/AWS/Lambda/GetMicrovm.ts

Runtime binding for GetMicrovm.

Bind it to a MicrovmImage to get a callable that reads the state, endpoint, and configuration of a running MicroVM by microvmIdentifier.

const getMicrovm = yield* AWS.Lambda.GetMicrovm(Sandbox);
const vm = yield* getMicrovm({ microvmIdentifier: id });

Source: src/AWS/Lambda/GetMicrovmImage.ts

Runtime binding for GetMicrovmImage.

Bind it to a MicrovmImage to read the image’s state and versions at runtime (the imageIdentifier is injected).

const getMicrovmImage = yield* AWS.Lambda.GetMicrovmImage(Sandbox);
const image = yield* getMicrovmImage({});

Source: src/AWS/Lambda/GetMicrovmImageBuild.ts

Runtime binding for GetMicrovmImageBuild.

Bind it to a MicrovmImage to read a per-architecture build’s state and snapshot info (the imageIdentifier is injected).

Source: src/AWS/Lambda/GetMicrovmImageVersion.ts

Runtime binding for GetMicrovmImageVersion.

Bind it to a MicrovmImage to read the configuration and state of a specific image version (the imageIdentifier is injected).

Source: src/AWS/Lambda/InvokeWithResponseStream.ts

Runtime binding for lambda:InvokeFunction over the response-streaming API. Invokes the bound Function and returns the streaming response — EventStream yields PayloadChunk events followed by a final InvokeComplete event.

Provide the InvokeWithResponseStreamHttp layer on the Function to satisfy the binding.

InvokeWithResponseStream: Invoking Functions

Section titled “InvokeWithResponseStream: Invoking Functions”
const invokeStream = yield* AWS.Lambda.InvokeWithResponseStream(target);
const response = yield* invokeStream({
Payload: new TextEncoder().encode(JSON.stringify({ prompt: "hi" })),
});
const chunks = yield* Stream.runCollect(response.EventStream!);

Source: src/AWS/Lambda/LayerVersion.ts

A version of a Lambda layer — a zip archive of libraries, a custom runtime, or other dependencies that Lambda extracts into /opt alongside your function code.

Layer versions are immutable, so changing the content or any publish setting publishes a new version under the same layer and retires the one it supersedes. layerVersionArn and version therefore change on update; layerName and layerArn stay put.

Package a Local Directory

// ./layers/deps contains nodejs/node_modules/...
const deps = yield* LayerVersion("Deps", {
path: "./layers/deps",
compatibleRuntimes: ["nodejs22.x"],
});

Publish an Existing Archive

const layer = yield* LayerVersion("Ffmpeg", {
path: "./dist/ffmpeg-layer.zip",
description: "static ffmpeg build",
compatibleArchitectures: ["arm64"],
});

Publish From S3

const layer = yield* LayerVersion("BigLayer", {
s3: {
bucket,
key: "layers/big-layer.zip",
},
});
const fn = yield* Function("Handler", {
main: import.meta.resolve("./handler.ts"),
layers: [deps],
});

Source: src/AWS/Lambda/ListFunctions.ts

Runtime binding for lambda:ListFunctions.

An account-level binding — call it with no arguments to get a callable that lists function configurations in the region. Provide the ListFunctionsHttp layer on the Function to satisfy the binding.

const listFunctions = yield* AWS.Lambda.ListFunctions();
const response = yield* listFunctions({ MaxItems: 50 });
const names = response.Functions?.map((f) => f.FunctionName);

Source: src/AWS/Lambda/ListManagedMicrovmImages.ts

Runtime binding for ListManagedMicrovmImages (account-scoped).

Lists the AWS-managed base MicroVM images available for use as baseImage. Bind with no resource: yield* AWS.Lambda.ListManagedMicrovmImages().

ListManagedMicrovmImages: Managed Base Images

Section titled “ListManagedMicrovmImages: Managed Base Images”

Source: src/AWS/Lambda/ListManagedMicrovmImageVersions.ts

Runtime binding for ListManagedMicrovmImageVersions (account-scoped).

Lists versions of an AWS-managed base MicroVM image. Bind with no resource: yield* AWS.Lambda.ListManagedMicrovmImageVersions().

ListManagedMicrovmImageVersions: Managed Base Images

Section titled “ListManagedMicrovmImageVersions: Managed Base Images”

Source: src/AWS/Lambda/ListMicrovmImageBuilds.ts

Runtime binding for ListMicrovmImageBuilds.

Bind it to a MicrovmImage to list builds for an image version, optionally filtered by architecture/chipset (the imageIdentifier is injected).

Source: src/AWS/Lambda/ListMicrovmImageVersions.ts

Runtime binding for ListMicrovmImageVersions.

Bind it to a MicrovmImage to list the image’s versions (the imageIdentifier is injected).

Source: src/AWS/Lambda/ListMicrovms.ts

Runtime binding for ListMicrovms.

Bind it to a MicrovmImage to get a callable that lists the MicroVMs launched from that image (the imageIdentifier filter is injected).

const listMicrovms = yield* AWS.Lambda.ListMicrovms(Sandbox);
const { items } = yield* listMicrovms({});

Source: src/AWS/Lambda/LogGroupEventSource.ts

Lambda runtime implementation for AWS.Logs.consumeLogEvents(...).

This layer does two things:

  1. At deploy time it creates the backing AWS.Logs.SubscriptionFilter targeting the current Lambda function plus the lambda:InvokeFunction permission for logs.amazonaws.com.
  2. At runtime it decodes the gzipped/base64 awslogs.data payload of incoming invocations and forwards each log event into the supplied handler as a typed LogEventRecord stream.
yield* AWS.Logs.consumeLogEvents(
logGroup,
{ filterPattern: "?ERROR ?Error" },
(events) =>
Stream.runForEach(events, (event) =>
Effect.log(`${event.logStream}: ${event.message}`),
),
);

Source: src/AWS/Lambda/MicrovmImage.ts

A Lambda MicroVM image — a Firecracker snapshot that boots a fully initialized application in milliseconds. The model is image-then-launch: you create an image once (this resource), then launch isolated, stateful MicroVM instances from it at runtime (one per end-user/session) with the RunMicrovm binding from a Lambda Function.

The build runs server-side on AWS — there is no local Docker. You supply a code artifact (a zip of a Dockerfile + your code) and a base image (baseImage); AWS runs your Dockerfile on top of the base, initializes the app, and takes a Firecracker snapshot. The build is asynchronous: the provider uploads the artifact, calls CreateMicrovmImage/UpdateMicrovmImage, and polls until the image reaches CREATED/UPDATED (or surfaces the build failure). Build logs stream to CloudWatch at /aws/lambda/microvms/<name>.

Alchemy produces the code artifact for you in three ways, selected by which prop you set (maincontextcodeArtifact.uri):

  • Effectful (main): write the in-VM HTTP server in TypeScript as an Effect. Alchemy bundles it (with its capability bindings), generates a Dockerfile on the MicroVM base, zips both, and uploads to the Assets bucket.
  • External (context/dockerfile): bring your own Dockerfile + build context (any language). Alchemy zips the directory and uploads it.
  • Prebuilt (codeArtifact.uri): point at an existing S3 zip or ECR image URI; nothing is built or uploaded.

Re-deploys only trigger a new build when the artifact’s content hash or a build-affecting prop changes; otherwise the image is left untouched.

  • A build role (buildRole) Lambda assumes to read the code artifact and write build logs. Pass a Role instance and the required permissions are granted automatically — see the example below.
  • A bootstrapped Assets bucket (alchemy provider aws bootstrap) for effectful / external modes, which upload the artifact to S3.
  • The account must be onboarded to the Lambda MicroVM preview.

Pass a bare Role as buildRole and the MicroVM image grants everything it needs via a binding: the trust policy (so Lambda can assume it) plus the S3 (Assets bucket) and CloudWatch-logs permissions (folded into an alchemy-bindings inline policy). You don’t write any policy yourself.

const buildRole = yield* AWS.IAM.Role("MicrovmBuildRole", {});
// Pass the role instance; trust + permissions are attached for you.
const image = yield* AWS.Lambda.MicrovmImage("Sandbox", {
main: import.meta.filename,
buildRole,
});

Write the in-VM server in TypeScript. Alchemy bundles main and bakes it into the image; the server listens on port (default 8080), which becomes the MicroVM endpoint.

In-VM HTTP server (single file)

export default class Sandbox extends AWS.Lambda.MicrovmImage<Sandbox>()(
"Sandbox",
{ main: import.meta.filename, buildRole },
Effect.gen(function* () {
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
return HttpServerResponse.text(`hello from ${request.url}`);
}),
};
}),
) {}

With capability bindings and env vars

export default class Sandbox extends AWS.Lambda.MicrovmImage<Sandbox>()(
"Sandbox",
{
main: import.meta.filename,
buildRole,
runtime: "bun",
env: { LOG_LEVEL: "info" },
},
Effect.gen(function* () {
// bindings are bundled into the image and resolved at runtime
const getObject = yield* AWS.S3.GetObject(bucket);
return {
fetch: Effect.gen(function* () {
const obj = yield* getObject({ key: "data.json" });
return HttpServerResponse.json(yield* obj.json);
}),
};
}).pipe(Effect.provide(AWS.S3.GetObjectHttp)),
) {}

Class + .make() (two files, for a Lambda orchestrator)

When a Lambda Function imports the image to bind its instance operations, keep the class (a typed handle) and the .make() runtime in separate files so the orchestrator’s bundle doesn’t pull in the VM’s runtime deps.

// sandbox.ts — imported by the orchestrator
export class Sandbox extends AWS.Lambda.MicrovmImage<Sandbox>()("Sandbox") {}
// sandbox.live.ts — provided on the Stack; bundled into the image.
// Must be the `default` export: the bundler resolves the image entrypoint
// from the code artifact's default export.
export default Sandbox.make(
{ main: import.meta.filename, buildRole },
Effect.gen(function* () {
return { fetch: Effect.gen(function* () {
return HttpServerResponse.text("ok");
}) };
}),
);

Beyond (or instead of) a raw fetch handler, an image can expose a typed RPC Shape as the second type parameter. The in-VM runtime serves those methods over an /__rpc__/* protocol and falls through to fetch for every other request, so an image can offer BOTH a typed RPC surface and ordinary HTTP routes. A caller gets a fully-typed client with connectMicrovm: value methods yield* as Effects, streaming methods pipe as Streams.

Define a tagged-RPC image (RPC + fetch)

// sandbox.ts — the typed handle imported by the orchestrator Lambda
export class Sandbox extends AWS.Lambda.MicrovmImage<
Sandbox,
{ hello: (message: string) => Effect.Effect<string> }
>()("Sandbox") {}
// sandbox.live.ts — provided on the Stack; bundled into the image (default export)
export default Sandbox.make(
{ main: import.meta.filename, buildRole },
Effect.gen(function* () {
return {
// RPC method — reached with `connectMicrovm` below
hello: (message: string) => Effect.succeed(`hello, ${message}!`),
// raw HTTP route — reached with a plain HTTPS request to the endpoint
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const url = new URL(request.url, "http://microvm");
return HttpServerResponse.json({ path: url.pathname });
}),
};
}),
);

Call the RPC method from a Lambda

connectMicrovm builds the typed stub over an HttpClient (provide FetchHttpClient.layer for the request scope), pointing at the running MicroVM’s endpoint and authenticating with the authToken headers.

const vm = yield* runMicrovm({});
// ...wait until `vm.state` is RUNNING (poll `getMicrovm`)...
const { authToken } = yield* createAuthToken({
microvmIdentifier: vm.microvmId,
expirationInMinutes: 5,
allowedPorts: [{ port: 8080 }], // the in-VM server's port
});
const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, {
endpoint: vm.endpoint,
authToken,
});
const reply = yield* sandbox.hello("world"); // "hello, world!"

Call the same MicroVM’s fetch route directly

For the raw HTTP path, send the auth token as request headers via microvmAuthHeaders.

const client = yield* HttpClient.HttpClient;
const res = yield* client.get(`https://${vm.endpoint}/echo?message=hi`, {
headers: AWS.Lambda.microvmAuthHeaders(authToken),
});
const body = yield* res.json;

main is bundled with rolldown at deploy time. Unused code is tree-shaken. effect, alchemy, and @distilled.cloud are marked pure so unused parts prune more aggressively. Your app is not marked pure.

Mark additional packages as pure

Only list packages with no top-level side effects.

{
main: import.meta.url,
build: {
pure: { packages: ["my-lib", "@my-scope/*"] },
},
}

Turn it off

{
main: import.meta.url,
build: { pure: false },
}

MicrovmImage: External Images (your own Dockerfile)

Section titled “MicrovmImage: External Images (your own Dockerfile)”

Bring a build context directory containing a Dockerfile (any language). Alchemy zips and uploads it; AWS runs your Dockerfile. Your Dockerfile should build on a MicroVM-compatible base (e.g. FROM public.ecr.aws/lambda/microvms:al2023-minimal).

Flask app from a Dockerfile

const image = yield* AWS.Lambda.MicrovmImage("Flask", {
context: `${import.meta.dirname}/app`, // dir with Dockerfile + app.py
buildRole,
});

Custom Dockerfile path within the context

const image = yield* AWS.Lambda.MicrovmImage("Worker", {
context: `${import.meta.dirname}/app`,
dockerfile: "docker/worker.Dockerfile", // relative to `context`
buildRole,
});

Skip the build entirely and point at an artifact you already produced.

const image = yield* AWS.Lambda.MicrovmImage("Prebuilt", {
codeArtifact: { uri: "s3://my-bucket/app.zip" }, // or an ECR image URI
buildRole,
});

baseImage defaults to the latest AWS-managed al2023 base (discovered via listManagedMicrovmImages). Override it, and tune CPU/memory, explicitly.

const image = yield* AWS.Lambda.MicrovmImage("Sized", {
main: import.meta.filename,
buildRole,
baseImage: "arn:aws:lambda:us-east-1:aws:microvm-image:al2023-1",
cpuConfigurations: [{ architecture: "ARM_64" }],
resources: [{ minimumMemoryInMiB: 2048 }],
});
const image = yield* AWS.Lambda.MicrovmImage("Logged", {
main: import.meta.filename,
buildRole,
logging: { cloudWatch: { logGroup: "/aws/microvm/my-app" } },
// or disable: logging: { disabled: {} }
});

Give the MicroVM a managed egress path into your VPC with a NetworkConnector (reference it by ARN).

const egress = yield* AWS.Lambda.NetworkConnector("Egress", {
subnetIds: [subnet.subnetId],
securityGroupIds: [sg.groupId],
operatorRole: operatorRole.roleArn,
});
const image = yield* AWS.Lambda.MicrovmImage("Connected", {
main: import.meta.filename,
buildRole,
egressNetworkConnectors: [egress.networkConnectorArn],
});

The image is just the template. Launch and drive instances from a Lambda Function using the per-operation bindings (RunMicrovm, GetMicrovm, CreateAuthToken, TerminateMicrovm, …). Each binding’s IAM policy is scoped to this image automatically.

Always terminate the MicroVM you launched — wrap the work in Effect.ensuring so a failure (or a client retry) never leaks a running MicroVM against your account’s memory quota. Give the Function a generous timeout since it waits for the MicroVM to reach RUNNING in-line.

export default class Api extends AWS.Lambda.Function<Api>()(
"Api",
{ main: import.meta.filename, functionUrl: true, timeout: Duration.seconds(120) },
Effect.gen(function* () {
const runMicrovm = yield* AWS.Lambda.RunMicrovm(Sandbox);
const getMicrovm = yield* AWS.Lambda.GetMicrovm(Sandbox);
const createAuthToken = yield* AWS.Lambda.CreateAuthToken(Sandbox);
const terminateMicrovm = yield* AWS.Lambda.TerminateMicrovm(Sandbox);
return {
fetch: Effect.gen(function* () {
const vm = yield* runMicrovm({
idlePolicy: {
maxIdleDurationSeconds: 900,
suspendedDurationSeconds: 300,
autoResumeEnabled: true,
},
});
return yield* Effect.gen(function* () {
// wait until the MicroVM is RUNNING before connecting
yield* getMicrovm({ microvmIdentifier: vm.microvmId }).pipe(
Effect.flatMap((m) =>
m.state === "RUNNING"
? Effect.void
: Effect.fail(new Error(`microvm ${m.state}`)),
),
Effect.retry({ schedule: Schedule.spaced("2 seconds"), times: 30 }),
);
const { authToken } = yield* createAuthToken({
microvmIdentifier: vm.microvmId,
expirationInMinutes: 5,
allowedPorts: [{ port: 8080 }],
});
const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, {
endpoint: vm.endpoint,
authToken,
});
const reply = yield* sandbox.hello("world");
return yield* HttpServerResponse.json({ reply });
}).pipe(
// terminate on success OR failure — never leak a running MicroVM
Effect.ensuring(
terminateMicrovm({ microvmIdentifier: vm.microvmId }).pipe(
Effect.ignore,
),
),
// the in-VM endpoint calls need an HttpClient for this scope
Effect.provide(FetchHttpClient.layer),
);
}),
};
}).pipe(
Effect.provide(
Layer.mergeAll(
AWS.Lambda.RunMicrovmHttp,
AWS.Lambda.GetMicrovmHttp,
AWS.Lambda.CreateAuthTokenHttp,
AWS.Lambda.TerminateMicrovmHttp,
),
),
),
) {}

Source: src/AWS/Lambda/NetworkConnector.ts

A Lambda network connector that gives Lambda compute resources — notably MicrovmImage MicroVMs — a managed egress path into your VPC. The connector provisions elastic network interfaces (ENIs) in the subnets you specify so workloads can reach private resources such as databases, caches, and internal APIs.

Creation is asynchronous: the connector starts in PENDING while ENIs are provisioned (this can take several minutes) and the provider waits until it reaches ACTIVE. The connector name is immutable, so renaming it replaces the connector; the VPC configuration and operator role can be updated in place.

NetworkConnector: Creating a Network Connector

Section titled “NetworkConnector: Creating a Network Connector”
const connector = yield* AWS.Lambda.NetworkConnector("Egress", {
subnetIds: [subnetA.subnetId, subnetB.subnetId],
securityGroupIds: [securityGroup.groupId],
operatorRole: role.roleArn,
});
const connector = yield* AWS.Lambda.NetworkConnector("DualStack", {
subnetIds: [subnet.subnetId],
securityGroupIds: [securityGroup.groupId],
networkProtocol: "DualStack",
});

NetworkConnector: Using a Connector with MicroVMs

Section titled “NetworkConnector: Using a Connector with MicroVMs”

A connector is the producer; a MicrovmImage (or a per-run RunMicrovm call) is the consumer. Reference it by ARN in egressNetworkConnectors.

const image = yield* AWS.Lambda.MicrovmImage("Sandbox", {
main: import.meta.filename,
buildRole,
egressNetworkConnectors: [connector.networkConnectorArn],
});

Source: src/AWS/Lambda/Permission.ts

A Lambda permission that grants an AWS service or another account permission to invoke a function.

S3 Notification Permission

const perm = yield* Permission("S3Invoke", {
action: "lambda:InvokeFunction",
functionName: yield* fn.functionArn(),
principal: "s3.amazonaws.com",
sourceArn: yield* bucket.bucketArn,
sourceAccount: (yield* AWSEnvironment.current).accountId,
});

Cross Account Invoke

const perm = yield* Permission("CrossAccount", {
action: "lambda:InvokeFunction",
functionName: yield* fn.functionArn(),
principal: "123456789012",
});

Public Function URL

const perm = yield* Permission("PublicUrl", {
action: "lambda:InvokeFunctionUrl",
functionName: yield* fn.functionArn(),
principal: "*",
functionUrlAuthType: "NONE",
});

Source: src/AWS/Lambda/RestApiEventSource.ts

Connects a REST API (v1) route to the current Lambda function.

At deploy time this layer materializes the path Resource chain, the Method with an AWS_PROXY integration, and the API Gateway invoke Permission for each registered route — and registers each child as a RestApiBinding on the API so any Deployment of the same API is ordered after them. At runtime it dispatches matching REST proxy events to the registered handler.

RestApiEventSource: Handling REST API routes

Section titled “RestApiEventSource: Handling REST API routes”
yield* AWS.ApiGateway.onRestApiRoute(
api,
{ path: "/items", httpMethod: "GET" },
() =>
Effect.succeed({
statusCode: 200,
body: JSON.stringify({ items: [] }),
}),
);

Source: src/AWS/Lambda/ResumeMicrovm.ts

Runtime binding for ResumeMicrovm.

Bind it to a MicrovmImage to get a callable that resumes a suspended MicroVM by microvmIdentifier, restoring it to RUNNING.

const resumeMicrovm = yield* AWS.Lambda.ResumeMicrovm(Sandbox);
yield* resumeMicrovm({ microvmIdentifier: id });

Source: src/AWS/Lambda/RoomMessageReviewEventSource.ts

Connects an IVS Chat room’s message review handler to the current Lambda function.

At deploy time this layer injects the function ARN into the room’s messageReviewHandler through the room’s binding contract and materializes the lambda:InvokeFunction Permission for ivschat.amazonaws.com; at runtime it dispatches review invocations (matched on RoomArn) to the registered handler and returns the verdict to IVS Chat.

RoomMessageReviewEventSource: Reviewing room messages

Section titled “RoomMessageReviewEventSource: Reviewing room messages”
yield* IVSChat.onReviewMessage(room, (event) =>
Effect.succeed(
event.Content.includes("banned-word")
? { ReviewResult: "DENY", Attributes: { Reason: "moderated" } }
: undefined,
),
);

Source: src/AWS/Lambda/RunMicrovm.ts

Runtime binding for RunMicrovm.

Bind it to a MicrovmImage inside a Lambda Function to get a callable that launches a MicroVM from that image (the imageIdentifier is injected). The response carries the MicroVM endpoint; connect to it with an X-aws-proxy-auth token from CreateAuthToken.

const runMicrovm = yield* AWS.Lambda.RunMicrovm(Sandbox);
const vm = yield* runMicrovm({
idlePolicy: {
maxIdleDurationSeconds: 900,
suspendedDurationSeconds: 300,
autoResumeEnabled: true,
},
});

Source: src/AWS/Lambda/ScheduleEventSource.ts

Lambda runtime implementation for AWS.Scheduler.consumeSchedule(...) — the “cron handler” DX where a Lambda consumes its own EventBridge Scheduler invocations.

This layer does two things:

  1. At deploy time it creates the backing Schedule targeting the current Lambda function, plus the synthesized execution role that allows EventBridge Scheduler to invoke it.
  2. At runtime it matches incoming Lambda events against the schedule’s typed envelope (isScheduleEvent + the stable route id) and dispatches them to the supplied handler.

ScheduleEventSource: Consuming Scheduled Invocations

Section titled “ScheduleEventSource: Consuming Scheduled Invocations”

Run A Handler Every 5 Minutes

yield* AWS.Scheduler.consumeSchedule(
AWS.Scheduler.every("5 minutes"),
(event) => Effect.log(`fired at ${event.scheduledTime}`),
);

Nightly Cron With An Explicit Route Id

yield* AWS.Scheduler.consumeSchedule(
"NightlyCleanup",
AWS.Scheduler.cron("cron(0 3 * * ? *)"),
(event) => Effect.log(`cleanup ${event.executionId}`),
);

Source: src/AWS/Lambda/SecretRotationEventSource.ts

Lambda runtime implementation for AWS.SecretsManager.onSecretRotation(...).

This layer does three things at deploy time:

  1. Grants secretsmanager.amazonaws.com permission to invoke the current function (scoped to this account via aws:SourceAccount).
  2. Attaches the rotation-protocol IAM actions for the bound secret (DescribeSecret, GetSecretValue, PutSecretValue, UpdateSecretVersionStage on the secret + GetRandomPassword).
  3. Provisions the RotationSchedule configuring the secret’s rotation to invoke this function — threaded through the Permission so Secrets Manager’s invoke-permission validation passes.

At runtime it narrows incoming invocations to rotation events for the bound secret and forwards them to the supplied handler.

SecretRotationEventSource: Rotating Secrets

Section titled “SecretRotationEventSource: Rotating Secrets”
yield* SecretsManager.onSecretRotation(
secret,
{ rotationRules: { automaticallyAfter: "30 days" } },
(event) => rotate(event).pipe(Effect.orDie),
);

Source: src/AWS/Lambda/SuspendMicrovm.ts

Runtime binding for SuspendMicrovm.

Bind it to a MicrovmImage to get a callable that suspends a running MicroVM (snapshotting memory + disk) by microvmIdentifier.

const suspendMicrovm = yield* AWS.Lambda.SuspendMicrovm(Sandbox);
yield* suspendMicrovm({ microvmIdentifier: id });

Source: src/AWS/Lambda/TerminateMicrovm.ts

Runtime binding for TerminateMicrovm.

Bind it to a MicrovmImage to get a callable that terminates a running MicroVM by microvmIdentifier. Idempotent.

const terminateMicrovm = yield* AWS.Lambda.TerminateMicrovm(Sandbox);
yield* terminateMicrovm({ microvmIdentifier: id });

Source: src/AWS/Lambda/UpdateMicrovmImageVersion.ts

Runtime binding for UpdateMicrovmImageVersion.

Bind it to a MicrovmImage to update a version’s status (e.g. mark it ACTIVE/INACTIVE); the imageIdentifier is injected.

Source: src/AWS/Lambda/UserPoolTriggerEventSource.ts

Connects a Cognito user pool Lambda trigger to the current Lambda function.

At deploy time this layer injects the function ARN into the pool’s LambdaConfig through the pool’s binding contract and materializes the lambda:InvokeFunction Permission for cognito-idp.amazonaws.com; at runtime it dispatches matching trigger events (matched on userPoolId + triggerSource prefix) to the registered handler and returns the handler’s (mutated) event to Cognito.

UserPoolTriggerEventSource: Handling user pool triggers

Section titled “UserPoolTriggerEventSource: Handling user pool triggers”
yield* Cognito.onPreSignUp(pool, (event) =>
Effect.sync(() => Cognito.autoConfirmUser(event, { verifyEmail: true })),
);

Source: src/AWS/Lambda/Version.ts

An immutable numbered version of a managed Lambda function.

The provider publishes only after the Function’s code and configuration have settled. Re-applying unchanged code and versioned configuration reuses the existing version. Function-level operational changes, such as reserved concurrency, do not publish a new version.

Versions default to retain on removal. Moving or deleting an alias never deletes an older version, so in-flight durable executions can continue replaying against the code that started them. Use destroy() only when the exact numbered version is safe to remove.

const fn = yield* AWS.Lambda.Function("Handler", {
main: import.meta.resolve("./handler.ts"),
});
const version = yield* AWS.Lambda.Version("HandlerVersion", {
function: fn,
});
const version = yield* AWS.Lambda.Version("CampaignRunVersion", {
function: campaign.function,
});
const live = yield* AWS.Lambda.Alias("CampaignRunLive", {
version,
aliasName: "live",
});
import { destroy } from "alchemy/RemovalPolicy";
const disposable = yield* AWS.Lambda.Version("PreviewVersion", {
function: fn,
}).pipe(destroy());

Source: src/AWS/Lambda/WebSocketEventSource.ts

Connects a WebSocket API route to the current Lambda function.

At deploy time this layer materializes the AWS_PROXY Integration, the Route, and the API Gateway invoke Permission for each registered route key; at runtime it dispatches matching WebSocket proxy events to the registered handler.

WebSocketEventSource: Handling WebSocket routes

Section titled “WebSocketEventSource: Handling WebSocket routes”
const connections = yield* AWS.ApiGatewayV2.ManageConnections(stage);
yield* AWS.ApiGatewayV2.onWebSocketRoute(api, { routeKey: "$connect" }, () =>
Effect.succeed({ statusCode: 200 }),
);
yield* AWS.ApiGatewayV2.onWebSocketRoute(api, { routeKey: "$default" }, (event) =>
connections
.postToConnection({
ConnectionId: event.requestContext.connectionId,
Data: `echo:${event.body ?? ""}`,
})
.pipe(
Effect.asVoid,
Effect.catchTag("GoneException", () => Effect.void),
Effect.orDie,
),
);

Source: src/AWS/Lambda/WirelessDestinationEventSource.ts

Connects an IoT Wireless Destination’s uplink traffic to the current Lambda function.

At deploy time this layer creates the IoT topic rule named by the destination’s expression (the destination must use expressionType: "RuleName") with a Lambda action targeting this function, and grants iot.amazonaws.com permission to invoke it; at runtime it dispatches uplink invocations to the registered handler.

Section titled “WirelessDestinationEventSource: Consuming wireless uplinks”
yield* IoTWireless.consumeUplinks(destination, (uplinks) =>
uplinks.pipe(
Stream.runForEach((uplink) => Effect.log(uplink.PayloadData)),
Effect.orDie,
),
);