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.
Runtimes and packages
Section titled “Runtimes and packages”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.
What a handler sees
Section titled “What a handler sees”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.
Limits
Section titled “Limits”| 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 |
Asynchronous invocations
Section titled “Asynchronous invocations”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.
Event source mappings
Section titled “Event source mappings”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.
Watching functions run
Section titled “Watching functions run”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`); }, },});