@hyperframes/aws-lambda 0.8.176 → 0.8.178
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 +9 -4
- package/dist/_chunks/chunk-AK7J3QQN.js +1006 -0
- package/dist/_chunks/chunk-ON46F5DI.js +589 -0
- package/dist/_chunks/chunk-YS3KY3C5.js +19 -0
- package/dist/_chunks/chunk-ZY5YIH5W.js +380 -0
- package/dist/cdk/index.js +13 -1
- package/dist/contentStore.d.ts +5 -0
- package/dist/handler.js +5 -1137
- package/dist/index.js +26 -1648
- package/dist/s3PlanV2Publisher.d.ts +1 -0
- package/dist/s3Transport.d.ts +14 -1
- package/dist/sdk/deploySite.d.ts +17 -17
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +13 -573
- package/dist/sdk/renderToLambda.d.ts +2 -2
- package/dist/siteManifest.d.ts +18 -0
- package/package.json +3 -2
package/dist/sdk/index.js
CHANGED
|
@@ -1,585 +1,25 @@
|
|
|
1
|
-
// src/sdk/deploySite.ts
|
|
2
|
-
import { mkdtempSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { HeadObjectCommand as HeadObjectCommand2, S3Client } from "@aws-sdk/client-s3";
|
|
6
|
-
import { hashProjectDir } from "@hyperframes/producer/distributed";
|
|
7
|
-
|
|
8
|
-
// src/s3Transport.ts
|
|
9
1
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { dirname } from "node:path";
|
|
18
|
-
import {
|
|
19
|
-
GetObjectCommand,
|
|
20
|
-
HeadObjectCommand,
|
|
21
|
-
PutObjectCommand
|
|
22
|
-
} from "@aws-sdk/client-s3";
|
|
23
|
-
import * as tar from "tar";
|
|
24
|
-
function parseS3Uri(uri) {
|
|
25
|
-
if (!uri.startsWith("s3://")) {
|
|
26
|
-
throw new Error(`[s3Transport] expected s3:// URI, got: ${JSON.stringify(uri)}`);
|
|
27
|
-
}
|
|
28
|
-
const rest = uri.slice("s3://".length);
|
|
29
|
-
const slash = rest.indexOf("/");
|
|
30
|
-
if (slash === -1) {
|
|
31
|
-
throw new Error(`[s3Transport] missing key in s3 URI: ${JSON.stringify(uri)}`);
|
|
32
|
-
}
|
|
33
|
-
const bucket = rest.slice(0, slash);
|
|
34
|
-
const key = rest.slice(slash + 1);
|
|
35
|
-
if (!bucket || !key) {
|
|
36
|
-
throw new Error(`[s3Transport] empty bucket or key in s3 URI: ${JSON.stringify(uri)}`);
|
|
37
|
-
}
|
|
38
|
-
return { bucket, key };
|
|
39
|
-
}
|
|
40
|
-
function formatS3Uri(loc) {
|
|
41
|
-
return `s3://${loc.bucket}/${loc.key}`;
|
|
42
|
-
}
|
|
43
|
-
async function uploadFileToS3(client, localPath, uri, contentType) {
|
|
44
|
-
if (!existsSync(localPath)) {
|
|
45
|
-
throw new Error(`[s3Transport] upload source missing: ${localPath}`);
|
|
46
|
-
}
|
|
47
|
-
const { bucket, key } = parseS3Uri(uri);
|
|
48
|
-
const size = statSync(localPath).size;
|
|
49
|
-
await client.send(
|
|
50
|
-
new PutObjectCommand({
|
|
51
|
-
Bucket: bucket,
|
|
52
|
-
Key: key,
|
|
53
|
-
Body: createReadStream(localPath),
|
|
54
|
-
ContentType: contentType,
|
|
55
|
-
ContentLength: size
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
async function tarDirectory(sourceDir, destTarball) {
|
|
60
|
-
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
|
61
|
-
throw new Error(`[s3Transport] tar source must be an existing directory: ${sourceDir}`);
|
|
62
|
-
}
|
|
63
|
-
mkdirSync(dirname(destTarball), { recursive: true });
|
|
64
|
-
await tar.create({ gzip: true, file: destTarball, cwd: sourceDir }, ["."]);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/sdk/deploySite.ts
|
|
68
|
-
async function deploySite(opts) {
|
|
69
|
-
if (!statSync2(opts.projectDir).isDirectory()) {
|
|
70
|
-
throw new Error(`[deploySite] projectDir is not a directory: ${opts.projectDir}`);
|
|
71
|
-
}
|
|
72
|
-
const siteId = opts.siteId ?? hashProjectDir(opts.projectDir);
|
|
73
|
-
const key = `sites/${siteId}/project.tar.gz`;
|
|
74
|
-
const projectS3Uri = formatS3Uri({ bucket: opts.bucketName, key });
|
|
75
|
-
const s3 = opts.s3 ?? new S3Client({ region: opts.region });
|
|
76
|
-
const existing = await headObject(s3, opts.bucketName, key);
|
|
77
|
-
if (existing) {
|
|
78
|
-
return {
|
|
79
|
-
siteId,
|
|
80
|
-
bucketName: opts.bucketName,
|
|
81
|
-
projectS3Uri,
|
|
82
|
-
bytes: existing.bytes,
|
|
83
|
-
uploadedAt: existing.lastModified,
|
|
84
|
-
uploaded: false
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
const workdir = mkdtempSync(join(tmpdir(), "hf-deploy-site-"));
|
|
88
|
-
try {
|
|
89
|
-
const tarball = join(workdir, "project.tar.gz");
|
|
90
|
-
await tarDirectory(opts.projectDir, tarball);
|
|
91
|
-
const size = statSync2(tarball).size;
|
|
92
|
-
await uploadFileToS3(s3, tarball, projectS3Uri, "application/gzip");
|
|
93
|
-
return {
|
|
94
|
-
siteId,
|
|
95
|
-
bucketName: opts.bucketName,
|
|
96
|
-
projectS3Uri,
|
|
97
|
-
bytes: size,
|
|
98
|
-
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
-
uploaded: true
|
|
100
|
-
};
|
|
101
|
-
} finally {
|
|
102
|
-
rmSync2(workdir, { recursive: true, force: true });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
async function headObject(s3, bucket, key) {
|
|
106
|
-
try {
|
|
107
|
-
const res = await s3.send(new HeadObjectCommand2({ Bucket: bucket, Key: key }));
|
|
108
|
-
return {
|
|
109
|
-
bytes: typeof res.ContentLength === "number" ? res.ContentLength : 0,
|
|
110
|
-
lastModified: res.LastModified instanceof Date ? res.LastModified.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
111
|
-
};
|
|
112
|
-
} catch (err) {
|
|
113
|
-
const status = err.$metadata?.httpStatusCode;
|
|
114
|
-
if (status === 404) return null;
|
|
115
|
-
const name = err.name;
|
|
116
|
-
if (name === "NotFound" || name === "NoSuchKey") return null;
|
|
117
|
-
throw err;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/sdk/renderToLambda.ts
|
|
122
|
-
import { randomUUID } from "node:crypto";
|
|
123
|
-
import { SFNClient, StartExecutionCommand } from "@aws-sdk/client-sfn";
|
|
124
|
-
|
|
125
|
-
// src/formatExtension.ts
|
|
126
|
-
var FORMAT_EXTENSIONS = {
|
|
127
|
-
mp4: ".mp4",
|
|
128
|
-
mov: ".mov",
|
|
129
|
-
webm: ".webm",
|
|
130
|
-
"png-sequence": ""
|
|
131
|
-
};
|
|
132
|
-
function formatExtension(format) {
|
|
133
|
-
return FORMAT_EXTENSIONS[format];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/sdk/validateConfig.ts
|
|
137
|
-
import { InvalidConfigError } from "@hyperframes/producer/distributed";
|
|
138
|
-
import {
|
|
139
|
-
InvalidConfigError as InvalidConfigError2,
|
|
2
|
+
InvalidConfigError,
|
|
3
|
+
MAX_STEP_FUNCTIONS_INPUT_BYTES,
|
|
4
|
+
computeRenderCost,
|
|
5
|
+
deploySite,
|
|
6
|
+
getRenderProgress,
|
|
7
|
+
renderToLambda,
|
|
8
|
+
siteHandleFromId,
|
|
140
9
|
validateDistributedRenderConfig,
|
|
10
|
+
validateStepFunctionsInputSize,
|
|
141
11
|
validateVariablesPayload
|
|
142
|
-
} from "
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
function validateStepFunctionsInputSize(input) {
|
|
146
|
-
let serialized;
|
|
147
|
-
try {
|
|
148
|
-
serialized = JSON.stringify(input);
|
|
149
|
-
} catch (err) {
|
|
150
|
-
throw new InvalidConfigError(
|
|
151
|
-
"config",
|
|
152
|
-
`Step Functions execution input is not JSON-serializable: ${err instanceof Error ? err.message : String(err)}`
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
if (serialized === void 0) {
|
|
156
|
-
throw new InvalidConfigError(
|
|
157
|
-
"config",
|
|
158
|
-
"Step Functions execution input is not JSON-serializable (JSON.stringify returned undefined). Check that all fields, including config.variables, are plain JSON values."
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
const byteLength = Buffer.byteLength(serialized, "utf8");
|
|
162
|
-
if (byteLength > MAX_STEP_FUNCTIONS_INPUT_BYTES) {
|
|
163
|
-
throw new InvalidConfigError(
|
|
164
|
-
"config",
|
|
165
|
-
`Step Functions execution input is ${byteLength} bytes, which exceeds the ${MAX_STEP_FUNCTIONS_INPUT_BYTES}-byte (256 KiB) limit for Standard workflows. Variables are for typed data (strings, numbers, structured records); media assets (images, audio, video) should be passed as URL references the composition resolves at render time, not inlined as base64. See ${LARGE_VARIABLES_DOCS_URL} for the URL-your-assets convention.`
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/sdk/renderToLambda.ts
|
|
171
|
-
async function renderToLambda(opts) {
|
|
172
|
-
validateDistributedRenderConfig(opts.config);
|
|
173
|
-
if (!opts.bucketName) {
|
|
174
|
-
throw new Error("[renderToLambda] bucketName is required");
|
|
175
|
-
}
|
|
176
|
-
if (!opts.stateMachineArn) {
|
|
177
|
-
throw new Error("[renderToLambda] stateMachineArn is required");
|
|
178
|
-
}
|
|
179
|
-
if (!opts.siteHandle && !opts.projectDir) {
|
|
180
|
-
throw new Error("[renderToLambda] either siteHandle or projectDir must be supplied");
|
|
181
|
-
}
|
|
182
|
-
const executionName = opts.executionName ?? `hf-render-${randomUUID()}`;
|
|
183
|
-
const ext = formatExtension(opts.config.format);
|
|
184
|
-
const outputKey = opts.outputKey ?? `renders/${executionName}/output${ext}`;
|
|
185
|
-
const planOutputS3Prefix = formatS3Uri({
|
|
186
|
-
bucket: opts.bucketName,
|
|
187
|
-
key: `renders/${executionName}/`
|
|
188
|
-
});
|
|
189
|
-
const outputS3Uri = formatS3Uri({ bucket: opts.bucketName, key: outputKey });
|
|
190
|
-
const site = opts.siteHandle ?? await deploySite({
|
|
191
|
-
projectDir: opts.projectDir,
|
|
192
|
-
bucketName: opts.bucketName,
|
|
193
|
-
region: opts.region,
|
|
194
|
-
s3: opts.s3
|
|
195
|
-
});
|
|
196
|
-
const input = {
|
|
197
|
-
ProjectS3Uri: site.projectS3Uri,
|
|
198
|
-
PlanOutputS3Prefix: planOutputS3Prefix,
|
|
199
|
-
OutputS3Uri: outputS3Uri,
|
|
200
|
-
Config: opts.config,
|
|
201
|
-
PlanProtocol: opts.planProtocol ?? "v2"
|
|
202
|
-
};
|
|
203
|
-
validateStepFunctionsInputSize(input);
|
|
204
|
-
const sfn = opts.sfn ?? new SFNClient({ region: opts.region });
|
|
205
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
206
|
-
const response = await sfn.send(
|
|
207
|
-
new StartExecutionCommand({
|
|
208
|
-
stateMachineArn: opts.stateMachineArn,
|
|
209
|
-
name: executionName,
|
|
210
|
-
input: JSON.stringify(input)
|
|
211
|
-
})
|
|
212
|
-
);
|
|
213
|
-
if (!response.executionArn) {
|
|
214
|
-
throw new Error("[renderToLambda] StartExecution returned no executionArn");
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
renderId: executionName,
|
|
218
|
-
executionArn: response.executionArn,
|
|
219
|
-
bucketName: opts.bucketName,
|
|
220
|
-
stateMachineArn: opts.stateMachineArn,
|
|
221
|
-
outputS3Uri,
|
|
222
|
-
projectS3Uri: site.projectS3Uri,
|
|
223
|
-
startedAt
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// src/sdk/getRenderProgress.ts
|
|
228
|
-
import {
|
|
229
|
-
DescribeExecutionCommand,
|
|
230
|
-
DescribeMapRunCommand,
|
|
231
|
-
GetExecutionHistoryCommand,
|
|
232
|
-
ListExecutionsCommand,
|
|
233
|
-
SFNClient as SFNClient2
|
|
234
|
-
} from "@aws-sdk/client-sfn";
|
|
235
|
-
|
|
236
|
-
// src/sdk/costAccounting.ts
|
|
237
|
-
var LAMBDA_USD_PER_GB_SECOND = 166667e-10;
|
|
238
|
-
var SFN_USD_PER_TRANSITION = 25e-6;
|
|
239
|
-
function computeRenderCost(lambdaInvocations, stateTransitions) {
|
|
240
|
-
let lambdaUsd = 0;
|
|
241
|
-
let anyEstimated = false;
|
|
242
|
-
for (const inv of lambdaInvocations) {
|
|
243
|
-
const gbSeconds = inv.memorySizeMb / 1024 * (inv.billedDurationMs / 1e3);
|
|
244
|
-
lambdaUsd += gbSeconds * LAMBDA_USD_PER_GB_SECOND;
|
|
245
|
-
if (inv.estimated) anyEstimated = true;
|
|
246
|
-
}
|
|
247
|
-
const stepFunctionsUsd = stateTransitions * SFN_USD_PER_TRANSITION;
|
|
248
|
-
const accruedSoFarUsd = roundUsd(lambdaUsd + stepFunctionsUsd);
|
|
249
|
-
return {
|
|
250
|
-
accruedSoFarUsd,
|
|
251
|
-
displayCost: formatUsd(accruedSoFarUsd),
|
|
252
|
-
breakdown: {
|
|
253
|
-
lambdaUsd: roundUsd(lambdaUsd),
|
|
254
|
-
stepFunctionsUsd: roundUsd(stepFunctionsUsd),
|
|
255
|
-
s3Estimate: "not-included",
|
|
256
|
-
estimated: anyEstimated
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
function roundUsd(usd) {
|
|
261
|
-
return Math.round(usd * 1e4) / 1e4;
|
|
262
|
-
}
|
|
263
|
-
function formatUsd(usd) {
|
|
264
|
-
return `$${usd.toFixed(4)}`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/sdk/getRenderProgress.ts
|
|
268
|
-
var DEFAULT_MEMORY_MB = 10240;
|
|
269
|
-
function createProgressSfnClient(config = {}) {
|
|
270
|
-
return new SFNClient2({ maxAttempts: 8, ...config });
|
|
271
|
-
}
|
|
272
|
-
async function getRenderProgress(opts) {
|
|
273
|
-
if (!opts.executionArn) {
|
|
274
|
-
throw new Error("[getRenderProgress] executionArn is required");
|
|
275
|
-
}
|
|
276
|
-
const sfn = opts.sfn ?? createProgressSfnClient({ region: opts.region });
|
|
277
|
-
const memoryMb = opts.defaultMemorySizeMb ?? DEFAULT_MEMORY_MB;
|
|
278
|
-
const describe = await sfn.send(
|
|
279
|
-
new DescribeExecutionCommand({ executionArn: opts.executionArn })
|
|
280
|
-
);
|
|
281
|
-
const status = describe.status ?? "RUNNING";
|
|
282
|
-
const startedAt = describe.startDate?.toISOString() ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
283
|
-
const endedAt = describe.stopDate?.toISOString() ?? null;
|
|
284
|
-
const history = await loadFullHistory(sfn, opts.executionArn);
|
|
285
|
-
const summary = summarizeHistory(history, memoryMb);
|
|
286
|
-
const mapRun = findChunkMapRun(history);
|
|
287
|
-
const warnings = [];
|
|
288
|
-
if (mapRun?.exited) addChunkResults(summary, mapRun.output, memoryMb);
|
|
289
|
-
else if (mapRun) {
|
|
290
|
-
await addRunningMapRun(sfn, mapRun.arn, summary, memoryMb).catch((err) => {
|
|
291
|
-
if (err?.name !== "AccessDeniedException") throw err;
|
|
292
|
-
warnings.push(MAP_RUN_ACCESS_DENIED);
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
const costs = computeRenderCost(summary.lambdaInvocations, summary.stateTransitions);
|
|
296
|
-
const overallProgress = computeOverallProgress({
|
|
297
|
-
status,
|
|
298
|
-
totalFrames: summary.totalFrames,
|
|
299
|
-
framesRendered: summary.framesRendered,
|
|
300
|
-
assembleComplete: summary.assembleComplete
|
|
301
|
-
});
|
|
302
|
-
return {
|
|
303
|
-
status,
|
|
304
|
-
overallProgress,
|
|
305
|
-
framesRendered: summary.framesRendered,
|
|
306
|
-
totalFrames: summary.totalFrames,
|
|
307
|
-
lambdasInvoked: summary.lambdasInvoked,
|
|
308
|
-
costs,
|
|
309
|
-
outputFile: summary.outputFile,
|
|
310
|
-
errors: summary.errors,
|
|
311
|
-
fatalErrorEncountered: isTerminalFailure(status),
|
|
312
|
-
warnings,
|
|
313
|
-
startedAt,
|
|
314
|
-
endedAt
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
async function loadFullHistory(sfn, executionArn) {
|
|
318
|
-
const events = [];
|
|
319
|
-
let nextToken;
|
|
320
|
-
for (let page = 0; page < 50; page++) {
|
|
321
|
-
const res = await sfn.send(
|
|
322
|
-
new GetExecutionHistoryCommand({
|
|
323
|
-
executionArn,
|
|
324
|
-
maxResults: 1e3,
|
|
325
|
-
nextToken,
|
|
326
|
-
reverseOrder: false
|
|
327
|
-
})
|
|
328
|
-
);
|
|
329
|
-
if (res.events) events.push(...res.events);
|
|
330
|
-
nextToken = res.nextToken;
|
|
331
|
-
if (!nextToken) break;
|
|
332
|
-
}
|
|
333
|
-
return events;
|
|
334
|
-
}
|
|
335
|
-
var FAILED_CHUNK_HISTORIES_READ = 5;
|
|
336
|
-
var MAP_RUN_ACCESS_DENIED = "Chunk progress is hidden until the chunks finish: this caller lacks states:DescribeMapRun. Re-apply the policy from `hyperframes lambda policies user`.";
|
|
337
|
-
function findChunkMapRun(events) {
|
|
338
|
-
let run = null;
|
|
339
|
-
for (const ev of events) {
|
|
340
|
-
const arn = ev.mapRunStartedEventDetails?.mapRunArn;
|
|
341
|
-
if (arn) run = { arn, exited: false, output: void 0 };
|
|
342
|
-
const exit = ev.stateExitedEventDetails;
|
|
343
|
-
if (run && ev.type === "MapStateExited" && isRenderChunksMapState(exit?.name)) {
|
|
344
|
-
run = { ...run, exited: true, output: exit?.output };
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return run;
|
|
348
|
-
}
|
|
349
|
-
function addChunkResults(summary, mapOutput, memoryMb) {
|
|
350
|
-
const chunks = parseJson(mapOutput)?.Chunks;
|
|
351
|
-
for (const chunk of Array.isArray(chunks) ? chunks : []) {
|
|
352
|
-
summary.lambdasInvoked++;
|
|
353
|
-
summary.stateTransitions++;
|
|
354
|
-
const billedDurationMs = inferBilledMs(chunk);
|
|
355
|
-
summary.lambdaInvocations.push({
|
|
356
|
-
billedDurationMs,
|
|
357
|
-
memorySizeMb: memoryMb,
|
|
358
|
-
estimated: billedDurationMs === 0
|
|
359
|
-
});
|
|
360
|
-
applyPayloadFrameCounts(chunk, "RenderChunk", (delta) => {
|
|
361
|
-
summary.framesRendered += delta;
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
async function addRunningMapRun(sfn, mapRunArn, summary, memoryMb) {
|
|
366
|
-
const res = await sfn.send(new DescribeMapRunCommand({ mapRunArn }));
|
|
367
|
-
const { total = 0, pending = 0, succeeded = 0, failed = 0 } = res.itemCounts ?? {};
|
|
368
|
-
summary.lambdasInvoked += total - pending;
|
|
369
|
-
summary.stateTransitions += total - pending;
|
|
370
|
-
if (summary.totalFrames !== null && total > 0) {
|
|
371
|
-
summary.framesRendered += Math.floor(summary.totalFrames * succeeded / total);
|
|
372
|
-
}
|
|
373
|
-
if (failed > 0) summary.errors.push(...await failedChunkErrors(sfn, mapRunArn, memoryMb));
|
|
374
|
-
}
|
|
375
|
-
async function failedChunkErrors(sfn, mapRunArn, memoryMb) {
|
|
376
|
-
const failed = await sfn.send(
|
|
377
|
-
new ListExecutionsCommand({
|
|
378
|
-
mapRunArn,
|
|
379
|
-
statusFilter: "FAILED",
|
|
380
|
-
maxResults: FAILED_CHUNK_HISTORIES_READ
|
|
381
|
-
})
|
|
382
|
-
);
|
|
383
|
-
const errors = [];
|
|
384
|
-
for (const child of failed.executions ?? []) {
|
|
385
|
-
if (!child.executionArn) continue;
|
|
386
|
-
const childHistory = await loadFullHistory(sfn, child.executionArn);
|
|
387
|
-
errors.push(...summarizeHistory(childHistory, memoryMb).errors);
|
|
388
|
-
}
|
|
389
|
-
return errors;
|
|
390
|
-
}
|
|
391
|
-
function summarizeHistory(events, memoryMb) {
|
|
392
|
-
let framesRendered = 0;
|
|
393
|
-
let totalFrames = null;
|
|
394
|
-
let lambdasInvoked = 0;
|
|
395
|
-
let assembleComplete = false;
|
|
396
|
-
let outputFile = null;
|
|
397
|
-
let stateTransitions = 0;
|
|
398
|
-
const errors = [];
|
|
399
|
-
const lambdaInvocations = [];
|
|
400
|
-
let currentLambdaState = null;
|
|
401
|
-
for (const ev of events) {
|
|
402
|
-
switch (ev.type) {
|
|
403
|
-
case "TaskStateEntered":
|
|
404
|
-
case "MapStateEntered":
|
|
405
|
-
case "PassStateEntered":
|
|
406
|
-
case "ChoiceStateEntered":
|
|
407
|
-
case "SucceedStateEntered":
|
|
408
|
-
case "FailStateEntered":
|
|
409
|
-
case "WaitStateEntered":
|
|
410
|
-
case "ParallelStateEntered":
|
|
411
|
-
stateTransitions++;
|
|
412
|
-
currentLambdaState = ev.stateEnteredEventDetails?.name ?? currentLambdaState;
|
|
413
|
-
break;
|
|
414
|
-
// Optimized `lambda:invoke` task emits Task* events; raw
|
|
415
|
-
// `lambda:invokeFunction.sync` emits LambdaFunction*. Handle both.
|
|
416
|
-
case "TaskScheduled":
|
|
417
|
-
if (ev.taskScheduledEventDetails?.resourceType === "lambda") {
|
|
418
|
-
lambdasInvoked++;
|
|
419
|
-
}
|
|
420
|
-
break;
|
|
421
|
-
case "LambdaFunctionScheduled":
|
|
422
|
-
lambdasInvoked++;
|
|
423
|
-
break;
|
|
424
|
-
case "TaskSucceeded": {
|
|
425
|
-
if (ev.taskSucceededEventDetails?.resourceType !== "lambda") break;
|
|
426
|
-
const wrapped = parseJson(ev.taskSucceededEventDetails?.output);
|
|
427
|
-
const payload = unwrapLambdaPayload(wrapped);
|
|
428
|
-
const billedDurationMs = inferBilledMs(payload);
|
|
429
|
-
lambdaInvocations.push({
|
|
430
|
-
billedDurationMs,
|
|
431
|
-
memorySizeMb: memoryMb,
|
|
432
|
-
estimated: billedDurationMs === 0
|
|
433
|
-
});
|
|
434
|
-
applyPayloadFrameCounts(payload, currentLambdaState, (delta) => {
|
|
435
|
-
framesRendered += delta;
|
|
436
|
-
});
|
|
437
|
-
if (payload && typeof payload === "object") {
|
|
438
|
-
const obj = payload;
|
|
439
|
-
if (typeof obj.TotalFrames === "number") totalFrames = obj.TotalFrames;
|
|
440
|
-
}
|
|
441
|
-
break;
|
|
442
|
-
}
|
|
443
|
-
case "LambdaFunctionSucceeded": {
|
|
444
|
-
const payload = parseJson(ev.lambdaFunctionSucceededEventDetails?.output);
|
|
445
|
-
const billedDurationMs = inferBilledMs(payload);
|
|
446
|
-
lambdaInvocations.push({
|
|
447
|
-
billedDurationMs,
|
|
448
|
-
memorySizeMb: memoryMb,
|
|
449
|
-
estimated: billedDurationMs === 0
|
|
450
|
-
});
|
|
451
|
-
applyPayloadFrameCounts(payload, currentLambdaState, (delta) => {
|
|
452
|
-
framesRendered += delta;
|
|
453
|
-
});
|
|
454
|
-
if (payload && typeof payload === "object") {
|
|
455
|
-
const obj = payload;
|
|
456
|
-
if (typeof obj.TotalFrames === "number") totalFrames = obj.TotalFrames;
|
|
457
|
-
}
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
case "TaskStateExited":
|
|
461
|
-
case "MapStateExited":
|
|
462
|
-
if (isAssembleState(ev.stateExitedEventDetails?.name)) {
|
|
463
|
-
assembleComplete = true;
|
|
464
|
-
const exitPayload = parseJson(ev.stateExitedEventDetails?.output);
|
|
465
|
-
if (exitPayload && typeof exitPayload === "object") {
|
|
466
|
-
const obj = exitPayload;
|
|
467
|
-
const out = obj.Output;
|
|
468
|
-
const outputS3Uri = typeof out?.OutputS3Uri === "string" ? out.OutputS3Uri : null;
|
|
469
|
-
const bytes = typeof out?.FileSize === "number" ? out.FileSize : null;
|
|
470
|
-
outputFile = outputS3Uri ? { s3Uri: outputS3Uri, bytes } : outputFile;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
break;
|
|
474
|
-
case "TaskFailed":
|
|
475
|
-
if (ev.taskFailedEventDetails?.resourceType !== "lambda") break;
|
|
476
|
-
errors.push({
|
|
477
|
-
state: currentLambdaState ?? "<unknown>",
|
|
478
|
-
error: ev.taskFailedEventDetails?.error ?? "UNKNOWN",
|
|
479
|
-
cause: ev.taskFailedEventDetails?.cause ?? ""
|
|
480
|
-
});
|
|
481
|
-
break;
|
|
482
|
-
case "LambdaFunctionFailed":
|
|
483
|
-
errors.push({
|
|
484
|
-
state: currentLambdaState ?? "<unknown>",
|
|
485
|
-
error: ev.lambdaFunctionFailedEventDetails?.error ?? "UNKNOWN",
|
|
486
|
-
cause: ev.lambdaFunctionFailedEventDetails?.cause ?? ""
|
|
487
|
-
});
|
|
488
|
-
break;
|
|
489
|
-
case "ExecutionFailed":
|
|
490
|
-
errors.push({
|
|
491
|
-
state: "<execution>",
|
|
492
|
-
error: ev.executionFailedEventDetails?.error ?? "UNKNOWN",
|
|
493
|
-
cause: ev.executionFailedEventDetails?.cause ?? ""
|
|
494
|
-
});
|
|
495
|
-
break;
|
|
496
|
-
case "ExecutionAborted":
|
|
497
|
-
errors.push({
|
|
498
|
-
state: "<execution>",
|
|
499
|
-
error: ev.executionAbortedEventDetails?.error ?? "ABORTED",
|
|
500
|
-
cause: ev.executionAbortedEventDetails?.cause ?? ""
|
|
501
|
-
});
|
|
502
|
-
break;
|
|
503
|
-
case "ExecutionTimedOut":
|
|
504
|
-
errors.push({
|
|
505
|
-
state: "<execution>",
|
|
506
|
-
error: "TIMEOUT",
|
|
507
|
-
cause: ev.executionTimedOutEventDetails?.cause ?? ""
|
|
508
|
-
});
|
|
509
|
-
break;
|
|
510
|
-
default:
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return {
|
|
515
|
-
lambdaInvocations,
|
|
516
|
-
stateTransitions,
|
|
517
|
-
framesRendered,
|
|
518
|
-
totalFrames,
|
|
519
|
-
lambdasInvoked,
|
|
520
|
-
assembleComplete,
|
|
521
|
-
outputFile,
|
|
522
|
-
errors
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
function parseJson(s) {
|
|
526
|
-
if (!s) return null;
|
|
527
|
-
try {
|
|
528
|
-
return JSON.parse(s);
|
|
529
|
-
} catch {
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
function unwrapLambdaPayload(payload) {
|
|
534
|
-
if (payload && typeof payload === "object" && "Payload" in payload) {
|
|
535
|
-
const inner = payload.Payload;
|
|
536
|
-
if (inner && typeof inner === "object") return inner;
|
|
537
|
-
}
|
|
538
|
-
return payload;
|
|
539
|
-
}
|
|
540
|
-
function applyPayloadFrameCounts(payload, currentLambdaState, bump) {
|
|
541
|
-
if (!isRenderChunkState(currentLambdaState)) return;
|
|
542
|
-
if (!payload || typeof payload !== "object") return;
|
|
543
|
-
const obj = payload;
|
|
544
|
-
if (typeof obj.FramesEncoded === "number") bump(obj.FramesEncoded);
|
|
545
|
-
}
|
|
546
|
-
function isRenderChunkState(name) {
|
|
547
|
-
return name === "RenderChunk" || name === "RenderChunkV2";
|
|
548
|
-
}
|
|
549
|
-
function isRenderChunksMapState(name) {
|
|
550
|
-
return name === "RenderChunks" || name === "RenderChunksV2";
|
|
551
|
-
}
|
|
552
|
-
function isAssembleState(name) {
|
|
553
|
-
return name === "Assemble" || name === "AssembleV2";
|
|
554
|
-
}
|
|
555
|
-
function inferBilledMs(payload) {
|
|
556
|
-
if (!payload || typeof payload !== "object") return 0;
|
|
557
|
-
const obj = payload;
|
|
558
|
-
if (typeof obj.DurationMs === "number") return obj.DurationMs;
|
|
559
|
-
return 0;
|
|
560
|
-
}
|
|
561
|
-
function computeOverallProgress({
|
|
562
|
-
status,
|
|
563
|
-
totalFrames,
|
|
564
|
-
framesRendered,
|
|
565
|
-
assembleComplete
|
|
566
|
-
}) {
|
|
567
|
-
if (status === "SUCCEEDED") return 1;
|
|
568
|
-
if (assembleComplete) return 1;
|
|
569
|
-
if (totalFrames === null) return 0;
|
|
570
|
-
const chunkProgress = Math.min(1, framesRendered / totalFrames);
|
|
571
|
-
return 0.1 + 0.8 * chunkProgress;
|
|
572
|
-
}
|
|
573
|
-
function isTerminalFailure(status) {
|
|
574
|
-
return status === "FAILED" || status === "TIMED_OUT" || status === "ABORTED";
|
|
575
|
-
}
|
|
12
|
+
} from "../_chunks/chunk-ON46F5DI.js";
|
|
13
|
+
import "../_chunks/chunk-ZY5YIH5W.js";
|
|
14
|
+
import "../_chunks/chunk-YS3KY3C5.js";
|
|
576
15
|
export {
|
|
577
|
-
|
|
16
|
+
InvalidConfigError,
|
|
578
17
|
MAX_STEP_FUNCTIONS_INPUT_BYTES,
|
|
579
18
|
computeRenderCost,
|
|
580
19
|
deploySite,
|
|
581
20
|
getRenderProgress,
|
|
582
21
|
renderToLambda,
|
|
22
|
+
siteHandleFromId,
|
|
583
23
|
validateDistributedRenderConfig,
|
|
584
24
|
validateStepFunctionsInputSize,
|
|
585
25
|
validateVariablesPayload
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
* execution name so the caller can predict the final object URL.
|
|
18
18
|
*/
|
|
19
19
|
import { SFNClient } from "@aws-sdk/client-sfn";
|
|
20
|
-
import
|
|
20
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
21
21
|
import type { LambdaPlanProtocol, SerializableDistributedRenderConfig } from "../events.js";
|
|
22
22
|
import { type SiteHandle } from "./deploySite.js";
|
|
23
23
|
/** Options for {@link renderToLambda}. */
|
|
24
24
|
export interface RenderToLambdaOptions {
|
|
25
|
-
/** Local project directory. Required
|
|
25
|
+
/** Local project directory. Required without `siteHandle`; with one, re-uploads expired files. */
|
|
26
26
|
projectDir?: string;
|
|
27
27
|
/** Re-use an existing `deploySite` upload (skips tar+S3 PUT). */
|
|
28
28
|
siteHandle?: SiteHandle;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { S3Client } from "@aws-sdk/client-s3";
|
|
2
|
+
import type { RemoteProjectFile } from "@hyperframes/producer/distributed";
|
|
3
|
+
/** Stacks that read site manifests list this as a terminal plan error; the SDK checks for it. */
|
|
4
|
+
export declare const SITE_MANIFEST_INVALID = "SITE_MANIFEST_INVALID";
|
|
5
|
+
export interface SiteFile {
|
|
6
|
+
/** `/`-separated, relative to the project root. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly sha256: string;
|
|
9
|
+
readonly sizeBytes: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function siteManifestUri(bucket: string, siteId: string): string;
|
|
12
|
+
/** Sites uploaded before site manifests are one tarball; the plan step still reads them. */
|
|
13
|
+
export declare function siteTarballUri(bucket: string, siteId: string): string;
|
|
14
|
+
export declare function isSiteManifestUri(uri: string): boolean;
|
|
15
|
+
export declare function serializeSiteManifest(files: readonly SiteFile[]): string;
|
|
16
|
+
export declare function parseSiteManifest(text: string): SiteFile[];
|
|
17
|
+
/** Rebuild the project under `projectDir`, SHA-256 verified; videos can stay remote, as URLs. */
|
|
18
|
+
export declare function downloadSiteFiles(s3: S3Client, manifestUri: string, projectDir: string, leaveVideosRemote: boolean): Promise<Record<string, RemoteProjectFile>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/aws-lambda",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.178",
|
|
4
4
|
"description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,12 +41,13 @@
|
|
|
41
41
|
"@aws-sdk/client-lambda": "^3.1146.0",
|
|
42
42
|
"@aws-sdk/client-s3": "^3.1146.0",
|
|
43
43
|
"@aws-sdk/client-sfn": "^3.1146.0",
|
|
44
|
+
"@aws-sdk/s3-request-presigner": "3.1146.0",
|
|
44
45
|
"@sparticuz/chromium": "148.0.0",
|
|
45
46
|
"ffmpeg-static": "^5.2.0",
|
|
46
47
|
"ffprobe-static": "^3.1.0",
|
|
47
48
|
"puppeteer-core": "^25.8.0",
|
|
48
49
|
"tar": "^7.4.3",
|
|
49
|
-
"@hyperframes/producer": "^0.8.
|
|
50
|
+
"@hyperframes/producer": "^0.8.178"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/aws-lambda": "^8.10.146",
|