@hyperframes/aws-lambda 0.7.70 → 0.7.72
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 +21 -1
- package/dist/cdk/HyperframesRenderStack.d.ts.map +1 -1
- package/dist/cdk/index.js +130 -2
- package/dist/cdk/index.js.map +2 -2
- package/dist/events.d.ts +61 -14
- package/dist/events.d.ts.map +1 -1
- package/dist/handler.d.ts +2 -1
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +297 -7
- package/dist/handler.js.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +311 -12
- package/dist/index.js.map +3 -3
- package/dist/s3Transport.d.ts +11 -0
- package/dist/s3Transport.d.ts.map +1 -1
- package/dist/sdk/index.js +17 -6
- package/dist/sdk/index.js.map +3 -3
- package/dist/sdk/renderToLambda.d.ts +6 -1
- package/dist/sdk/renderToLambda.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/handler.js
CHANGED
|
@@ -5,7 +5,11 @@ import { basename, join } from "node:path";
|
|
|
5
5
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
6
6
|
import {
|
|
7
7
|
assemble,
|
|
8
|
+
listPlanV2ArtifactsForTarget,
|
|
9
|
+
materializePlanV2Target,
|
|
8
10
|
plan,
|
|
11
|
+
planV2,
|
|
12
|
+
readPlanV2Manifest,
|
|
9
13
|
renderChunk
|
|
10
14
|
} from "@hyperframes/producer/distributed";
|
|
11
15
|
|
|
@@ -90,9 +94,14 @@ import {
|
|
|
90
94
|
rmSync,
|
|
91
95
|
statSync
|
|
92
96
|
} from "node:fs";
|
|
97
|
+
import { createHash } from "node:crypto";
|
|
93
98
|
import { dirname } from "node:path";
|
|
94
99
|
import { pipeline } from "node:stream/promises";
|
|
95
|
-
import {
|
|
100
|
+
import {
|
|
101
|
+
GetObjectCommand,
|
|
102
|
+
HeadObjectCommand,
|
|
103
|
+
PutObjectCommand
|
|
104
|
+
} from "@aws-sdk/client-s3";
|
|
96
105
|
import * as tar from "tar";
|
|
97
106
|
function parseS3Uri(uri) {
|
|
98
107
|
if (!uri.startsWith("s3://")) {
|
|
@@ -120,6 +129,19 @@ async function downloadS3ObjectToFile(client, uri, destPath) {
|
|
|
120
129
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
121
130
|
await pipeline(body, createWriteStream(destPath));
|
|
122
131
|
}
|
|
132
|
+
async function downloadS3ObjectToFileVerified(client, uri, destPath, expectedSha256) {
|
|
133
|
+
assertSha256(expectedSha256);
|
|
134
|
+
await downloadS3ObjectToFile(client, uri, destPath);
|
|
135
|
+
const actual = await sha256File(destPath);
|
|
136
|
+
if (actual !== expectedSha256) {
|
|
137
|
+
rmSync(destPath, { force: true });
|
|
138
|
+
const error = new Error(
|
|
139
|
+
`[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: ${uri} expected ${expectedSha256}, got ${actual}`
|
|
140
|
+
);
|
|
141
|
+
error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
123
145
|
async function uploadFileToS3(client, localPath, uri, contentType) {
|
|
124
146
|
if (!existsSync2(localPath)) {
|
|
125
147
|
throw new Error(`[s3Transport] upload source missing: ${localPath}`);
|
|
@@ -136,6 +158,68 @@ async function uploadFileToS3(client, localPath, uri, contentType) {
|
|
|
136
158
|
})
|
|
137
159
|
);
|
|
138
160
|
}
|
|
161
|
+
async function uploadContentAddressedFileToS3(client, localPath, uri, expectedSha256, contentType) {
|
|
162
|
+
assertSha256(expectedSha256);
|
|
163
|
+
if (!existsSync2(localPath)) {
|
|
164
|
+
throw new Error(`[s3Transport] upload source missing: ${localPath}`);
|
|
165
|
+
}
|
|
166
|
+
const actualSha256 = await sha256File(localPath);
|
|
167
|
+
if (actualSha256 !== expectedSha256) {
|
|
168
|
+
const error = new Error(
|
|
169
|
+
`[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: local artifact ${localPath} expected ${expectedSha256}, got ${actualSha256}`
|
|
170
|
+
);
|
|
171
|
+
error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
const { bucket, key } = parseS3Uri(uri);
|
|
175
|
+
const size = statSync(localPath).size;
|
|
176
|
+
try {
|
|
177
|
+
const existing = await client.send(
|
|
178
|
+
new HeadObjectCommand({ Bucket: bucket, Key: key, ChecksumMode: "ENABLED" })
|
|
179
|
+
);
|
|
180
|
+
if (existing.ContentLength === size && existing.Metadata?.sha256 === expectedSha256) {
|
|
181
|
+
return "reused";
|
|
182
|
+
}
|
|
183
|
+
const error = new Error(
|
|
184
|
+
`[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: immutable object ${uri} already exists with different digest metadata or size`
|
|
185
|
+
);
|
|
186
|
+
error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
|
|
187
|
+
throw error;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (!isS3NotFound(error)) throw error;
|
|
190
|
+
}
|
|
191
|
+
await client.send(
|
|
192
|
+
new PutObjectCommand({
|
|
193
|
+
Bucket: bucket,
|
|
194
|
+
Key: key,
|
|
195
|
+
Body: createReadStream(localPath),
|
|
196
|
+
ContentType: contentType,
|
|
197
|
+
ContentLength: size,
|
|
198
|
+
Metadata: { sha256: expectedSha256 },
|
|
199
|
+
ChecksumSHA256: Buffer.from(expectedSha256, "hex").toString("base64")
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
return "uploaded";
|
|
203
|
+
}
|
|
204
|
+
async function sha256File(path) {
|
|
205
|
+
const hash = createHash("sha256");
|
|
206
|
+
for await (const chunk of createReadStream(path)) {
|
|
207
|
+
hash.update(chunk);
|
|
208
|
+
}
|
|
209
|
+
return hash.digest("hex");
|
|
210
|
+
}
|
|
211
|
+
function assertSha256(value) {
|
|
212
|
+
if (!/^[a-f0-9]{64}$/.test(value)) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`[s3Transport] expected lowercase SHA-256 digest, got ${JSON.stringify(value)}`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function isS3NotFound(error) {
|
|
219
|
+
if (!error || typeof error !== "object") return false;
|
|
220
|
+
const candidate = error;
|
|
221
|
+
return candidate.name === "NotFound" || candidate.name === "NoSuchKey" || candidate.$metadata?.httpStatusCode === 404;
|
|
222
|
+
}
|
|
139
223
|
async function tarDirectory(sourceDir, destTarball) {
|
|
140
224
|
if (!existsSync2(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
|
141
225
|
throw new Error(`[s3Transport] tar source must be an existing directory: ${sourceDir}`);
|
|
@@ -184,15 +268,24 @@ async function handler(event, deps) {
|
|
|
184
268
|
}
|
|
185
269
|
}
|
|
186
270
|
} catch (err) {
|
|
271
|
+
normalizeTerminalErrorName(err);
|
|
187
272
|
logEvent({
|
|
188
273
|
event: "handler_error",
|
|
189
274
|
action: unwrapped.Action,
|
|
275
|
+
input: summarizeEvent(unwrapped),
|
|
190
276
|
message: err instanceof Error ? err.message : String(err),
|
|
191
277
|
name: err instanceof Error ? err.name : void 0
|
|
192
278
|
});
|
|
193
279
|
throw err;
|
|
194
280
|
}
|
|
195
281
|
}
|
|
282
|
+
function normalizeTerminalErrorName(error) {
|
|
283
|
+
if (!error || typeof error !== "object") return;
|
|
284
|
+
const candidate = error;
|
|
285
|
+
if (candidate.code === "PLAN_PROTOCOL_UNSUPPORTED" || candidate.code === "PLAN_TOO_LARGE" || candidate.code === "PLAN_V2_INTEGRITY_UNRECOVERABLE") {
|
|
286
|
+
candidate.name = candidate.code;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
196
289
|
var MAX_ENVELOPE_DEPTH = 4;
|
|
197
290
|
function unwrapEvent(event) {
|
|
198
291
|
let cursor = event;
|
|
@@ -229,18 +322,21 @@ function summarizeEvent(event) {
|
|
|
229
322
|
return {
|
|
230
323
|
projectS3Uri: event.ProjectS3Uri,
|
|
231
324
|
planOutputS3Prefix: event.PlanOutputS3Prefix,
|
|
325
|
+
planProtocol: event.PlanProtocol ?? "v1",
|
|
232
326
|
format: event.Config.format,
|
|
233
327
|
fps: event.Config.fps
|
|
234
328
|
};
|
|
235
329
|
case "renderChunk":
|
|
236
330
|
return {
|
|
237
|
-
|
|
331
|
+
planProtocol: event.PlanProtocol ?? "v1",
|
|
332
|
+
...event.PlanProtocol === "v2" ? { planV2ManifestS3Uri: event.PlanV2ManifestS3Uri } : { planS3Uri: event.PlanS3Uri },
|
|
238
333
|
chunkIndex: event.ChunkIndex,
|
|
239
334
|
format: event.Format
|
|
240
335
|
};
|
|
241
336
|
case "assemble":
|
|
242
337
|
return {
|
|
243
|
-
|
|
338
|
+
planProtocol: event.PlanProtocol ?? "v1",
|
|
339
|
+
...event.PlanProtocol === "v2" ? { planV2ManifestS3Uri: event.PlanV2ManifestS3Uri } : { planS3Uri: event.PlanS3Uri },
|
|
244
340
|
chunkCount: event.ChunkS3Uris.length,
|
|
245
341
|
hasAudio: event.AudioS3Uri !== null,
|
|
246
342
|
outputS3Uri: event.OutputS3Uri,
|
|
@@ -259,6 +355,9 @@ function primeRuntimeEnv() {
|
|
|
259
355
|
}
|
|
260
356
|
}
|
|
261
357
|
async function handlePlan(event, deps) {
|
|
358
|
+
if (event.PlanProtocol === "v2") {
|
|
359
|
+
return handlePlanV2(event, deps);
|
|
360
|
+
}
|
|
262
361
|
const started = Date.now();
|
|
263
362
|
const s3 = deps?.s3 ?? getS3Client();
|
|
264
363
|
const primitive = deps?.primitives?.plan ?? plan;
|
|
@@ -307,7 +406,73 @@ async function handlePlan(event, deps) {
|
|
|
307
406
|
cleanupDir(work);
|
|
308
407
|
}
|
|
309
408
|
}
|
|
409
|
+
async function handlePlanV2(event, deps) {
|
|
410
|
+
const started = Date.now();
|
|
411
|
+
const s3 = deps?.s3 ?? getS3Client();
|
|
412
|
+
const primitive = deps?.primitives?.planV2 ?? planV2;
|
|
413
|
+
if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
414
|
+
process.env.PRODUCER_HEADLESS_SHELL_PATH = await resolveChromeExecutablePath();
|
|
415
|
+
}
|
|
416
|
+
const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-plan-v2-"));
|
|
417
|
+
const projectArchive = join(work, "project.tar.gz");
|
|
418
|
+
const projectDir = join(work, "project");
|
|
419
|
+
const planV2Dir = join(work, "plan-v2");
|
|
420
|
+
try {
|
|
421
|
+
await downloadS3ObjectToFile(s3, event.ProjectS3Uri, projectArchive);
|
|
422
|
+
await untarDirectory(projectArchive, projectDir);
|
|
423
|
+
const result = await primitive(projectDir, { ...event.Config }, planV2Dir);
|
|
424
|
+
const manifest = readPlanV2Manifest(planV2Dir);
|
|
425
|
+
if (manifest.planHash !== result.planHash) {
|
|
426
|
+
throwPlanHashMismatch(result.planHash, manifest.planHash);
|
|
427
|
+
}
|
|
428
|
+
const outputPrefix = `${trimTrailingSlash(event.PlanOutputS3Prefix)}/v2`;
|
|
429
|
+
const artifactPrefix = `${outputPrefix}/artifacts/sha256`;
|
|
430
|
+
const uniqueArtifacts = [
|
|
431
|
+
...new Map(manifest.artifacts.map((artifact) => [artifact.sha256, artifact])).values()
|
|
432
|
+
];
|
|
433
|
+
await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
|
|
434
|
+
const localPath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
435
|
+
await uploadContentAddressedFileToS3(
|
|
436
|
+
s3,
|
|
437
|
+
localPath,
|
|
438
|
+
planV2BlobUri(artifactPrefix, artifact.sha256),
|
|
439
|
+
artifact.sha256
|
|
440
|
+
);
|
|
441
|
+
});
|
|
442
|
+
const manifestUri = `${outputPrefix}/manifest.json`;
|
|
443
|
+
await uploadContentAddressedFileToS3(
|
|
444
|
+
s3,
|
|
445
|
+
result.manifestPath,
|
|
446
|
+
manifestUri,
|
|
447
|
+
await sha256File(result.manifestPath),
|
|
448
|
+
"application/json"
|
|
449
|
+
);
|
|
450
|
+
return {
|
|
451
|
+
Action: "plan",
|
|
452
|
+
PlanProtocol: "v2",
|
|
453
|
+
PlanV2ManifestS3Uri: manifestUri,
|
|
454
|
+
PlanV2ArtifactS3Prefix: artifactPrefix,
|
|
455
|
+
PlanHash: result.planHash,
|
|
456
|
+
ChunkCount: result.chunkCount,
|
|
457
|
+
TotalFrames: result.totalFrames,
|
|
458
|
+
Fps: result.fps,
|
|
459
|
+
Width: result.width,
|
|
460
|
+
Height: result.height,
|
|
461
|
+
Format: result.format,
|
|
462
|
+
HasAudio: manifest.artifacts.some((artifact) => artifact.path === "audio.aac"),
|
|
463
|
+
AudioS3Uri: null,
|
|
464
|
+
FfmpegVersion: result.ffmpegVersion,
|
|
465
|
+
ProducerVersion: result.producerVersion,
|
|
466
|
+
DurationMs: Date.now() - started
|
|
467
|
+
};
|
|
468
|
+
} finally {
|
|
469
|
+
cleanupDir(work);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
310
472
|
async function handleRenderChunk(event, deps) {
|
|
473
|
+
if (event.PlanProtocol === "v2") {
|
|
474
|
+
return handleRenderChunkV2(event, deps);
|
|
475
|
+
}
|
|
311
476
|
const started = Date.now();
|
|
312
477
|
const s3 = deps?.s3 ?? getS3Client();
|
|
313
478
|
const primitive = deps?.primitives?.renderChunk ?? renderChunk;
|
|
@@ -345,6 +510,44 @@ async function handleRenderChunk(event, deps) {
|
|
|
345
510
|
cleanupDir(work);
|
|
346
511
|
}
|
|
347
512
|
}
|
|
513
|
+
async function handleRenderChunkV2(event, deps) {
|
|
514
|
+
const started = Date.now();
|
|
515
|
+
const s3 = deps?.s3 ?? getS3Client();
|
|
516
|
+
const primitive = deps?.primitives?.renderChunk ?? renderChunk;
|
|
517
|
+
if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
518
|
+
process.env.PRODUCER_HEADLESS_SHELL_PATH = await resolveChromeExecutablePath();
|
|
519
|
+
}
|
|
520
|
+
const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-chunk-v2-"));
|
|
521
|
+
try {
|
|
522
|
+
const planDir = await downloadAndMaterializePlanV2(
|
|
523
|
+
s3,
|
|
524
|
+
event,
|
|
525
|
+
{ role: "chunk", chunkIndex: event.ChunkIndex },
|
|
526
|
+
work
|
|
527
|
+
);
|
|
528
|
+
const chunkOutputBase = join(
|
|
529
|
+
work,
|
|
530
|
+
event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
|
|
531
|
+
);
|
|
532
|
+
const result = await primitive(planDir, event.ChunkIndex, chunkOutputBase);
|
|
533
|
+
const chunkUri = await uploadChunkOutput(
|
|
534
|
+
s3,
|
|
535
|
+
result,
|
|
536
|
+
event.ChunkOutputS3Prefix,
|
|
537
|
+
event.ChunkIndex
|
|
538
|
+
);
|
|
539
|
+
return {
|
|
540
|
+
Action: "renderChunk",
|
|
541
|
+
ChunkS3Uri: chunkUri,
|
|
542
|
+
ChunkIndex: event.ChunkIndex,
|
|
543
|
+
Sha256: result.sha256,
|
|
544
|
+
FramesEncoded: result.framesEncoded,
|
|
545
|
+
DurationMs: Date.now() - started
|
|
546
|
+
};
|
|
547
|
+
} finally {
|
|
548
|
+
cleanupDir(work);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
348
551
|
async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
|
|
349
552
|
const trimmed = trimTrailingSlash(prefix);
|
|
350
553
|
if (result.outputKind === "file") {
|
|
@@ -360,6 +563,9 @@ async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
|
|
|
360
563
|
return uri;
|
|
361
564
|
}
|
|
362
565
|
async function handleAssemble(event, deps) {
|
|
566
|
+
if (event.PlanProtocol === "v2") {
|
|
567
|
+
return handleAssembleV2(event, deps);
|
|
568
|
+
}
|
|
363
569
|
const started = Date.now();
|
|
364
570
|
const s3 = deps?.s3 ?? getS3Client();
|
|
365
571
|
const primitive = deps?.primitives?.assemble ?? assemble;
|
|
@@ -397,6 +603,87 @@ async function handleAssemble(event, deps) {
|
|
|
397
603
|
cleanupDir(work);
|
|
398
604
|
}
|
|
399
605
|
}
|
|
606
|
+
async function handleAssembleV2(event, deps) {
|
|
607
|
+
const started = Date.now();
|
|
608
|
+
const s3 = deps?.s3 ?? getS3Client();
|
|
609
|
+
const primitive = deps?.primitives?.assemble ?? assemble;
|
|
610
|
+
const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-assemble-v2-"));
|
|
611
|
+
try {
|
|
612
|
+
const planDir = await downloadAndMaterializePlanV2(s3, event, { role: "assembler" }, work);
|
|
613
|
+
const audioPath = existsSync3(join(planDir, "audio.aac")) ? join(planDir, "audio.aac") : null;
|
|
614
|
+
const chunkPaths = await downloadChunkObjects(s3, event.ChunkS3Uris, work, event.Format);
|
|
615
|
+
const finalOutput = event.Format === "png-sequence" ? join(work, "output-frames") : join(work, `output${formatExtension(event.Format)}`);
|
|
616
|
+
const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
|
|
617
|
+
cfr: event.Cfr === true
|
|
618
|
+
});
|
|
619
|
+
if (event.Format === "png-sequence") {
|
|
620
|
+
const tarball = `${finalOutput}.tar.gz`;
|
|
621
|
+
await tarDirectory(finalOutput, tarball);
|
|
622
|
+
await uploadFileToS3(s3, tarball, event.OutputS3Uri, "application/gzip");
|
|
623
|
+
} else {
|
|
624
|
+
await uploadFileToS3(s3, finalOutput, event.OutputS3Uri);
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
Action: "assemble",
|
|
628
|
+
OutputS3Uri: event.OutputS3Uri,
|
|
629
|
+
FramesEncoded: result.framesEncoded,
|
|
630
|
+
FileSize: result.fileSize,
|
|
631
|
+
DurationMs: Date.now() - started
|
|
632
|
+
};
|
|
633
|
+
} finally {
|
|
634
|
+
cleanupDir(work);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async function downloadAndMaterializePlanV2(s3, event, target, work) {
|
|
638
|
+
const transportDir = join(work, "plan-v2");
|
|
639
|
+
mkdirSync2(transportDir, { recursive: true });
|
|
640
|
+
await downloadS3ObjectToFile(s3, event.PlanV2ManifestS3Uri, join(transportDir, "plan.json"));
|
|
641
|
+
const manifest = readPlanV2Manifest(transportDir);
|
|
642
|
+
if (manifest.planHash !== event.PlanHash) {
|
|
643
|
+
throwPlanHashMismatch(event.PlanHash, manifest.planHash);
|
|
644
|
+
}
|
|
645
|
+
const artifacts = listPlanV2ArtifactsForTarget(manifest, target);
|
|
646
|
+
const uniqueArtifacts = [
|
|
647
|
+
...new Map(artifacts.map((artifact) => [artifact.sha256, artifact])).values()
|
|
648
|
+
];
|
|
649
|
+
await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
|
|
650
|
+
await downloadPlanV2Artifact(s3, event.PlanV2ArtifactS3Prefix, transportDir, artifact);
|
|
651
|
+
});
|
|
652
|
+
const planDir = join(work, "plan");
|
|
653
|
+
materializePlanV2Target(transportDir, target, planDir);
|
|
654
|
+
return planDir;
|
|
655
|
+
}
|
|
656
|
+
async function downloadPlanV2Artifact(s3, artifactPrefix, planV2Dir, artifact) {
|
|
657
|
+
await downloadS3ObjectToFileVerified(
|
|
658
|
+
s3,
|
|
659
|
+
planV2BlobUri(artifactPrefix, artifact.sha256),
|
|
660
|
+
planV2BlobPath(planV2Dir, artifact.sha256),
|
|
661
|
+
artifact.sha256
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
function planV2BlobPath(planV2Dir, digest) {
|
|
665
|
+
return join(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
666
|
+
}
|
|
667
|
+
function planV2BlobUri(prefix, digest) {
|
|
668
|
+
return `${trimTrailingSlash(prefix)}/${digest.slice(0, 2)}/${digest}`;
|
|
669
|
+
}
|
|
670
|
+
function throwPlanHashMismatch(expected, actual) {
|
|
671
|
+
const error = new Error(
|
|
672
|
+
`PLAN_HASH_MISMATCH: event PlanHash=${expected} did not match v2 manifest planHash=${actual}`
|
|
673
|
+
);
|
|
674
|
+
error.name = "PLAN_HASH_MISMATCH";
|
|
675
|
+
throw error;
|
|
676
|
+
}
|
|
677
|
+
async function mapConcurrent(values, concurrency, fn) {
|
|
678
|
+
let cursor = 0;
|
|
679
|
+
async function worker() {
|
|
680
|
+
while (cursor < values.length) {
|
|
681
|
+
const index = cursor++;
|
|
682
|
+
await fn(values[index]);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => worker()));
|
|
686
|
+
}
|
|
400
687
|
async function downloadChunkObjects(s3, uris, workDir, format) {
|
|
401
688
|
const chunksDir = join(workDir, "chunks");
|
|
402
689
|
mkdirSync2(chunksDir, { recursive: true });
|
|
@@ -425,11 +712,14 @@ function getEventS3Uris(event) {
|
|
|
425
712
|
case "plan":
|
|
426
713
|
return [event.ProjectS3Uri, event.PlanOutputS3Prefix];
|
|
427
714
|
case "renderChunk":
|
|
428
|
-
return [event.PlanS3Uri, event.ChunkOutputS3Prefix];
|
|
715
|
+
return event.PlanProtocol === "v2" ? [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix, event.ChunkOutputS3Prefix] : [event.PlanS3Uri, event.ChunkOutputS3Prefix];
|
|
429
716
|
case "assemble":
|
|
430
|
-
return [
|
|
431
|
-
|
|
432
|
-
|
|
717
|
+
return [
|
|
718
|
+
...event.PlanProtocol === "v2" ? [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix] : [event.PlanS3Uri],
|
|
719
|
+
...event.ChunkS3Uris,
|
|
720
|
+
event.OutputS3Uri,
|
|
721
|
+
event.AudioS3Uri
|
|
722
|
+
].filter((u) => u != null);
|
|
433
723
|
}
|
|
434
724
|
}
|
|
435
725
|
function validateEventS3Uris(event) {
|