AWS Lambda Custom Runtime for MetaScript. Compiles to a native bootstrap
binary that plugs straight into Lambda's provided.al2 / provided.al2023
runtimes.
Status: v0.1 — usable for experiments and internal projects. Primary real user: Metacraft Studio. Not production-commercial-stable.
- Small cold start. No JIT, no VM warm-up — the binary is what runs.
- Small binaries. A plain-HTTP handler compiles to ~1 MB; HTTPS (via
fetchorstd/https) adds mbedtls (~4 MB total). - Typed events.
parseApiGwV2gives you a struct, not astring. - Result-first errors. Handler returns
Result<string, LambdaError>; failures auto-format to AWS's/errorshape.
Write the handler:
import {
startLambda, LambdaContext, LambdaError, getTimeRemaining,
} from "@metascript/lambda-runtime";
import {
parseApiGwV2, serializeApiGwV2Response, ApiGwV2Response,
} from "@metascript/lambda-runtime/events";
import { createHeaders, setHeader } from "std/http";
function handler(event: string, ctx: LambdaContext): Result<string, LambdaError> {
const reqR = parseApiGwV2(event);
if (!reqR.ok) {
return Result.err({ errorType: "BadEvent", errorMessage: reqR.error });
}
const req = reqR.value;
const headers = createHeaders();
setHeader(headers, "content-type", "application/json");
const resp: ApiGwV2Response = {
statusCode: 200,
headers: headers,
body: `{"method":"${req.method}","path":"${req.path}","timeLeft":${getTimeRemaining(ctx)}}`,
};
return Result.ok(serializeApiGwV2Response(resp));
}
startLambda(handler);
Build + package:
# Linux arm64 (Graviton) — cheapest + fastest cold start on Lambda
msc build handler.ms --target=c --os=linux --cpu=arm64 --release --strip --output=bootstrap
chmod +x bootstrap
zip deploy.zip bootstrapDeploy (AWS CLI):
aws lambda create-function \
--function-name metascript-hello \
--runtime provided.al2023 \
--architectures arm64 \
--handler bootstrap \
--role arn:aws:iam::<account>:role/lambda-basic-exec \
--zip-file fileb://deploy.zipWire an HTTP API to invoke it, or call directly:
aws lambda invoke --function-name metascript-hello \
--payload '{"version":"2.0","rawPath":"/hi","headers":{},"body":"","isBase64Encoded":false,"requestContext":{"requestId":"r-1","http":{"method":"GET","path":"/hi"}}}' \
out.json
cat out.json| Symbol | Purpose |
|---|---|
startLambda(handler) |
Start the runtime loop. Blocks. |
LambdaContext |
Per-invocation fields: requestId, deadline, traceId, function info, etc. |
LambdaError |
{ errorType, errorMessage } — serialized to AWS /error format. |
LambdaHandler |
(event: string, ctx: LambdaContext) → Result<string, LambdaError>. |
getTimeRemaining(ctx) |
int64 ms until deadline. Negative means overdue. Mirrors AWS SDK. |
| Symbol | Purpose |
|---|---|
ApiGwV2Event |
Parsed HTTP API / Function URL request: method, path, headers, body, requestId. |
ApiGwV2Response |
Response builder: statusCode, headers, body. |
parseApiGwV2(json) |
Result<ApiGwV2Event, string>. |
serializeApiGwV2Response(resp) |
JSON string ready for /response POST. |
Event types in scope for v0.1: APIGw v2 / Function URL only. SQS, S3,
EventBridge, APIGw v1 deferred — hand-parse via std/core/json in the
meantime, or open an issue if you hit one.
- Invocation loop: GET
/invocation/next(blocks), dispatch to handler, POST/responseor/error, repeat. - Bounded retry: 3 consecutive transport failures → process exits,
Lambda recycles the container. Counter resets after a successful
invocation cycle. Mirrors
aws-lambda-cpp. - X-Ray trace propagation:
_X_AMZN_TRACE_IDenv var is set per invocation from theLambda-Runtime-Trace-Idheader so AWS SDK child calls inherit the trace context. - Handler errors don't kill the loop:
Result.errgoes to/error, runtime moves on to the next invocation.
- No
/init/erroron cold-start crash — top-level MS exceptions don't yet auto-report to Lambda. - No persistent HTTP client / connection pool. Runtime API calls open a new TCP connection each invocation. Measured <0.2 ms/req on localhost — not a bottleneck today, but a candidate for v0.2 if real workloads argue for it.
- Event types beyond APIGw v2 (SQS, S3, EventBridge) not yet typed —
hand-parse via
std/core/json. - No SnapStart support (runtime concern).
lambda-runtime/
├── index.cms barrel — public API
├── types.cms LambdaContext, LambdaError, LambdaHandler, getTimeRemaining
├── runtime.cms startLambda loop (bounded retry + X-Ray + POST /error)
├── environment.cms readConfig — AWS_LAMBDA_* env vars at cold start
├── events/
│ ├── index.cms barrel
│ └── apigw-v2.cms parseApiGwV2 + serializeApiGwV2Response
└── examples/
├── hello/ raw-string handler
└── apigw-hello/ typed APIGw v2 handler + deploy recipe
Inline tests live next to the code they cover — run with:
msc test lambda-runtime/events/apigw-v2.cmsexamples/apigw-hello/deploy.md— step-by-step AWS deploy walkthroughCLAUDE.md— design notes, reference alignment (aws-lambda-cpp), open questionshttps://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html— AWS Runtime API spec