@hyperframes/aws-lambda 0.8.231 → 0.9.1

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 CHANGED
@@ -37,7 +37,7 @@ smoke flow; the SDK + CDK are the supported public surface for adopters.
37
37
  │ pure functions over local paths
38
38
  ▼
39
39
  ┌──────────────────────────────────────────────────────────────────┐
40
- │ S3 bucket — v1 plan tar or v2 manifest/blobs + chunks + output │
40
+ │ S3 bucket — Plan v2 manifest/blobs + chunks + output │
41
41
  └──────────────────────────────────────────────────────────────────┘
42
42
  ```
43
43
 
@@ -45,11 +45,11 @@ The handler downloads inputs from S3 into `/tmp`, calls the OSS primitive,
45
45
  uploads outputs back to S3, and returns a small JSON result that fits
46
46
  inside Step Functions' history budget (under 200 bytes per chunk).
47
47
 
48
- ### Plan transport selection
48
+ ### Plan transport
49
49
 
50
- Plan v2 is the default for new renders. When `planProtocol` is omitted,
51
- `renderToLambda` sends an explicit `PlanProtocol: "v2"` so the SDK and the
52
- deployed state machine agree:
50
+ Plan v2 is the only supported distributed plan transport.
51
+ `renderToLambda` sends `PlanProtocol: "v2"` without requiring a protocol
52
+ option:
53
53
 
54
54
  ```ts
55
55
  await renderToLambda({
@@ -63,8 +63,7 @@ only manifest-selected chunk artifacts, while the assembler fetches its
63
63
  own metadata and audio subset. Blobs are immutable SHA-256-addressed
64
64
  objects, verified on upload and download, and the manifest is published
65
65
  last. Unknown protocols and digest mismatches are terminal Step Functions
66
- errors. The monolithic v1 transport remains available as deprecated
67
- compatibility by passing `planProtocol: "v1"` explicitly.
66
+ errors.
68
67
 
69
68
  #### Upgrade order
70
69
 
@@ -78,7 +77,7 @@ infrastructure upgrade. Before upgrading an application that calls
78
77
  3. Resume renders, then upgrade the application/SDK dependency.
79
78
 
80
79
  A site is a `sites/<id>/files.json` file list that an older stack cannot
81
- read, under either plan protocol. Before it uploads anything,
80
+ read. Before it uploads anything,
82
81
  `renderToLambda` reads the state machine definition
83
82
  (`states:DescribeStateMachine`) and fails with `STACK_TOO_OLD` when the
84
83
  stack predates that format. If infrastructure cannot be redeployed first,
@@ -21,7 +21,7 @@ import {
21
21
  } from "./chunk-4CK3RKM2.js";
22
22
 
23
23
  // src/handler.ts
24
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
24
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
25
25
  import { availableParallelism, tmpdir as tmpdir2 } from "node:os";
26
26
  import { basename, join as join2 } from "node:path";
27
27
  import { GetAccountSettingsCommand, InvokeCommand, LambdaClient } from "@aws-sdk/client-lambda";
@@ -35,7 +35,6 @@ import {
35
35
  DEFAULT_MAX_PARALLEL_CHUNKS,
36
36
  listPlanV2ArtifactsForTarget,
37
37
  materializePlanV2Target,
38
- plan,
39
38
  isPlanAudioArtifactPath,
40
39
  PLAN_AUDIO_RELATIVE_PATH,
41
40
  resolvePlanAudioPath,
@@ -452,7 +451,7 @@ async function handler(event, deps) {
452
451
  function normalizeTerminalErrorName(error) {
453
452
  if (!error || typeof error !== "object") return;
454
453
  const candidate = error;
455
- if (candidate.code === "PLAN_PROTOCOL_UNSUPPORTED" || candidate.code === "PLAN_TOO_LARGE" || candidate.code === "PLAN_V2_INTEGRITY_UNRECOVERABLE" || candidate.code === "FONT_FETCH_FAILED" || candidate.code === "FONT_FETCH_UNAVAILABLE" || candidate.code === "NOT_MEDIA_PAYLOAD" || candidate.code === "VIDEO_SOURCE_UNRENDERABLE" || candidate.code === "VIDEO_EXTRACTION_FAILED" || candidate.code === "INVALID_VIDEO_METADATA" || candidate.code === "FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED" || candidate.code === "CHUNK_EXCEEDS_WORKER_TIMEOUT") {
454
+ if (candidate.code === "PLAN_PROTOCOL_UNSUPPORTED" || candidate.code === "PLAN_V2_INTEGRITY_UNRECOVERABLE" || candidate.code === "FONT_FETCH_FAILED" || candidate.code === "FONT_FETCH_UNAVAILABLE" || candidate.code === "NOT_MEDIA_PAYLOAD" || candidate.code === "VIDEO_SOURCE_UNRENDERABLE" || candidate.code === "VIDEO_EXTRACTION_FAILED" || candidate.code === "INVALID_VIDEO_METADATA" || candidate.code === "FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED" || candidate.code === "CHUNK_EXCEEDS_WORKER_TIMEOUT") {
456
455
  candidate.name = candidate.code;
457
456
  }
458
457
  }
@@ -486,22 +485,20 @@ function isLambdaAction(value) {
486
485
  function validatePlanProtocolShape(event) {
487
486
  const raw = event;
488
487
  const protocol = raw.PlanProtocol;
489
- if (protocol !== void 0 && protocol !== "v1" && protocol !== "v2") {
488
+ if (protocol !== void 0 && protocol !== "v2") {
490
489
  const error = new Error(
491
- `[handler] unsupported PlanProtocol ${JSON.stringify(protocol)}; expected "v1", "v2", or absent`
490
+ `[handler] unsupported PlanProtocol ${JSON.stringify(protocol)}; expected "v2" or absent`
492
491
  );
493
492
  error.name = "PLAN_PROTOCOL_UNSUPPORTED";
494
493
  throw error;
495
494
  }
496
495
  if (event.Action === "plan" || event.Action === "audio") return;
497
- const effectiveProtocol = protocol ?? "v2";
498
496
  const hasV1Locator = typeof raw.PlanS3Uri === "string";
499
497
  const hasV2Manifest = typeof raw.PlanV2ManifestS3Uri === "string";
500
498
  const hasV2Prefix = typeof raw.PlanV2ArtifactS3Prefix === "string";
501
- const valid = effectiveProtocol === "v2" ? !hasV1Locator && hasV2Manifest && hasV2Prefix : hasV1Locator && !hasV2Manifest && !hasV2Prefix;
502
- if (!valid) {
499
+ if (hasV1Locator || !hasV2Manifest || !hasV2Prefix) {
503
500
  const error = new Error(
504
- `[handler] ${effectiveProtocol} ${event.Action} event has mixed or missing plan locators`
501
+ `[handler] v2 ${event.Action} event has mixed or missing plan locators`
505
502
  );
506
503
  error.name = "PLAN_PROTOCOL_UNSUPPORTED";
507
504
  throw error;
@@ -516,7 +513,7 @@ function summarizeEvent(event) {
516
513
  return {
517
514
  projectS3Uri: event.ProjectS3Uri,
518
515
  planOutputS3Prefix: event.PlanOutputS3Prefix,
519
- planProtocol: event.PlanProtocol ?? "v2",
516
+ planProtocol: "v2",
520
517
  format: event.Config.format,
521
518
  fps: event.Config.fps
522
519
  };
@@ -529,15 +526,15 @@ function summarizeEvent(event) {
529
526
  };
530
527
  case "renderChunk":
531
528
  return {
532
- planProtocol: event.PlanProtocol ?? "v2",
533
- ...event.PlanProtocol !== "v1" ? { planV2ManifestS3Uri: event.PlanV2ManifestS3Uri } : { planS3Uri: event.PlanS3Uri },
529
+ planProtocol: "v2",
530
+ planV2ManifestS3Uri: event.PlanV2ManifestS3Uri,
534
531
  chunkIndex: event.ChunkIndex,
535
532
  format: event.Format
536
533
  };
537
534
  case "assemble":
538
535
  return {
539
- planProtocol: event.PlanProtocol ?? "v2",
540
- ...event.PlanProtocol !== "v1" ? { planV2ManifestS3Uri: event.PlanV2ManifestS3Uri } : { planS3Uri: event.PlanS3Uri },
536
+ planProtocol: "v2",
537
+ planV2ManifestS3Uri: event.PlanV2ManifestS3Uri,
541
538
  chunkCount: event.ChunkS3Uris.length,
542
539
  hasAudio: event.AudioS3Uri !== null,
543
540
  outputS3Uri: event.OutputS3Uri,
@@ -596,53 +593,6 @@ async function planConfig(event, deps) {
596
593
  }
597
594
  return config;
598
595
  }
599
- async function handlePlan(event, deps) {
600
- if (event.PlanProtocol !== "v1") {
601
- return handlePlanV2(event, deps);
602
- }
603
- const started = Date.now();
604
- const config = await planConfig(event, deps);
605
- const s3 = deps?.s3 ?? getS3Client();
606
- const primitive = deps?.primitives?.plan ?? plan;
607
- await ensureChromePath(deps);
608
- const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-plan-"));
609
- const planDir = join2(work, "plan");
610
- try {
611
- const { projectDir } = await fetchProject(s3, event.ProjectS3Uri, work);
612
- const result = await primitive(projectDir, config, planDir, {
613
- chunksCarryAudio: true
614
- });
615
- const planTar = join2(work, "plan.tar.gz");
616
- await tarDirectory(planDir, planTar);
617
- const planTarUri = `${trimTrailingSlash2(event.PlanOutputS3Prefix)}/plan.tar.gz`;
618
- const audioPath = join2(planDir, PLAN_AUDIO_RELATIVE_PATH);
619
- const hasAudio = existsSync2(audioPath) && statSync2(audioPath).size > 0;
620
- const audioUri = hasAudio ? `${trimTrailingSlash2(event.PlanOutputS3Prefix)}/${PLAN_AUDIO_RELATIVE_PATH}` : null;
621
- await Promise.all([
622
- uploadFileToS3(s3, planTar, planTarUri, "application/gzip"),
623
- hasAudio && audioUri ? uploadFileToS3(s3, audioPath, audioUri, "audio/aac") : null
624
- ]);
625
- return {
626
- Action: "plan",
627
- PlanS3Uri: planTarUri,
628
- PlanHash: result.planHash,
629
- ChunkCount: result.chunkCount,
630
- MaxChunkConcurrency: Math.min(config.workerConcurrency, result.chunkCount),
631
- TotalFrames: result.totalFrames,
632
- Fps: result.fps,
633
- Width: result.width,
634
- Height: result.height,
635
- Format: result.format,
636
- HasAudio: audioUri !== null,
637
- AudioS3Uri: audioUri,
638
- FfmpegVersion: result.ffmpegVersion,
639
- ProducerVersion: result.producerVersion,
640
- DurationMs: Date.now() - started
641
- };
642
- } finally {
643
- cleanupDir(work);
644
- }
645
- }
646
596
  async function fetchProject(s3, projectS3Uri, work, leaveVideosRemote = false) {
647
597
  const projectDir = join2(work, "project");
648
598
  if (isSiteManifestUri(projectS3Uri)) {
@@ -658,7 +608,7 @@ async function fetchProject(s3, projectS3Uri, work, leaveVideosRemote = false) {
658
608
  logEvent({ event: "plan_stage", stage: "untar", ms: Date.now() - stageStarted });
659
609
  return { projectDir, remoteFiles: {} };
660
610
  }
661
- async function handlePlanV2(event, deps) {
611
+ async function handlePlan(event, deps) {
662
612
  const started = Date.now();
663
613
  const config = await planConfig(event, deps);
664
614
  const s3 = deps?.s3 ?? getS3Client();
@@ -750,58 +700,6 @@ function lambdaMaxCaptureWorkers() {
750
700
  return Math.max(1, Math.min(byMemory, availableParallelism() - 1));
751
701
  }
752
702
  async function handleRenderChunk(event, deps) {
753
- if (event.PlanProtocol !== "v1") {
754
- return handleRenderChunkV2(event, deps);
755
- }
756
- const started = Date.now();
757
- const s3 = deps?.s3 ?? getS3Client();
758
- const primitive = deps?.primitives?.renderChunk ?? renderChunk;
759
- const chromeStarted = Date.now();
760
- await ensureChromePath(deps);
761
- const chromeMs = Date.now() - chromeStarted;
762
- const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-chunk-"));
763
- const planTar = join2(work, "plan.tar.gz");
764
- const planDir = join2(work, "plan");
765
- try {
766
- await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar);
767
- await untarDirectory(planTar, planDir);
768
- verifyPlanHash(planDir, event.PlanHash);
769
- const chunkOutputBase = join2(
770
- work,
771
- event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
772
- );
773
- const primitiveStarted = Date.now();
774
- const result = await primitive(planDir, event.ChunkIndex, chunkOutputBase, {
775
- maxCaptureWorkers: lambdaMaxCaptureWorkers(),
776
- interleaveCapture: true
777
- });
778
- logEvent({
779
- event: "chunk_capture",
780
- chunkIndex: event.ChunkIndex,
781
- workers: result.workers,
782
- captureMode: result.captureMode,
783
- ...chunkStageTimings(result, primitiveStarted - started, chromeMs)
784
- });
785
- const chunkUri = await uploadChunkOutput(
786
- s3,
787
- result,
788
- event.ChunkOutputS3Prefix,
789
- event.ChunkIndex
790
- );
791
- return {
792
- Action: "renderChunk",
793
- ChunkS3Uri: chunkUri,
794
- ChunkIndex: event.ChunkIndex,
795
- Sha256: result.sha256,
796
- FramesEncoded: result.framesEncoded,
797
- CaptureMode: result.captureMode,
798
- DurationMs: Date.now() - started
799
- };
800
- } finally {
801
- cleanupDir(work);
802
- }
803
- }
804
- async function handleRenderChunkV2(event, deps) {
805
703
  const started = Date.now();
806
704
  const s3 = deps?.s3 ?? getS3Client();
807
705
  const primitive = deps?.primitives?.renderChunk ?? renderChunk;
@@ -878,47 +776,6 @@ async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
878
776
  return uri;
879
777
  }
880
778
  async function handleAssemble(event, deps) {
881
- if (event.PlanProtocol !== "v1") {
882
- return handleAssembleV2(event, deps);
883
- }
884
- const started = Date.now();
885
- const s3 = deps?.s3 ?? getS3Client();
886
- const primitive = deps?.primitives?.assemble ?? assemble;
887
- const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-assemble-"));
888
- const planTar = join2(work, "plan.tar.gz");
889
- const planDir = join2(work, "plan");
890
- try {
891
- await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar);
892
- await untarDirectory(planTar, planDir);
893
- const chunkPaths = await downloadChunkObjects(s3, event.ChunkS3Uris, work, event.Format);
894
- let audioPath = null;
895
- if (event.AudioS3Uri) {
896
- audioPath = resolvePlanAudioPath(planDir) ?? join2(planDir, PLAN_AUDIO_RELATIVE_PATH);
897
- await downloadS3ObjectToFile(s3, event.AudioS3Uri, audioPath);
898
- }
899
- const finalOutput = event.Format === "png-sequence" ? join2(work, "output-frames") : join2(work, `output${formatExtension(event.Format)}`);
900
- const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
901
- cfr: event.Cfr === true
902
- });
903
- if (event.Format === "png-sequence") {
904
- const tarball = `${finalOutput}.tar.gz`;
905
- await tarDirectory(finalOutput, tarball);
906
- await uploadFileToS3(s3, tarball, event.OutputS3Uri, "application/gzip");
907
- } else {
908
- await uploadFileToS3(s3, finalOutput, event.OutputS3Uri);
909
- }
910
- return {
911
- Action: "assemble",
912
- OutputS3Uri: event.OutputS3Uri,
913
- FramesEncoded: result.framesEncoded,
914
- FileSize: result.fileSize,
915
- DurationMs: Date.now() - started
916
- };
917
- } finally {
918
- cleanupDir(work);
919
- }
920
- }
921
- async function handleAssembleV2(event, deps) {
922
779
  const started = Date.now();
923
780
  const s3 = deps?.s3 ?? getS3Client();
924
781
  const primitive = deps?.primitives?.assemble ?? assemble;
@@ -976,10 +833,10 @@ async function handleAssembleV2(event, deps) {
976
833
  cleanupDir(work);
977
834
  }
978
835
  }
979
- async function stitchChunksInS3(s3, event, plan2, started, lambda) {
980
- if (event.ChunkS3Uris.length !== plan2.chunkCount) {
836
+ async function stitchChunksInS3(s3, event, plan, started, lambda) {
837
+ if (event.ChunkS3Uris.length !== plan.chunkCount) {
981
838
  throw new Error(
982
- `[handler] ${event.ChunkS3Uris.length} chunk URIs for a plan of ${plan2.chunkCount} chunks`
839
+ `[handler] ${event.ChunkS3Uris.length} chunk URIs for a plan of ${plan.chunkCount} chunks`
983
840
  );
984
841
  }
985
842
  const stitchStarted = Date.now();
@@ -1000,7 +857,7 @@ async function stitchChunksInS3(s3, event, plan2, started, lambda) {
1000
857
  return {
1001
858
  Action: "assemble",
1002
859
  OutputS3Uri: event.OutputS3Uri,
1003
- FramesEncoded: plan2.totalFrames,
860
+ FramesEncoded: plan.totalFrames,
1004
861
  FileSize: stitched.fileSize,
1005
862
  DurationMs: Date.now() - started
1006
863
  };
@@ -1095,10 +952,11 @@ function getEventS3Uris(event) {
1095
952
  case "audio":
1096
953
  return [event.ProjectS3Uri, event.PlanOutputS3Prefix];
1097
954
  case "renderChunk":
1098
- return event.PlanProtocol !== "v1" ? [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix, event.ChunkOutputS3Prefix] : [event.PlanS3Uri, event.ChunkOutputS3Prefix];
955
+ return [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix, event.ChunkOutputS3Prefix];
1099
956
  case "assemble":
1100
957
  return [
1101
- ...event.PlanProtocol !== "v1" ? [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix] : [event.PlanS3Uri],
958
+ event.PlanV2ManifestS3Uri,
959
+ event.PlanV2ArtifactS3Prefix,
1102
960
  ...event.ChunkS3Uris,
1103
961
  event.OutputS3Uri,
1104
962
  event.AudioS3Uri
@@ -1168,26 +1026,6 @@ function cleanupDir(dir) {
1168
1026
  } catch {
1169
1027
  }
1170
1028
  }
1171
- function verifyPlanHash(planDir, expected) {
1172
- const planJsonPath = join2(planDir, "plan.json");
1173
- let parsed;
1174
- try {
1175
- parsed = JSON.parse(readFileSync(planJsonPath, "utf-8"));
1176
- } catch (err) {
1177
- const msg = err instanceof Error ? err.message : String(err);
1178
- const error = new Error(`PLAN_HASH_MISMATCH: failed to read ${planJsonPath}: ${msg}`);
1179
- error.name = "PLAN_HASH_MISMATCH";
1180
- throw error;
1181
- }
1182
- const actual = parsed.planHash;
1183
- if (typeof actual !== "string" || actual !== expected) {
1184
- const error = new Error(
1185
- `PLAN_HASH_MISMATCH: event PlanHash=${expected} did not match plan.json planHash=${String(actual)}`
1186
- );
1187
- error.name = "PLAN_HASH_MISMATCH";
1188
- throw error;
1189
- }
1190
- }
1191
1029
 
1192
1030
  export {
1193
1031
  ChromeBinaryUnavailableError,
@@ -221,7 +221,17 @@ function validateStepFunctionsInputSize(input) {
221
221
  import { randomUUID } from "node:crypto";
222
222
  import { DescribeStateMachineCommand, SFNClient, StartExecutionCommand } from "@aws-sdk/client-sfn";
223
223
  import { S3Client as S3Client2 } from "@aws-sdk/client-s3";
224
+ function rejectUnsupportedLegacyPlanProtocol(opts) {
225
+ const planProtocol = Reflect.get(opts, "planProtocol");
226
+ if (planProtocol === void 0 || planProtocol === "v2") return;
227
+ const error = new Error(
228
+ `[renderToLambda] planProtocol is no longer configurable; expected "v2" or absent, got ${JSON.stringify(planProtocol)}`
229
+ );
230
+ error.name = "PLAN_PROTOCOL_UNSUPPORTED";
231
+ throw error;
232
+ }
224
233
  async function renderToLambda(opts) {
234
+ rejectUnsupportedLegacyPlanProtocol(opts);
225
235
  validateDistributedRenderConfig(opts.config);
226
236
  if (!opts.bucketName) {
227
237
  throw new Error("[renderToLambda] bucketName is required");
@@ -258,7 +268,7 @@ async function renderToLambda(opts) {
258
268
  PlanOutputS3Prefix: planOutputS3Prefix,
259
269
  OutputS3Uri: outputS3Uri,
260
270
  Config: opts.config,
261
- PlanProtocol: opts.planProtocol ?? "v2"
271
+ PlanProtocol: "v2"
262
272
  };
263
273
  validateStepFunctionsInputSize(input);
264
274
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
package/dist/cdk/index.js CHANGED
@@ -160,8 +160,6 @@ var HyperframesRenderStack = class extends Construct {
160
160
  "S3_URI_NOT_ALLOWED",
161
161
  "BROWSER_GPU_NOT_SOFTWARE",
162
162
  "FONT_FETCH_FAILED",
163
- "PLAN_TOO_LARGE",
164
- "PlanTooLargeError",
165
163
  "PLAN_PROTOCOL_UNSUPPORTED",
166
164
  "PlanProtocolUnsupportedError",
167
165
  "PLAN_V2_INTEGRITY_UNRECOVERABLE",
@@ -181,8 +179,6 @@ var HyperframesRenderStack = class extends Construct {
181
179
  "PLAN_HASH_MISMATCH",
182
180
  "S3_URI_NOT_ALLOWED",
183
181
  "BROWSER_GPU_NOT_SOFTWARE",
184
- "PLAN_TOO_LARGE",
185
- "PlanTooLargeError",
186
182
  "PLAN_PROTOCOL_UNSUPPORTED",
187
183
  "PlanProtocolUnsupportedError",
188
184
  "PLAN_V2_INTEGRITY_UNRECOVERABLE",
@@ -200,8 +196,6 @@ var HyperframesRenderStack = class extends Construct {
200
196
  "PLAN_V2_INTEGRITY_UNRECOVERABLE",
201
197
  "PlanV2IntegrityError",
202
198
  "FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED",
203
- "PLAN_TOO_LARGE",
204
- "PlanTooLargeError",
205
199
  "PLAN_ARTIFACT_DIGEST_MISMATCH",
206
200
  "ChromeBinaryUnavailableError"
207
201
  ];
@@ -213,39 +207,6 @@ var HyperframesRenderStack = class extends Construct {
213
207
  maxDelay: Duration.seconds(60),
214
208
  jitterStrategy: sfn.JitterType.FULL
215
209
  };
216
- const plan = new tasks.LambdaInvoke(this, "Plan", {
217
- lambdaFunction: this.renderFunction,
218
- payload: sfn.TaskInput.fromObject({
219
- Action: "plan",
220
- PlanProtocol: "v1",
221
- "ProjectS3Uri.$": "$.ProjectS3Uri",
222
- "PlanOutputS3Prefix.$": "$.PlanOutputS3Prefix",
223
- "Config.$": "$.Config"
224
- }),
225
- resultSelector: {
226
- PlanProtocol: "v1",
227
- "PlanS3Uri.$": "$.Payload.PlanS3Uri",
228
- "PlanHash.$": "$.Payload.PlanHash",
229
- "ChunkCount.$": "$.Payload.ChunkCount",
230
- "MaxChunkConcurrency.$": "$.Payload.MaxChunkConcurrency",
231
- "Format.$": "$.Payload.Format",
232
- "HasAudio.$": "$.Payload.HasAudio",
233
- "AudioS3Uri.$": "$.Payload.AudioS3Uri"
234
- },
235
- resultPath: "$.Plan"
236
- });
237
- plan.addRetry({
238
- errors: NON_RETRYABLE_PLAN,
239
- maxAttempts: 0
240
- });
241
- plan.addRetry(THROTTLED_RETRY);
242
- plan.addRetry({
243
- errors: ["States.ALL"],
244
- interval: Duration.seconds(2),
245
- maxAttempts: 4,
246
- backoffRate: 2,
247
- maxDelay: Duration.seconds(60)
248
- });
249
210
  const planV2 = new tasks.LambdaInvoke(this, "PlanV2", {
250
211
  lambdaFunction: this.renderFunction,
251
212
  payload: sfn.TaskInput.fromObject({
@@ -284,87 +245,9 @@ var HyperframesRenderStack = class extends Construct {
284
245
  resultPath: "$.Iterator"
285
246
  });
286
247
  const planProducedZero = new sfn.Fail(this, "PlanProducedZeroChunks", {
287
- error: "PLAN_TOO_LARGE",
248
+ error: "PLAN_EMPTY",
288
249
  cause: "Plan returned ChunkCount=0 \u2014 non-retryable producer-side invariant violation."
289
250
  });
290
- const renderChunkTask = new tasks.LambdaInvoke(this, "RenderChunk", {
291
- lambdaFunction: this.renderFunction,
292
- payload: sfn.TaskInput.fromObject({
293
- Action: "renderChunk",
294
- PlanProtocol: "v1",
295
- "ChunkIndex.$": "$.ChunkIndex",
296
- "PlanS3Uri.$": "$.PlanS3Uri",
297
- "PlanHash.$": "$.PlanHash",
298
- "ChunkOutputS3Prefix.$": "$.ChunkOutputS3Prefix",
299
- "Format.$": "$.Format"
300
- }),
301
- resultSelector: {
302
- "ChunkS3Uri.$": "$.Payload.ChunkS3Uri",
303
- "ChunkIndex.$": "$.Payload.ChunkIndex",
304
- "Sha256.$": "$.Payload.Sha256",
305
- "FramesEncoded.$": "$.Payload.FramesEncoded",
306
- "DurationMs.$": "$.Payload.DurationMs"
307
- }
308
- });
309
- renderChunkTask.addRetry({
310
- errors: NON_RETRYABLE_CHUNK,
311
- maxAttempts: 0
312
- });
313
- renderChunkTask.addRetry(THROTTLED_RETRY);
314
- renderChunkTask.addRetry({
315
- errors: ["States.ALL"],
316
- interval: Duration.seconds(2),
317
- maxAttempts: 4,
318
- backoffRate: 2,
319
- maxDelay: Duration.seconds(60)
320
- });
321
- const renderChunks = new sfn.DistributedMap(this, "RenderChunks", {
322
- label: "RenderChunks",
323
- // Standard-type children: express ones stop at 5 minutes and a chunk may run the Lambda's full 15.
324
- mapExecutionType: sfn.StateMachineType.STANDARD,
325
- itemsPath: "$.Iterator.ChunkIndexes",
326
- itemSelector: {
327
- "ChunkIndex.$": "$$.Map.Item.Value",
328
- "PlanS3Uri.$": "$.Plan.PlanS3Uri",
329
- "PlanHash.$": "$.Plan.PlanHash",
330
- "ChunkOutputS3Prefix.$": "$.PlanOutputS3Prefix",
331
- "Format.$": "$.Plan.Format"
332
- },
333
- // Chunks past the plan's concurrency queue here instead of being throttled by Lambda.
334
- maxConcurrencyPath: "$.Plan.MaxChunkConcurrency",
335
- resultPath: "$.Chunks"
336
- });
337
- renderChunks.itemProcessor(renderChunkTask);
338
- const assemble = new tasks.LambdaInvoke(this, "Assemble", {
339
- lambdaFunction: this.renderFunction,
340
- payload: sfn.TaskInput.fromObject({
341
- Action: "assemble",
342
- PlanProtocol: "v1",
343
- "PlanS3Uri.$": "$.Plan.PlanS3Uri",
344
- "ChunkS3Uris.$": "$.Chunks[*].ChunkS3Uri",
345
- "AudioS3Uri.$": "$.Plan.AudioS3Uri",
346
- "OutputS3Uri.$": "$.OutputS3Uri",
347
- "Format.$": "$.Plan.Format"
348
- }),
349
- resultSelector: {
350
- "OutputS3Uri.$": "$.Payload.OutputS3Uri",
351
- "FramesEncoded.$": "$.Payload.FramesEncoded",
352
- "FileSize.$": "$.Payload.FileSize"
353
- },
354
- resultPath: "$.Output"
355
- });
356
- assemble.addRetry({
357
- errors: NON_RETRYABLE_ASSEMBLE,
358
- maxAttempts: 0
359
- });
360
- assemble.addRetry(THROTTLED_RETRY);
361
- assemble.addRetry({
362
- errors: ["States.ALL"],
363
- interval: Duration.seconds(2),
364
- maxAttempts: 4,
365
- backoffRate: 2,
366
- maxDelay: Duration.seconds(60)
367
- });
368
251
  const renderChunkV2Task = new tasks.LambdaInvoke(this, "RenderChunkV2", {
369
252
  lambdaFunction: this.renderFunction,
370
253
  payload: sfn.TaskInput.fromObject({
@@ -472,16 +355,14 @@ var HyperframesRenderStack = class extends Construct {
472
355
  resultSelector: { "Chunks.$": "$[0].Chunks", "Audio.$": "$[1].Audio" },
473
356
  resultPath: "$.Render"
474
357
  }).branch(renderChunksV2).branch(audioNeeded);
475
- const selectWorkerProtocol = new sfn.Choice(this, "SelectWorkerProtocol").when(sfn.Condition.stringEquals("$.Plan.PlanProtocol", "v2"), renderV2.next(assembleV2)).otherwise(renderChunks.next(assemble));
476
- const assertChunkCount = new sfn.Choice(this, "AssertChunkCount").when(sfn.Condition.numberGreaterThan("$.Plan.ChunkCount", 0), selectWorkerProtocol).otherwise(planProducedZero);
477
- plan.next(buildChunkList);
358
+ const assertChunkCount = new sfn.Choice(this, "AssertChunkCount").when(sfn.Condition.numberGreaterThan("$.Plan.ChunkCount", 0), renderV2.next(assembleV2)).otherwise(planProducedZero);
478
359
  planV2.next(buildChunkList);
479
360
  buildChunkList.next(assertChunkCount);
480
361
  const unsupportedPlanProtocol = new sfn.Fail(this, "UnsupportedPlanProtocol", {
481
362
  error: "PLAN_PROTOCOL_UNSUPPORTED",
482
- cause: 'PlanProtocol must be "v1", "v2", or absent (defaults to v2).'
363
+ cause: 'PlanProtocol must be "v2" or absent (defaults to v2).'
483
364
  });
484
- return new sfn.Choice(this, "SelectPlanProtocol").when(sfn.Condition.stringEquals("$.PlanProtocol", "v2"), planV2).when(sfn.Condition.stringEquals("$.PlanProtocol", "v1"), plan).when(sfn.Condition.isPresent("$.PlanProtocol"), unsupportedPlanProtocol).otherwise(planV2);
365
+ return new sfn.Choice(this, "SelectPlanProtocol").when(sfn.Condition.isNotPresent("$.PlanProtocol"), planV2).when(sfn.Condition.stringEquals("$.PlanProtocol", "v2"), planV2).when(sfn.Condition.isPresent("$.PlanProtocol"), unsupportedPlanProtocol).otherwise(planV2);
485
366
  }
486
367
  };
487
368
  function defaultHandlerZipPath() {
package/dist/events.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * dispatches on the `Action` field. Each action maps 1:1 onto one of the
6
6
  * three OSS distributed primitives:
7
7
  *
8
- * "plan" → `plan(projectDir, config, planDir)` (Activity A)
8
+ * "plan" → `planV2WithPublisher(...)` (Activity A)
9
9
  * "renderChunk" → `renderChunk(planDir, chunkIndex, output)` (Activity B)
10
10
  * "assemble" → `assemble(planDir, chunkPaths, audio, out)` (Activity C)
11
11
  *
@@ -37,8 +37,6 @@ export interface StitchPartsEvent {
37
37
  }[];
38
38
  }[];
39
39
  }
40
- /** Transport protocol selected for one complete distributed render. */
41
- export type LambdaPlanProtocol = "v1" | "v2";
42
40
  /**
43
41
  * Top-level shape of any event the handler may receive.
44
42
  *
@@ -53,7 +51,7 @@ export type LambdaEvent = PlanEvent | AudioEvent | RenderChunkEvent | AssembleEv
53
51
  Input: LambdaEvent;
54
52
  };
55
53
  /** Activity A: produce a planDir, upload to S3. */
56
- interface PlanEventBase {
54
+ export interface PlanEvent {
57
55
  Action: "plan";
58
56
  /** S3 URI pointing at a `tar -czf`-archived project directory (`s3://bucket/key.tar.gz`). */
59
57
  ProjectS3Uri: string;
@@ -61,33 +59,23 @@ interface PlanEventBase {
61
59
  PlanOutputS3Prefix: string;
62
60
  /** `DistributedRenderConfig` minus runtime-only fields (logger, abortSignal). */
63
61
  Config: SerializableDistributedRenderConfig;
64
- }
65
- /**
66
- * Legacy plan transport. Callers must select it explicitly.
67
- *
68
- * @deprecated Use {@link PlanV2Event} for new integrations.
69
- */
70
- export interface PlanV1Event extends PlanEventBase {
71
- PlanProtocol: "v1";
72
- }
73
- /** Default content-addressed v2 plan transport. */
74
- export interface PlanV2Event extends PlanEventBase {
62
+ /** Optional for direct handler callers; the SDK and orchestrator send it explicitly. */
75
63
  PlanProtocol?: "v2";
76
64
  }
77
- export type PlanEvent = PlanV1Event | PlanV2Event;
65
+ /** Compatibility alias for the only supported plan event. */
66
+ export type PlanV2Event = PlanEvent;
78
67
  /** Makes a v2 plan's audio beside its chunks, from the same project and config as the plan. */
79
- export interface AudioEvent extends Omit<PlanEventBase, "Action"> {
68
+ export interface AudioEvent extends Omit<PlanEvent, "Action" | "PlanProtocol"> {
80
69
  Action: "audio";
81
70
  }
82
71
  /** Activity B: fetch planDir, render one chunk, upload result. */
83
- interface RenderChunkEventBase {
72
+ export interface RenderChunkEvent {
84
73
  Action: "renderChunk";
85
74
  /**
86
- * `PlanResult.planHash` from the Plan invocation. For v1, the handler
87
- * verifies it against the untarred planDir's `plan.json`; for v2, it
88
- * verifies it against the content-addressed manifest before invoking the
89
- * producer. Divergence throws a typed `PLAN_HASH_MISMATCH` so the state
90
- * machine routes it as non-retryable.
75
+ * `PlanResult.planHash` from the Plan invocation. The handler verifies it
76
+ * against the content-addressed manifest before invoking the producer.
77
+ * Divergence throws a typed `PLAN_HASH_MISMATCH` so the state machine routes
78
+ * it as non-retryable.
91
79
  */
92
80
  PlanHash: string;
93
81
  /** 0-based chunk index this invocation should render. */
@@ -96,29 +84,14 @@ interface RenderChunkEventBase {
96
84
  ChunkOutputS3Prefix: string;
97
85
  /** Output container format from the plan's encoder.json; drives file vs frame-dir handling. */
98
86
  Format: DistributedFormat;
99
- }
100
- /**
101
- * Legacy chunk event. Callers must select it explicitly.
102
- *
103
- * @deprecated Use {@link RenderChunkV2Event} for new integrations.
104
- */
105
- export interface RenderChunkV1Event extends RenderChunkEventBase {
106
- PlanProtocol: "v1";
107
- /** S3 URI of the v1 plan tar produced by a PlanEvent invocation. */
108
- PlanS3Uri: string;
109
- }
110
- /**
111
- * V2 chunk event. It intentionally cannot carry `PlanS3Uri`: the manifest
112
- * describes the exact content-addressed artifacts needed by this chunk.
113
- */
114
- export interface RenderChunkV2Event extends RenderChunkEventBase {
115
87
  PlanProtocol?: "v2";
116
88
  PlanV2ManifestS3Uri: string;
117
89
  PlanV2ArtifactS3Prefix: string;
118
90
  }
119
- export type RenderChunkEvent = RenderChunkV1Event | RenderChunkV2Event;
91
+ /** Compatibility alias for the only supported chunk event. */
92
+ export type RenderChunkV2Event = RenderChunkEvent;
120
93
  /** Activity C: fetch planDir + all chunks + audio, assemble, upload final. */
121
- interface AssembleEventBase {
94
+ export interface AssembleEvent {
122
95
  Action: "assemble";
123
96
  /** S3 URIs of every chunk, ordered by chunk index. Length must equal `chunkCount`. */
124
97
  ChunkS3Uris: string[];
@@ -138,27 +111,15 @@ interface AssembleEventBase {
138
111
  * unset preserves current `-c copy` behavior.
139
112
  */
140
113
  Cfr?: boolean;
141
- }
142
- /**
143
- * Legacy assemble event. Callers must select it explicitly.
144
- *
145
- * @deprecated Use {@link AssembleV2Event} for new integrations.
146
- */
147
- export interface AssembleV1Event extends AssembleEventBase {
148
- PlanProtocol: "v1";
149
- /** S3 URI of the v1 plan tar produced by a PlanEvent invocation. */
150
- PlanS3Uri: string;
151
- }
152
- /** V2 assemble event, scoped to manifest-declared assembler artifacts. */
153
- export interface AssembleV2Event extends AssembleEventBase {
154
114
  PlanProtocol?: "v2";
155
115
  PlanV2ManifestS3Uri: string;
156
116
  PlanV2ArtifactS3Prefix: string;
157
117
  PlanHash: string;
158
118
  }
159
- export type AssembleEvent = AssembleV1Event | AssembleV2Event;
119
+ /** Compatibility alias for the only supported assemble event. */
120
+ export type AssembleV2Event = AssembleEvent;
160
121
  /** Result of a `plan` invocation. Carries enough to size the Map(N) state. */
161
- interface PlanLambdaResultBase {
122
+ export interface PlanLambdaResult {
162
123
  Action: "plan";
163
124
  PlanHash: string;
164
125
  ChunkCount: number;
@@ -173,24 +134,14 @@ interface PlanLambdaResultBase {
173
134
  FfmpegVersion: string;
174
135
  ProducerVersion: string;
175
136
  DurationMs: number;
176
- }
177
- /**
178
- * Existing v1 result. Kept unchanged for wire compatibility.
179
- *
180
- * @deprecated New integrations should consume {@link PlanV2LambdaResult}.
181
- */
182
- export interface PlanV1LambdaResult extends PlanLambdaResultBase {
183
- PlanS3Uri: string;
184
- }
185
- /** V2 result. The two v2 locators are never aliases for `PlanS3Uri`. */
186
- export interface PlanV2LambdaResult extends PlanLambdaResultBase {
187
137
  PlanProtocol: "v2";
188
138
  PlanV2ManifestS3Uri: string;
189
139
  PlanV2ArtifactS3Prefix: string;
190
140
  /** The plan left its audio to the audio task, which has a worker kept free for it. */
191
141
  SeparateAudio: boolean;
192
142
  }
193
- export type PlanLambdaResult = PlanV1LambdaResult | PlanV2LambdaResult;
143
+ /** Compatibility alias for the only supported plan result. */
144
+ export type PlanV2LambdaResult = PlanLambdaResult;
194
145
  export interface AudioLambdaResult {
195
146
  Action: "audio";
196
147
  HasAudio: boolean;
package/dist/handler.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { LambdaClient } from "@aws-sdk/client-lambda";
14
14
  import { S3Client } from "@aws-sdk/client-s3";
15
- import { assemble, buildPlanAudio, type ChunkRenderer, plan, planV2WithPublisher } from "@hyperframes/producer/distributed";
15
+ import { assemble, buildPlanAudio, type ChunkRenderer, planV2WithPublisher } from "@hyperframes/producer/distributed";
16
16
  import { type StitchHelper } from "./s3Stitch.js";
17
17
  import type { AssembleEvent, AudioEvent, LambdaEvent, LambdaResult, PlanEvent, RenderChunkEvent } from "./events.js";
18
18
  /**
@@ -27,7 +27,6 @@ export interface HandlerDeps {
27
27
  /** Reads the account's concurrency when the render does not set one. */
28
28
  lambda?: LambdaClient;
29
29
  primitives?: {
30
- plan: typeof plan;
31
30
  planV2WithPublisher?: typeof planV2WithPublisher;
32
31
  buildPlanAudio?: typeof buildPlanAudio;
33
32
  renderChunk: ChunkRenderer;
package/dist/handler.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  handler,
3
3
  stitchHelper,
4
4
  unwrapEvent
5
- } from "./_chunks/chunk-QU5TSGIK.js";
5
+ } from "./_chunks/chunk-2XZPJGCQ.js";
6
6
  import "./_chunks/chunk-6TIVMKTV.js";
7
7
  import "./_chunks/chunk-4CK3RKM2.js";
8
8
  export {
package/dist/index.d.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  * install it separately.
23
23
  */
24
24
  export { handler, type HandlerDeps, unwrapEvent } from "./handler.js";
25
- export { type AssembleEvent, type AssembleLambdaResult, type LambdaAction, type LambdaEvent, type LambdaPlanProtocol, type LambdaResult, type PlanEvent, type PlanLambdaResult, type PlanV1Event, type PlanV1LambdaResult, type PlanV2Event, type PlanV2LambdaResult, type RenderChunkEvent, type RenderChunkLambdaResult, type RenderChunkV1Event, type RenderChunkV2Event, type AssembleV1Event, type AssembleV2Event, type SerializableDistributedRenderConfig, } from "./events.js";
25
+ export { type AssembleEvent, type AssembleLambdaResult, type LambdaAction, type LambdaEvent, type LambdaResult, type PlanEvent, type PlanLambdaResult, type PlanV2Event, type PlanV2LambdaResult, type RenderChunkEvent, type RenderChunkLambdaResult, type RenderChunkV2Event, type AssembleV2Event, type SerializableDistributedRenderConfig, } from "./events.js";
26
26
  export { ChromeBinaryUnavailableError, type ChromeSource, resolveChromeArgs, resolveChromeExecutablePath, resolveChromeSource, } from "./chromium.js";
27
27
  export { downloadS3ObjectToFile, downloadS3ObjectToFileVerified, formatS3Uri, parseS3Uri, type S3Location, tarDirectory, untarDirectory, uploadContentAddressedFileToS3, uploadFileToS3, } from "./s3Transport.js";
28
28
  export { S3PlanV2ArtifactPublisher, type S3PlanV2ArtifactPublisherOptions, } from "./s3PlanV2Publisher.js";
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  resolveChromeExecutablePath,
7
7
  resolveChromeSource,
8
8
  unwrapEvent
9
- } from "./_chunks/chunk-QU5TSGIK.js";
9
+ } from "./_chunks/chunk-2XZPJGCQ.js";
10
10
  import {
11
11
  InvalidConfigError,
12
12
  computeRenderCost,
@@ -14,7 +14,7 @@ import {
14
14
  getRenderProgress,
15
15
  renderToLambda,
16
16
  validateDistributedRenderConfig
17
- } from "./_chunks/chunk-Q6NRBJWH.js";
17
+ } from "./_chunks/chunk-JZCUNVDN.js";
18
18
  import {
19
19
  downloadS3ObjectToFile,
20
20
  downloadS3ObjectToFileVerified,
package/dist/sdk/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  validateDistributedRenderConfig,
11
11
  validateStepFunctionsInputSize,
12
12
  validateVariablesPayload
13
- } from "../_chunks/chunk-Q6NRBJWH.js";
13
+ } from "../_chunks/chunk-JZCUNVDN.js";
14
14
  import {
15
15
  sha256File
16
16
  } from "../_chunks/chunk-6TIVMKTV.js";
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { SFNClient } from "@aws-sdk/client-sfn";
20
20
  import { S3Client } from "@aws-sdk/client-s3";
21
- import type { LambdaPlanProtocol, SerializableDistributedRenderConfig } from "../events.js";
21
+ import type { SerializableDistributedRenderConfig } from "../events.js";
22
22
  import { type SiteHandle } from "./deploySite.js";
23
23
  /** Options for {@link renderToLambda}. */
24
24
  export interface RenderToLambdaOptions {
@@ -29,11 +29,6 @@ export interface RenderToLambdaOptions {
29
29
  onNotice?: (message: string) => void;
30
30
  /** Validated `SerializableDistributedRenderConfig` (no logger / abortSignal). */
31
31
  config: SerializableDistributedRenderConfig;
32
- /**
33
- * Distributed plan transport. Defaults to `"v2"`. Select `"v1"`
34
- * explicitly only for deprecated compatibility with the monolithic plan.
35
- */
36
- planProtocol?: LambdaPlanProtocol;
37
32
  /** S3 bucket from the SAM stack output (`RenderBucketName`). */
38
33
  bucketName: string;
39
34
  /** State machine ARN from the SAM stack output (`RenderStateMachineArn`). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/aws-lambda",
3
- "version": "0.8.231",
3
+ "version": "0.9.1",
4
4
  "description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,7 +52,7 @@
52
52
  "ffprobe-static": "^3.1.0",
53
53
  "puppeteer-core": "^25.8.0",
54
54
  "tar": "^7.4.3",
55
- "@hyperframes/producer": "^0.8.231"
55
+ "@hyperframes/producer": "^0.9.1"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/aws-lambda": "^8.10.146",
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.8.231",
3
- "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.231/hyperframes-lambda-handler.zip",
4
- "sha256": "62ee613d34de27b26bb6fce9b5870892f52596310bba12f71477f33af1dd6a2d",
5
- "bytes": 129521302
2
+ "version": "0.9.1",
3
+ "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.9.1/hyperframes-lambda-handler.zip",
4
+ "sha256": "53dacd375455a5d8d558b3f00c6185af36be9a61b341fa54a89bcad3e8634876",
5
+ "bytes": 129520617
6
6
  }
@@ -266,6 +266,11 @@ Resources:
266
266
  SelectPlanProtocol:
267
267
  Type: Choice
268
268
  Choices:
269
+ # A comparison on a missing path is a States.Runtime error, not a non-match, so an
270
+ # omitted PlanProtocol has to be caught before any StringEquals rule.
271
+ - Variable: $.PlanProtocol
272
+ IsPresent: false
273
+ Next: !If [HasVideoFunction, WarmRender, PlanV2]
269
274
  # Only a stack with the arm64 video function has a cold x86 function to warm.
270
275
  # Without one the compared value can never match.
271
276
  - Variable: $.PlanProtocol
@@ -274,9 +279,6 @@ Resources:
274
279
  - Variable: $.PlanProtocol
275
280
  StringEquals: v2
276
281
  Next: PlanV2
277
- - Variable: $.PlanProtocol
278
- StringEquals: v1
279
- Next: Plan
280
282
  - Variable: $.PlanProtocol
281
283
  IsPresent: true
282
284
  Next: UnsupportedPlanProtocol
@@ -302,72 +304,7 @@ Resources:
302
304
  UnsupportedPlanProtocol:
303
305
  Type: Fail
304
306
  Error: PLAN_PROTOCOL_UNSUPPORTED
305
- Cause: PlanProtocol must be "v1", "v2", or absent (defaults to v2).
306
-
307
- Plan:
308
- Type: Task
309
- Resource: arn:aws:states:::lambda:invoke
310
- Parameters:
311
- # Chunks must run the ffmpeg build the plan records, so the plan runs where they do.
312
- FunctionName:
313
- !If [
314
- HasVideoFunction,
315
- !GetAtt VideoFunctionStack.Outputs.FunctionArn,
316
- !GetAtt RenderFunction.Arn,
317
- ]
318
- Payload:
319
- Action: plan
320
- PlanProtocol: v1
321
- ProjectS3Uri.$: "$.ProjectS3Uri"
322
- PlanOutputS3Prefix.$: "$.PlanOutputS3Prefix"
323
- Config.$: "$.Config"
324
- ResultSelector:
325
- PlanProtocol: v1
326
- PlanS3Uri.$: "$.Payload.PlanS3Uri"
327
- PlanHash.$: "$.Payload.PlanHash"
328
- ChunkCount.$: "$.Payload.ChunkCount"
329
- MaxChunkConcurrency.$: "$.Payload.MaxChunkConcurrency"
330
- Format.$: "$.Payload.Format"
331
- HasAudio.$: "$.Payload.HasAudio"
332
- AudioS3Uri.$: "$.Payload.AudioS3Uri"
333
- ResultPath: $.Plan
334
- Retry:
335
- - ErrorEquals:
336
- # These error names are thrown by the producer's plan
337
- # stage when retrying can never help — version skew,
338
- # determinism violations, GPU misconfiguration, font
339
- # fetch failures, plan-size cap, unsupported format.
340
- # Fail fast rather than burning ~120s of retry budget.
341
- - FFMPEG_VERSION_MISMATCH
342
- - PLAN_HASH_MISMATCH
343
- - BROWSER_GPU_NOT_SOFTWARE
344
- - FONT_FETCH_FAILED
345
- - PLAN_TOO_LARGE
346
- - PlanTooLargeError
347
- - PLAN_PROTOCOL_UNSUPPORTED
348
- - PlanProtocolUnsupportedError
349
- - VIDEO_SOURCE_UNRENDERABLE
350
- - INVALID_VIDEO_METADATA
351
- - NOT_MEDIA_PAYLOAD
352
- - NotMediaPayloadError
353
- - PLAN_ARTIFACT_DIGEST_MISMATCH
354
- - FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
355
- - CHUNK_EXCEEDS_WORKER_TIMEOUT
356
- - SITE_MANIFEST_INVALID
357
- MaxAttempts: 0
358
- # Another render on the same function can still take the reserved slots; wait minutes.
359
- - ErrorEquals: [Lambda.TooManyRequestsException]
360
- IntervalSeconds: 5
361
- MaxAttempts: 10
362
- BackoffRate: 2
363
- MaxDelaySeconds: 60
364
- JitterStrategy: FULL
365
- - ErrorEquals: [States.ALL]
366
- IntervalSeconds: 2
367
- MaxAttempts: 4
368
- BackoffRate: 2
369
- MaxDelaySeconds: 60
370
- Next: BuildChunkList
307
+ Cause: PlanProtocol must be "v2" or absent (defaults to v2).
371
308
 
372
309
  PlanV2:
373
310
  Type: Task
@@ -403,8 +340,6 @@ Resources:
403
340
  - S3_URI_NOT_ALLOWED
404
341
  - BROWSER_GPU_NOT_SOFTWARE
405
342
  - FONT_FETCH_FAILED
406
- - PLAN_TOO_LARGE
407
- - PlanTooLargeError
408
343
  - PLAN_PROTOCOL_UNSUPPORTED
409
344
  - PlanProtocolUnsupportedError
410
345
  - PLAN_V2_INTEGRITY_UNRECOVERABLE
@@ -453,140 +388,14 @@ Resources:
453
388
  Choices:
454
389
  - Variable: $.Plan.ChunkCount
455
390
  NumericGreaterThan: 0
456
- Next: SelectWorkerProtocol
391
+ Next: RenderV2
457
392
  Default: PlanProducedZeroChunks
458
393
 
459
394
  PlanProducedZeroChunks:
460
395
  Type: Fail
461
- Error: PLAN_TOO_LARGE
396
+ Error: PLAN_EMPTY
462
397
  Cause: Plan returned ChunkCount=0 — non-retryable producer-side invariant violation.
463
398
 
464
- SelectWorkerProtocol:
465
- Type: Choice
466
- Choices:
467
- - Variable: $.Plan.PlanProtocol
468
- StringEquals: v2
469
- Next: RenderV2
470
- Default: RenderChunks
471
-
472
- RenderChunks:
473
- Type: Map
474
- Label: RenderChunks
475
- ItemsPath: $.Iterator.ChunkIndexes
476
- ItemSelector:
477
- ChunkIndex.$: "$$.Map.Item.Value"
478
- PlanS3Uri.$: "$.Plan.PlanS3Uri"
479
- PlanHash.$: "$.Plan.PlanHash"
480
- ChunkOutputS3Prefix.$: "$.PlanOutputS3Prefix"
481
- Format.$: "$.Plan.Format"
482
- # Chunks past the plan's concurrency queue here instead of being throttled by Lambda.
483
- MaxConcurrencyPath: $.Plan.MaxChunkConcurrency
484
- ResultPath: $.Chunks
485
- ItemProcessor:
486
- ProcessorConfig:
487
- # Inline Map runs at most 40 iterations at once. STANDARD children
488
- # because EXPRESS ones stop at 5 minutes and a chunk may run 15.
489
- Mode: DISTRIBUTED
490
- ExecutionType: STANDARD
491
- StartAt: RenderChunk
492
- States:
493
- RenderChunk:
494
- Type: Task
495
- Resource: arn:aws:states:::lambda:invoke
496
- Parameters:
497
- FunctionName:
498
- !If [
499
- HasVideoFunction,
500
- !GetAtt VideoFunctionStack.Outputs.FunctionArn,
501
- !GetAtt RenderFunction.Arn,
502
- ]
503
- Payload:
504
- Action: renderChunk
505
- PlanProtocol: v1
506
- ChunkIndex.$: "$.ChunkIndex"
507
- PlanS3Uri.$: "$.PlanS3Uri"
508
- PlanHash.$: "$.PlanHash"
509
- ChunkOutputS3Prefix.$: "$.ChunkOutputS3Prefix"
510
- Format.$: "$.Format"
511
- ResultSelector:
512
- ChunkS3Uri.$: "$.Payload.ChunkS3Uri"
513
- ChunkIndex.$: "$.Payload.ChunkIndex"
514
- Sha256.$: "$.Payload.Sha256"
515
- FramesEncoded.$: "$.Payload.FramesEncoded"
516
- DurationMs.$: "$.Payload.DurationMs"
517
- Retry:
518
- - ErrorEquals:
519
- - FFMPEG_VERSION_MISMATCH
520
- - PLAN_HASH_MISMATCH
521
- - BROWSER_GPU_NOT_SOFTWARE
522
- - PLAN_TOO_LARGE
523
- - PlanTooLargeError
524
- - PLAN_PROTOCOL_UNSUPPORTED
525
- - PlanProtocolUnsupportedError
526
- - INVALID_VIDEO_METADATA
527
- - PLAN_ARTIFACT_DIGEST_MISMATCH
528
- MaxAttempts: 0
529
- - ErrorEquals: [Lambda.TooManyRequestsException]
530
- IntervalSeconds: 5
531
- MaxAttempts: 10
532
- BackoffRate: 2
533
- MaxDelaySeconds: 60
534
- JitterStrategy: FULL
535
- - ErrorEquals: [States.ALL]
536
- IntervalSeconds: 2
537
- MaxAttempts: 4
538
- BackoffRate: 2
539
- MaxDelaySeconds: 60
540
- End: true
541
- Next: Assemble
542
-
543
- Assemble:
544
- Type: Task
545
- Resource: arn:aws:states:::lambda:invoke
546
- Parameters:
547
- FunctionName: !GetAtt RenderFunction.Arn
548
- Payload:
549
- Action: assemble
550
- PlanProtocol: v1
551
- PlanS3Uri.$: "$.Plan.PlanS3Uri"
552
- ChunkS3Uris.$: "$.Chunks[*].ChunkS3Uri"
553
- AudioS3Uri.$: "$.Plan.AudioS3Uri"
554
- OutputS3Uri.$: "$.OutputS3Uri"
555
- Format.$: "$.Plan.Format"
556
- ResultSelector:
557
- OutputS3Uri.$: "$.Payload.OutputS3Uri"
558
- FramesEncoded.$: "$.Payload.FramesEncoded"
559
- FileSize.$: "$.Payload.FileSize"
560
- ResultPath: $.Output
561
- Retry:
562
- - ErrorEquals:
563
- # Same non-retryable error names as the Plan state's
564
- # gate — these surface at assemble time too because
565
- # ffmpeg-driven concat picks up version drift and we
566
- # re-verify plan hash + format at assemble. Skip the
567
- # retry storm; fail fast.
568
- - FFMPEG_VERSION_MISMATCH
569
- - PLAN_HASH_MISMATCH
570
- - FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
571
- - PLAN_TOO_LARGE
572
- - PlanTooLargeError
573
- - PLAN_PROTOCOL_UNSUPPORTED
574
- - PlanProtocolUnsupportedError
575
- - PLAN_ARTIFACT_DIGEST_MISMATCH
576
- MaxAttempts: 0
577
- - ErrorEquals: [Lambda.TooManyRequestsException]
578
- IntervalSeconds: 5
579
- MaxAttempts: 10
580
- BackoffRate: 2
581
- MaxDelaySeconds: 60
582
- JitterStrategy: FULL
583
- - ErrorEquals: [States.ALL]
584
- IntervalSeconds: 2
585
- MaxAttempts: 4
586
- BackoffRate: 2
587
- MaxDelaySeconds: 60
588
- End: true
589
-
590
399
  RenderV2:
591
400
  # Chunks render video only while the audio task mixes the track AssembleV2 stitches in.
592
401
  Type: Parallel
@@ -644,8 +453,6 @@ Resources:
644
453
  - PLAN_HASH_MISMATCH
645
454
  - S3_URI_NOT_ALLOWED
646
455
  - BROWSER_GPU_NOT_SOFTWARE
647
- - PLAN_TOO_LARGE
648
- - PlanTooLargeError
649
456
  - PLAN_PROTOCOL_UNSUPPORTED
650
457
  - PlanProtocolUnsupportedError
651
458
  - PLAN_V2_INTEGRITY_UNRECOVERABLE
@@ -705,8 +512,6 @@ Resources:
705
512
  - S3_URI_NOT_ALLOWED
706
513
  - BROWSER_GPU_NOT_SOFTWARE
707
514
  - FONT_FETCH_FAILED
708
- - PLAN_TOO_LARGE
709
- - PlanTooLargeError
710
515
  - PLAN_PROTOCOL_UNSUPPORTED
711
516
  - PlanProtocolUnsupportedError
712
517
  - PLAN_V2_INTEGRITY_UNRECOVERABLE
@@ -765,8 +570,6 @@ Resources:
765
570
  - PLAN_HASH_MISMATCH
766
571
  - S3_URI_NOT_ALLOWED
767
572
  - FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
768
- - PLAN_TOO_LARGE
769
- - PlanTooLargeError
770
573
  - PLAN_PROTOCOL_UNSUPPORTED
771
574
  - PlanProtocolUnsupportedError
772
575
  - PLAN_V2_INTEGRITY_UNRECOVERABLE
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.8.231",
3
- "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.231/hyperframes-lambda-video-arm64.oci.tar",
4
- "sha256": "c80295ca876bf376f7aa80aa3e8dccbcf9764b044dc6f262a07d9faf6c4d25eb",
2
+ "version": "0.9.1",
3
+ "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.9.1/hyperframes-lambda-video-arm64.oci.tar",
4
+ "sha256": "ebcdf52194aac5d690539f4cd1747355809e3c21762e759b97346e2133424e5e",
5
5
  "bytes": 244900864
6
6
  }