@hyperframes/aws-lambda 0.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/dist/cdk/HyperframesRenderStack.d.ts +65 -0
- package/dist/cdk/HyperframesRenderStack.d.ts.map +1 -0
- package/dist/cdk/index.d.ts +10 -0
- package/dist/cdk/index.d.ts.map +1 -0
- package/dist/cdk/index.js +263 -0
- package/dist/cdk/index.js.map +7 -0
- package/dist/chromium.d.ts +77 -0
- package/dist/chromium.d.ts.map +1 -0
- package/dist/events.d.ts +120 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/formatExtension.d.ts +10 -0
- package/dist/formatExtension.d.ts.map +1 -0
- package/dist/handler.d.ts +42 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +432 -0
- package/dist/handler.js.map +7 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +951 -0
- package/dist/index.js.map +7 -0
- package/dist/s3Transport.d.ts +55 -0
- package/dist/s3Transport.d.ts.map +1 -0
- package/dist/sdk/costAccounting.d.ts +51 -0
- package/dist/sdk/costAccounting.d.ts.map +1 -0
- package/dist/sdk/deploySite.d.ts +55 -0
- package/dist/sdk/deploySite.d.ts.map +1 -0
- package/dist/sdk/getRenderProgress.d.ts +72 -0
- package/dist/sdk/getRenderProgress.d.ts.map +1 -0
- package/dist/sdk/index.d.ts +16 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +578 -0
- package/dist/sdk/index.js.map +7 -0
- package/dist/sdk/renderToLambda.d.ts +66 -0
- package/dist/sdk/renderToLambda.d.ts.map +1 -0
- package/dist/sdk/validateConfig.d.ts +35 -0
- package/dist/sdk/validateConfig.d.ts.map +1 -0
- package/package.json +84 -0
- package/scripts/_formatBytes.ts +15 -0
- package/scripts/build-zip.ts +480 -0
- package/scripts/probe-beginframe.dockerfile +61 -0
- package/scripts/probe-beginframe.ts +157 -0
- package/scripts/verify-zip-size.ts +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @hyperframes/aws-lambda
|
|
2
|
+
|
|
3
|
+
AWS Lambda adapter for HyperFrames distributed rendering. Ships three
|
|
4
|
+
things together:
|
|
5
|
+
|
|
6
|
+
1. The **Lambda handler** that wraps the OSS `plan` / `renderChunk` /
|
|
7
|
+
`assemble` primitives behind a single dispatch boundary Step Functions
|
|
8
|
+
can drive (`src/handler.ts`).
|
|
9
|
+
2. A **client-side SDK** — `renderToLambda`, `getRenderProgress`,
|
|
10
|
+
`deploySite`, plus `validateDistributedRenderConfig` and
|
|
11
|
+
`computeRenderCost` (`src/sdk/`).
|
|
12
|
+
3. An **`aws-cdk-lib` L2 construct** (`HyperframesRenderStack`) that
|
|
13
|
+
provisions the same topology as `examples/aws-lambda/template.yaml`
|
|
14
|
+
inside an adopter's own CDK app (`src/cdk/`).
|
|
15
|
+
|
|
16
|
+
The handler ZIP and the SAM template still drive a maintainer-run real-AWS
|
|
17
|
+
smoke flow; the SDK + CDK are the supported public surface for adopters.
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ Step Functions state machine │
|
|
24
|
+
│ Plan → Map(N) RenderChunk → Assemble │
|
|
25
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
26
|
+
│ dispatches by event.Action
|
|
27
|
+
▼
|
|
28
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ One Lambda function (this package's `dist/handler.zip`) │
|
|
30
|
+
│ handler.mjs │
|
|
31
|
+
│ ├─ Action="plan" → @hyperframes/producer/distributed │
|
|
32
|
+
│ ├─ Action="renderChunk" → @hyperframes/producer/distributed │
|
|
33
|
+
│ └─ Action="assemble" → @hyperframes/producer/distributed │
|
|
34
|
+
│ bin/ffmpeg — ffmpeg-static │
|
|
35
|
+
│ node_modules/@sparticuz/chromium/ — Lambda-optimised Chromium │
|
|
36
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
37
|
+
│ pure functions over local paths
|
|
38
|
+
▼
|
|
39
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ S3 bucket — plan tarball + per-chunk outputs + final mp4 │
|
|
41
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The handler downloads inputs from S3 into `/tmp`, calls the OSS primitive,
|
|
45
|
+
uploads outputs back to S3, and returns a small JSON result that fits
|
|
46
|
+
inside Step Functions' history budget (under 200 bytes per chunk).
|
|
47
|
+
|
|
48
|
+
## Chrome runtime
|
|
49
|
+
|
|
50
|
+
The package supports two Chromium sources:
|
|
51
|
+
|
|
52
|
+
| Source | Default | Size | When to pick it |
|
|
53
|
+
| ------------------------------- | ------- | ------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `@sparticuz/chromium` | yes | ~70 MiB compressed | Lambda. Decompresses into `/tmp` at runtime; the rest of the ecosystem already uses it for headless-Chrome-in-Lambda. |
|
|
55
|
+
| Bundled `chrome-headless-shell` | no | ~140 MiB | Fallback. Used if `@sparticuz/chromium` ever drops `HeadlessExperimental.beginFrame` support. |
|
|
56
|
+
|
|
57
|
+
Pick the source at build time:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bun run --cwd packages/aws-lambda build:zip
|
|
61
|
+
bun run --cwd packages/aws-lambda build:zip -- --source=chrome-headless-shell
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The handler reads `HYPERFRAMES_LAMBDA_CHROME_SOURCE` at boot. The build
|
|
65
|
+
script sets that env var via Lambda function configuration in
|
|
66
|
+
`examples/aws-lambda/template.yaml`.
|
|
67
|
+
|
|
68
|
+
## BeginFrame regression guard
|
|
69
|
+
|
|
70
|
+
HyperFrames' renderer drives Chrome via the CDP
|
|
71
|
+
`HeadlessExperimental.beginFrame` command — same path the K8s deploy uses.
|
|
72
|
+
The Lambda adapter assumes that `@sparticuz/chromium`'s
|
|
73
|
+
chrome-headless-shell build honours BeginFrame. To prove it (and re-prove
|
|
74
|
+
it on every release), the package ships a Docker probe:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Build the Lambda-like container and run the probe.
|
|
78
|
+
bun run --cwd packages/aws-lambda probe:beginframe:docker
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The probe boots `@sparticuz/chromium` inside
|
|
82
|
+
`public.ecr.aws/lambda/nodejs:22` and asserts CDP `beginFrame` with
|
|
83
|
+
`screenshot: true` returns a PNG buffer. Exit code 0 = green; non-zero =
|
|
84
|
+
fall back to bundling chrome-headless-shell directly via `--source=chrome-headless-shell`.
|
|
85
|
+
|
|
86
|
+
## Building the ZIP
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
bun install # at the monorepo root
|
|
90
|
+
bun run --cwd packages/aws-lambda build:zip # → packages/aws-lambda/dist/handler.zip
|
|
91
|
+
bun run --cwd packages/aws-lambda verify:zip-size # CI gate
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The build script bundles `src/handler.ts` via esbuild, stages
|
|
95
|
+
`@sparticuz/chromium` and `puppeteer-core` under `node_modules/`, copies
|
|
96
|
+
ffmpeg-static into `bin/`, and zips the result. The unzipped layout is
|
|
97
|
+
designed to extract cleanly into Lambda's `/var/task/`.
|
|
98
|
+
|
|
99
|
+
`verify:zip-size` enforces:
|
|
100
|
+
|
|
101
|
+
- Unzipped ≤ 248 MiB (in-house budget; Lambda hard ceiling is 250 MiB unzipped — AWS docs label this "250 MB" but use binary mebibytes)
|
|
102
|
+
- Zipped ≤ 150 MiB (in-house budget; Lambda has no hard zipped cap for S3-deployed functions)
|
|
103
|
+
|
|
104
|
+
CI fails the PR if either is exceeded.
|
|
105
|
+
|
|
106
|
+
## Running tests
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bun run --cwd packages/aws-lambda test # unit tests (no Chrome)
|
|
110
|
+
bun run --cwd packages/aws-lambda probe:beginframe # local probe (Linux only)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Using the SDK
|
|
114
|
+
|
|
115
|
+
After deploying the stack (via the SAM template, CDK construct below, or
|
|
116
|
+
your own CFN of choice), drive renders from Node:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { deploySite, getRenderProgress, renderToLambda } from "@hyperframes/aws-lambda";
|
|
120
|
+
|
|
121
|
+
// One-time upload per project version.
|
|
122
|
+
const site = await deploySite({
|
|
123
|
+
projectDir: "./my-composition",
|
|
124
|
+
bucketName: "hyperframes-render-bucket",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Start a render. Returns immediately — does NOT poll.
|
|
128
|
+
const handle = await renderToLambda({
|
|
129
|
+
siteHandle: site,
|
|
130
|
+
bucketName: site.bucketName,
|
|
131
|
+
stateMachineArn: "arn:aws:states:us-east-1:123:stateMachine:hyperframes-render",
|
|
132
|
+
config: {
|
|
133
|
+
fps: 30,
|
|
134
|
+
width: 1920,
|
|
135
|
+
height: 1080,
|
|
136
|
+
format: "mp4",
|
|
137
|
+
chunkSize: 240,
|
|
138
|
+
maxParallelChunks: 16,
|
|
139
|
+
runtimeCap: "lambda",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Poll progress + cost on your own cadence.
|
|
144
|
+
const progress = await getRenderProgress({ executionArn: handle.executionArn });
|
|
145
|
+
console.log(progress.overallProgress, progress.costs.displayCost);
|
|
146
|
+
if (progress.status === "SUCCEEDED" && progress.outputFile) {
|
|
147
|
+
console.log("Render landed at", progress.outputFile.s3Uri);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`renderToLambda` validates the config client-side via
|
|
152
|
+
`validateDistributedRenderConfig` and throws a typed `InvalidConfigError`
|
|
153
|
+
before the Step Functions execution starts, so shape errors surface
|
|
154
|
+
synchronously instead of as opaque `ExecutionFailed` results.
|
|
155
|
+
|
|
156
|
+
`getRenderProgress` reports an approximate per-render cost
|
|
157
|
+
(`accruedSoFarUsd` plus a formatted `displayCost`) derived from Lambda
|
|
158
|
+
billed-duration × memory × the us-east-1 on-demand rate plus the Step
|
|
159
|
+
Functions transition price. The math is documented in
|
|
160
|
+
`src/sdk/costAccounting.ts`; numbers are best-effort and exclude S3
|
|
161
|
+
transfer.
|
|
162
|
+
|
|
163
|
+
## Using the CDK construct
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { App, Stack } from "aws-cdk-lib";
|
|
167
|
+
import { HyperframesRenderStack } from "@hyperframes/aws-lambda/cdk";
|
|
168
|
+
|
|
169
|
+
const app = new App();
|
|
170
|
+
const stack = new Stack(app, "MyApp");
|
|
171
|
+
const render = new HyperframesRenderStack(stack, "Render", {
|
|
172
|
+
// optional: reservedConcurrency: 8,
|
|
173
|
+
// optional: lambdaMemoryMb: 10240,
|
|
174
|
+
// optional: chromeSource: "sparticuz",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Re-export so an adopter app can wire dashboards / SNS topics.
|
|
178
|
+
new CfnOutput(stack, "RenderBucketName", { value: render.bucket.bucketName });
|
|
179
|
+
new CfnOutput(stack, "StateMachineArn", { value: render.stateMachine.stateMachineArn });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`aws-cdk-lib` and `constructs` are **optional peer dependencies**: SDK-only
|
|
183
|
+
consumers don't pull them at runtime. The construct itself imports from
|
|
184
|
+
`@hyperframes/aws-lambda/cdk`.
|
|
185
|
+
|
|
186
|
+
## What's still ahead
|
|
187
|
+
|
|
188
|
+
- `hyperframes lambda` CLI (deploy / sites create / render / progress / destroy) — PR 6.5.
|
|
189
|
+
- IAM bootstrap subcommand (`policies role | user | validate`) — PR 6.9.
|
|
190
|
+
- Lambda-local regression harness (`--mode=lambda-local`) — PR 6.6.
|
|
191
|
+
- Adopter-facing migration guide — PR 6.8.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `HyperframesRenderStack` — aws-cdk-lib L2 construct that emits the same
|
|
3
|
+
* topology as `examples/aws-lambda/template.yaml`.
|
|
4
|
+
*
|
|
5
|
+
* Adopters who embed HyperFrames inside their own CDK app can extend this
|
|
6
|
+
* construct or compose alongside it; the construct exposes its `.bucket`,
|
|
7
|
+
* `.renderFunction`, and `.stateMachine` properties so additional
|
|
8
|
+
* resources (alarms, dashboards, SNS topics) can be wired without
|
|
9
|
+
* re-deriving the ARNs from a stack export.
|
|
10
|
+
*
|
|
11
|
+
* `aws-cdk-lib` and `constructs` are **peerDependencies**. The package
|
|
12
|
+
* still type-checks (and the snapshot test still runs) because they're
|
|
13
|
+
* also `devDependencies`, but adopters who only consume the SDK side of
|
|
14
|
+
* `@hyperframes/aws-lambda` don't pull the CDK tree at runtime.
|
|
15
|
+
*
|
|
16
|
+
* Drift from the SAM template is guarded by the snapshot test
|
|
17
|
+
* (`HyperframesRenderStack.snapshot.test.ts`), which diffs the synthed
|
|
18
|
+
* CloudFormation against the SAM-rendered CloudFormation modulo
|
|
19
|
+
* normalisation.
|
|
20
|
+
*/
|
|
21
|
+
import { RemovalPolicy } from "aws-cdk-lib";
|
|
22
|
+
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
23
|
+
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
24
|
+
import * as sfn from "aws-cdk-lib/aws-stepfunctions";
|
|
25
|
+
import { Construct } from "constructs";
|
|
26
|
+
/** Construction-time props for {@link HyperframesRenderStack}. */
|
|
27
|
+
export interface HyperframesRenderStackProps {
|
|
28
|
+
/** Name prefix applied to function / state-machine / alarm names. Default `"hyperframes"`. */
|
|
29
|
+
projectName?: string;
|
|
30
|
+
/** Lambda memory in MB. Allowed: 2048..10240 in 1024 steps. Default 10240. */
|
|
31
|
+
lambdaMemoryMb?: 2048 | 3072 | 4096 | 5120 | 6144 | 7168 | 8192 | 9216 | 10240;
|
|
32
|
+
/** Per-invocation Lambda timeout. Default 900 (15 min, Lambda hard cap). */
|
|
33
|
+
lambdaTimeoutSec?: number;
|
|
34
|
+
/** Lambda reserved concurrency cap. `undefined` = unreserved (account default). */
|
|
35
|
+
reservedConcurrency?: number;
|
|
36
|
+
/** Which Chrome runtime was bundled into the handler ZIP. Default `"sparticuz"`. */
|
|
37
|
+
chromeSource?: "sparticuz" | "chrome-headless-shell";
|
|
38
|
+
/** Threshold for the runaway-invocations alarm. Default 1000 invocations/hour. */
|
|
39
|
+
chunkInvocationAlarmThreshold?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Absolute path to the handler ZIP produced by
|
|
42
|
+
* `bun run --cwd packages/aws-lambda build:zip`. Defaults to the
|
|
43
|
+
* package-relative path the build script writes to. Adopters who
|
|
44
|
+
* deploy the published handler ZIP set this explicitly.
|
|
45
|
+
*/
|
|
46
|
+
handlerZipPath?: string;
|
|
47
|
+
/** S3 bucket retention policy on stack delete. Default RETAIN. */
|
|
48
|
+
bucketRemovalPolicy?: RemovalPolicy;
|
|
49
|
+
}
|
|
50
|
+
export declare class HyperframesRenderStack extends Construct {
|
|
51
|
+
/** S3 bucket for plan tarballs, chunk outputs, and final renders. */
|
|
52
|
+
readonly bucket: s3.Bucket;
|
|
53
|
+
/** The single Lambda function dispatching plan / renderChunk / assemble. */
|
|
54
|
+
readonly renderFunction: lambda.Function;
|
|
55
|
+
/** The Step Functions state machine orchestrating the render. */
|
|
56
|
+
readonly stateMachine: sfn.StateMachine;
|
|
57
|
+
constructor(scope: Construct, id: string, props?: HyperframesRenderStackProps);
|
|
58
|
+
/**
|
|
59
|
+
* Build the state-machine chain. Kept in a single method so the SAM
|
|
60
|
+
* template and this construct can be diffed shape-for-shape during
|
|
61
|
+
* the snapshot test.
|
|
62
|
+
*/
|
|
63
|
+
private buildStateMachineDefinition;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=HyperframesRenderStack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HyperframesRenderStack.d.ts","sourceRoot":"","sources":["../../src/cdk/HyperframesRenderStack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,EAAY,aAAa,EAAQ,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,KAAK,GAAG,MAAM,+BAA+B,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,kEAAkE;AAClE,MAAM,WAAW,2BAA2B;IAC1C,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;IAC/E,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,YAAY,CAAC,EAAE,WAAW,GAAG,uBAAuB,CAAC;IACrD,kFAAkF;IAClF,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,aAAa,CAAC;CACrC;AAOD,qBAAa,sBAAuB,SAAQ,SAAS;IACnD,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC;IACzC,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,GAAG,CAAC,YAAY,CAAC;gBAE5B,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,GAAE,2BAAgC;IAmHjF;;;;OAIG;IACH,OAAO,CAAC,2BAA2B;CA2IpC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDK subpath export — `@hyperframes/aws-lambda/cdk`.
|
|
3
|
+
*
|
|
4
|
+
* Pulled into its own subpath so SDK-only consumers don't import
|
|
5
|
+
* `aws-cdk-lib`. The construct itself depends on `aws-cdk-lib` and
|
|
6
|
+
* `constructs` as peer dependencies; adopters using CDK already have
|
|
7
|
+
* both installed.
|
|
8
|
+
*/
|
|
9
|
+
export { HyperframesRenderStack, type HyperframesRenderStackProps, } from "./HyperframesRenderStack.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// src/cdk/HyperframesRenderStack.ts
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Duration, RemovalPolicy, Size } from "aws-cdk-lib";
|
|
5
|
+
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
|
|
6
|
+
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
7
|
+
import * as logs from "aws-cdk-lib/aws-logs";
|
|
8
|
+
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
9
|
+
import * as sfn from "aws-cdk-lib/aws-stepfunctions";
|
|
10
|
+
import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks";
|
|
11
|
+
import { Construct } from "constructs";
|
|
12
|
+
var DEFAULT_MEMORY_MB = 10240;
|
|
13
|
+
var DEFAULT_TIMEOUT_SEC = 900;
|
|
14
|
+
var DEFAULT_CHROME_SOURCE = "sparticuz";
|
|
15
|
+
var DEFAULT_ALARM_THRESHOLD = 1e3;
|
|
16
|
+
var HyperframesRenderStack = class extends Construct {
|
|
17
|
+
/** S3 bucket for plan tarballs, chunk outputs, and final renders. */
|
|
18
|
+
bucket;
|
|
19
|
+
/** The single Lambda function dispatching plan / renderChunk / assemble. */
|
|
20
|
+
renderFunction;
|
|
21
|
+
/** The Step Functions state machine orchestrating the render. */
|
|
22
|
+
stateMachine;
|
|
23
|
+
constructor(scope, id, props = {}) {
|
|
24
|
+
super(scope, id);
|
|
25
|
+
const projectName = props.projectName ?? "hyperframes";
|
|
26
|
+
const memorySize = props.lambdaMemoryMb ?? DEFAULT_MEMORY_MB;
|
|
27
|
+
const timeoutSec = props.lambdaTimeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
28
|
+
const chromeSource = props.chromeSource ?? DEFAULT_CHROME_SOURCE;
|
|
29
|
+
const alarmThreshold = props.chunkInvocationAlarmThreshold ?? DEFAULT_ALARM_THRESHOLD;
|
|
30
|
+
const handlerZipPath = props.handlerZipPath ?? defaultHandlerZipPath();
|
|
31
|
+
this.bucket = new s3.Bucket(this, "RenderBucket", {
|
|
32
|
+
removalPolicy: props.bucketRemovalPolicy ?? RemovalPolicy.RETAIN,
|
|
33
|
+
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
34
|
+
// `Suspended` is the cheapest mode that still satisfies KMS / replication
|
|
35
|
+
// prerequisites callers can layer on later. Adopters who treat the final
|
|
36
|
+
// mp4 as user-keepable can switch to `Enabled`.
|
|
37
|
+
versioned: false,
|
|
38
|
+
lifecycleRules: [
|
|
39
|
+
{
|
|
40
|
+
id: "ExpireIntermediates",
|
|
41
|
+
enabled: true,
|
|
42
|
+
prefix: "renders/",
|
|
43
|
+
expiration: Duration.days(7)
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
this.renderFunction = new lambda.Function(this, "RenderFunction", {
|
|
48
|
+
functionName: `${projectName}-render`,
|
|
49
|
+
runtime: lambda.Runtime.NODEJS_22_X,
|
|
50
|
+
handler: "handler.handler",
|
|
51
|
+
code: lambda.Code.fromAsset(handlerZipPath),
|
|
52
|
+
memorySize,
|
|
53
|
+
timeout: Duration.seconds(timeoutSec),
|
|
54
|
+
ephemeralStorageSize: Size.gibibytes(10),
|
|
55
|
+
architecture: lambda.Architecture.X86_64,
|
|
56
|
+
reservedConcurrentExecutions: props.reservedConcurrency,
|
|
57
|
+
tracing: lambda.Tracing.ACTIVE,
|
|
58
|
+
environment: {
|
|
59
|
+
NODE_OPTIONS: "--enable-source-maps",
|
|
60
|
+
TMPDIR: "/tmp",
|
|
61
|
+
HYPERFRAMES_LAMBDA_CHROME_SOURCE: chromeSource
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
this.bucket.grantReadWrite(this.renderFunction);
|
|
65
|
+
const stateMachineLogGroup = new logs.LogGroup(this, "RenderStateMachineLogGroup", {
|
|
66
|
+
logGroupName: `/aws/states/${projectName}-render`,
|
|
67
|
+
retention: logs.RetentionDays.ONE_MONTH,
|
|
68
|
+
removalPolicy: RemovalPolicy.DESTROY
|
|
69
|
+
});
|
|
70
|
+
const definition = this.buildStateMachineDefinition();
|
|
71
|
+
this.stateMachine = new sfn.StateMachine(this, "RenderStateMachine", {
|
|
72
|
+
stateMachineName: `${projectName}-render`,
|
|
73
|
+
stateMachineType: sfn.StateMachineType.STANDARD,
|
|
74
|
+
definitionBody: sfn.DefinitionBody.fromChainable(definition),
|
|
75
|
+
tracingEnabled: true,
|
|
76
|
+
timeout: Duration.hours(1),
|
|
77
|
+
logs: {
|
|
78
|
+
destination: stateMachineLogGroup,
|
|
79
|
+
level: sfn.LogLevel.ERROR,
|
|
80
|
+
includeExecutionData: false
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
this.renderFunction.grantInvoke(this.stateMachine);
|
|
84
|
+
new cloudwatch.Alarm(this, "RenderChunkInvocationAlarm", {
|
|
85
|
+
alarmName: `${projectName}-runaway-chunk-invocations`,
|
|
86
|
+
alarmDescription: "Fires if RenderChunk Lambda invocations exceed the configured threshold in a 1-hour window.",
|
|
87
|
+
metric: this.renderFunction.metricInvocations({
|
|
88
|
+
period: Duration.hours(1),
|
|
89
|
+
statistic: cloudwatch.Stats.SUM
|
|
90
|
+
}),
|
|
91
|
+
threshold: alarmThreshold,
|
|
92
|
+
evaluationPeriods: 1,
|
|
93
|
+
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
|
|
94
|
+
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING
|
|
95
|
+
});
|
|
96
|
+
new cloudwatch.Alarm(this, "RenderFunctionErrorsAlarm", {
|
|
97
|
+
alarmName: `${projectName}-render-function-errors`,
|
|
98
|
+
alarmDescription: "Fires if the render Lambda reports any errors in a 5-minute window.",
|
|
99
|
+
metric: this.renderFunction.metricErrors({
|
|
100
|
+
period: Duration.minutes(5),
|
|
101
|
+
statistic: cloudwatch.Stats.SUM
|
|
102
|
+
}),
|
|
103
|
+
threshold: 1,
|
|
104
|
+
evaluationPeriods: 1,
|
|
105
|
+
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
106
|
+
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING
|
|
107
|
+
});
|
|
108
|
+
new cloudwatch.Alarm(this, "RenderStateMachineFailedAlarm", {
|
|
109
|
+
alarmName: `${projectName}-render-state-machine-failed`,
|
|
110
|
+
alarmDescription: "Fires when the render state machine reports a failed execution.",
|
|
111
|
+
metric: this.stateMachine.metricFailed({
|
|
112
|
+
period: Duration.minutes(5),
|
|
113
|
+
statistic: cloudwatch.Stats.SUM
|
|
114
|
+
}),
|
|
115
|
+
threshold: 1,
|
|
116
|
+
evaluationPeriods: 1,
|
|
117
|
+
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
118
|
+
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build the state-machine chain. Kept in a single method so the SAM
|
|
123
|
+
* template and this construct can be diffed shape-for-shape during
|
|
124
|
+
* the snapshot test.
|
|
125
|
+
*/
|
|
126
|
+
buildStateMachineDefinition() {
|
|
127
|
+
const NON_RETRYABLE_PLAN = [
|
|
128
|
+
"FFMPEG_VERSION_MISMATCH",
|
|
129
|
+
"PLAN_HASH_MISMATCH",
|
|
130
|
+
"BROWSER_GPU_NOT_SOFTWARE",
|
|
131
|
+
"FONT_FETCH_FAILED",
|
|
132
|
+
"PLAN_TOO_LARGE",
|
|
133
|
+
"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED"
|
|
134
|
+
];
|
|
135
|
+
const NON_RETRYABLE_CHUNK = [
|
|
136
|
+
"FFMPEG_VERSION_MISMATCH",
|
|
137
|
+
"PLAN_HASH_MISMATCH",
|
|
138
|
+
"BROWSER_GPU_NOT_SOFTWARE"
|
|
139
|
+
];
|
|
140
|
+
const NON_RETRYABLE_ASSEMBLE = [
|
|
141
|
+
"FFMPEG_VERSION_MISMATCH",
|
|
142
|
+
"PLAN_HASH_MISMATCH",
|
|
143
|
+
"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED"
|
|
144
|
+
];
|
|
145
|
+
const plan = new tasks.LambdaInvoke(this, "Plan", {
|
|
146
|
+
lambdaFunction: this.renderFunction,
|
|
147
|
+
payload: sfn.TaskInput.fromObject({
|
|
148
|
+
Action: "plan",
|
|
149
|
+
"ProjectS3Uri.$": "$.ProjectS3Uri",
|
|
150
|
+
"PlanOutputS3Prefix.$": "$.PlanOutputS3Prefix",
|
|
151
|
+
"Config.$": "$.Config"
|
|
152
|
+
}),
|
|
153
|
+
resultSelector: {
|
|
154
|
+
"PlanS3Uri.$": "$.Payload.PlanS3Uri",
|
|
155
|
+
"PlanHash.$": "$.Payload.PlanHash",
|
|
156
|
+
"ChunkCount.$": "$.Payload.ChunkCount",
|
|
157
|
+
"Format.$": "$.Payload.Format",
|
|
158
|
+
"HasAudio.$": "$.Payload.HasAudio",
|
|
159
|
+
"AudioS3Uri.$": "$.Payload.AudioS3Uri"
|
|
160
|
+
},
|
|
161
|
+
resultPath: "$.Plan"
|
|
162
|
+
});
|
|
163
|
+
plan.addRetry({
|
|
164
|
+
errors: NON_RETRYABLE_PLAN,
|
|
165
|
+
maxAttempts: 0
|
|
166
|
+
});
|
|
167
|
+
plan.addRetry({
|
|
168
|
+
errors: ["States.ALL"],
|
|
169
|
+
interval: Duration.seconds(2),
|
|
170
|
+
maxAttempts: 4,
|
|
171
|
+
backoffRate: 2,
|
|
172
|
+
maxDelay: Duration.seconds(60)
|
|
173
|
+
});
|
|
174
|
+
const buildChunkList = new sfn.Pass(this, "BuildChunkList", {
|
|
175
|
+
parameters: {
|
|
176
|
+
"ChunkIndexes.$": "States.ArrayRange(0, States.MathAdd($.Plan.ChunkCount, -1), 1)"
|
|
177
|
+
},
|
|
178
|
+
resultPath: "$.Iterator"
|
|
179
|
+
});
|
|
180
|
+
const planProducedZero = new sfn.Fail(this, "PlanProducedZeroChunks", {
|
|
181
|
+
error: "PLAN_TOO_LARGE",
|
|
182
|
+
cause: "Plan returned ChunkCount=0 \u2014 non-retryable producer-side invariant violation."
|
|
183
|
+
});
|
|
184
|
+
const renderChunkTask = new tasks.LambdaInvoke(this, "RenderChunk", {
|
|
185
|
+
lambdaFunction: this.renderFunction,
|
|
186
|
+
payload: sfn.TaskInput.fromObject({
|
|
187
|
+
Action: "renderChunk",
|
|
188
|
+
"ChunkIndex.$": "$.ChunkIndex",
|
|
189
|
+
"PlanS3Uri.$": "$.PlanS3Uri",
|
|
190
|
+
"PlanHash.$": "$.PlanHash",
|
|
191
|
+
"ChunkOutputS3Prefix.$": "$.ChunkOutputS3Prefix",
|
|
192
|
+
"Format.$": "$.Format"
|
|
193
|
+
}),
|
|
194
|
+
resultSelector: {
|
|
195
|
+
"ChunkS3Uri.$": "$.Payload.ChunkS3Uri",
|
|
196
|
+
"ChunkIndex.$": "$.Payload.ChunkIndex",
|
|
197
|
+
"Sha256.$": "$.Payload.Sha256"
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
renderChunkTask.addRetry({
|
|
201
|
+
errors: NON_RETRYABLE_CHUNK,
|
|
202
|
+
maxAttempts: 0
|
|
203
|
+
});
|
|
204
|
+
renderChunkTask.addRetry({
|
|
205
|
+
errors: ["States.ALL"],
|
|
206
|
+
interval: Duration.seconds(2),
|
|
207
|
+
maxAttempts: 4,
|
|
208
|
+
backoffRate: 2,
|
|
209
|
+
maxDelay: Duration.seconds(60)
|
|
210
|
+
});
|
|
211
|
+
const renderChunks = new sfn.Map(this, "RenderChunks", {
|
|
212
|
+
itemsPath: "$.Iterator.ChunkIndexes",
|
|
213
|
+
itemSelector: {
|
|
214
|
+
"ChunkIndex.$": "$$.Map.Item.Value",
|
|
215
|
+
"PlanS3Uri.$": "$.Plan.PlanS3Uri",
|
|
216
|
+
"PlanHash.$": "$.Plan.PlanHash",
|
|
217
|
+
"ChunkOutputS3Prefix.$": "$.PlanOutputS3Prefix",
|
|
218
|
+
"Format.$": "$.Plan.Format"
|
|
219
|
+
},
|
|
220
|
+
maxConcurrencyPath: "$.Plan.ChunkCount",
|
|
221
|
+
resultPath: "$.Chunks"
|
|
222
|
+
});
|
|
223
|
+
renderChunks.itemProcessor(renderChunkTask);
|
|
224
|
+
const assemble = new tasks.LambdaInvoke(this, "Assemble", {
|
|
225
|
+
lambdaFunction: this.renderFunction,
|
|
226
|
+
payload: sfn.TaskInput.fromObject({
|
|
227
|
+
Action: "assemble",
|
|
228
|
+
"PlanS3Uri.$": "$.Plan.PlanS3Uri",
|
|
229
|
+
"ChunkS3Uris.$": "$.Chunks[*].ChunkS3Uri",
|
|
230
|
+
"AudioS3Uri.$": "$.Plan.AudioS3Uri",
|
|
231
|
+
"OutputS3Uri.$": "$.OutputS3Uri",
|
|
232
|
+
"Format.$": "$.Plan.Format"
|
|
233
|
+
}),
|
|
234
|
+
resultSelector: {
|
|
235
|
+
"OutputS3Uri.$": "$.Payload.OutputS3Uri",
|
|
236
|
+
"FramesEncoded.$": "$.Payload.FramesEncoded",
|
|
237
|
+
"FileSize.$": "$.Payload.FileSize"
|
|
238
|
+
},
|
|
239
|
+
resultPath: "$.Output"
|
|
240
|
+
});
|
|
241
|
+
assemble.addRetry({
|
|
242
|
+
errors: NON_RETRYABLE_ASSEMBLE,
|
|
243
|
+
maxAttempts: 0
|
|
244
|
+
});
|
|
245
|
+
assemble.addRetry({
|
|
246
|
+
errors: ["States.ALL"],
|
|
247
|
+
interval: Duration.seconds(2),
|
|
248
|
+
maxAttempts: 4,
|
|
249
|
+
backoffRate: 2,
|
|
250
|
+
maxDelay: Duration.seconds(60)
|
|
251
|
+
});
|
|
252
|
+
const assertChunkCount = new sfn.Choice(this, "AssertChunkCount").when(sfn.Condition.numberGreaterThan("$.Plan.ChunkCount", 0), renderChunks.next(assemble)).otherwise(planProducedZero);
|
|
253
|
+
return plan.next(buildChunkList).next(assertChunkCount);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
function defaultHandlerZipPath() {
|
|
257
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
258
|
+
return resolve(here, "..", "..", "dist", "handler.zip");
|
|
259
|
+
}
|
|
260
|
+
export {
|
|
261
|
+
HyperframesRenderStack
|
|
262
|
+
};
|
|
263
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/cdk/HyperframesRenderStack.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `HyperframesRenderStack` \u2014 aws-cdk-lib L2 construct that emits the same\n * topology as `examples/aws-lambda/template.yaml`.\n *\n * Adopters who embed HyperFrames inside their own CDK app can extend this\n * construct or compose alongside it; the construct exposes its `.bucket`,\n * `.renderFunction`, and `.stateMachine` properties so additional\n * resources (alarms, dashboards, SNS topics) can be wired without\n * re-deriving the ARNs from a stack export.\n *\n * `aws-cdk-lib` and `constructs` are **peerDependencies**. The package\n * still type-checks (and the snapshot test still runs) because they're\n * also `devDependencies`, but adopters who only consume the SDK side of\n * `@hyperframes/aws-lambda` don't pull the CDK tree at runtime.\n *\n * Drift from the SAM template is guarded by the snapshot test\n * (`HyperframesRenderStack.snapshot.test.ts`), which diffs the synthed\n * CloudFormation against the SAM-rendered CloudFormation modulo\n * normalisation.\n */\n\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Duration, RemovalPolicy, Size } from \"aws-cdk-lib\";\nimport * as cloudwatch from \"aws-cdk-lib/aws-cloudwatch\";\nimport * as lambda from \"aws-cdk-lib/aws-lambda\";\nimport * as logs from \"aws-cdk-lib/aws-logs\";\nimport * as s3 from \"aws-cdk-lib/aws-s3\";\nimport * as sfn from \"aws-cdk-lib/aws-stepfunctions\";\nimport * as tasks from \"aws-cdk-lib/aws-stepfunctions-tasks\";\nimport { Construct } from \"constructs\";\n\n/** Construction-time props for {@link HyperframesRenderStack}. */\nexport interface HyperframesRenderStackProps {\n /** Name prefix applied to function / state-machine / alarm names. Default `\"hyperframes\"`. */\n projectName?: string;\n /** Lambda memory in MB. Allowed: 2048..10240 in 1024 steps. Default 10240. */\n lambdaMemoryMb?: 2048 | 3072 | 4096 | 5120 | 6144 | 7168 | 8192 | 9216 | 10240;\n /** Per-invocation Lambda timeout. Default 900 (15 min, Lambda hard cap). */\n lambdaTimeoutSec?: number;\n /** Lambda reserved concurrency cap. `undefined` = unreserved (account default). */\n reservedConcurrency?: number;\n /** Which Chrome runtime was bundled into the handler ZIP. Default `\"sparticuz\"`. */\n chromeSource?: \"sparticuz\" | \"chrome-headless-shell\";\n /** Threshold for the runaway-invocations alarm. Default 1000 invocations/hour. */\n chunkInvocationAlarmThreshold?: number;\n /**\n * Absolute path to the handler ZIP produced by\n * `bun run --cwd packages/aws-lambda build:zip`. Defaults to the\n * package-relative path the build script writes to. Adopters who\n * deploy the published handler ZIP set this explicitly.\n */\n handlerZipPath?: string;\n /** S3 bucket retention policy on stack delete. Default RETAIN. */\n bucketRemovalPolicy?: RemovalPolicy;\n}\n\nconst DEFAULT_MEMORY_MB = 10240;\nconst DEFAULT_TIMEOUT_SEC = 900;\nconst DEFAULT_CHROME_SOURCE = \"sparticuz\";\nconst DEFAULT_ALARM_THRESHOLD = 1000;\n\nexport class HyperframesRenderStack extends Construct {\n /** S3 bucket for plan tarballs, chunk outputs, and final renders. */\n readonly bucket: s3.Bucket;\n /** The single Lambda function dispatching plan / renderChunk / assemble. */\n readonly renderFunction: lambda.Function;\n /** The Step Functions state machine orchestrating the render. */\n readonly stateMachine: sfn.StateMachine;\n\n constructor(scope: Construct, id: string, props: HyperframesRenderStackProps = {}) {\n super(scope, id);\n\n const projectName = props.projectName ?? \"hyperframes\";\n const memorySize = props.lambdaMemoryMb ?? DEFAULT_MEMORY_MB;\n const timeoutSec = props.lambdaTimeoutSec ?? DEFAULT_TIMEOUT_SEC;\n const chromeSource = props.chromeSource ?? DEFAULT_CHROME_SOURCE;\n const alarmThreshold = props.chunkInvocationAlarmThreshold ?? DEFAULT_ALARM_THRESHOLD;\n const handlerZipPath = props.handlerZipPath ?? defaultHandlerZipPath();\n\n this.bucket = new s3.Bucket(this, \"RenderBucket\", {\n removalPolicy: props.bucketRemovalPolicy ?? RemovalPolicy.RETAIN,\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n // `Suspended` is the cheapest mode that still satisfies KMS / replication\n // prerequisites callers can layer on later. Adopters who treat the final\n // mp4 as user-keepable can switch to `Enabled`.\n versioned: false,\n lifecycleRules: [\n {\n id: \"ExpireIntermediates\",\n enabled: true,\n prefix: \"renders/\",\n expiration: Duration.days(7),\n },\n ],\n });\n\n this.renderFunction = new lambda.Function(this, \"RenderFunction\", {\n functionName: `${projectName}-render`,\n runtime: lambda.Runtime.NODEJS_22_X,\n handler: \"handler.handler\",\n code: lambda.Code.fromAsset(handlerZipPath),\n memorySize,\n timeout: Duration.seconds(timeoutSec),\n ephemeralStorageSize: Size.gibibytes(10),\n architecture: lambda.Architecture.X86_64,\n reservedConcurrentExecutions: props.reservedConcurrency,\n tracing: lambda.Tracing.ACTIVE,\n environment: {\n NODE_OPTIONS: \"--enable-source-maps\",\n TMPDIR: \"/tmp\",\n HYPERFRAMES_LAMBDA_CHROME_SOURCE: chromeSource,\n },\n });\n\n // Scoped S3 perms only \u2014 explicitly NOT `CloudWatchLogsFullAccess`,\n // which would grant `logs:*` on `*` and overscope adopter accounts.\n // SAM's AWSLambdaBasicExecutionRole equivalent is included by the\n // default `new lambda.Function` execution role.\n this.bucket.grantReadWrite(this.renderFunction);\n\n const stateMachineLogGroup = new logs.LogGroup(this, \"RenderStateMachineLogGroup\", {\n logGroupName: `/aws/states/${projectName}-render`,\n retention: logs.RetentionDays.ONE_MONTH,\n removalPolicy: RemovalPolicy.DESTROY,\n });\n\n const definition = this.buildStateMachineDefinition();\n\n this.stateMachine = new sfn.StateMachine(this, \"RenderStateMachine\", {\n stateMachineName: `${projectName}-render`,\n stateMachineType: sfn.StateMachineType.STANDARD,\n definitionBody: sfn.DefinitionBody.fromChainable(definition),\n tracingEnabled: true,\n timeout: Duration.hours(1),\n logs: {\n destination: stateMachineLogGroup,\n level: sfn.LogLevel.ERROR,\n includeExecutionData: false,\n },\n });\n\n this.renderFunction.grantInvoke(this.stateMachine);\n\n new cloudwatch.Alarm(this, \"RenderChunkInvocationAlarm\", {\n alarmName: `${projectName}-runaway-chunk-invocations`,\n alarmDescription:\n \"Fires if RenderChunk Lambda invocations exceed the configured threshold in a 1-hour window.\",\n metric: this.renderFunction.metricInvocations({\n period: Duration.hours(1),\n statistic: cloudwatch.Stats.SUM,\n }),\n threshold: alarmThreshold,\n evaluationPeriods: 1,\n comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,\n });\n\n new cloudwatch.Alarm(this, \"RenderFunctionErrorsAlarm\", {\n alarmName: `${projectName}-render-function-errors`,\n alarmDescription: \"Fires if the render Lambda reports any errors in a 5-minute window.\",\n metric: this.renderFunction.metricErrors({\n period: Duration.minutes(5),\n statistic: cloudwatch.Stats.SUM,\n }),\n threshold: 1,\n evaluationPeriods: 1,\n comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,\n treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,\n });\n\n new cloudwatch.Alarm(this, \"RenderStateMachineFailedAlarm\", {\n alarmName: `${projectName}-render-state-machine-failed`,\n alarmDescription: \"Fires when the render state machine reports a failed execution.\",\n metric: this.stateMachine.metricFailed({\n period: Duration.minutes(5),\n statistic: cloudwatch.Stats.SUM,\n }),\n threshold: 1,\n evaluationPeriods: 1,\n comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,\n treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,\n });\n }\n\n /**\n * Build the state-machine chain. Kept in a single method so the SAM\n * template and this construct can be diffed shape-for-shape during\n * the snapshot test.\n */\n private buildStateMachineDefinition(): sfn.IChainable {\n const NON_RETRYABLE_PLAN = [\n \"FFMPEG_VERSION_MISMATCH\",\n \"PLAN_HASH_MISMATCH\",\n \"BROWSER_GPU_NOT_SOFTWARE\",\n \"FONT_FETCH_FAILED\",\n \"PLAN_TOO_LARGE\",\n \"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED\",\n ];\n const NON_RETRYABLE_CHUNK = [\n \"FFMPEG_VERSION_MISMATCH\",\n \"PLAN_HASH_MISMATCH\",\n \"BROWSER_GPU_NOT_SOFTWARE\",\n ];\n const NON_RETRYABLE_ASSEMBLE = [\n \"FFMPEG_VERSION_MISMATCH\",\n \"PLAN_HASH_MISMATCH\",\n \"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED\",\n ];\n\n const plan = new tasks.LambdaInvoke(this, \"Plan\", {\n lambdaFunction: this.renderFunction,\n payload: sfn.TaskInput.fromObject({\n Action: \"plan\",\n \"ProjectS3Uri.$\": \"$.ProjectS3Uri\",\n \"PlanOutputS3Prefix.$\": \"$.PlanOutputS3Prefix\",\n \"Config.$\": \"$.Config\",\n }),\n resultSelector: {\n \"PlanS3Uri.$\": \"$.Payload.PlanS3Uri\",\n \"PlanHash.$\": \"$.Payload.PlanHash\",\n \"ChunkCount.$\": \"$.Payload.ChunkCount\",\n \"Format.$\": \"$.Payload.Format\",\n \"HasAudio.$\": \"$.Payload.HasAudio\",\n \"AudioS3Uri.$\": \"$.Payload.AudioS3Uri\",\n },\n resultPath: \"$.Plan\",\n });\n plan.addRetry({\n errors: NON_RETRYABLE_PLAN,\n maxAttempts: 0,\n });\n plan.addRetry({\n errors: [\"States.ALL\"],\n interval: Duration.seconds(2),\n maxAttempts: 4,\n backoffRate: 2,\n maxDelay: Duration.seconds(60),\n });\n\n const buildChunkList = new sfn.Pass(this, \"BuildChunkList\", {\n parameters: {\n \"ChunkIndexes.$\": \"States.ArrayRange(0, States.MathAdd($.Plan.ChunkCount, -1), 1)\",\n },\n resultPath: \"$.Iterator\",\n });\n\n const planProducedZero = new sfn.Fail(this, \"PlanProducedZeroChunks\", {\n error: \"PLAN_TOO_LARGE\",\n cause: \"Plan returned ChunkCount=0 \u2014 non-retryable producer-side invariant violation.\",\n });\n\n const renderChunkTask = new tasks.LambdaInvoke(this, \"RenderChunk\", {\n lambdaFunction: this.renderFunction,\n payload: sfn.TaskInput.fromObject({\n Action: \"renderChunk\",\n \"ChunkIndex.$\": \"$.ChunkIndex\",\n \"PlanS3Uri.$\": \"$.PlanS3Uri\",\n \"PlanHash.$\": \"$.PlanHash\",\n \"ChunkOutputS3Prefix.$\": \"$.ChunkOutputS3Prefix\",\n \"Format.$\": \"$.Format\",\n }),\n resultSelector: {\n \"ChunkS3Uri.$\": \"$.Payload.ChunkS3Uri\",\n \"ChunkIndex.$\": \"$.Payload.ChunkIndex\",\n \"Sha256.$\": \"$.Payload.Sha256\",\n },\n });\n renderChunkTask.addRetry({\n errors: NON_RETRYABLE_CHUNK,\n maxAttempts: 0,\n });\n renderChunkTask.addRetry({\n errors: [\"States.ALL\"],\n interval: Duration.seconds(2),\n maxAttempts: 4,\n backoffRate: 2,\n maxDelay: Duration.seconds(60),\n });\n\n const renderChunks = new sfn.Map(this, \"RenderChunks\", {\n itemsPath: \"$.Iterator.ChunkIndexes\",\n itemSelector: {\n \"ChunkIndex.$\": \"$$.Map.Item.Value\",\n \"PlanS3Uri.$\": \"$.Plan.PlanS3Uri\",\n \"PlanHash.$\": \"$.Plan.PlanHash\",\n \"ChunkOutputS3Prefix.$\": \"$.PlanOutputS3Prefix\",\n \"Format.$\": \"$.Plan.Format\",\n },\n maxConcurrencyPath: \"$.Plan.ChunkCount\",\n resultPath: \"$.Chunks\",\n });\n renderChunks.itemProcessor(renderChunkTask);\n\n const assemble = new tasks.LambdaInvoke(this, \"Assemble\", {\n lambdaFunction: this.renderFunction,\n payload: sfn.TaskInput.fromObject({\n Action: \"assemble\",\n \"PlanS3Uri.$\": \"$.Plan.PlanS3Uri\",\n \"ChunkS3Uris.$\": \"$.Chunks[*].ChunkS3Uri\",\n \"AudioS3Uri.$\": \"$.Plan.AudioS3Uri\",\n \"OutputS3Uri.$\": \"$.OutputS3Uri\",\n \"Format.$\": \"$.Plan.Format\",\n }),\n resultSelector: {\n \"OutputS3Uri.$\": \"$.Payload.OutputS3Uri\",\n \"FramesEncoded.$\": \"$.Payload.FramesEncoded\",\n \"FileSize.$\": \"$.Payload.FileSize\",\n },\n resultPath: \"$.Output\",\n });\n assemble.addRetry({\n errors: NON_RETRYABLE_ASSEMBLE,\n maxAttempts: 0,\n });\n assemble.addRetry({\n errors: [\"States.ALL\"],\n interval: Duration.seconds(2),\n maxAttempts: 4,\n backoffRate: 2,\n maxDelay: Duration.seconds(60),\n });\n\n const assertChunkCount = new sfn.Choice(this, \"AssertChunkCount\")\n .when(sfn.Condition.numberGreaterThan(\"$.Plan.ChunkCount\", 0), renderChunks.next(assemble))\n .otherwise(planProducedZero);\n\n return plan.next(buildChunkList).next(assertChunkCount);\n }\n}\n\n/**\n * Default location of the handler ZIP relative to this source file. Two\n * parents up = `packages/aws-lambda/`; the build script writes the ZIP\n * to `packages/aws-lambda/dist/handler.zip`. The package is published with\n * `main: \"./src/index.ts\"`, so this path resolves correctly both in the\n * source tree (during `bun test` / local CDK synth) and in a consumer's\n * `node_modules/@hyperframes/aws-lambda/` install.\n */\nfunction defaultHandlerZipPath(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n return resolve(here, \"..\", \"..\", \"dist\", \"handler.zip\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lambda-runtime Chrome resolver.
|
|
3
|
+
*
|
|
4
|
+
* `renderChunk()` (the only primitive that needs a browser) launches Chrome
|
|
5
|
+
* via the engine's `BrowserManager`. In Lambda we can't ship the full
|
|
6
|
+
* Puppeteer-managed Chrome download — Puppeteer's Chrome binary is ~330 MB
|
|
7
|
+
* unzipped, well over Lambda's 250 MB ZIP-deploy ceiling.
|
|
8
|
+
*
|
|
9
|
+
* Two valid runtime sources:
|
|
10
|
+
*
|
|
11
|
+
* 1. `@sparticuz/chromium` (primary). Decompresses a Lambda-optimised
|
|
12
|
+
* `chrome-headless-shell` build into `/tmp` at runtime. ~70 MB
|
|
13
|
+
* compressed; the same binary the rest of the ecosystem uses for
|
|
14
|
+
* headless-Chrome-in-Lambda. CDP-level BeginFrame works because the
|
|
15
|
+
* command lives in the protocol, not the binary; the
|
|
16
|
+
* `scripts/probe-beginframe.ts` regression guard pins this.
|
|
17
|
+
*
|
|
18
|
+
* 2. A bundled `chrome-headless-shell` binary (fallback). If
|
|
19
|
+
* `@sparticuz/chromium`'s build ever drops `HeadlessExperimental`
|
|
20
|
+
* support, we fall back to the same `chrome-headless-shell` build
|
|
21
|
+
* the K8s deploy uses. The fallback raises the ZIP from ~70 MB
|
|
22
|
+
* Chrome to ~140 MB Chrome — still well under 250 MB.
|
|
23
|
+
*
|
|
24
|
+
* The runtime path is selected by the `HYPERFRAMES_LAMBDA_CHROME_SOURCE`
|
|
25
|
+
* env var (set by `build-zip.ts`):
|
|
26
|
+
*
|
|
27
|
+
* "sparticuz" → use `@sparticuz/chromium.executablePath()`
|
|
28
|
+
* "chrome-headless-shell" → use `process.env.HYPERFRAMES_LAMBDA_CHROME_PATH`
|
|
29
|
+
*
|
|
30
|
+
* Adapters that bundle this package can override
|
|
31
|
+
* `HYPERFRAMES_LAMBDA_CHROME_PATH` directly when running outside Lambda
|
|
32
|
+
* (e.g. the SAM-local RIE smoke).
|
|
33
|
+
*/
|
|
34
|
+
/** Discriminator for the two supported Chrome sources. */
|
|
35
|
+
export type ChromeSource = "sparticuz" | "chrome-headless-shell";
|
|
36
|
+
/**
|
|
37
|
+
* Read which Chrome source the bundled ZIP was built against. Defaults to
|
|
38
|
+
* `"sparticuz"` so a fresh build with no env override picks the primary
|
|
39
|
+
* path.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveChromeSource(): ChromeSource;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the absolute path to a Chrome binary suitable for BeginFrame.
|
|
44
|
+
*
|
|
45
|
+
* For `"sparticuz"`: dynamically import `@sparticuz/chromium` and call
|
|
46
|
+
* `chromium.executablePath()`. The module is dynamic so a build-zip that
|
|
47
|
+
* never reaches the import (because the fallback Chrome is bundled) can
|
|
48
|
+
* tree-shake it out.
|
|
49
|
+
*
|
|
50
|
+
* For `"chrome-headless-shell"`: read the path from
|
|
51
|
+
* `HYPERFRAMES_LAMBDA_CHROME_PATH`. Throws if absent or non-existent so a
|
|
52
|
+
* misconfigured deploy fails loudly at boot rather than at first frame.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveChromeExecutablePath(): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the Chromium launch args for the selected source. For
|
|
57
|
+
* `@sparticuz/chromium` we forward `chromium.args` (Lambda-tuned defaults
|
|
58
|
+
* — single-process, no-sandbox, /tmp paths). For the shell fallback the
|
|
59
|
+
* engine's own arg builder owns it; we return an empty array so the
|
|
60
|
+
* engine's defaults apply.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveChromeArgs(): Promise<string[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Dynamic import wrapper isolated so unit tests can stub the module without
|
|
65
|
+
* jest-style module mocking gymnastics. The narrow type here pins the
|
|
66
|
+
* subset of `@sparticuz/chromium`'s surface this package depends on; if
|
|
67
|
+
* the upstream module ever changes shape the type error here surfaces
|
|
68
|
+
* before runtime.
|
|
69
|
+
*/
|
|
70
|
+
interface SparticuzChromiumModule {
|
|
71
|
+
args: string[];
|
|
72
|
+
executablePath(): Promise<string>;
|
|
73
|
+
}
|
|
74
|
+
/** Test-only seam: replace the cached `@sparticuz/chromium` module. */
|
|
75
|
+
export declare function _setSparticuzChromiumForTests(mod: SparticuzChromiumModule | null): void;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=chromium.d.ts.map
|