feat(aws-lambda): validate variables + 256 KiB Step Functions input cap (#976)

Add client-side validation for the new config.variables field
(introduced in PR 9.1) and a 256 KiB cap on the full Step Functions
Standard execution input. Both checks throw a typed InvalidConfigError
BEFORE the SDK calls StartExecution — catching the obvious mistakes
locally instead of as a States.DataLimitExceeded 50 ms into the
execution.

validateVariablesPayload walks the variables tree and rejects:
- functions, Symbols, BigInts, non-finite numbers
- undefined leaves (silently dropped by JSON.stringify — would
  surprise the caller when their value doesn't show up in the render)
- non-plain objects (Date, Map, class instances) — Date's toJSON does
  round-trip as a string, but the composition gets a string, not a
  Date, so explicit reject is clearer

validateStepFunctionsInputSize measures the actual UTF-8 byte length
of JSON.stringify(input) against the 256 KiB cap. We use Standard
workflows (per the plan §6.2 / §15.2) for execution-history
visibility, so the cap is 256 KiB (Express would be 32 KiB). The error
message names the actual byte count, the cap, and points at the
templates-on-lambda#working-with-large-variables section so users
know to URL-reference media assets instead of inlining them.

Both helpers are exported from @hyperframes/aws-lambda/sdk so adapters
that build custom Step Functions inputs (batch verbs, future Temporal
ports) can reuse the same gates.

Phase 9 PR 9.2 of the distributed rendering plan.
This commit is contained in:
James Russo
2026-05-19 19:53:31 -04:00
committed by GitHub
parent 0decb88946
commit 87fdd556c4
5 changed files with 451 additions and 3 deletions
+14 -1
View File
@@ -24,7 +24,10 @@ import type { SerializableDistributedRenderConfig } from "../events.js";
import { formatExtension } from "../formatExtension.js";
import { formatS3Uri } from "../s3Transport.js";
import { deploySite, type SiteHandle } from "./deploySite.js";
import { validateDistributedRenderConfig } from "./validateConfig.js";
import {
validateDistributedRenderConfig,
validateStepFunctionsInputSize,
} from "./validateConfig.js";
/** Options for {@link renderToLambda}. */
export interface RenderToLambdaOptions {
@@ -70,6 +73,7 @@ export interface RenderHandle {
startedAt: string;
}
// fallow-ignore-next-line complexity
export async function renderToLambda(opts: RenderToLambdaOptions): Promise<RenderHandle> {
validateDistributedRenderConfig(opts.config);
@@ -108,6 +112,15 @@ export async function renderToLambda(opts: RenderToLambdaOptions): Promise<Rende
Config: opts.config,
};
// Reject oversize input client-side. Step Functions Standard caps the
// execution input at 256 KiB; without this check, the input bloat
// (typically from `config.variables` containing inlined media) surfaces
// as `States.DataLimitExceeded` 50 ms into the execution, far from the
// caller's stack frame. Measured AFTER `deploySite` so the synthesised
// `ProjectS3Uri` is counted (a few hundred bytes either way, but the
// check should be against the actual wire payload).
validateStepFunctionsInputSize(input);
const sfn = opts.sfn ?? new SFNClient({ region: opts.region });
const startedAt = new Date().toISOString();
const response = await sfn.send(