@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.
Files changed (43) hide show
  1. package/README.md +191 -0
  2. package/dist/cdk/HyperframesRenderStack.d.ts +65 -0
  3. package/dist/cdk/HyperframesRenderStack.d.ts.map +1 -0
  4. package/dist/cdk/index.d.ts +10 -0
  5. package/dist/cdk/index.d.ts.map +1 -0
  6. package/dist/cdk/index.js +263 -0
  7. package/dist/cdk/index.js.map +7 -0
  8. package/dist/chromium.d.ts +77 -0
  9. package/dist/chromium.d.ts.map +1 -0
  10. package/dist/events.d.ts +120 -0
  11. package/dist/events.d.ts.map +1 -0
  12. package/dist/formatExtension.d.ts +10 -0
  13. package/dist/formatExtension.d.ts.map +1 -0
  14. package/dist/handler.d.ts +42 -0
  15. package/dist/handler.d.ts.map +1 -0
  16. package/dist/handler.js +432 -0
  17. package/dist/handler.js.map +7 -0
  18. package/dist/index.d.ts +33 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +951 -0
  21. package/dist/index.js.map +7 -0
  22. package/dist/s3Transport.d.ts +55 -0
  23. package/dist/s3Transport.d.ts.map +1 -0
  24. package/dist/sdk/costAccounting.d.ts +51 -0
  25. package/dist/sdk/costAccounting.d.ts.map +1 -0
  26. package/dist/sdk/deploySite.d.ts +55 -0
  27. package/dist/sdk/deploySite.d.ts.map +1 -0
  28. package/dist/sdk/getRenderProgress.d.ts +72 -0
  29. package/dist/sdk/getRenderProgress.d.ts.map +1 -0
  30. package/dist/sdk/index.d.ts +16 -0
  31. package/dist/sdk/index.d.ts.map +1 -0
  32. package/dist/sdk/index.js +578 -0
  33. package/dist/sdk/index.js.map +7 -0
  34. package/dist/sdk/renderToLambda.d.ts +66 -0
  35. package/dist/sdk/renderToLambda.d.ts.map +1 -0
  36. package/dist/sdk/validateConfig.d.ts +35 -0
  37. package/dist/sdk/validateConfig.d.ts.map +1 -0
  38. package/package.json +84 -0
  39. package/scripts/_formatBytes.ts +15 -0
  40. package/scripts/build-zip.ts +480 -0
  41. package/scripts/probe-beginframe.dockerfile +61 -0
  42. package/scripts/probe-beginframe.ts +157 -0
  43. package/scripts/verify-zip-size.ts +83 -0
@@ -0,0 +1,578 @@
1
+ // src/sdk/deploySite.ts
2
+ import { mkdtempSync, readdirSync as readdirSync2, readFileSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { tmpdir } from "node:os";
5
+ import { join as join2, relative } from "node:path";
6
+ import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
7
+ import { PLAN_PROJECT_DIR_SKIP_SEGMENTS } from "@hyperframes/producer/distributed";
8
+
9
+ // src/s3Transport.ts
10
+ import {
11
+ createReadStream,
12
+ createWriteStream,
13
+ existsSync,
14
+ mkdirSync,
15
+ readdirSync,
16
+ rmSync,
17
+ statSync
18
+ } from "node:fs";
19
+ import { dirname, join } from "node:path";
20
+ import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
21
+ import * as tar from "tar";
22
+ function parseS3Uri(uri) {
23
+ if (!uri.startsWith("s3://")) {
24
+ throw new Error(`[s3Transport] expected s3:// URI, got: ${JSON.stringify(uri)}`);
25
+ }
26
+ const rest = uri.slice("s3://".length);
27
+ const slash = rest.indexOf("/");
28
+ if (slash === -1) {
29
+ throw new Error(`[s3Transport] missing key in s3 URI: ${JSON.stringify(uri)}`);
30
+ }
31
+ const bucket = rest.slice(0, slash);
32
+ const key = rest.slice(slash + 1);
33
+ if (!bucket || !key) {
34
+ throw new Error(`[s3Transport] empty bucket or key in s3 URI: ${JSON.stringify(uri)}`);
35
+ }
36
+ return { bucket, key };
37
+ }
38
+ function formatS3Uri(loc) {
39
+ return `s3://${loc.bucket}/${loc.key}`;
40
+ }
41
+ async function uploadFileToS3(client, localPath, uri, contentType) {
42
+ if (!existsSync(localPath)) {
43
+ throw new Error(`[s3Transport] upload source missing: ${localPath}`);
44
+ }
45
+ const { bucket, key } = parseS3Uri(uri);
46
+ const size = statSync(localPath).size;
47
+ await client.send(
48
+ new PutObjectCommand({
49
+ Bucket: bucket,
50
+ Key: key,
51
+ Body: createReadStream(localPath),
52
+ ContentType: contentType,
53
+ ContentLength: size
54
+ })
55
+ );
56
+ }
57
+ async function tarDirectory(sourceDir, destTarball) {
58
+ if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
59
+ throw new Error(`[s3Transport] tar source must be an existing directory: ${sourceDir}`);
60
+ }
61
+ mkdirSync(dirname(destTarball), { recursive: true });
62
+ await tar.create({ gzip: true, file: destTarball, cwd: sourceDir }, ["."]);
63
+ }
64
+
65
+ // src/sdk/deploySite.ts
66
+ async function deploySite(opts) {
67
+ if (!statSync2(opts.projectDir).isDirectory()) {
68
+ throw new Error(`[deploySite] projectDir is not a directory: ${opts.projectDir}`);
69
+ }
70
+ const siteId = opts.siteId ?? hashProjectDir(opts.projectDir);
71
+ const key = `sites/${siteId}/project.tar.gz`;
72
+ const projectS3Uri = formatS3Uri({ bucket: opts.bucketName, key });
73
+ const s3 = opts.s3 ?? new S3Client({ region: opts.region });
74
+ const existing = await headObject(s3, opts.bucketName, key);
75
+ if (existing) {
76
+ return {
77
+ siteId,
78
+ bucketName: opts.bucketName,
79
+ projectS3Uri,
80
+ bytes: existing.bytes,
81
+ uploadedAt: existing.lastModified,
82
+ uploaded: false
83
+ };
84
+ }
85
+ const workdir = mkdtempSync(join2(tmpdir(), "hf-deploy-site-"));
86
+ try {
87
+ const tarball = join2(workdir, "project.tar.gz");
88
+ await tarDirectory(opts.projectDir, tarball);
89
+ const size = statSync2(tarball).size;
90
+ await uploadFileToS3(s3, tarball, projectS3Uri, "application/gzip");
91
+ return {
92
+ siteId,
93
+ bucketName: opts.bucketName,
94
+ projectS3Uri,
95
+ bytes: size,
96
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
97
+ uploaded: true
98
+ };
99
+ } finally {
100
+ rmSync2(workdir, { recursive: true, force: true });
101
+ }
102
+ }
103
+ function hashProjectDir(projectDir) {
104
+ const hash = createHash("sha256");
105
+ const files = [];
106
+ function walk(dir, isRoot) {
107
+ for (const entry of readdirSync2(dir, { withFileTypes: true }).sort(
108
+ (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
109
+ )) {
110
+ if (isRoot && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(entry.name)) continue;
111
+ const full = join2(dir, entry.name);
112
+ if (entry.isDirectory()) walk(full, false);
113
+ else if (entry.isFile()) files.push(full);
114
+ }
115
+ }
116
+ walk(projectDir, true);
117
+ for (const file of files) {
118
+ const rel = relative(projectDir, file).replaceAll("\\", "/");
119
+ hash.update(rel);
120
+ hash.update("\0");
121
+ hash.update(readFileSync(file));
122
+ }
123
+ return hash.digest("hex").slice(0, 16);
124
+ }
125
+ async function headObject(s3, bucket, key) {
126
+ try {
127
+ const res = await s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
128
+ return {
129
+ bytes: typeof res.ContentLength === "number" ? res.ContentLength : 0,
130
+ lastModified: res.LastModified instanceof Date ? res.LastModified.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
131
+ };
132
+ } catch (err) {
133
+ const status = err.$metadata?.httpStatusCode;
134
+ if (status === 404) return null;
135
+ const name = err.name;
136
+ if (name === "NotFound" || name === "NoSuchKey") return null;
137
+ throw err;
138
+ }
139
+ }
140
+
141
+ // src/sdk/renderToLambda.ts
142
+ import { randomUUID } from "node:crypto";
143
+ import { SFNClient, StartExecutionCommand } from "@aws-sdk/client-sfn";
144
+
145
+ // src/formatExtension.ts
146
+ function formatExtension(format) {
147
+ switch (format) {
148
+ case "mp4":
149
+ return ".mp4";
150
+ case "mov":
151
+ return ".mov";
152
+ case "png-sequence":
153
+ return "";
154
+ default: {
155
+ const _exhaustive = format;
156
+ throw new Error(`[formatExtension] unsupported format: ${_exhaustive}`);
157
+ }
158
+ }
159
+ }
160
+
161
+ // src/sdk/validateConfig.ts
162
+ var InvalidConfigError = class extends Error {
163
+ name = "InvalidConfigError";
164
+ /** Dotted JSON-pointer-ish path to the offending field, e.g. `config.fps`. */
165
+ field;
166
+ constructor(field, message) {
167
+ super(`[validateConfig] ${field}: ${message}`);
168
+ this.field = field;
169
+ }
170
+ };
171
+ var ALLOWED_FPS = [24, 30, 60];
172
+ var ALLOWED_FORMATS = ["mp4", "mov", "png-sequence"];
173
+ var ALLOWED_CODECS = ["h264", "h265"];
174
+ var ALLOWED_QUALITIES = ["draft", "standard", "high"];
175
+ var ALLOWED_RUNTIME_CAPS = ["lambda", "temporal", "cloud-run-job", "k8s-job", "none"];
176
+ var ALLOWED_HDR_MODES = ["auto", "force-sdr"];
177
+ var MAX_DIMENSION = 7680;
178
+ var MIN_DIMENSION = 16;
179
+ var MAX_CHUNK_SIZE = 3600;
180
+ var MAX_PARALLEL_CHUNKS_CEILING = 256;
181
+ function validateDistributedRenderConfig(config) {
182
+ if (config === null || typeof config !== "object") {
183
+ throw new InvalidConfigError("config", "must be an object");
184
+ }
185
+ if (!ALLOWED_FPS.includes(config.fps)) {
186
+ throw new InvalidConfigError(
187
+ "config.fps",
188
+ `must be one of ${ALLOWED_FPS.join(", ")}; got ${String(config.fps)}`
189
+ );
190
+ }
191
+ validateIntDimension("config.width", config.width);
192
+ validateIntDimension("config.height", config.height);
193
+ if (!ALLOWED_FORMATS.includes(config.format)) {
194
+ throw new InvalidConfigError(
195
+ "config.format",
196
+ `must be one of ${ALLOWED_FORMATS.join(", ")}; got ${String(config.format)}`
197
+ );
198
+ }
199
+ if (config.codec !== void 0) {
200
+ if (config.format !== "mp4") {
201
+ throw new InvalidConfigError(
202
+ "config.codec",
203
+ `is only valid with format="mp4"; got format=${String(config.format)}`
204
+ );
205
+ }
206
+ if (!ALLOWED_CODECS.includes(config.codec)) {
207
+ throw new InvalidConfigError(
208
+ "config.codec",
209
+ `must be one of ${ALLOWED_CODECS.join(", ")}; got ${String(config.codec)}`
210
+ );
211
+ }
212
+ }
213
+ if (config.quality !== void 0 && !ALLOWED_QUALITIES.includes(config.quality)) {
214
+ throw new InvalidConfigError(
215
+ "config.quality",
216
+ `must be one of ${ALLOWED_QUALITIES.join(", ")}; got ${String(config.quality)}`
217
+ );
218
+ }
219
+ if (config.crf !== void 0 && config.bitrate !== void 0) {
220
+ throw new InvalidConfigError("config.crf", "is mutually exclusive with config.bitrate");
221
+ }
222
+ if (config.crf !== void 0 && (!Number.isInteger(config.crf) || config.crf < 0 || config.crf > 51)) {
223
+ throw new InvalidConfigError("config.crf", `must be an integer in [0, 51]; got ${config.crf}`);
224
+ }
225
+ if (config.bitrate !== void 0 && !/^\d+(\.\d+)?[kKmM]?$/.test(config.bitrate)) {
226
+ throw new InvalidConfigError(
227
+ "config.bitrate",
228
+ `must look like "10M" or "5000k"; got ${JSON.stringify(config.bitrate)}`
229
+ );
230
+ }
231
+ if (config.chunkSize !== void 0) {
232
+ if (!Number.isInteger(config.chunkSize) || config.chunkSize < 1) {
233
+ throw new InvalidConfigError(
234
+ "config.chunkSize",
235
+ `must be a positive integer; got ${config.chunkSize}`
236
+ );
237
+ }
238
+ if (config.chunkSize > MAX_CHUNK_SIZE) {
239
+ throw new InvalidConfigError(
240
+ "config.chunkSize",
241
+ // Lambda 15-min cap leaves no useful headroom past ~3600 frames
242
+ // at 4 fps capture-equivalent throughput; rejecting up front
243
+ // avoids a 14-minute Plan-state retry storm.
244
+ `must be \u2264 ${MAX_CHUNK_SIZE} (Lambda 15-min cap); got ${config.chunkSize}`
245
+ );
246
+ }
247
+ }
248
+ if (config.maxParallelChunks !== void 0) {
249
+ if (!Number.isInteger(config.maxParallelChunks) || config.maxParallelChunks < 1) {
250
+ throw new InvalidConfigError(
251
+ "config.maxParallelChunks",
252
+ `must be a positive integer; got ${config.maxParallelChunks}`
253
+ );
254
+ }
255
+ if (config.maxParallelChunks > MAX_PARALLEL_CHUNKS_CEILING) {
256
+ throw new InvalidConfigError(
257
+ "config.maxParallelChunks",
258
+ `must be \u2264 ${MAX_PARALLEL_CHUNKS_CEILING}; got ${config.maxParallelChunks}`
259
+ );
260
+ }
261
+ }
262
+ if (config.runtimeCap !== void 0 && !ALLOWED_RUNTIME_CAPS.includes(config.runtimeCap)) {
263
+ throw new InvalidConfigError(
264
+ "config.runtimeCap",
265
+ `must be one of ${ALLOWED_RUNTIME_CAPS.join(", ")}; got ${String(config.runtimeCap)}`
266
+ );
267
+ }
268
+ if (config.hdrMode !== void 0 && !ALLOWED_HDR_MODES.includes(config.hdrMode)) {
269
+ throw new InvalidConfigError(
270
+ "config.hdrMode",
271
+ `distributed mode supports only ${ALLOWED_HDR_MODES.join(", ")}; got ${String(config.hdrMode)}`
272
+ );
273
+ }
274
+ return config;
275
+ }
276
+ function validateIntDimension(field, value) {
277
+ if (typeof value !== "number" || !Number.isInteger(value)) {
278
+ throw new InvalidConfigError(field, `must be an integer; got ${String(value)}`);
279
+ }
280
+ if (value < MIN_DIMENSION || value > MAX_DIMENSION) {
281
+ throw new InvalidConfigError(
282
+ field,
283
+ `must be in [${MIN_DIMENSION}, ${MAX_DIMENSION}]; got ${value}`
284
+ );
285
+ }
286
+ if (value % 2 !== 0) {
287
+ throw new InvalidConfigError(field, `must be even (yuv420p constraint); got ${value}`);
288
+ }
289
+ }
290
+
291
+ // src/sdk/renderToLambda.ts
292
+ async function renderToLambda(opts) {
293
+ validateDistributedRenderConfig(opts.config);
294
+ if (!opts.bucketName) {
295
+ throw new Error("[renderToLambda] bucketName is required");
296
+ }
297
+ if (!opts.stateMachineArn) {
298
+ throw new Error("[renderToLambda] stateMachineArn is required");
299
+ }
300
+ if (!opts.siteHandle && !opts.projectDir) {
301
+ throw new Error("[renderToLambda] either siteHandle or projectDir must be supplied");
302
+ }
303
+ const executionName = opts.executionName ?? `hf-render-${randomUUID()}`;
304
+ const ext = formatExtension(opts.config.format);
305
+ const outputKey = opts.outputKey ?? `renders/${executionName}/output${ext}`;
306
+ const planOutputS3Prefix = formatS3Uri({
307
+ bucket: opts.bucketName,
308
+ key: `renders/${executionName}/`
309
+ });
310
+ const outputS3Uri = formatS3Uri({ bucket: opts.bucketName, key: outputKey });
311
+ const site = opts.siteHandle ?? await deploySite({
312
+ projectDir: opts.projectDir,
313
+ bucketName: opts.bucketName,
314
+ region: opts.region,
315
+ s3: opts.s3
316
+ });
317
+ const input = {
318
+ ProjectS3Uri: site.projectS3Uri,
319
+ PlanOutputS3Prefix: planOutputS3Prefix,
320
+ OutputS3Uri: outputS3Uri,
321
+ Config: opts.config
322
+ };
323
+ const sfn = opts.sfn ?? new SFNClient({ region: opts.region });
324
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
325
+ const response = await sfn.send(
326
+ new StartExecutionCommand({
327
+ stateMachineArn: opts.stateMachineArn,
328
+ name: executionName,
329
+ input: JSON.stringify(input)
330
+ })
331
+ );
332
+ if (!response.executionArn) {
333
+ throw new Error("[renderToLambda] StartExecution returned no executionArn");
334
+ }
335
+ return {
336
+ renderId: executionName,
337
+ executionArn: response.executionArn,
338
+ bucketName: opts.bucketName,
339
+ stateMachineArn: opts.stateMachineArn,
340
+ outputS3Uri,
341
+ projectS3Uri: site.projectS3Uri,
342
+ startedAt
343
+ };
344
+ }
345
+
346
+ // src/sdk/getRenderProgress.ts
347
+ import {
348
+ DescribeExecutionCommand,
349
+ GetExecutionHistoryCommand,
350
+ SFNClient as SFNClient2
351
+ } from "@aws-sdk/client-sfn";
352
+
353
+ // src/sdk/costAccounting.ts
354
+ var LAMBDA_USD_PER_GB_SECOND = 166667e-10;
355
+ var SFN_USD_PER_TRANSITION = 25e-6;
356
+ function computeRenderCost(lambdaInvocations, stateTransitions) {
357
+ let lambdaUsd = 0;
358
+ let anyEstimated = false;
359
+ for (const inv of lambdaInvocations) {
360
+ const gbSeconds = inv.memorySizeMb / 1024 * (inv.billedDurationMs / 1e3);
361
+ lambdaUsd += gbSeconds * LAMBDA_USD_PER_GB_SECOND;
362
+ if (inv.estimated) anyEstimated = true;
363
+ }
364
+ const stepFunctionsUsd = stateTransitions * SFN_USD_PER_TRANSITION;
365
+ const accruedSoFarUsd = roundUsd(lambdaUsd + stepFunctionsUsd);
366
+ return {
367
+ accruedSoFarUsd,
368
+ displayCost: formatUsd(accruedSoFarUsd),
369
+ breakdown: {
370
+ lambdaUsd: roundUsd(lambdaUsd),
371
+ stepFunctionsUsd: roundUsd(stepFunctionsUsd),
372
+ s3Estimate: "not-included",
373
+ estimated: anyEstimated
374
+ }
375
+ };
376
+ }
377
+ function roundUsd(usd) {
378
+ return Math.round(usd * 1e4) / 1e4;
379
+ }
380
+ function formatUsd(usd) {
381
+ return `$${usd.toFixed(4)}`;
382
+ }
383
+
384
+ // src/sdk/getRenderProgress.ts
385
+ var DEFAULT_MEMORY_MB = 10240;
386
+ async function getRenderProgress(opts) {
387
+ if (!opts.executionArn) {
388
+ throw new Error("[getRenderProgress] executionArn is required");
389
+ }
390
+ const sfn = opts.sfn ?? new SFNClient2({ region: opts.region });
391
+ const memoryMb = opts.defaultMemorySizeMb ?? DEFAULT_MEMORY_MB;
392
+ const describe = await sfn.send(
393
+ new DescribeExecutionCommand({ executionArn: opts.executionArn })
394
+ );
395
+ const status = describe.status ?? "RUNNING";
396
+ const startedAt = describe.startDate?.toISOString() ?? (/* @__PURE__ */ new Date(0)).toISOString();
397
+ const endedAt = describe.stopDate?.toISOString() ?? null;
398
+ const history = await loadFullHistory(sfn, opts.executionArn);
399
+ const summary = summarizeHistory(history, memoryMb);
400
+ const costs = computeRenderCost(summary.lambdaInvocations, summary.stateTransitions);
401
+ const overallProgress = computeOverallProgress({
402
+ status,
403
+ totalFrames: summary.totalFrames,
404
+ framesRendered: summary.framesRendered,
405
+ assembleComplete: summary.assembleComplete
406
+ });
407
+ return {
408
+ status,
409
+ overallProgress,
410
+ framesRendered: summary.framesRendered,
411
+ totalFrames: summary.totalFrames,
412
+ lambdasInvoked: summary.lambdasInvoked,
413
+ costs,
414
+ outputFile: summary.outputFile,
415
+ errors: summary.errors,
416
+ fatalErrorEncountered: isTerminalFailure(status),
417
+ startedAt,
418
+ endedAt
419
+ };
420
+ }
421
+ async function loadFullHistory(sfn, executionArn) {
422
+ const events = [];
423
+ let nextToken;
424
+ for (let page = 0; page < 50; page++) {
425
+ const res = await sfn.send(
426
+ new GetExecutionHistoryCommand({
427
+ executionArn,
428
+ maxResults: 1e3,
429
+ nextToken,
430
+ reverseOrder: false
431
+ })
432
+ );
433
+ if (res.events) events.push(...res.events);
434
+ nextToken = res.nextToken;
435
+ if (!nextToken) break;
436
+ }
437
+ return events;
438
+ }
439
+ function summarizeHistory(events, memoryMb) {
440
+ let framesRendered = 0;
441
+ let totalFrames = null;
442
+ let lambdasInvoked = 0;
443
+ let assembleComplete = false;
444
+ let outputFile = null;
445
+ let stateTransitions = 0;
446
+ const errors = [];
447
+ const lambdaInvocations = [];
448
+ let currentLambdaState = null;
449
+ for (const ev of events) {
450
+ switch (ev.type) {
451
+ case "TaskStateEntered":
452
+ case "MapStateEntered":
453
+ case "PassStateEntered":
454
+ case "ChoiceStateEntered":
455
+ case "SucceedStateEntered":
456
+ case "FailStateEntered":
457
+ case "WaitStateEntered":
458
+ case "ParallelStateEntered":
459
+ stateTransitions++;
460
+ currentLambdaState = ev.stateEnteredEventDetails?.name ?? currentLambdaState;
461
+ break;
462
+ case "LambdaFunctionScheduled":
463
+ lambdasInvoked++;
464
+ break;
465
+ case "LambdaFunctionSucceeded": {
466
+ const payload = parseJson(ev.lambdaFunctionSucceededEventDetails?.output);
467
+ const billedDurationMs = inferBilledMs(payload);
468
+ lambdaInvocations.push({
469
+ billedDurationMs,
470
+ memorySizeMb: memoryMb,
471
+ estimated: billedDurationMs === 0
472
+ });
473
+ if (payload && typeof payload === "object") {
474
+ const obj = payload;
475
+ if (typeof obj.TotalFrames === "number") totalFrames = obj.TotalFrames;
476
+ if (typeof obj.FramesEncoded === "number") {
477
+ if (currentLambdaState === "RenderChunk") {
478
+ framesRendered += obj.FramesEncoded;
479
+ }
480
+ }
481
+ }
482
+ break;
483
+ }
484
+ case "TaskStateExited":
485
+ case "MapStateExited":
486
+ if (ev.stateExitedEventDetails?.name === "Assemble") {
487
+ assembleComplete = true;
488
+ const exitPayload = parseJson(ev.stateExitedEventDetails?.output);
489
+ if (exitPayload && typeof exitPayload === "object") {
490
+ const obj = exitPayload;
491
+ const out = obj.Output;
492
+ const outputS3Uri = typeof out?.OutputS3Uri === "string" ? out.OutputS3Uri : null;
493
+ const bytes = typeof out?.FileSize === "number" ? out.FileSize : null;
494
+ outputFile = outputS3Uri ? { s3Uri: outputS3Uri, bytes } : outputFile;
495
+ }
496
+ }
497
+ break;
498
+ case "LambdaFunctionFailed":
499
+ errors.push({
500
+ state: currentLambdaState ?? "<unknown>",
501
+ error: ev.lambdaFunctionFailedEventDetails?.error ?? "UNKNOWN",
502
+ cause: ev.lambdaFunctionFailedEventDetails?.cause ?? ""
503
+ });
504
+ break;
505
+ case "ExecutionFailed":
506
+ errors.push({
507
+ state: "<execution>",
508
+ error: ev.executionFailedEventDetails?.error ?? "UNKNOWN",
509
+ cause: ev.executionFailedEventDetails?.cause ?? ""
510
+ });
511
+ break;
512
+ case "ExecutionAborted":
513
+ errors.push({
514
+ state: "<execution>",
515
+ error: ev.executionAbortedEventDetails?.error ?? "ABORTED",
516
+ cause: ev.executionAbortedEventDetails?.cause ?? ""
517
+ });
518
+ break;
519
+ case "ExecutionTimedOut":
520
+ errors.push({
521
+ state: "<execution>",
522
+ error: "TIMEOUT",
523
+ cause: ev.executionTimedOutEventDetails?.cause ?? ""
524
+ });
525
+ break;
526
+ default:
527
+ break;
528
+ }
529
+ }
530
+ return {
531
+ lambdaInvocations,
532
+ stateTransitions,
533
+ framesRendered,
534
+ totalFrames,
535
+ lambdasInvoked,
536
+ assembleComplete,
537
+ outputFile,
538
+ errors
539
+ };
540
+ }
541
+ function parseJson(s) {
542
+ if (!s) return null;
543
+ try {
544
+ return JSON.parse(s);
545
+ } catch {
546
+ return null;
547
+ }
548
+ }
549
+ function inferBilledMs(payload) {
550
+ if (!payload || typeof payload !== "object") return 0;
551
+ const obj = payload;
552
+ if (typeof obj.DurationMs === "number") return obj.DurationMs;
553
+ return 0;
554
+ }
555
+ function computeOverallProgress({
556
+ status,
557
+ totalFrames,
558
+ framesRendered,
559
+ assembleComplete
560
+ }) {
561
+ if (status === "SUCCEEDED") return 1;
562
+ if (assembleComplete) return 1;
563
+ if (totalFrames === null) return 0;
564
+ const chunkProgress = Math.min(1, framesRendered / totalFrames);
565
+ return 0.1 + 0.8 * chunkProgress;
566
+ }
567
+ function isTerminalFailure(status) {
568
+ return status === "FAILED" || status === "TIMED_OUT" || status === "ABORTED";
569
+ }
570
+ export {
571
+ InvalidConfigError,
572
+ computeRenderCost,
573
+ deploySite,
574
+ getRenderProgress,
575
+ renderToLambda,
576
+ validateDistributedRenderConfig
577
+ };
578
+ //# sourceMappingURL=index.js.map