@hyperframes/aws-lambda 0.8.177 → 0.8.179
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-PK7PO3MH.js +596 -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 +3 -2
- package/dist/sdk/index.js +38 -572
- package/dist/sdk/prebuiltHandler.d.ts +17 -0
- package/dist/sdk/renderToLambda.d.ts +8 -2
- package/dist/siteManifest.d.ts +18 -0
- package/package.json +4 -2
- package/prebuilt/handler-zip.json +6 -0
- package/prebuilt/template.yaml +782 -0
- package/scripts/build-zip.ts +3 -3
- package/scripts/pin-handler-zip.test.ts +101 -0
- package/scripts/pin-handler-zip.ts +59 -0
package/dist/sdk/index.js
CHANGED
|
@@ -1,585 +1,51 @@
|
|
|
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
|
-
import {
|
|
10
|
-
createReadStream,
|
|
11
|
-
createWriteStream,
|
|
12
|
-
existsSync,
|
|
13
|
-
mkdirSync,
|
|
14
|
-
rmSync,
|
|
15
|
-
statSync
|
|
16
|
-
} from "node:fs";
|
|
17
|
-
import { dirname } from "node:path";
|
|
18
1
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
assertStackReadsSites,
|
|
5
|
+
computeRenderCost,
|
|
6
|
+
deploySite,
|
|
7
|
+
getRenderProgress,
|
|
8
|
+
renderToLambda,
|
|
9
|
+
siteHandleFromId,
|
|
140
10
|
validateDistributedRenderConfig,
|
|
11
|
+
validateStepFunctionsInputSize,
|
|
141
12
|
validateVariablesPayload
|
|
142
|
-
} from "
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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";
|
|
13
|
+
} from "../_chunks/chunk-PK7PO3MH.js";
|
|
14
|
+
import "../_chunks/chunk-ZY5YIH5W.js";
|
|
15
|
+
import "../_chunks/chunk-YS3KY3C5.js";
|
|
16
|
+
|
|
17
|
+
// src/sdk/prebuiltHandler.ts
|
|
18
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
var PREBUILT_DIR = "prebuilt";
|
|
22
|
+
var HANDLER_MANIFEST_FILE = "handler-zip.json";
|
|
23
|
+
function adapterPackageRoot() {
|
|
24
|
+
const from = fileURLToPath(import.meta.url);
|
|
25
|
+
for (let dir = dirname(from); dir !== dirname(dir); dir = dirname(dir)) {
|
|
26
|
+
const pkg = join(dir, "package.json");
|
|
27
|
+
if (existsSync(pkg) && JSON.parse(readFileSync(pkg, "utf-8")).name === "@hyperframes/aws-lambda")
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`[aws-lambda] no @hyperframes/aws-lambda package.json above ${from}`);
|
|
31
|
+
}
|
|
32
|
+
function prebuiltHandler(root = adapterPackageRoot()) {
|
|
33
|
+
const dir = join(root, PREBUILT_DIR);
|
|
34
|
+
const manifestPath = join(dir, HANDLER_MANIFEST_FILE);
|
|
35
|
+
if (!existsSync(manifestPath)) return void 0;
|
|
36
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
37
|
+
return { ...manifest, templatePath: join(dir, "template.yaml") };
|
|
575
38
|
}
|
|
576
39
|
export {
|
|
577
|
-
|
|
40
|
+
InvalidConfigError,
|
|
578
41
|
MAX_STEP_FUNCTIONS_INPUT_BYTES,
|
|
42
|
+
assertStackReadsSites,
|
|
579
43
|
computeRenderCost,
|
|
580
44
|
deploySite,
|
|
581
45
|
getRenderProgress,
|
|
46
|
+
prebuiltHandler,
|
|
582
47
|
renderToLambda,
|
|
48
|
+
siteHandleFromId,
|
|
583
49
|
validateDistributedRenderConfig,
|
|
584
50
|
validateStepFunctionsInputSize,
|
|
585
51
|
validateVariablesPayload
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const HANDLER_ZIP_ASSET = "hyperframes-lambda-handler.zip";
|
|
2
|
+
export declare const PREBUILT_DIR = "prebuilt";
|
|
3
|
+
export declare const HANDLER_MANIFEST_FILE = "handler-zip.json";
|
|
4
|
+
export interface HandlerZipManifest {
|
|
5
|
+
version: string;
|
|
6
|
+
url: string;
|
|
7
|
+
sha256: string;
|
|
8
|
+
bytes: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PrebuiltHandler extends HandlerZipManifest {
|
|
11
|
+
/** SAM template whose `CodeUri` is `handler.zip` in the same directory. */
|
|
12
|
+
templatePath: string;
|
|
13
|
+
}
|
|
14
|
+
/** This package's root, found by walking up from the running module (source or bundle). */
|
|
15
|
+
export declare function adapterPackageRoot(): string;
|
|
16
|
+
/** The pinned handler for this install, or undefined when this build carries none. */
|
|
17
|
+
export declare function prebuiltHandler(root?: string): PrebuiltHandler | undefined;
|
|
@@ -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;
|
|
@@ -68,3 +68,9 @@ export interface RenderHandle {
|
|
|
68
68
|
startedAt: string;
|
|
69
69
|
}
|
|
70
70
|
export declare function renderToLambda(opts: RenderToLambdaOptions): Promise<RenderHandle>;
|
|
71
|
+
/** An older stack's plan step would try to untar a site manifest and fail after retries. */
|
|
72
|
+
export declare function assertStackReadsSites(opts: {
|
|
73
|
+
stateMachineArn: string;
|
|
74
|
+
region?: string;
|
|
75
|
+
sfn?: SFNClient;
|
|
76
|
+
}): Promise<void>;
|
|
@@ -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>>;
|