AWS Lambda
Lambda is the platform with no scheduling and no Durable Objects at all — every invocation is a discrete call with no standing process behind it, and there's no native primitive for a stateful actor. What it does have, fully wired, is a queue: an SQS event source pushes batches straight into a generated handler branch, no polling loop required.
Configure the strategy
import { defineHeximonConfig } from "@heximon/build";
import { LambdaStrategy } from "@heximon/aws";
import { HttpPlugin } from "@heximon/http/compiler";
export default defineHeximonConfig({
platform: new LambdaStrategy(),
plugins: [new HttpPlugin()],
});
LambdaStrategy extends NodeStrategy — it's a node-class strategy (standard Node SSR externalization,
node_modules deployed alongside the handler), platform name "lambda".
What vp build emits
vp build # → dist/lambda.js
Point the Lambda function at dist/lambda.js with handler lambda.handler. LambdaPlugin emits
lambda-entry.js — a cold-start-once export const handler over the API Gateway HTTP API v2 / Function URL
event shape:
import { createServer, H3FetchClient } from "@heximon/http";
import { routes } from "./router.js";
import { createApp } from "./apps/main.wiring.js";
async function boot() {
let server;
const internalClient = new H3FetchClient((input, init) =>
server.fetch(new Request(new URL(input, "http://localhost"), init)));
const app = await createApp({ InternalClient: internalClient });
server = createServer(routes, app);
return server;
}
let appPromise;
export const handler = async (event) => {
appPromise ??= boot();
const server = await appPromise;
const request = toWebRequest(event);
const response = await server.fetch(request);
await otelProvider?.forceFlush();
return await toLambdaResult(response);
};
The event-to-Request adapter targets API Gateway HTTP API v2 / Lambda Function URL — the modern default:
event.requestContext.http.method/.path carry the method and path, event.headers is a flat single-value
map, event.cookies folds into a Cookie header, and a Base64-encoded body decodes via event.isBase64Encoded.
The Response serializes back as { statusCode, headers, body, isBase64Encoded: true }.
event.httpMethod, event.path, event.multiValueHeaders) — an app behind a v1 REST API
needs its own adapter, or should migrate to HTTP API / Function URL.SQS queue
List QueuePlugin and Lambda queue handlers run over a real push consumer — no polling. The strategy binds
LambdaSqsQueueConsumer (from @heximon/queue/sqs) as the QueueConsumer satisfier, which flips the queue
plugin's generated consumer to the push shape and grows lambda-entry.js an SQS branch:
import { SqsQueue, LambdaSqsQueueConsumer } from "@heximon/queue/sqs";
When an SQS batch arrives (event.Records[0].eventSource === "aws:sqs"), the entry detects it before the
HTTP path, resolves LambdaSqsQueueConsumer off the booted app, and returns drainBatch(event) — the AWS
ReportBatchItemFailures response, so SQS only redelivers the records that actually failed.
Point an SQS event
source mapping (with the ReportBatchItemFailures response type enabled) at the function; SqsQueue — or any
SQS publisher — feeds the queue. DLQ redrive is native SQS configuration, no extra wiring.
With no queue handlers declared, the SQS branch is simply absent and the entry is byte-identical to the HTTP-only shape.
Feature support
| Feature | Supported |
|---|---|
| HTTP controllers | Yes |
Queues (@heximon/queue / SQS) | Yes — via LambdaSqsQueueConsumer over an SQS event source mapping |
| Cron / scheduled invocations | No — configure EventBridge manually; each invocation is a separate Lambda call |
Durable Objects (@heximon/durable) | No — Lambda freezes between invocations; fails loud at build time |
Workflows (@heximon/workflow) | No |
| WebSocket upgrades | No — API Gateway WebSocket APIs use a different event shape |
Compiling a Durable Object or a workflow against LambdaStrategy emits a PlatformEdgeUnsupported diagnostic
naming "lambda" and the offending class before any code is generated — even though LambdaStrategy is a
node-class target (correct for externalization), the diagnostic is driven by supportedFeatures, not the
target class.
OpenTelemetry
Lambda freezes the execution context between invocations — no graceful shutdown hook, no waitUntil seam.
LambdaStrategy declares otelFlushModel: "perInvocation", which selects the @heximon/otel/lambda leg: a
SimpleSpanProcessor-backed provider whose forceFlush() runs synchronously inside the invocation, before the
handler returns — because anything after that point may never execute.
pnpm add @heximon/otel @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http
The OtelPlugin wires the Lambda leg automatically once it's listed and platform is LambdaStrategy.
S3 blob store
S3BlobStore (from @heximon/blob/s3) satisfies the BlobStore port with an AWS S3 backend — the natural
pairing for a Lambda deploy that needs object storage.
Dev
vp dev runs your app as a standard Node HTTP server — the strategy's built-in NodeStrategy dev default, no
AWS-local emulator required. The LambdaSqsQueueConsumer path and any SqsQueue producer still run against a
real (or locally-pointed) SQS queue in dev; only the Lambda invocation shape itself is emulated by the plain
Node server.
Ship it
@heximon/deploy deliberately does not wire Lambda: AWS has no prebuilt one-shot CLI deploy the way
wrangler deploy or vercel deploy --prebuilt do — a Lambda deploy is provision-first (the function, API
Gateway or a Function URL, IAM), and updating code needs aws lambda update-function-code against a function
that already exists.
Ship dist/lambda.js with your own infrastructure tooling (Terraform, CDK, SAM, or the
AWS CLI directly) rather than expect heximon deploy to do it.
See also
- Queue — the
Queueport, channel handlers, and the sharedPollingQueueConsumerbase. - Observability —
OtelPlugin, spans, and the injectableTracer. - Blob storage —
BlobStoreand the S3/R2/filesystem/memory drivers. - Deploy overview — how
supportedFeaturesgates a build across every platform.