@hyperframes/aws-lambda 0.7.72 → 0.7.74

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/dist/events.d.ts CHANGED
@@ -157,6 +157,8 @@ export interface RenderChunkLambdaResult {
157
157
  ChunkIndex: number;
158
158
  Sha256: string;
159
159
  FramesEncoded: number;
160
+ /** Effective engine mode after browser probing. Emitted by current handlers. */
161
+ CaptureMode?: "beginframe" | "screenshot" | "drawelement";
160
162
  DurationMs: number;
161
163
  }
162
164
  /** Result of an `assemble` invocation. */
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,mCAAmC,EACpC,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,mCAAmC,EAAE,MAAM,mCAAmC,CAAC;AAE7F,uEAAuE;AACvE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,CAAC;AAC/D,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,gBAAgB,GAChB,aAAa,GACb;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAE3B,mDAAmD;AACnD,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,MAAM,EAAE,mCAAmC,CAAC;CAC7C;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,YAAY,CAAC,EAAE,IAAI,CAAC;CACrB;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,YAAY,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;AAElD,kEAAkE;AAClE,UAAU,oBAAoB;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+FAA+F;IAC/F,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,YAAY,EAAE,IAAI,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAEvE,8EAA8E;AAC9E,UAAU,iBAAiB;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,sFAAsF;IACtF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,IAAI,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,eAAe,CAAC;AAI9D,8EAA8E;AAC9E,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iEAAiE;AACjE,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,YAAY,EAAE,IAAI,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAEvE,uEAAuE;AACvE,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
package/dist/handler.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * primitive → S3 upload → return small JSON result.
12
12
  */
13
13
  import { S3Client } from "@aws-sdk/client-s3";
14
- import { assemble, plan, planV2, renderChunk } from "@hyperframes/producer/distributed";
14
+ import { assemble, plan, planV2WithPublisher, renderChunk } from "@hyperframes/producer/distributed";
15
15
  import type { AssembleEvent, LambdaEvent, LambdaResult, PlanEvent, RenderChunkEvent } from "./events.js";
16
16
  /**
17
17
  * Optional injection points used by the handler's unit tests. Production
@@ -24,7 +24,7 @@ export interface HandlerDeps {
24
24
  s3?: S3Client;
25
25
  primitives?: {
26
26
  plan: typeof plan;
27
- planV2?: typeof planV2;
27
+ planV2WithPublisher?: typeof planV2WithPublisher;
28
28
  renderChunk: typeof renderChunk;
29
29
  assemble: typeof assemble;
30
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EACL,QAAQ,EAMR,IAAI,EACJ,MAAM,EAMN,WAAW,EACZ,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EACV,aAAa,EAGb,WAAW,EACX,YAAY,EACZ,SAAS,EAET,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAuBrB;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,OAAO,IAAI,CAAC;QAClB,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC;QACvB,WAAW,EAAE,OAAO,WAAW,CAAC;QAChC,QAAQ,EAAE,OAAO,QAAQ,CAAC;KAC3B,CAAC;IACF,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA0C3F;AA8BD,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,aAAa,CAsB5F"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EACL,QAAQ,EAMR,IAAI,EACJ,mBAAmB,EAMnB,WAAW,EACZ,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EACV,aAAa,EAGb,WAAW,EACX,YAAY,EACZ,SAAS,EAET,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAsBrB;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,OAAO,IAAI,CAAC;QAClB,mBAAmB,CAAC,EAAE,OAAO,mBAAmB,CAAC;QACjD,WAAW,EAAE,OAAO,WAAW,CAAC;QAChC,QAAQ,EAAE,OAAO,QAAQ,CAAC;KAC3B,CAAC;IACF,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA0C3F;AA8BD,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,aAAa,CAsB5F"}
package/dist/handler.js CHANGED
@@ -1,14 +1,14 @@
1
1
  // src/handler.ts
2
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, readFileSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { basename, join } from "node:path";
2
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
3
+ import { tmpdir as tmpdir2 } from "node:os";
4
+ import { basename, join as join2 } from "node:path";
5
5
  import { S3Client } from "@aws-sdk/client-s3";
6
6
  import {
7
7
  assemble,
8
8
  listPlanV2ArtifactsForTarget,
9
9
  materializePlanV2Target,
10
10
  plan,
11
- planV2,
11
+ planV2WithPublisher,
12
12
  readPlanV2Manifest,
13
13
  renderChunk
14
14
  } from "@hyperframes/producer/distributed";
@@ -173,33 +173,36 @@ async function uploadContentAddressedFileToS3(client, localPath, uri, expectedSh
173
173
  }
174
174
  const { bucket, key } = parseS3Uri(uri);
175
175
  const size = statSync(localPath).size;
176
+ const existing = await inspectContentAddressedObject(client, bucket, key, size, expectedSha256);
177
+ if (existing === "matching") return "reused";
178
+ if (existing === "conflict") throwImmutableObjectConflict(uri);
179
+ const body = createReadStream(localPath);
176
180
  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`
181
+ await client.send(
182
+ new PutObjectCommand({
183
+ Bucket: bucket,
184
+ Key: key,
185
+ Body: body,
186
+ ContentType: contentType,
187
+ ContentLength: size,
188
+ Metadata: { sha256: expectedSha256 },
189
+ ChecksumSHA256: Buffer.from(expectedSha256, "hex").toString("base64"),
190
+ // HEAD followed by an unconditional PUT can overwrite a conflicting
191
+ // object published by a concurrent planner. Conditional create makes
192
+ // immutable CAS and fixed-key manifest publication race-safe.
193
+ IfNoneMatch: "*"
194
+ })
185
195
  );
186
- error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
187
- throw error;
196
+ return "uploaded";
188
197
  } catch (error) {
189
- if (!isS3NotFound(error)) throw error;
198
+ if (!isS3PreconditionFailed(error)) throw error;
199
+ const raced = await inspectContentAddressedObject(client, bucket, key, size, expectedSha256);
200
+ if (raced === "matching") return "reused";
201
+ if (raced === "conflict") throwImmutableObjectConflict(uri);
202
+ throw error;
203
+ } finally {
204
+ body.destroy();
190
205
  }
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
206
  }
204
207
  async function sha256File(path) {
205
208
  const hash = createHash("sha256");
@@ -215,10 +218,36 @@ function assertSha256(value) {
215
218
  );
216
219
  }
217
220
  }
221
+ async function inspectContentAddressedObject(client, bucket, key, expectedSize, expectedSha256) {
222
+ try {
223
+ const existing = await client.send(
224
+ new HeadObjectCommand({ Bucket: bucket, Key: key, ChecksumMode: "ENABLED" })
225
+ );
226
+ return existing.ContentLength === expectedSize && existing.Metadata?.sha256 === expectedSha256 ? "matching" : "conflict";
227
+ } catch (error) {
228
+ if (isS3NotFound(error)) return "missing";
229
+ throw error;
230
+ }
231
+ }
232
+ function throwImmutableObjectConflict(uri) {
233
+ const error = new Error(
234
+ `[s3Transport] PLAN_ARTIFACT_DIGEST_MISMATCH: immutable object ${uri} already exists with different digest metadata or size`
235
+ );
236
+ error.name = "PLAN_ARTIFACT_DIGEST_MISMATCH";
237
+ throw error;
238
+ }
218
239
  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;
240
+ if (!isRecord(error)) return false;
241
+ const metadata = isRecord(error.$metadata) ? error.$metadata : void 0;
242
+ return error.name === "NotFound" || error.name === "NoSuchKey" || metadata?.httpStatusCode === 404;
243
+ }
244
+ function isS3PreconditionFailed(error) {
245
+ if (!isRecord(error)) return false;
246
+ const metadata = isRecord(error.$metadata) ? error.$metadata : void 0;
247
+ return error.name === "PreconditionFailed" || metadata?.httpStatusCode === 412;
248
+ }
249
+ function isRecord(value) {
250
+ return value !== null && typeof value === "object" && !Array.isArray(value);
222
251
  }
223
252
  async function tarDirectory(sourceDir, destTarball) {
224
253
  if (!existsSync2(sourceDir) || !statSync(sourceDir).isDirectory()) {
@@ -238,6 +267,112 @@ async function untarDirectory(tarballPath, destDir) {
238
267
  await tar.extract({ file: tarballPath, cwd: destDir });
239
268
  }
240
269
 
270
+ // src/s3PlanV2Publisher.ts
271
+ import { createHash as createHash2 } from "node:crypto";
272
+ import { mkdirSync as mkdirSync2, mkdtempSync, rmSync as rmSync2, statSync as statSync2, writeFileSync } from "node:fs";
273
+ import { tmpdir } from "node:os";
274
+ import { join } from "node:path";
275
+ import {
276
+ PlanV2IntegrityError
277
+ } from "@hyperframes/producer/distributed";
278
+ function isRecord2(value) {
279
+ return value !== null && typeof value === "object" && !Array.isArray(value);
280
+ }
281
+ function assertSha2562(value, label) {
282
+ if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
283
+ throw new PlanV2IntegrityError(`${label} must be a lowercase SHA-256 digest`);
284
+ }
285
+ return value;
286
+ }
287
+ function manifestDigests(manifestBytes) {
288
+ let value;
289
+ try {
290
+ value = JSON.parse(manifestBytes);
291
+ } catch {
292
+ throw new PlanV2IntegrityError("S3 publisher received invalid manifest JSON");
293
+ }
294
+ if (!isRecord2(value) || !Array.isArray(value.artifacts)) {
295
+ throw new PlanV2IntegrityError("S3 publisher manifest requires an artifacts array");
296
+ }
297
+ return new Set(
298
+ value.artifacts.map((artifact, index) => {
299
+ if (!isRecord2(artifact)) {
300
+ throw new PlanV2IntegrityError(`S3 publisher artifacts[${index}] must be an object`);
301
+ }
302
+ return assertSha2562(artifact.sha256, `S3 publisher artifacts[${index}].sha256`);
303
+ })
304
+ );
305
+ }
306
+ function trimTrailingSlash(value) {
307
+ let end = value.length;
308
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
309
+ return value.slice(0, end);
310
+ }
311
+ var S3PlanV2ArtifactPublisher = class {
312
+ artifactPrefix;
313
+ manifestUri;
314
+ #s3;
315
+ #temporaryRoot;
316
+ #publishedDigests = /* @__PURE__ */ new Set();
317
+ #state = "open";
318
+ constructor(options) {
319
+ const outputPrefix = `${trimTrailingSlash(options.planOutputS3Prefix)}/v2`;
320
+ parseS3Uri(outputPrefix);
321
+ this.#s3 = options.s3;
322
+ this.artifactPrefix = `${outputPrefix}/artifacts/sha256`;
323
+ this.manifestUri = `${outputPrefix}/manifest.json`;
324
+ this.#temporaryRoot = options.temporaryRoot ?? tmpdir();
325
+ mkdirSync2(this.#temporaryRoot, { recursive: true });
326
+ }
327
+ async putBlob(blob) {
328
+ this.#assertOpen("publish a blob");
329
+ const digest = assertSha2562(blob.sha256, "S3 published blob sha256");
330
+ const sourceSize = statSync2(blob.sourcePath).size;
331
+ if (sourceSize !== blob.sizeBytes) {
332
+ throw new PlanV2IntegrityError(
333
+ `S3 published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
334
+ );
335
+ }
336
+ const uri = `${this.artifactPrefix}/${digest.slice(0, 2)}/${digest}`;
337
+ await uploadContentAddressedFileToS3(this.#s3, blob.sourcePath, uri, digest);
338
+ this.#publishedDigests.add(digest);
339
+ }
340
+ async commitManifest(manifestBytes) {
341
+ this.#assertOpen("commit a manifest");
342
+ for (const digest of manifestDigests(manifestBytes)) {
343
+ if (!this.#publishedDigests.has(digest)) {
344
+ throw new PlanV2IntegrityError(
345
+ `cannot commit S3 manifest before referenced blob is durable: ${digest}`
346
+ );
347
+ }
348
+ }
349
+ const manifestDigest = createHash2("sha256").update(manifestBytes, "utf8").digest("hex");
350
+ const stagingDir = mkdtempSync(join(this.#temporaryRoot, "hf-plan-v2-manifest-"));
351
+ const manifestPath = join(stagingDir, "manifest.json");
352
+ try {
353
+ writeFileSync(manifestPath, manifestBytes, "utf8");
354
+ await uploadContentAddressedFileToS3(
355
+ this.#s3,
356
+ manifestPath,
357
+ this.manifestUri,
358
+ manifestDigest,
359
+ "application/json"
360
+ );
361
+ this.#state = "committed";
362
+ } finally {
363
+ rmSync2(stagingDir, { recursive: true, force: true });
364
+ }
365
+ }
366
+ async abort() {
367
+ if (this.#state === "open") this.#state = "aborted";
368
+ }
369
+ #assertOpen(operation) {
370
+ if (this.#state !== "open") {
371
+ throw new PlanV2IntegrityError(`cannot ${operation} after publisher is ${this.#state}`);
372
+ }
373
+ }
374
+ };
375
+
241
376
  // src/handler.ts
242
377
  var cachedS3Client = null;
243
378
  function getS3Client() {
@@ -349,7 +484,7 @@ function primeRuntimeEnv() {
349
484
  if (runtimeEnvPrimed) return;
350
485
  runtimeEnvPrimed = true;
351
486
  const taskRoot = process.env.LAMBDA_TASK_ROOT ?? "/var/task";
352
- const bin = join(taskRoot, "bin");
487
+ const bin = join2(taskRoot, "bin");
353
488
  if (existsSync3(bin)) {
354
489
  process.env.PATH = `${bin}:${process.env.PATH ?? ""}`;
355
490
  }
@@ -365,10 +500,10 @@ async function handlePlan(event, deps) {
365
500
  const chromePath = await resolveChromeExecutablePath();
366
501
  process.env.PRODUCER_HEADLESS_SHELL_PATH = chromePath;
367
502
  }
368
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-plan-"));
369
- const projectArchive = join(work, "project.tar.gz");
370
- const projectDir = join(work, "project");
371
- const planDir = join(work, "plan");
503
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-plan-"));
504
+ const projectArchive = join2(work, "project.tar.gz");
505
+ const projectDir = join2(work, "project");
506
+ const planDir = join2(work, "plan");
372
507
  try {
373
508
  await downloadS3ObjectToFile(s3, event.ProjectS3Uri, projectArchive);
374
509
  await untarDirectory(projectArchive, projectDir);
@@ -376,12 +511,12 @@ async function handlePlan(event, deps) {
376
511
  ...event.Config
377
512
  };
378
513
  const result = await primitive(projectDir, config, planDir);
379
- const planTar = join(work, "plan.tar.gz");
514
+ const planTar = join2(work, "plan.tar.gz");
380
515
  await tarDirectory(planDir, planTar);
381
- const planTarUri = `${trimTrailingSlash(event.PlanOutputS3Prefix)}/plan.tar.gz`;
382
- const audioPath = join(planDir, "audio.aac");
383
- const hasAudio = existsSync3(audioPath) && statSync2(audioPath).size > 0;
384
- const audioUri = hasAudio ? `${trimTrailingSlash(event.PlanOutputS3Prefix)}/audio.aac` : null;
516
+ const planTarUri = `${trimTrailingSlash2(event.PlanOutputS3Prefix)}/plan.tar.gz`;
517
+ const audioPath = join2(planDir, "audio.aac");
518
+ const hasAudio = existsSync3(audioPath) && statSync3(audioPath).size > 0;
519
+ const audioUri = hasAudio ? `${trimTrailingSlash2(event.PlanOutputS3Prefix)}/audio.aac` : null;
385
520
  await Promise.all([
386
521
  uploadFileToS3(s3, planTar, planTarUri, "application/gzip"),
387
522
  hasAudio && audioUri ? uploadFileToS3(s3, audioPath, audioUri, "audio/aac") : null
@@ -409,60 +544,40 @@ async function handlePlan(event, deps) {
409
544
  async function handlePlanV2(event, deps) {
410
545
  const started = Date.now();
411
546
  const s3 = deps?.s3 ?? getS3Client();
412
- const primitive = deps?.primitives?.planV2 ?? planV2;
547
+ const primitive = deps?.primitives?.planV2WithPublisher ?? planV2WithPublisher;
413
548
  if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
414
549
  process.env.PRODUCER_HEADLESS_SHELL_PATH = await resolveChromeExecutablePath();
415
550
  }
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");
551
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-plan-v2-"));
552
+ const projectArchive = join2(work, "project.tar.gz");
553
+ const projectDir = join2(work, "project");
420
554
  try {
421
555
  await downloadS3ObjectToFile(s3, event.ProjectS3Uri, projectArchive);
422
556
  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(
557
+ const publisher = new S3PlanV2ArtifactPublisher({
444
558
  s3,
445
- result.manifestPath,
446
- manifestUri,
447
- await sha256File(result.manifestPath),
448
- "application/json"
449
- );
559
+ planOutputS3Prefix: event.PlanOutputS3Prefix,
560
+ temporaryRoot: work
561
+ });
562
+ const manifest = await primitive(projectDir, { ...event.Config }, publisher, {
563
+ stagingParentDir: work
564
+ });
450
565
  return {
451
566
  Action: "plan",
452
567
  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,
568
+ PlanV2ManifestS3Uri: publisher.manifestUri,
569
+ PlanV2ArtifactS3Prefix: publisher.artifactPrefix,
570
+ PlanHash: manifest.planHash,
571
+ ChunkCount: manifest.chunkCount,
572
+ TotalFrames: manifest.totalFrames,
573
+ Fps: manifest.fps,
574
+ Width: manifest.width,
575
+ Height: manifest.height,
576
+ Format: manifest.format,
462
577
  HasAudio: manifest.artifacts.some((artifact) => artifact.path === "audio.aac"),
463
578
  AudioS3Uri: null,
464
- FfmpegVersion: result.ffmpegVersion,
465
- ProducerVersion: result.producerVersion,
579
+ FfmpegVersion: manifest.ffmpegVersion,
580
+ ProducerVersion: manifest.producerVersion,
466
581
  DurationMs: Date.now() - started
467
582
  };
468
583
  } finally {
@@ -480,14 +595,14 @@ async function handleRenderChunk(event, deps) {
480
595
  const chromePath = await resolveChromeExecutablePath();
481
596
  process.env.PRODUCER_HEADLESS_SHELL_PATH = chromePath;
482
597
  }
483
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-chunk-"));
484
- const planTar = join(work, "plan.tar.gz");
485
- const planDir = join(work, "plan");
598
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-chunk-"));
599
+ const planTar = join2(work, "plan.tar.gz");
600
+ const planDir = join2(work, "plan");
486
601
  try {
487
602
  await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar);
488
603
  await untarDirectory(planTar, planDir);
489
604
  verifyPlanHash(planDir, event.PlanHash);
490
- const chunkOutputBase = join(
605
+ const chunkOutputBase = join2(
491
606
  work,
492
607
  event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
493
608
  );
@@ -504,6 +619,7 @@ async function handleRenderChunk(event, deps) {
504
619
  ChunkIndex: event.ChunkIndex,
505
620
  Sha256: result.sha256,
506
621
  FramesEncoded: result.framesEncoded,
622
+ CaptureMode: result.captureMode,
507
623
  DurationMs: Date.now() - started
508
624
  };
509
625
  } finally {
@@ -517,7 +633,7 @@ async function handleRenderChunkV2(event, deps) {
517
633
  if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
518
634
  process.env.PRODUCER_HEADLESS_SHELL_PATH = await resolveChromeExecutablePath();
519
635
  }
520
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-chunk-v2-"));
636
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-chunk-v2-"));
521
637
  try {
522
638
  const planDir = await downloadAndMaterializePlanV2(
523
639
  s3,
@@ -525,7 +641,7 @@ async function handleRenderChunkV2(event, deps) {
525
641
  { role: "chunk", chunkIndex: event.ChunkIndex },
526
642
  work
527
643
  );
528
- const chunkOutputBase = join(
644
+ const chunkOutputBase = join2(
529
645
  work,
530
646
  event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
531
647
  );
@@ -542,6 +658,7 @@ async function handleRenderChunkV2(event, deps) {
542
658
  ChunkIndex: event.ChunkIndex,
543
659
  Sha256: result.sha256,
544
660
  FramesEncoded: result.framesEncoded,
661
+ CaptureMode: result.captureMode,
545
662
  DurationMs: Date.now() - started
546
663
  };
547
664
  } finally {
@@ -549,7 +666,7 @@ async function handleRenderChunkV2(event, deps) {
549
666
  }
550
667
  }
551
668
  async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
552
- const trimmed = trimTrailingSlash(prefix);
669
+ const trimmed = trimTrailingSlash2(prefix);
553
670
  if (result.outputKind === "file") {
554
671
  const ext = result.outputPath.slice(result.outputPath.lastIndexOf("."));
555
672
  const uri2 = `${trimmed}/chunks/${pad(chunkIndex)}${ext}`;
@@ -569,19 +686,19 @@ async function handleAssemble(event, deps) {
569
686
  const started = Date.now();
570
687
  const s3 = deps?.s3 ?? getS3Client();
571
688
  const primitive = deps?.primitives?.assemble ?? assemble;
572
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-assemble-"));
573
- const planTar = join(work, "plan.tar.gz");
574
- const planDir = join(work, "plan");
689
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-assemble-"));
690
+ const planTar = join2(work, "plan.tar.gz");
691
+ const planDir = join2(work, "plan");
575
692
  try {
576
693
  await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar);
577
694
  await untarDirectory(planTar, planDir);
578
695
  const chunkPaths = await downloadChunkObjects(s3, event.ChunkS3Uris, work, event.Format);
579
696
  let audioPath = null;
580
697
  if (event.AudioS3Uri) {
581
- audioPath = join(planDir, "audio.aac");
698
+ audioPath = join2(planDir, "audio.aac");
582
699
  await downloadS3ObjectToFile(s3, event.AudioS3Uri, audioPath);
583
700
  }
584
- const finalOutput = event.Format === "png-sequence" ? join(work, "output-frames") : join(work, `output${formatExtension(event.Format)}`);
701
+ const finalOutput = event.Format === "png-sequence" ? join2(work, "output-frames") : join2(work, `output${formatExtension(event.Format)}`);
585
702
  const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
586
703
  cfr: event.Cfr === true
587
704
  });
@@ -607,12 +724,12 @@ async function handleAssembleV2(event, deps) {
607
724
  const started = Date.now();
608
725
  const s3 = deps?.s3 ?? getS3Client();
609
726
  const primitive = deps?.primitives?.assemble ?? assemble;
610
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-assemble-v2-"));
727
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-assemble-v2-"));
611
728
  try {
612
729
  const planDir = await downloadAndMaterializePlanV2(s3, event, { role: "assembler" }, work);
613
- const audioPath = existsSync3(join(planDir, "audio.aac")) ? join(planDir, "audio.aac") : null;
730
+ const audioPath = existsSync3(join2(planDir, "audio.aac")) ? join2(planDir, "audio.aac") : null;
614
731
  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)}`);
732
+ const finalOutput = event.Format === "png-sequence" ? join2(work, "output-frames") : join2(work, `output${formatExtension(event.Format)}`);
616
733
  const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
617
734
  cfr: event.Cfr === true
618
735
  });
@@ -635,9 +752,9 @@ async function handleAssembleV2(event, deps) {
635
752
  }
636
753
  }
637
754
  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"));
755
+ const transportDir = join2(work, "plan-v2");
756
+ mkdirSync3(transportDir, { recursive: true });
757
+ await downloadS3ObjectToFile(s3, event.PlanV2ManifestS3Uri, join2(transportDir, "plan.json"));
641
758
  const manifest = readPlanV2Manifest(transportDir);
642
759
  if (manifest.planHash !== event.PlanHash) {
643
760
  throwPlanHashMismatch(event.PlanHash, manifest.planHash);
@@ -649,7 +766,7 @@ async function downloadAndMaterializePlanV2(s3, event, target, work) {
649
766
  await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
650
767
  await downloadPlanV2Artifact(s3, event.PlanV2ArtifactS3Prefix, transportDir, artifact);
651
768
  });
652
- const planDir = join(work, "plan");
769
+ const planDir = join2(work, "plan");
653
770
  materializePlanV2Target(transportDir, target, planDir);
654
771
  return planDir;
655
772
  }
@@ -662,10 +779,10 @@ async function downloadPlanV2Artifact(s3, artifactPrefix, planV2Dir, artifact) {
662
779
  );
663
780
  }
664
781
  function planV2BlobPath(planV2Dir, digest) {
665
- return join(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
782
+ return join2(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
666
783
  }
667
784
  function planV2BlobUri(prefix, digest) {
668
- return `${trimTrailingSlash(prefix)}/${digest.slice(0, 2)}/${digest}`;
785
+ return `${trimTrailingSlash2(prefix)}/${digest.slice(0, 2)}/${digest}`;
669
786
  }
670
787
  function throwPlanHashMismatch(expected, actual) {
671
788
  const error = new Error(
@@ -682,11 +799,17 @@ async function mapConcurrent(values, concurrency, fn) {
682
799
  await fn(values[index]);
683
800
  }
684
801
  }
685
- await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => worker()));
802
+ const results = await Promise.allSettled(
803
+ Array.from({ length: Math.min(concurrency, values.length) }, () => worker())
804
+ );
805
+ const failure = results.find(
806
+ (result) => result.status === "rejected"
807
+ );
808
+ if (failure) throw failure.reason;
686
809
  }
687
810
  async function downloadChunkObjects(s3, uris, workDir, format) {
688
- const chunksDir = join(workDir, "chunks");
689
- mkdirSync2(chunksDir, { recursive: true });
811
+ const chunksDir = join2(workDir, "chunks");
812
+ mkdirSync3(chunksDir, { recursive: true });
690
813
  const local = new Array(uris.length);
691
814
  await Promise.all(
692
815
  uris.map(async (uri, i) => {
@@ -694,10 +817,10 @@ async function downloadChunkObjects(s3, uris, workDir, format) {
694
817
  throw new Error(`[handler] chunk URI at index ${i} is empty`);
695
818
  }
696
819
  const { key } = parseS3Uri(uri);
697
- const localPath = join(chunksDir, basename(key));
820
+ const localPath = join2(chunksDir, basename(key));
698
821
  await downloadS3ObjectToFile(s3, uri, localPath);
699
822
  if (format === "png-sequence") {
700
- const dirPath = join(chunksDir, `frames-${pad(i)}`);
823
+ const dirPath = join2(chunksDir, `frames-${pad(i)}`);
701
824
  await untarDirectory(localPath, dirPath);
702
825
  local[i] = dirPath;
703
826
  } else {
@@ -739,17 +862,17 @@ function validateEventS3Uris(event) {
739
862
  function pad(n) {
740
863
  return n.toString().padStart(4, "0");
741
864
  }
742
- function trimTrailingSlash(prefix) {
865
+ function trimTrailingSlash2(prefix) {
743
866
  return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
744
867
  }
745
868
  function cleanupDir(dir) {
746
869
  try {
747
- rmSync2(dir, { recursive: true, force: true });
870
+ rmSync3(dir, { recursive: true, force: true });
748
871
  } catch {
749
872
  }
750
873
  }
751
874
  function verifyPlanHash(planDir, expected) {
752
- const planJsonPath = join(planDir, "plan.json");
875
+ const planJsonPath = join2(planDir, "plan.json");
753
876
  let parsed;
754
877
  try {
755
878
  parsed = JSON.parse(readFileSync(planJsonPath, "utf-8"));