@hyperframes/aws-lambda 0.7.71 → 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/index.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
|
|
|
@@ -95,9 +99,14 @@ import {
|
|
|
95
99
|
rmSync,
|
|
96
100
|
statSync
|
|
97
101
|
} from "node:fs";
|
|
102
|
+
import { createHash } from "node:crypto";
|
|
98
103
|
import { dirname } from "node:path";
|
|
99
104
|
import { pipeline } from "node:stream/promises";
|
|
100
|
-
import {
|
|
105
|
+
import {
|
|
106
|
+
GetObjectCommand,
|
|
107
|
+
HeadObjectCommand,
|
|
108
|
+
PutObjectCommand
|
|
109
|
+
} from "@aws-sdk/client-s3";
|
|
101
110
|
import * as tar from "tar";
|
|
102
111
|
function parseS3Uri(uri) {
|
|
103
112
|
if (!uri.startsWith("s3://")) {
|
|
@@ -128,6 +137,19 @@ async function downloadS3ObjectToFile(client, uri, destPath) {
|
|
|
128
137
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
129
138
|
await pipeline(body, createWriteStream(destPath));
|
|
130
139
|
}
|
|
140
|
+
async function downloadS3ObjectToFileVerified(client, uri, destPath, expectedSha256) {
|
|
141
|
+
assertSha256(expectedSha256);
|
|
142
|
+
await downloadS3ObjectToFile(client, uri, destPath);
|
|
143
|
+
const actual = await sha256File(destPath);
|
|
144
|
+
if (actual !== expectedSha256) {
|
|
145
|
+
rmSync(destPath, { force: true });
|
|
146
|
+
const error = new Error(
|
|
147
|
+
`[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: ${uri} expected ${expectedSha256}, got ${actual}`
|
|
148
|
+
);
|
|
149
|
+
error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
131
153
|
async function uploadFileToS3(client, localPath, uri, contentType) {
|
|
132
154
|
if (!existsSync2(localPath)) {
|
|
133
155
|
throw new Error(`[s3Transport] upload source missing: ${localPath}`);
|
|
@@ -144,6 +166,68 @@ async function uploadFileToS3(client, localPath, uri, contentType) {
|
|
|
144
166
|
})
|
|
145
167
|
);
|
|
146
168
|
}
|
|
169
|
+
async function uploadContentAddressedFileToS3(client, localPath, uri, expectedSha256, contentType) {
|
|
170
|
+
assertSha256(expectedSha256);
|
|
171
|
+
if (!existsSync2(localPath)) {
|
|
172
|
+
throw new Error(`[s3Transport] upload source missing: ${localPath}`);
|
|
173
|
+
}
|
|
174
|
+
const actualSha256 = await sha256File(localPath);
|
|
175
|
+
if (actualSha256 !== expectedSha256) {
|
|
176
|
+
const error = new Error(
|
|
177
|
+
`[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: local artifact ${localPath} expected ${expectedSha256}, got ${actualSha256}`
|
|
178
|
+
);
|
|
179
|
+
error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
const { bucket, key } = parseS3Uri(uri);
|
|
183
|
+
const size = statSync(localPath).size;
|
|
184
|
+
try {
|
|
185
|
+
const existing = await client.send(
|
|
186
|
+
new HeadObjectCommand({ Bucket: bucket, Key: key, ChecksumMode: "ENABLED" })
|
|
187
|
+
);
|
|
188
|
+
if (existing.ContentLength === size && existing.Metadata?.sha256 === expectedSha256) {
|
|
189
|
+
return "reused";
|
|
190
|
+
}
|
|
191
|
+
const error = new Error(
|
|
192
|
+
`[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: immutable object ${uri} already exists with different digest metadata or size`
|
|
193
|
+
);
|
|
194
|
+
error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
|
|
195
|
+
throw error;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (!isS3NotFound(error)) throw error;
|
|
198
|
+
}
|
|
199
|
+
await client.send(
|
|
200
|
+
new PutObjectCommand({
|
|
201
|
+
Bucket: bucket,
|
|
202
|
+
Key: key,
|
|
203
|
+
Body: createReadStream(localPath),
|
|
204
|
+
ContentType: contentType,
|
|
205
|
+
ContentLength: size,
|
|
206
|
+
Metadata: { sha256: expectedSha256 },
|
|
207
|
+
ChecksumSHA256: Buffer.from(expectedSha256, "hex").toString("base64")
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
return "uploaded";
|
|
211
|
+
}
|
|
212
|
+
async function sha256File(path) {
|
|
213
|
+
const hash = createHash("sha256");
|
|
214
|
+
for await (const chunk of createReadStream(path)) {
|
|
215
|
+
hash.update(chunk);
|
|
216
|
+
}
|
|
217
|
+
return hash.digest("hex");
|
|
218
|
+
}
|
|
219
|
+
function assertSha256(value) {
|
|
220
|
+
if (!/^[a-f0-9]{64}$/.test(value)) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`[s3Transport] expected lowercase SHA-256 digest, got ${JSON.stringify(value)}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function isS3NotFound(error) {
|
|
227
|
+
if (!error || typeof error !== "object") return false;
|
|
228
|
+
const candidate = error;
|
|
229
|
+
return candidate.name === "NotFound" || candidate.name === "NoSuchKey" || candidate.$metadata?.httpStatusCode === 404;
|
|
230
|
+
}
|
|
147
231
|
async function tarDirectory(sourceDir, destTarball) {
|
|
148
232
|
if (!existsSync2(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
|
149
233
|
throw new Error(`[s3Transport] tar source must be an existing directory: ${sourceDir}`);
|
|
@@ -192,15 +276,24 @@ async function handler(event, deps) {
|
|
|
192
276
|
}
|
|
193
277
|
}
|
|
194
278
|
} catch (err) {
|
|
279
|
+
normalizeTerminalErrorName(err);
|
|
195
280
|
logEvent({
|
|
196
281
|
event: "handler_error",
|
|
197
282
|
action: unwrapped.Action,
|
|
283
|
+
input: summarizeEvent(unwrapped),
|
|
198
284
|
message: err instanceof Error ? err.message : String(err),
|
|
199
285
|
name: err instanceof Error ? err.name : void 0
|
|
200
286
|
});
|
|
201
287
|
throw err;
|
|
202
288
|
}
|
|
203
289
|
}
|
|
290
|
+
function normalizeTerminalErrorName(error) {
|
|
291
|
+
if (!error || typeof error !== "object") return;
|
|
292
|
+
const candidate = error;
|
|
293
|
+
if (candidate.code === "PLAN_PROTOCOL_UNSUPPORTED" || candidate.code === "PLAN_TOO_LARGE" || candidate.code === "PLAN_V2_INTEGRITY_UNRECOVERABLE") {
|
|
294
|
+
candidate.name = candidate.code;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
204
297
|
var MAX_ENVELOPE_DEPTH = 4;
|
|
205
298
|
function unwrapEvent(event) {
|
|
206
299
|
let cursor = event;
|
|
@@ -237,18 +330,21 @@ function summarizeEvent(event) {
|
|
|
237
330
|
return {
|
|
238
331
|
projectS3Uri: event.ProjectS3Uri,
|
|
239
332
|
planOutputS3Prefix: event.PlanOutputS3Prefix,
|
|
333
|
+
planProtocol: event.PlanProtocol ?? "v1",
|
|
240
334
|
format: event.Config.format,
|
|
241
335
|
fps: event.Config.fps
|
|
242
336
|
};
|
|
243
337
|
case "renderChunk":
|
|
244
338
|
return {
|
|
245
|
-
|
|
339
|
+
planProtocol: event.PlanProtocol ?? "v1",
|
|
340
|
+
...event.PlanProtocol === "v2" ? { planV2ManifestS3Uri: event.PlanV2ManifestS3Uri } : { planS3Uri: event.PlanS3Uri },
|
|
246
341
|
chunkIndex: event.ChunkIndex,
|
|
247
342
|
format: event.Format
|
|
248
343
|
};
|
|
249
344
|
case "assemble":
|
|
250
345
|
return {
|
|
251
|
-
|
|
346
|
+
planProtocol: event.PlanProtocol ?? "v1",
|
|
347
|
+
...event.PlanProtocol === "v2" ? { planV2ManifestS3Uri: event.PlanV2ManifestS3Uri } : { planS3Uri: event.PlanS3Uri },
|
|
252
348
|
chunkCount: event.ChunkS3Uris.length,
|
|
253
349
|
hasAudio: event.AudioS3Uri !== null,
|
|
254
350
|
outputS3Uri: event.OutputS3Uri,
|
|
@@ -267,6 +363,9 @@ function primeRuntimeEnv() {
|
|
|
267
363
|
}
|
|
268
364
|
}
|
|
269
365
|
async function handlePlan(event, deps) {
|
|
366
|
+
if (event.PlanProtocol === "v2") {
|
|
367
|
+
return handlePlanV2(event, deps);
|
|
368
|
+
}
|
|
270
369
|
const started = Date.now();
|
|
271
370
|
const s3 = deps?.s3 ?? getS3Client();
|
|
272
371
|
const primitive = deps?.primitives?.plan ?? plan;
|
|
@@ -315,7 +414,73 @@ async function handlePlan(event, deps) {
|
|
|
315
414
|
cleanupDir(work);
|
|
316
415
|
}
|
|
317
416
|
}
|
|
417
|
+
async function handlePlanV2(event, deps) {
|
|
418
|
+
const started = Date.now();
|
|
419
|
+
const s3 = deps?.s3 ?? getS3Client();
|
|
420
|
+
const primitive = deps?.primitives?.planV2 ?? planV2;
|
|
421
|
+
if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
422
|
+
process.env.PRODUCER_HEADLESS_SHELL_PATH = await resolveChromeExecutablePath();
|
|
423
|
+
}
|
|
424
|
+
const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-plan-v2-"));
|
|
425
|
+
const projectArchive = join(work, "project.tar.gz");
|
|
426
|
+
const projectDir = join(work, "project");
|
|
427
|
+
const planV2Dir = join(work, "plan-v2");
|
|
428
|
+
try {
|
|
429
|
+
await downloadS3ObjectToFile(s3, event.ProjectS3Uri, projectArchive);
|
|
430
|
+
await untarDirectory(projectArchive, projectDir);
|
|
431
|
+
const result = await primitive(projectDir, { ...event.Config }, planV2Dir);
|
|
432
|
+
const manifest = readPlanV2Manifest(planV2Dir);
|
|
433
|
+
if (manifest.planHash !== result.planHash) {
|
|
434
|
+
throwPlanHashMismatch(result.planHash, manifest.planHash);
|
|
435
|
+
}
|
|
436
|
+
const outputPrefix = `${trimTrailingSlash(event.PlanOutputS3Prefix)}/v2`;
|
|
437
|
+
const artifactPrefix = `${outputPrefix}/artifacts/sha256`;
|
|
438
|
+
const uniqueArtifacts = [
|
|
439
|
+
...new Map(manifest.artifacts.map((artifact) => [artifact.sha256, artifact])).values()
|
|
440
|
+
];
|
|
441
|
+
await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
|
|
442
|
+
const localPath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
443
|
+
await uploadContentAddressedFileToS3(
|
|
444
|
+
s3,
|
|
445
|
+
localPath,
|
|
446
|
+
planV2BlobUri(artifactPrefix, artifact.sha256),
|
|
447
|
+
artifact.sha256
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
const manifestUri = `${outputPrefix}/manifest.json`;
|
|
451
|
+
await uploadContentAddressedFileToS3(
|
|
452
|
+
s3,
|
|
453
|
+
result.manifestPath,
|
|
454
|
+
manifestUri,
|
|
455
|
+
await sha256File(result.manifestPath),
|
|
456
|
+
"application/json"
|
|
457
|
+
);
|
|
458
|
+
return {
|
|
459
|
+
Action: "plan",
|
|
460
|
+
PlanProtocol: "v2",
|
|
461
|
+
PlanV2ManifestS3Uri: manifestUri,
|
|
462
|
+
PlanV2ArtifactS3Prefix: artifactPrefix,
|
|
463
|
+
PlanHash: result.planHash,
|
|
464
|
+
ChunkCount: result.chunkCount,
|
|
465
|
+
TotalFrames: result.totalFrames,
|
|
466
|
+
Fps: result.fps,
|
|
467
|
+
Width: result.width,
|
|
468
|
+
Height: result.height,
|
|
469
|
+
Format: result.format,
|
|
470
|
+
HasAudio: manifest.artifacts.some((artifact) => artifact.path === "audio.aac"),
|
|
471
|
+
AudioS3Uri: null,
|
|
472
|
+
FfmpegVersion: result.ffmpegVersion,
|
|
473
|
+
ProducerVersion: result.producerVersion,
|
|
474
|
+
DurationMs: Date.now() - started
|
|
475
|
+
};
|
|
476
|
+
} finally {
|
|
477
|
+
cleanupDir(work);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
318
480
|
async function handleRenderChunk(event, deps) {
|
|
481
|
+
if (event.PlanProtocol === "v2") {
|
|
482
|
+
return handleRenderChunkV2(event, deps);
|
|
483
|
+
}
|
|
319
484
|
const started = Date.now();
|
|
320
485
|
const s3 = deps?.s3 ?? getS3Client();
|
|
321
486
|
const primitive = deps?.primitives?.renderChunk ?? renderChunk;
|
|
@@ -353,6 +518,44 @@ async function handleRenderChunk(event, deps) {
|
|
|
353
518
|
cleanupDir(work);
|
|
354
519
|
}
|
|
355
520
|
}
|
|
521
|
+
async function handleRenderChunkV2(event, deps) {
|
|
522
|
+
const started = Date.now();
|
|
523
|
+
const s3 = deps?.s3 ?? getS3Client();
|
|
524
|
+
const primitive = deps?.primitives?.renderChunk ?? renderChunk;
|
|
525
|
+
if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
526
|
+
process.env.PRODUCER_HEADLESS_SHELL_PATH = await resolveChromeExecutablePath();
|
|
527
|
+
}
|
|
528
|
+
const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-chunk-v2-"));
|
|
529
|
+
try {
|
|
530
|
+
const planDir = await downloadAndMaterializePlanV2(
|
|
531
|
+
s3,
|
|
532
|
+
event,
|
|
533
|
+
{ role: "chunk", chunkIndex: event.ChunkIndex },
|
|
534
|
+
work
|
|
535
|
+
);
|
|
536
|
+
const chunkOutputBase = join(
|
|
537
|
+
work,
|
|
538
|
+
event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
|
|
539
|
+
);
|
|
540
|
+
const result = await primitive(planDir, event.ChunkIndex, chunkOutputBase);
|
|
541
|
+
const chunkUri = await uploadChunkOutput(
|
|
542
|
+
s3,
|
|
543
|
+
result,
|
|
544
|
+
event.ChunkOutputS3Prefix,
|
|
545
|
+
event.ChunkIndex
|
|
546
|
+
);
|
|
547
|
+
return {
|
|
548
|
+
Action: "renderChunk",
|
|
549
|
+
ChunkS3Uri: chunkUri,
|
|
550
|
+
ChunkIndex: event.ChunkIndex,
|
|
551
|
+
Sha256: result.sha256,
|
|
552
|
+
FramesEncoded: result.framesEncoded,
|
|
553
|
+
DurationMs: Date.now() - started
|
|
554
|
+
};
|
|
555
|
+
} finally {
|
|
556
|
+
cleanupDir(work);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
356
559
|
async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
|
|
357
560
|
const trimmed = trimTrailingSlash(prefix);
|
|
358
561
|
if (result.outputKind === "file") {
|
|
@@ -368,6 +571,9 @@ async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
|
|
|
368
571
|
return uri;
|
|
369
572
|
}
|
|
370
573
|
async function handleAssemble(event, deps) {
|
|
574
|
+
if (event.PlanProtocol === "v2") {
|
|
575
|
+
return handleAssembleV2(event, deps);
|
|
576
|
+
}
|
|
371
577
|
const started = Date.now();
|
|
372
578
|
const s3 = deps?.s3 ?? getS3Client();
|
|
373
579
|
const primitive = deps?.primitives?.assemble ?? assemble;
|
|
@@ -405,6 +611,87 @@ async function handleAssemble(event, deps) {
|
|
|
405
611
|
cleanupDir(work);
|
|
406
612
|
}
|
|
407
613
|
}
|
|
614
|
+
async function handleAssembleV2(event, deps) {
|
|
615
|
+
const started = Date.now();
|
|
616
|
+
const s3 = deps?.s3 ?? getS3Client();
|
|
617
|
+
const primitive = deps?.primitives?.assemble ?? assemble;
|
|
618
|
+
const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-assemble-v2-"));
|
|
619
|
+
try {
|
|
620
|
+
const planDir = await downloadAndMaterializePlanV2(s3, event, { role: "assembler" }, work);
|
|
621
|
+
const audioPath = existsSync3(join(planDir, "audio.aac")) ? join(planDir, "audio.aac") : null;
|
|
622
|
+
const chunkPaths = await downloadChunkObjects(s3, event.ChunkS3Uris, work, event.Format);
|
|
623
|
+
const finalOutput = event.Format === "png-sequence" ? join(work, "output-frames") : join(work, `output${formatExtension(event.Format)}`);
|
|
624
|
+
const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
|
|
625
|
+
cfr: event.Cfr === true
|
|
626
|
+
});
|
|
627
|
+
if (event.Format === "png-sequence") {
|
|
628
|
+
const tarball = `${finalOutput}.tar.gz`;
|
|
629
|
+
await tarDirectory(finalOutput, tarball);
|
|
630
|
+
await uploadFileToS3(s3, tarball, event.OutputS3Uri, "application/gzip");
|
|
631
|
+
} else {
|
|
632
|
+
await uploadFileToS3(s3, finalOutput, event.OutputS3Uri);
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
Action: "assemble",
|
|
636
|
+
OutputS3Uri: event.OutputS3Uri,
|
|
637
|
+
FramesEncoded: result.framesEncoded,
|
|
638
|
+
FileSize: result.fileSize,
|
|
639
|
+
DurationMs: Date.now() - started
|
|
640
|
+
};
|
|
641
|
+
} finally {
|
|
642
|
+
cleanupDir(work);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function downloadAndMaterializePlanV2(s3, event, target, work) {
|
|
646
|
+
const transportDir = join(work, "plan-v2");
|
|
647
|
+
mkdirSync2(transportDir, { recursive: true });
|
|
648
|
+
await downloadS3ObjectToFile(s3, event.PlanV2ManifestS3Uri, join(transportDir, "plan.json"));
|
|
649
|
+
const manifest = readPlanV2Manifest(transportDir);
|
|
650
|
+
if (manifest.planHash !== event.PlanHash) {
|
|
651
|
+
throwPlanHashMismatch(event.PlanHash, manifest.planHash);
|
|
652
|
+
}
|
|
653
|
+
const artifacts = listPlanV2ArtifactsForTarget(manifest, target);
|
|
654
|
+
const uniqueArtifacts = [
|
|
655
|
+
...new Map(artifacts.map((artifact) => [artifact.sha256, artifact])).values()
|
|
656
|
+
];
|
|
657
|
+
await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
|
|
658
|
+
await downloadPlanV2Artifact(s3, event.PlanV2ArtifactS3Prefix, transportDir, artifact);
|
|
659
|
+
});
|
|
660
|
+
const planDir = join(work, "plan");
|
|
661
|
+
materializePlanV2Target(transportDir, target, planDir);
|
|
662
|
+
return planDir;
|
|
663
|
+
}
|
|
664
|
+
async function downloadPlanV2Artifact(s3, artifactPrefix, planV2Dir, artifact) {
|
|
665
|
+
await downloadS3ObjectToFileVerified(
|
|
666
|
+
s3,
|
|
667
|
+
planV2BlobUri(artifactPrefix, artifact.sha256),
|
|
668
|
+
planV2BlobPath(planV2Dir, artifact.sha256),
|
|
669
|
+
artifact.sha256
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
function planV2BlobPath(planV2Dir, digest) {
|
|
673
|
+
return join(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
674
|
+
}
|
|
675
|
+
function planV2BlobUri(prefix, digest) {
|
|
676
|
+
return `${trimTrailingSlash(prefix)}/${digest.slice(0, 2)}/${digest}`;
|
|
677
|
+
}
|
|
678
|
+
function throwPlanHashMismatch(expected, actual) {
|
|
679
|
+
const error = new Error(
|
|
680
|
+
`PLAN_HASH_MISMATCH: event PlanHash=${expected} did not match v2 manifest planHash=${actual}`
|
|
681
|
+
);
|
|
682
|
+
error.name = "PLAN_HASH_MISMATCH";
|
|
683
|
+
throw error;
|
|
684
|
+
}
|
|
685
|
+
async function mapConcurrent(values, concurrency, fn) {
|
|
686
|
+
let cursor = 0;
|
|
687
|
+
async function worker() {
|
|
688
|
+
while (cursor < values.length) {
|
|
689
|
+
const index = cursor++;
|
|
690
|
+
await fn(values[index]);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => worker()));
|
|
694
|
+
}
|
|
408
695
|
async function downloadChunkObjects(s3, uris, workDir, format) {
|
|
409
696
|
const chunksDir = join(workDir, "chunks");
|
|
410
697
|
mkdirSync2(chunksDir, { recursive: true });
|
|
@@ -433,11 +720,14 @@ function getEventS3Uris(event) {
|
|
|
433
720
|
case "plan":
|
|
434
721
|
return [event.ProjectS3Uri, event.PlanOutputS3Prefix];
|
|
435
722
|
case "renderChunk":
|
|
436
|
-
return [event.PlanS3Uri, event.ChunkOutputS3Prefix];
|
|
723
|
+
return event.PlanProtocol === "v2" ? [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix, event.ChunkOutputS3Prefix] : [event.PlanS3Uri, event.ChunkOutputS3Prefix];
|
|
437
724
|
case "assemble":
|
|
438
|
-
return [
|
|
439
|
-
|
|
440
|
-
|
|
725
|
+
return [
|
|
726
|
+
...event.PlanProtocol === "v2" ? [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix] : [event.PlanS3Uri],
|
|
727
|
+
...event.ChunkS3Uris,
|
|
728
|
+
event.OutputS3Uri,
|
|
729
|
+
event.AudioS3Uri
|
|
730
|
+
].filter((u) => u != null);
|
|
441
731
|
}
|
|
442
732
|
}
|
|
443
733
|
function validateEventS3Uris(event) {
|
|
@@ -491,7 +781,7 @@ function verifyPlanHash(planDir, expected) {
|
|
|
491
781
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
|
|
492
782
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
493
783
|
import { join as join2 } from "node:path";
|
|
494
|
-
import { HeadObjectCommand, S3Client as S3Client2 } from "@aws-sdk/client-s3";
|
|
784
|
+
import { HeadObjectCommand as HeadObjectCommand2, S3Client as S3Client2 } from "@aws-sdk/client-s3";
|
|
495
785
|
import { hashProjectDir } from "@hyperframes/producer/distributed";
|
|
496
786
|
async function deploySite(opts) {
|
|
497
787
|
if (!statSync3(opts.projectDir).isDirectory()) {
|
|
@@ -532,7 +822,7 @@ async function deploySite(opts) {
|
|
|
532
822
|
}
|
|
533
823
|
async function headObject(s3, bucket, key) {
|
|
534
824
|
try {
|
|
535
|
-
const res = await s3.send(new
|
|
825
|
+
const res = await s3.send(new HeadObjectCommand2({ Bucket: bucket, Key: key }));
|
|
536
826
|
return {
|
|
537
827
|
bytes: typeof res.ContentLength === "number" ? res.ContentLength : 0,
|
|
538
828
|
lastModified: res.LastModified instanceof Date ? res.LastModified.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -614,7 +904,8 @@ async function renderToLambda(opts) {
|
|
|
614
904
|
ProjectS3Uri: site.projectS3Uri,
|
|
615
905
|
PlanOutputS3Prefix: planOutputS3Prefix,
|
|
616
906
|
OutputS3Uri: outputS3Uri,
|
|
617
|
-
Config: opts.config
|
|
907
|
+
Config: opts.config,
|
|
908
|
+
PlanProtocol: opts.planProtocol ?? "v1"
|
|
618
909
|
};
|
|
619
910
|
validateStepFunctionsInputSize(input);
|
|
620
911
|
const sfn = opts.sfn ?? new SFNClient({ region: opts.region });
|
|
@@ -804,7 +1095,7 @@ function summarizeHistory(events, memoryMb) {
|
|
|
804
1095
|
}
|
|
805
1096
|
case "TaskStateExited":
|
|
806
1097
|
case "MapStateExited":
|
|
807
|
-
if (ev.stateExitedEventDetails?.name
|
|
1098
|
+
if (isAssembleState(ev.stateExitedEventDetails?.name)) {
|
|
808
1099
|
assembleComplete = true;
|
|
809
1100
|
const exitPayload = parseJson(ev.stateExitedEventDetails?.output);
|
|
810
1101
|
if (exitPayload && typeof exitPayload === "object") {
|
|
@@ -883,11 +1174,17 @@ function unwrapLambdaPayload(payload) {
|
|
|
883
1174
|
return payload;
|
|
884
1175
|
}
|
|
885
1176
|
function applyPayloadFrameCounts(payload, currentLambdaState, bump) {
|
|
886
|
-
if (currentLambdaState
|
|
1177
|
+
if (!isRenderChunkState(currentLambdaState)) return;
|
|
887
1178
|
if (!payload || typeof payload !== "object") return;
|
|
888
1179
|
const obj = payload;
|
|
889
1180
|
if (typeof obj.FramesEncoded === "number") bump(obj.FramesEncoded);
|
|
890
1181
|
}
|
|
1182
|
+
function isRenderChunkState(name) {
|
|
1183
|
+
return name === "RenderChunk" || name === "RenderChunkV2";
|
|
1184
|
+
}
|
|
1185
|
+
function isAssembleState(name) {
|
|
1186
|
+
return name === "Assemble" || name === "AssembleV2";
|
|
1187
|
+
}
|
|
891
1188
|
function inferBilledMs(payload) {
|
|
892
1189
|
if (!payload || typeof payload !== "object") return 0;
|
|
893
1190
|
const obj = payload;
|
|
@@ -915,6 +1212,7 @@ export {
|
|
|
915
1212
|
computeRenderCost,
|
|
916
1213
|
deploySite,
|
|
917
1214
|
downloadS3ObjectToFile,
|
|
1215
|
+
downloadS3ObjectToFileVerified,
|
|
918
1216
|
formatS3Uri,
|
|
919
1217
|
getRenderProgress,
|
|
920
1218
|
handler,
|
|
@@ -926,6 +1224,7 @@ export {
|
|
|
926
1224
|
tarDirectory,
|
|
927
1225
|
untarDirectory,
|
|
928
1226
|
unwrapEvent,
|
|
1227
|
+
uploadContentAddressedFileToS3,
|
|
929
1228
|
uploadFileToS3,
|
|
930
1229
|
validateDistributedRenderConfig
|
|
931
1230
|
};
|