Skip to content

Lambda

MiniStack stores functions and answers the Lambda API. Each invocation runs a Node handler in an execution environment: a child process in Node, or a module Web Worker in a page. Create and invoke functions with the ordinary SDK. What isn’t covered here behaves as it does on AWS.

import { clientConfig, createRegion } from 'pocket-region/browser';
import { LambdaClient, CreateFunctionCommand, InvokeCommand } from '@aws-sdk/client-lambda';
import { strToU8, zipSync } from 'fflate';
const region = await createRegion();
const lambda = new LambdaClient(clientConfig(region));
const code = 'export const handler = async (event) => `hello, ${event.name}`;';
await lambda.send(new CreateFunctionCommand({
FunctionName: 'hello',
Runtime: 'nodejs22.x',
Handler: 'index.handler',
Role: 'arn:aws:iam::000000000000:role/lambda',
Code: { ZipFile: zipSync({ 'index.mjs': strToU8(code) }) },
}));
const { Payload } = await lambda.send(new InvokeCommand({ FunctionName: 'hello', Payload: '{ "name": "world" }' }));
console.log(Payload.transformToString());
await region.stop();

The deployment package is any zip. This one is built with fflate.

Only nodejs* runtimes run. python* and provided.* functions fail each invocation with a Runtime.HandlerError naming the runtime. Java, .NET, and Ruby functions answer MiniStack’s own 200 “Mock response” without running.

Node Page
Handler: 'index.handler' loads index.mjs, index.js, or index.cjs index.mjs or index.js
CommonJS Works, with exports read from default Doesn’t work
Relative imports Work Work, between the package’s ES modules. export … from and circular imports don’t
Bare imports, such as @aws-sdk/client-s3 Resolve from your project’s node_modules, the nearest one above the working directory Load from where the region’s resolve says: by default the page’s import map, else jsDelivr. SDK clients get the region as their defaults
Imports from a full URL Work Work
Parent environment variables Only PATH None

Lambda preinstalls the AWS SDK. A page loads it for the handler, and Node finds it in your project, so a handler that imports it bare and creates a client with no options runs in either as written.

Lambda’s standard AWS_LAMBDA_* variables are set, along with the function’s own Environment.Variables, which win. These point the handler at the region:

Variable Value
AWS_ENDPOINT_URL The region: http://127.0.0.1:<port> in Node, http://localhost:<port> in a page
AWS_REGION, AWS_DEFAULT_REGION us-east-1
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY test

So an SDK client created inside a handler with no options reaches the region, as it would on Lambda. In a page, process.env is provided for these, and fetch calls to the region’s port on localhost, 127.0.0.1, or [::1] are routed to the region. Every other fetch goes out as normal. One exception: the SDK’s browser build reads no environment, so a page handler that bundles the SDK rather than importing it bare must pass region, endpoint, credentials, and forcePathStyle from process.env itself.

Behaviour
Timeout Past it, the invocation fails with Runtime.ExitError, and that environment is stopped rather than reused
Concurrency ReservedConcurrentExecutions per function, and MiniStack’s account cap across all of them, measured at 97 in Node and in a page
An idle environment Stopped after 60 s
A code or configuration change New invocations get fresh environments. The old ones finish what they’re running

A failed Event invocation is retried twice by default, or up to the function’s MaximumRetryAttempts, with backoff of 1 s then 2 s, capped at 30 s, rather than AWS’s minutes. When retries run out, the event goes to the OnFailure destination or the DeadLetterConfig target. SQS targets are tested, SNS targets aren’t.

An EventBridge rule that targets a function runs it once, as MiniStack does, with no retries.

MiniStack’s poller hands a function batches from SQS queues, Kinesis streams, and DynamoDB Streams, checking every second while a mapping exists. A stream mapping runs one batch at a time. An SQS mapping runs as many batches at once as the function’s concurrency allows, taking the next as each finishes, which MiniStack doesn’t do yet. All three sources are tested. ReportBatchItemFailures is tested on SQS, where a handler’s batchItemFailures stay on the queue. As in MiniStack, stream mappings ignore it and move past the whole batch.

Pass lambda to createRegion:

type LambdaEnvironment = { functionName: string; environment: string };
type LambdaOutput = LambdaEnvironment & { text: string };
type LambdaObserver = {
onOutput?(output: LambdaOutput): void;
onEvent?(event: LambdaEvent): void;
};

environment names one execution environment by its log stream. onOutput gets every line a handler writes as text, including the START, END, and REPORT lines. The region’s own onOutput hears the same lines as stdout, without the function’s name. onEvent gets a LambdaEvent: the LambdaEnvironment fields, plus one of:

kind phase Fields
environment started, stopped reason when stopped
invocation started requestId, event (JSON text), coldStart
invocation completed requestId, durationMs, initMs (first invocation only), failed

Throttles aren’t reported here. MiniStack publishes them as the AWS/Lambda Throttles metric in CloudWatch.

const region = await createRegion({
lambda: {
onOutput: ({ functionName, environment, text }) => console.log(`[${functionName} ${environment}] ${text}`),
onEvent: (event) => { if (event.kind === 'invocation' && event.phase === 'completed' && event.failed) console.warn(`${event.functionName} failed`); },
},
});