mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +00:00
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:
@@ -179,6 +179,71 @@ describe("renderToLambda", () => {
|
||||
expect(handle.renderId).toMatch(/^hf-render-[0-9a-f-]{36}$/);
|
||||
});
|
||||
|
||||
it("threads variables through the Step Functions execution input", async () => {
|
||||
const sfn = new FakeSFN();
|
||||
const s3 = new FakeS3();
|
||||
const variables = { title: "Hello Alice", accent: "#ff0000" };
|
||||
await renderToLambda({
|
||||
projectDir,
|
||||
bucketName: "test-bucket",
|
||||
stateMachineArn: "arn:aws:states:us-east-1:1234:stateMachine:hf",
|
||||
config: { ...baseConfig, variables },
|
||||
executionName: "smoke-variables",
|
||||
sfn: asSFNClient(sfn),
|
||||
s3: asS3Client(s3),
|
||||
});
|
||||
expect(sfn.starts).toHaveLength(1);
|
||||
const start = sfn.starts[0]!;
|
||||
// The execution input carries the variables under Config.variables —
|
||||
// the Step Functions state machine forwards `Config` verbatim into the
|
||||
// PlanEvent's `Config` field, where the handler spreads it into the
|
||||
// producer's DistributedRenderConfig.
|
||||
const input = start.input as { Config: { variables?: Record<string, unknown> } };
|
||||
expect(input.Config.variables).toEqual(variables);
|
||||
});
|
||||
|
||||
it("rejects a config whose variables blob would push the execution input over 256 KiB", async () => {
|
||||
const sfn = new FakeSFN();
|
||||
const s3 = new FakeS3();
|
||||
const huge = "x".repeat(260 * 1024);
|
||||
await expect(
|
||||
renderToLambda({
|
||||
projectDir,
|
||||
bucketName: "test-bucket",
|
||||
stateMachineArn: "arn:aws:states:us-east-1:1234:stateMachine:hf",
|
||||
config: { ...baseConfig, variables: { blob: huge } },
|
||||
executionName: "smoke-too-big",
|
||||
sfn: asSFNClient(sfn),
|
||||
s3: asS3Client(s3),
|
||||
}),
|
||||
).rejects.toThrow(/256.*KiB|templates-on-lambda/);
|
||||
// The reject must happen BEFORE StartExecution — uncaught oversize input
|
||||
// surfaces as States.DataLimitExceeded 50ms in, far from this call site.
|
||||
expect(sfn.starts).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("rejects a config whose variables contain non-JSON-safe values", async () => {
|
||||
const sfn = new FakeSFN();
|
||||
const s3 = new FakeS3();
|
||||
await expect(
|
||||
renderToLambda({
|
||||
projectDir,
|
||||
bucketName: "test-bucket",
|
||||
stateMachineArn: "arn:aws:states:us-east-1:1234:stateMachine:hf",
|
||||
config: {
|
||||
...baseConfig,
|
||||
// BigInt would throw at JSON.stringify time; catch it at the validator
|
||||
// boundary with a typed error instead.
|
||||
variables: { count: 9_007_199_254_740_993n } as unknown as Record<string, unknown>,
|
||||
},
|
||||
executionName: "smoke-bigint",
|
||||
sfn: asSFNClient(sfn),
|
||||
s3: asS3Client(s3),
|
||||
}),
|
||||
).rejects.toThrow(InvalidConfigError);
|
||||
expect(sfn.starts).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("propagates a missing executionArn as an error", async () => {
|
||||
const sfn = {
|
||||
async send(_cmd: unknown): Promise<unknown> {
|
||||
|
||||
Reference in New Issue
Block a user