@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 +2 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/handler.d.ts +2 -2
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +234 -111
- package/dist/handler.js.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +243 -119
- package/dist/index.js.map +4 -4
- package/dist/s3PlanV2Publisher.d.ts +26 -0
- package/dist/s3PlanV2Publisher.d.ts.map +1 -0
- package/dist/s3Transport.d.ts.map +1 -1
- package/dist/sdk/index.js.map +2 -2
- package/package.json +2 -2
- package/scripts/probe-beginframe.test.ts +80 -0
- package/scripts/probe-beginframe.ts +246 -51
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. */
|
package/dist/events.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
27
|
+
planV2WithPublisher?: typeof planV2WithPublisher;
|
|
28
28
|
renderChunk: typeof renderChunk;
|
|
29
29
|
assemble: typeof assemble;
|
|
30
30
|
};
|
package/dist/handler.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
new
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
187
|
-
throw error;
|
|
196
|
+
return "uploaded";
|
|
188
197
|
} catch (error) {
|
|
189
|
-
if (!
|
|
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
|
|
220
|
-
const
|
|
221
|
-
return
|
|
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 =
|
|
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 =
|
|
369
|
-
const projectArchive =
|
|
370
|
-
const projectDir =
|
|
371
|
-
const planDir =
|
|
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 =
|
|
514
|
+
const planTar = join2(work, "plan.tar.gz");
|
|
380
515
|
await tarDirectory(planDir, planTar);
|
|
381
|
-
const planTarUri = `${
|
|
382
|
-
const audioPath =
|
|
383
|
-
const hasAudio = existsSync3(audioPath) &&
|
|
384
|
-
const audioUri = hasAudio ? `${
|
|
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?.
|
|
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 =
|
|
417
|
-
const projectArchive =
|
|
418
|
-
const projectDir =
|
|
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
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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:
|
|
456
|
-
ChunkCount:
|
|
457
|
-
TotalFrames:
|
|
458
|
-
Fps:
|
|
459
|
-
Width:
|
|
460
|
-
Height:
|
|
461
|
-
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:
|
|
465
|
-
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 =
|
|
484
|
-
const planTar =
|
|
485
|
-
const planDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
573
|
-
const planTar =
|
|
574
|
-
const planDir =
|
|
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 =
|
|
698
|
+
audioPath = join2(planDir, "audio.aac");
|
|
582
699
|
await downloadS3ObjectToFile(s3, event.AudioS3Uri, audioPath);
|
|
583
700
|
}
|
|
584
|
-
const finalOutput = event.Format === "png-sequence" ?
|
|
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 =
|
|
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(
|
|
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" ?
|
|
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 =
|
|
639
|
-
|
|
640
|
-
await downloadS3ObjectToFile(s3, event.PlanV2ManifestS3Uri,
|
|
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 =
|
|
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
|
|
782
|
+
return join2(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
666
783
|
}
|
|
667
784
|
function planV2BlobUri(prefix, digest) {
|
|
668
|
-
return `${
|
|
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
|
-
|
|
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 =
|
|
689
|
-
|
|
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 =
|
|
820
|
+
const localPath = join2(chunksDir, basename(key));
|
|
698
821
|
await downloadS3ObjectToFile(s3, uri, localPath);
|
|
699
822
|
if (format === "png-sequence") {
|
|
700
|
-
const dirPath =
|
|
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
|
|
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
|
-
|
|
870
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
748
871
|
} catch {
|
|
749
872
|
}
|
|
750
873
|
}
|
|
751
874
|
function verifyPlanHash(planDir, expected) {
|
|
752
|
-
const planJsonPath =
|
|
875
|
+
const planJsonPath = join2(planDir, "plan.json");
|
|
753
876
|
let parsed;
|
|
754
877
|
try {
|
|
755
878
|
parsed = JSON.parse(readFileSync(planJsonPath, "utf-8"));
|