Heximon Logo
Deploy

AWS Lambda

LambdaStrategy, lambda-entry.js, API Gateway HTTP API v2 event adapter, LambdaSqsQueueConsumer, per-invocation OTel flush.

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

heximon.config.ts
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

terminal
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:

lambda-entry.js (generated, simplified)
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 }.

The generated entry targets API Gateway v2 (HTTP API). API Gateway v1 (REST API) uses a different event shape entirely (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

FeatureSupported
HTTP controllersYes
Queues (@heximon/queue / SQS)Yes — via LambdaSqsQueueConsumer over an SQS event source mapping
Cron / scheduled invocationsNo — 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 upgradesNo — 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 Queue port, channel handlers, and the shared PollingQueueConsumer base.
  • ObservabilityOtelPlugin, spans, and the injectable Tracer.
  • Blob storageBlobStore and the S3/R2/filesystem/memory drivers.
  • Deploy overview — how supportedFeatures gates a build across every platform.
Copyright © 2026