@tileflow/capture 0.1.0-alpha.29 → 0.1.0-alpha.30
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 +62 -1
- package/dist/{chunk-VNDAYGXY.js → chunk-BI5PWIH4.js} +255 -1
- package/dist/index.d.ts +71 -12
- package/dist/index.js +990 -89
- package/dist/{receipt-DhFOATRO.d.ts → receipt-CfZkzyXd.d.ts} +92 -1
- package/dist/receipt.d.ts +1 -1
- package/dist/receipt.js +15 -3
- package/docs/receipts-and-comparison.md +8 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -106,6 +106,66 @@ The equivalent CLI command is `tileflow setup capture`. These settings control b
|
|
|
106
106
|
not the network access required by a map's remote resources. Tileflow never deletes the shared
|
|
107
107
|
per-user browser cache.
|
|
108
108
|
|
|
109
|
+
## Render a timed shot
|
|
110
|
+
|
|
111
|
+
A [capture shot](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/capture-shots.md)
|
|
112
|
+
is a committed camera move with keyframes and an optional theme blend. This module assumes a
|
|
113
|
+
`dusk-flyover` shot in `tileflow.config.ts` and writes its WebM video and receipt:
|
|
114
|
+
|
|
115
|
+
<!-- docs:check -->
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import {mkdir, writeFile} from 'node:fs/promises';
|
|
119
|
+
import {captureTileflowShot, serializeTileflowShotReceipt} from '@tileflow/capture';
|
|
120
|
+
|
|
121
|
+
const shot = await captureTileflowShot({shot: 'dusk-flyover', allowBrowserInstall: true});
|
|
122
|
+
if (!shot.video) throw new Error('The shot produced no video.');
|
|
123
|
+
|
|
124
|
+
await mkdir('.tileflow/captures/shots', {recursive: true});
|
|
125
|
+
await writeFile('.tileflow/captures/shots/dusk-flyover.webm', shot.video);
|
|
126
|
+
await writeFile(
|
|
127
|
+
'.tileflow/captures/shots/dusk-flyover.receipt.json',
|
|
128
|
+
serializeTileflowShotReceipt(shot.receipt),
|
|
129
|
+
);
|
|
130
|
+
console.log(shot.frameCount, shot.codec, shot.framesSha256);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Every frame moves the camera with MapLibre `jumpTo`, moves the blend position, and waits for
|
|
134
|
+
MapLibre `idle` and two animation frames before its screenshot, exactly as a scene does. A slow
|
|
135
|
+
machine or network makes the render take longer; the video keeps its frame rate and shows every
|
|
136
|
+
frame. `timeoutMs` bounds page setup and each frame separately.
|
|
137
|
+
|
|
138
|
+
The pinned browser encodes the captured PNGs with WebCodecs: VP9, or VP8 where VP9 is unavailable.
|
|
139
|
+
A browser that supports neither fails before the first frame with `VIDEO_ENCODER_UNAVAILABLE`.
|
|
140
|
+
Use `format: 'frames'` to receive the PNG frames through `onFrame` instead of a video; frames are
|
|
141
|
+
delivered in order and are not kept in memory:
|
|
142
|
+
|
|
143
|
+
<!-- docs:check -->
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import {mkdir, writeFile} from 'node:fs/promises';
|
|
147
|
+
import {createTileflowCaptureSession} from '@tileflow/capture';
|
|
148
|
+
|
|
149
|
+
await mkdir('frames', {recursive: true});
|
|
150
|
+
const session = createTileflowCaptureSession({allowBrowserInstall: true});
|
|
151
|
+
try {
|
|
152
|
+
const shot = await session.captureShot('dusk-flyover', {
|
|
153
|
+
format: 'frames',
|
|
154
|
+
onFrame: (frame) =>
|
|
155
|
+
writeFile(`frames/frame-${String(frame.index).padStart(4, '0')}.png`, frame.png),
|
|
156
|
+
});
|
|
157
|
+
console.log(shot.frameCount, shot.framesSha256);
|
|
158
|
+
} finally {
|
|
159
|
+
await session.close();
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`captureShots` and `captureAllShots` render several shots from one compiled snapshot. A shot
|
|
164
|
+
receipt records the shot, theme and blend styles, timing, dimensions, renderer, data identity, and
|
|
165
|
+
`frames.sha256`, a digest of every frame's PNG SHA-256. Frames are reproducible under the same
|
|
166
|
+
conditions as scene captures; encoder bytes may differ between runs, so `output.sha256` identifies
|
|
167
|
+
the written video while `frames.sha256` identifies its pixels.
|
|
168
|
+
|
|
109
169
|
## Capture the running application
|
|
110
170
|
|
|
111
171
|
Application capture uses the app's normal loopback server; it does not start a server or discover a
|
|
@@ -195,7 +255,8 @@ validation, browser startup, resource loading, MapLibre readiness, and screensho
|
|
|
195
255
|
Resource diagnostics contain safe classification and origin/status information, not credentials,
|
|
196
256
|
query strings, response bodies, raw browser stacks, or DOM contents.
|
|
197
257
|
|
|
198
|
-
Receipts use schema version 4; parsing rejects any other version.
|
|
258
|
+
Receipts use schema version 4; parsing rejects any other version. Shot receipts use their own
|
|
259
|
+
schema version 1. Import
|
|
199
260
|
`@tileflow/capture/receipt` for receipt-only tooling without loading the capture runtime. World selectors resolve once per capture session to
|
|
200
261
|
an immutable release. Capture retains the complete resolved TileJSON, including its zoom range,
|
|
201
262
|
bounds, attribution and vector-layer metadata. Above the data's maximum zoom, MapLibre enlarges
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
serializeCanonicalJson,
|
|
7
7
|
tileflowCaptureSceneLimits,
|
|
8
8
|
tileflowCaptureSceneNameSchema,
|
|
9
|
+
tileflowCaptureShotLimits,
|
|
9
10
|
tileflowPortableIdSchema,
|
|
10
11
|
tileflowThemeNameSchema
|
|
11
12
|
} from "@tileflow/core";
|
|
@@ -231,6 +232,219 @@ function validateTileflowCaptureReceipt(value) {
|
|
|
231
232
|
assertCanonicalReceiptByteLimit(normalized);
|
|
232
233
|
return normalized;
|
|
233
234
|
}
|
|
235
|
+
var tileflowShotReceiptSchemaVersion = 1;
|
|
236
|
+
var tileflowShotReceiptLimits = Object.freeze({ maximumBytes: 64 * 1024 });
|
|
237
|
+
function createTileflowShotReceipt(input) {
|
|
238
|
+
return validateTileflowShotReceipt({
|
|
239
|
+
schemaVersion: tileflowShotReceiptSchemaVersion,
|
|
240
|
+
kind: "shot",
|
|
241
|
+
shot: { name: input.shot, map: input.map, theme: input.theme, sha256: input.shotSha256 },
|
|
242
|
+
style: { sha256: input.styleSha256 },
|
|
243
|
+
...input.blend ? { blend: { themes: input.blend } } : {},
|
|
244
|
+
timing: { fps: input.fps, frameCount: input.frameCount, durationMs: input.durationMs },
|
|
245
|
+
image: {
|
|
246
|
+
cssWidth: input.width,
|
|
247
|
+
cssHeight: input.height,
|
|
248
|
+
physicalWidth: input.width * input.dpr,
|
|
249
|
+
physicalHeight: input.height * input.dpr,
|
|
250
|
+
dpr: input.dpr
|
|
251
|
+
},
|
|
252
|
+
frames: { sha256: input.framesSha256 },
|
|
253
|
+
output: input.output,
|
|
254
|
+
renderer: input.renderer,
|
|
255
|
+
platform: { os: process.platform, architecture: process.arch },
|
|
256
|
+
data: normalizeCaptureDataInput(input.data),
|
|
257
|
+
verification: { data: "rendered", style: "rendered" },
|
|
258
|
+
networkDependent: input.networkDependent
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function serializeTileflowShotReceipt(receipt) {
|
|
262
|
+
const serialized = `${serializeCanonicalJson(validateTileflowShotReceipt(receipt))}
|
|
263
|
+
`;
|
|
264
|
+
if (new TextEncoder().encode(serialized).byteLength > tileflowShotReceiptLimits.maximumBytes) {
|
|
265
|
+
throw invalidReceipt("The shot receipt exceeds the supported byte limit.");
|
|
266
|
+
}
|
|
267
|
+
return serialized;
|
|
268
|
+
}
|
|
269
|
+
function parseTileflowShotReceipt(source) {
|
|
270
|
+
const byteLength = typeof source === "string" ? new TextEncoder().encode(source).byteLength : source.byteLength;
|
|
271
|
+
if (byteLength > tileflowShotReceiptLimits.maximumBytes) {
|
|
272
|
+
throw invalidReceipt("The shot receipt exceeds the supported byte limit.");
|
|
273
|
+
}
|
|
274
|
+
let value;
|
|
275
|
+
let text;
|
|
276
|
+
try {
|
|
277
|
+
text = typeof source === "string" ? source : new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
278
|
+
value = JSON.parse(text);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
throw invalidReceipt("The shot receipt is not valid UTF-8 JSON.", error);
|
|
281
|
+
}
|
|
282
|
+
const receipt = validateTileflowShotReceipt(value);
|
|
283
|
+
if (text.trim() !== serializeCanonicalJson(value)) {
|
|
284
|
+
throw invalidReceipt("The shot receipt is not canonical JSON.");
|
|
285
|
+
}
|
|
286
|
+
return receipt;
|
|
287
|
+
}
|
|
288
|
+
function validateTileflowShotReceipt(value) {
|
|
289
|
+
assertPlainReceiptData(value, "receipt", {
|
|
290
|
+
allowArrays: true,
|
|
291
|
+
ancestors: /* @__PURE__ */ new WeakSet(),
|
|
292
|
+
nodes: 0
|
|
293
|
+
});
|
|
294
|
+
const receipt = requireRecord(value, "receipt");
|
|
295
|
+
if (receipt.schemaVersion !== tileflowShotReceiptSchemaVersion || receipt.kind !== "shot") {
|
|
296
|
+
throw invalidReceipt("The shot receipt schema version is unsupported.");
|
|
297
|
+
}
|
|
298
|
+
requireExactKeys(receipt, [
|
|
299
|
+
"schemaVersion",
|
|
300
|
+
"kind",
|
|
301
|
+
"shot",
|
|
302
|
+
"style",
|
|
303
|
+
...receipt.blend === void 0 ? [] : ["blend"],
|
|
304
|
+
"timing",
|
|
305
|
+
"image",
|
|
306
|
+
"frames",
|
|
307
|
+
"output",
|
|
308
|
+
"renderer",
|
|
309
|
+
"platform",
|
|
310
|
+
"data",
|
|
311
|
+
"verification",
|
|
312
|
+
"networkDependent"
|
|
313
|
+
]);
|
|
314
|
+
const shot = requireRecord(receipt.shot, "shot");
|
|
315
|
+
requireExactKeys(shot, ["name", "map", "theme", "sha256"]);
|
|
316
|
+
const style = requireRecord(receipt.style, "style");
|
|
317
|
+
requireExactKeys(style, ["sha256"]);
|
|
318
|
+
const blend = receipt.blend === void 0 ? void 0 : validateShotBlend(receipt.blend);
|
|
319
|
+
const timing = requireRecord(receipt.timing, "timing");
|
|
320
|
+
requireExactKeys(timing, ["fps", "frameCount", "durationMs"]);
|
|
321
|
+
const fps = timing.fps;
|
|
322
|
+
if (fps !== 24 && fps !== 25 && fps !== 30 && fps !== 50 && fps !== 60) {
|
|
323
|
+
throw invalidReceipt("The shot receipt has an invalid timing.fps.");
|
|
324
|
+
}
|
|
325
|
+
const frameCount = requirePositiveInteger(timing.frameCount, "timing.frameCount");
|
|
326
|
+
const durationMs = requirePositiveInteger(timing.durationMs, "timing.durationMs");
|
|
327
|
+
if (durationMs > tileflowCaptureShotLimits.durationMs.maximum || frameCount > tileflowCaptureShotLimits.maximumFrames || frameCount !== Math.floor(durationMs * fps / 1e3 + 1e-9) + 1) {
|
|
328
|
+
throw invalidReceipt("The shot receipt timing is inconsistent.");
|
|
329
|
+
}
|
|
330
|
+
const image = requireRecord(receipt.image, "image");
|
|
331
|
+
requireExactKeys(image, ["cssWidth", "cssHeight", "physicalWidth", "physicalHeight", "dpr"]);
|
|
332
|
+
const cssWidth = requirePositiveInteger(image.cssWidth, "image.cssWidth");
|
|
333
|
+
const cssHeight = requirePositiveInteger(image.cssHeight, "image.cssHeight");
|
|
334
|
+
const physicalWidth = requirePositiveInteger(image.physicalWidth, "image.physicalWidth");
|
|
335
|
+
const physicalHeight = requirePositiveInteger(image.physicalHeight, "image.physicalHeight");
|
|
336
|
+
if (image.dpr !== 1 && image.dpr !== 2) {
|
|
337
|
+
throw invalidReceipt("The shot receipt has an invalid image DPR.");
|
|
338
|
+
}
|
|
339
|
+
if (cssWidth * image.dpr !== physicalWidth || cssHeight * image.dpr !== physicalHeight) {
|
|
340
|
+
throw invalidReceipt("The shot receipt image dimensions are inconsistent.");
|
|
341
|
+
}
|
|
342
|
+
if (physicalWidth > tileflowCaptureSceneLimits.viewport.maximum || physicalHeight > tileflowCaptureSceneLimits.viewport.maximum || physicalWidth * physicalHeight > tileflowCaptureSceneLimits.maximumPhysicalPixels) {
|
|
343
|
+
throw invalidReceipt("The shot receipt image exceeds the supported pixel limit.");
|
|
344
|
+
}
|
|
345
|
+
const frames = requireRecord(receipt.frames, "frames");
|
|
346
|
+
requireExactKeys(frames, ["sha256"]);
|
|
347
|
+
const framesSha256 = requireHash(frames.sha256, "frames.sha256");
|
|
348
|
+
const output = requireRecord(receipt.output, "output");
|
|
349
|
+
if (output.kind === "webm") {
|
|
350
|
+
requireExactKeys(output, ["kind", "codec", "sha256"]);
|
|
351
|
+
if (output.codec !== "vp8" && output.codec !== "vp9") {
|
|
352
|
+
throw invalidReceipt("The shot receipt has an invalid output.codec.");
|
|
353
|
+
}
|
|
354
|
+
} else if (output.kind === "frames") {
|
|
355
|
+
requireExactKeys(output, ["kind", "sha256"]);
|
|
356
|
+
if (output.sha256 !== framesSha256) {
|
|
357
|
+
throw invalidReceipt("The shot receipt frame output does not match its frames.");
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
throw invalidReceipt("The shot receipt has an invalid output.kind.");
|
|
361
|
+
}
|
|
362
|
+
const renderer = requireRecord(receipt.renderer, "renderer");
|
|
363
|
+
requireExactKeys(renderer, [
|
|
364
|
+
"tileflow",
|
|
365
|
+
"maplibre",
|
|
366
|
+
"playwright",
|
|
367
|
+
"chromiumRevision",
|
|
368
|
+
"chromiumVersion"
|
|
369
|
+
]);
|
|
370
|
+
const platform = requireRecord(receipt.platform, "platform");
|
|
371
|
+
requireExactKeys(platform, ["os", "architecture"]);
|
|
372
|
+
const verification = requireRecord(receipt.verification, "verification");
|
|
373
|
+
requireExactKeys(verification, ["data", "style"]);
|
|
374
|
+
if (verification.data !== "rendered" || verification.style !== "rendered") {
|
|
375
|
+
throw invalidReceipt("The shot receipt verification must record rendered style and data.");
|
|
376
|
+
}
|
|
377
|
+
if (typeof receipt.networkDependent !== "boolean") {
|
|
378
|
+
throw invalidReceipt("The shot receipt has an invalid network dependency flag.");
|
|
379
|
+
}
|
|
380
|
+
const normalized = {
|
|
381
|
+
schemaVersion: tileflowShotReceiptSchemaVersion,
|
|
382
|
+
kind: "shot",
|
|
383
|
+
shot: {
|
|
384
|
+
name: requireSceneName(shot.name),
|
|
385
|
+
map: requirePortableIdentifier(shot.map, "shot.map"),
|
|
386
|
+
theme: requireShotTheme(shot.theme, "shot.theme"),
|
|
387
|
+
sha256: requireHash(shot.sha256, "shot.sha256")
|
|
388
|
+
},
|
|
389
|
+
style: { sha256: requireHash(style.sha256, "style.sha256") },
|
|
390
|
+
...blend ? { blend } : {},
|
|
391
|
+
timing: { fps, frameCount, durationMs },
|
|
392
|
+
image: { cssWidth, cssHeight, physicalWidth, physicalHeight, dpr: image.dpr },
|
|
393
|
+
frames: { sha256: framesSha256 },
|
|
394
|
+
output: output.kind === "webm" ? {
|
|
395
|
+
kind: "webm",
|
|
396
|
+
codec: output.codec,
|
|
397
|
+
sha256: requireHash(output.sha256, "output.sha256")
|
|
398
|
+
} : { kind: "frames", sha256: framesSha256 },
|
|
399
|
+
renderer: {
|
|
400
|
+
tileflow: requireBoundedString(renderer.tileflow, "renderer.tileflow"),
|
|
401
|
+
maplibre: requireBoundedString(renderer.maplibre, "renderer.maplibre"),
|
|
402
|
+
playwright: requireBoundedString(renderer.playwright, "renderer.playwright"),
|
|
403
|
+
chromiumRevision: requireBoundedString(
|
|
404
|
+
renderer.chromiumRevision,
|
|
405
|
+
"renderer.chromiumRevision"
|
|
406
|
+
),
|
|
407
|
+
chromiumVersion: requireBoundedString(renderer.chromiumVersion, "renderer.chromiumVersion")
|
|
408
|
+
},
|
|
409
|
+
platform: {
|
|
410
|
+
os: requireBoundedString(platform.os, "platform.os"),
|
|
411
|
+
architecture: requireBoundedString(platform.architecture, "platform.architecture")
|
|
412
|
+
},
|
|
413
|
+
data: validateDataIdentity(receipt.data),
|
|
414
|
+
verification: { data: "rendered", style: "rendered" },
|
|
415
|
+
networkDependent: receipt.networkDependent
|
|
416
|
+
};
|
|
417
|
+
const serialized = `${serializeCanonicalJson(normalized)}
|
|
418
|
+
`;
|
|
419
|
+
if (new TextEncoder().encode(serialized).byteLength > tileflowShotReceiptLimits.maximumBytes) {
|
|
420
|
+
throw invalidReceipt("The shot receipt exceeds the supported byte limit.");
|
|
421
|
+
}
|
|
422
|
+
return normalized;
|
|
423
|
+
}
|
|
424
|
+
function requireShotTheme(value, field) {
|
|
425
|
+
const result = tileflowThemeNameSchema.safeParse(value);
|
|
426
|
+
if (!result.success) throw invalidReceipt(`The shot receipt has an invalid ${field}.`);
|
|
427
|
+
return result.data;
|
|
428
|
+
}
|
|
429
|
+
function validateShotBlend(value) {
|
|
430
|
+
const blend = requireRecord(value, "blend");
|
|
431
|
+
requireExactKeys(blend, ["themes"]);
|
|
432
|
+
if (!Array.isArray(blend.themes) || blend.themes.length < tileflowCaptureShotLimits.blendThemes.minimum || blend.themes.length > tileflowCaptureShotLimits.blendThemes.maximum) {
|
|
433
|
+
throw invalidReceipt("The shot receipt has an invalid blend.themes list.");
|
|
434
|
+
}
|
|
435
|
+
const themes = blend.themes.map((entry, index) => {
|
|
436
|
+
const theme = requireRecord(entry, `blend.themes.${index}`);
|
|
437
|
+
requireExactKeys(theme, ["name", "styleSha256"]);
|
|
438
|
+
return {
|
|
439
|
+
name: requireShotTheme(theme.name, `blend.themes.${index}.name`),
|
|
440
|
+
styleSha256: requireHash(theme.styleSha256, `blend.themes.${index}.styleSha256`)
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
if (new Set(themes.map((theme) => theme.name)).size !== themes.length) {
|
|
444
|
+
throw invalidReceipt("The shot receipt blends a theme more than once.");
|
|
445
|
+
}
|
|
446
|
+
return { themes };
|
|
447
|
+
}
|
|
234
448
|
function validateDataIdentity(value) {
|
|
235
449
|
const data = requireRecord(value, "data");
|
|
236
450
|
if (data.kind === "tileflow-world") {
|
|
@@ -390,12 +604,17 @@ function assertCanonicalReceiptByteLimit(receipt) {
|
|
|
390
604
|
}
|
|
391
605
|
}
|
|
392
606
|
function assertPlainReceiptData(value, path = "receipt", state = {
|
|
607
|
+
allowArrays: false,
|
|
393
608
|
ancestors: /* @__PURE__ */ new WeakSet(),
|
|
394
609
|
nodes: 0
|
|
395
610
|
}, depth = 0) {
|
|
396
611
|
if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
397
612
|
return;
|
|
398
613
|
}
|
|
614
|
+
if (typeof value === "object" && Array.isArray(value) && state.allowArrays) {
|
|
615
|
+
assertPlainReceiptArray(value, path, state, depth);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
399
618
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
400
619
|
throw invalidReceipt(`The baseline receipt has an executable or non-plain ${path} value.`);
|
|
401
620
|
}
|
|
@@ -434,6 +653,35 @@ function assertPlainReceiptData(value, path = "receipt", state = {
|
|
|
434
653
|
state.ancestors.delete(value);
|
|
435
654
|
}
|
|
436
655
|
}
|
|
656
|
+
function assertPlainReceiptArray(value, path, state, depth) {
|
|
657
|
+
if (nodeUtilTypes.isProxy(value)) {
|
|
658
|
+
throw invalidReceipt(`The baseline receipt has an executable proxy at ${path}.`);
|
|
659
|
+
}
|
|
660
|
+
if (Object.getPrototypeOf(value) !== Array.prototype) {
|
|
661
|
+
throw invalidReceipt(`The baseline receipt has a non-plain ${path} array.`);
|
|
662
|
+
}
|
|
663
|
+
if (depth > 32 || ++state.nodes > 4096 || value.length > 4096) {
|
|
664
|
+
throw invalidReceipt("The baseline receipt exceeds the supported structural limit.");
|
|
665
|
+
}
|
|
666
|
+
if (state.ancestors.has(value)) {
|
|
667
|
+
throw invalidReceipt("The baseline receipt must not contain cyclic data.");
|
|
668
|
+
}
|
|
669
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) {
|
|
670
|
+
throw invalidReceipt(`The baseline receipt has a sparse or extended ${path} array.`);
|
|
671
|
+
}
|
|
672
|
+
state.ancestors.add(value);
|
|
673
|
+
try {
|
|
674
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
675
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, index);
|
|
676
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
677
|
+
throw invalidReceipt("The baseline receipt contains an accessor or non-enumerable field.");
|
|
678
|
+
}
|
|
679
|
+
assertPlainReceiptData(descriptor.value, `${path}.${index}`, state, depth + 1);
|
|
680
|
+
}
|
|
681
|
+
} finally {
|
|
682
|
+
state.ancestors.delete(value);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
437
685
|
function requireRecord(value, field) {
|
|
438
686
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
439
687
|
throw invalidReceipt(`The baseline receipt has an invalid ${field} object.`);
|
|
@@ -544,5 +792,11 @@ export {
|
|
|
544
792
|
createTileflowCaptureReceipt,
|
|
545
793
|
serializeTileflowCaptureReceipt,
|
|
546
794
|
parseTileflowCaptureReceipt,
|
|
547
|
-
validateTileflowCaptureReceipt
|
|
795
|
+
validateTileflowCaptureReceipt,
|
|
796
|
+
tileflowShotReceiptSchemaVersion,
|
|
797
|
+
tileflowShotReceiptLimits,
|
|
798
|
+
createTileflowShotReceipt,
|
|
799
|
+
serializeTileflowShotReceipt,
|
|
800
|
+
parseTileflowShotReceipt,
|
|
801
|
+
validateTileflowShotReceipt
|
|
548
802
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Browser } from 'playwright';
|
|
2
|
-
import { T as TileflowCaptureRendererIdentity, a as
|
|
3
|
-
export { C as CreateTileflowCaptureReceiptInput,
|
|
4
|
-
import { TileflowCaptureScene, MapLibreStyle, NormalizedTileflowCaptureScene } from '@tileflow/core';
|
|
5
|
-
import { TileflowIconResolutionOptions, TileflowBuildArtifacts
|
|
2
|
+
import { T as TileflowCaptureRendererIdentity, a as TileflowShotReceiptV1, b as TileflowCaptureReceipt, c as TileflowCaptureDataInput, d as TileflowCaptureWorldIdentity } from './receipt-CfZkzyXd.js';
|
|
3
|
+
export { C as CreateTileflowCaptureReceiptInput, e as CreateTileflowShotReceiptInput, f as TileflowCaptureDataBindings, g as TileflowCaptureDataCapabilities, h as TileflowCaptureDataIdentity, i as TileflowCaptureDataSemantics, j as TileflowCaptureDataSource, k as TileflowCaptureVectorIdentity, l as TileflowCaptureVerification, m as createTileflowCaptureReceipt, n as createTileflowCaptureRendererIdentity, o as createTileflowShotReceipt, p as parseTileflowCaptureReceipt, q as parseTileflowShotReceipt, s as serializeTileflowCaptureReceipt, r as serializeTileflowShotReceipt, t as tileflowCaptureReceiptLimits, u as tileflowCaptureReceiptSchemaVersion, v as tileflowCaptureRuntime, w as tileflowShotReceiptLimits, x as tileflowShotReceiptSchemaVersion, y as validateTileflowCaptureReceipt, z as validateTileflowShotReceipt } from './receipt-CfZkzyXd.js';
|
|
4
|
+
import { NormalizedTileflowCaptureShot, TileflowCaptureScene, MapLibreStyle, NormalizedTileflowCaptureScene } from '@tileflow/core';
|
|
5
|
+
import { TileflowBuildAsset, TileflowIconResolutionOptions, TileflowBuildArtifacts } from '@tileflow/dev/artifacts';
|
|
6
6
|
|
|
7
7
|
type TileflowBrowserInstallProgress = 'installing' | 'installed';
|
|
8
8
|
type TileflowBrowserLaunchOptions = {
|
|
@@ -24,6 +24,49 @@ declare class TileflowCaptureBrowserManager {
|
|
|
24
24
|
close(): Promise<void>;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
declare const tileflowSyntheticAssetOrigin = "https://tileflow.local.invalid";
|
|
28
|
+
type TileflowCaptureAsset = TileflowBuildAsset & {
|
|
29
|
+
url?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type TileflowShotVideoCodec = 'vp8' | 'vp9';
|
|
33
|
+
|
|
34
|
+
type TileflowShotFormat = 'frames' | 'webm';
|
|
35
|
+
/** One rendered frame, delivered in order while a shot renders. */
|
|
36
|
+
type TileflowShotFrame = {
|
|
37
|
+
shot: string;
|
|
38
|
+
index: number;
|
|
39
|
+
png: Uint8Array;
|
|
40
|
+
sha256: string;
|
|
41
|
+
};
|
|
42
|
+
type TileflowShotCapture = {
|
|
43
|
+
shot: string;
|
|
44
|
+
map: string;
|
|
45
|
+
theme: string;
|
|
46
|
+
format: TileflowShotFormat;
|
|
47
|
+
/** WebM bytes for the `webm` format. Frames reach `onFrame` and are not retained. */
|
|
48
|
+
video?: Uint8Array;
|
|
49
|
+
codec?: TileflowShotVideoCodec;
|
|
50
|
+
/** SHA-256 of the video, or of the frame set (`framesSha256`) for the `frames` format. */
|
|
51
|
+
sha256: string;
|
|
52
|
+
framesSha256: string;
|
|
53
|
+
frameSha256s: string[];
|
|
54
|
+
shotSha256: string;
|
|
55
|
+
styleSha256: string;
|
|
56
|
+
fps: NormalizedTileflowCaptureShot['fps'];
|
|
57
|
+
frameCount: number;
|
|
58
|
+
durationMs: number;
|
|
59
|
+
width: number;
|
|
60
|
+
height: number;
|
|
61
|
+
dpr: 1 | 2;
|
|
62
|
+
networkDependent: boolean;
|
|
63
|
+
renderer: TileflowCaptureRendererIdentity;
|
|
64
|
+
receipt: TileflowShotReceiptV1;
|
|
65
|
+
warnings: string[];
|
|
66
|
+
};
|
|
67
|
+
/** SHA-256 of each frame's PNG SHA-256 in lowercase hex, one per line, in frame order. */
|
|
68
|
+
declare function tileflowShotFramesSha256(frameSha256s: readonly string[]): Promise<string>;
|
|
69
|
+
|
|
27
70
|
declare const tileflowCaptureResultSchemaVersion: 1;
|
|
28
71
|
type TileflowCaptureOptions = {
|
|
29
72
|
allowBrowserInstall?: boolean;
|
|
@@ -68,18 +111,39 @@ type TileflowCaptureResult = {
|
|
|
68
111
|
schemaVersion: 1;
|
|
69
112
|
captures: TileflowCapture[];
|
|
70
113
|
};
|
|
114
|
+
type TileflowShotCaptureOptions = {
|
|
115
|
+
/** `webm` (the default) encodes a VP9 or VP8 video; `frames` delivers only PNG frames. */
|
|
116
|
+
format?: TileflowShotFormat;
|
|
117
|
+
/** Receives each frame's PNG in order while the shot renders. */
|
|
118
|
+
onFrame?: (frame: TileflowShotFrame) => Promise<void> | void;
|
|
119
|
+
signal?: AbortSignal;
|
|
120
|
+
};
|
|
121
|
+
type TileflowShotCaptureResult = {
|
|
122
|
+
schemaVersion: 1;
|
|
123
|
+
shots: TileflowShotCapture[];
|
|
124
|
+
};
|
|
125
|
+
type CaptureTileflowShotOptions = CreateTileflowCaptureSessionOptions & Omit<TileflowShotCaptureOptions, 'signal'> & {
|
|
126
|
+
shot: string;
|
|
127
|
+
};
|
|
71
128
|
type TileflowCaptureSession = {
|
|
72
129
|
capture(scenes: string[], signal?: AbortSignal): Promise<TileflowCaptureResult>;
|
|
73
130
|
captureAll(signal?: AbortSignal): Promise<TileflowCaptureResult>;
|
|
74
131
|
captureDefinitions(scenes: Record<string, TileflowCaptureScene>, signal?: AbortSignal): Promise<TileflowCaptureResult>;
|
|
75
132
|
captureArtifacts(artifacts: TileflowBuildArtifacts, requestedScenes: string[], signal?: AbortSignal): Promise<TileflowCaptureResult>;
|
|
133
|
+
/** Renders one committed shot. */
|
|
134
|
+
captureShot(shot: string, options?: TileflowShotCaptureOptions): Promise<TileflowShotCapture>;
|
|
135
|
+
/** Renders committed shots in name order from one compiled artifact snapshot. */
|
|
136
|
+
captureShots(shots: string[], options?: TileflowShotCaptureOptions): Promise<TileflowShotCaptureResult>;
|
|
137
|
+
captureAllShots(options?: TileflowShotCaptureOptions): Promise<TileflowShotCaptureResult>;
|
|
76
138
|
close(): Promise<void>;
|
|
77
139
|
};
|
|
78
140
|
declare function captureTileflowScenes(options: TileflowCaptureOptions): Promise<TileflowCaptureResult>;
|
|
141
|
+
/** Renders one committed shot with a temporary session. */
|
|
142
|
+
declare function captureTileflowShot(options: CaptureTileflowShotOptions): Promise<TileflowShotCapture>;
|
|
79
143
|
declare function createTileflowCaptureSession(options?: CreateTileflowCaptureSessionOptions): TileflowCaptureSession;
|
|
80
144
|
|
|
81
|
-
type TileflowCaptureErrorCode = 'ABORTED' | 'APPLICATION_ORIGIN_REQUIRED' | 'APPLICATION_ORIGIN_INVALID' | 'APPLICATION_NAVIGATION_FAILED' | 'APPLICATION_TARGET_NOT_FOUND' | 'APPLICATION_TARGET_AMBIGUOUS' | 'APPLICATION_ERROR' | 'BASELINE_INVALID' | 'BROWSER_INSTALL_FAILED' | 'BROWSER_MISSING' | 'BROWSER_START_FAILED' | 'CAPTURE_TIMEOUT' | 'INVALID_PNG' | 'MAP_LOAD_FAILED' | 'RENDER_FAILED' | 'RESOURCE_FAILED' | 'SCENE_NOT_FOUND' | 'SCREENSHOT_FAILED' | 'STYLE_INVALID' | 'SYNTHETIC_ASSET_NOT_FOUND' | 'WORLD_RESOLUTION_FAILED';
|
|
82
|
-
type TileflowCapturePhase = 'style-validation' | 'browser-start' | 'resource-load' | 'map-load' | 'map-idle' | 'screenshot';
|
|
145
|
+
type TileflowCaptureErrorCode = 'ABORTED' | 'APPLICATION_ORIGIN_REQUIRED' | 'APPLICATION_ORIGIN_INVALID' | 'APPLICATION_NAVIGATION_FAILED' | 'APPLICATION_TARGET_NOT_FOUND' | 'APPLICATION_TARGET_AMBIGUOUS' | 'APPLICATION_ERROR' | 'BASELINE_INVALID' | 'BROWSER_INSTALL_FAILED' | 'BROWSER_MISSING' | 'BROWSER_START_FAILED' | 'CAPTURE_TIMEOUT' | 'INVALID_PNG' | 'MAP_LOAD_FAILED' | 'RENDER_FAILED' | 'RESOURCE_FAILED' | 'SCENE_NOT_FOUND' | 'SCREENSHOT_FAILED' | 'SHOT_NOT_FOUND' | 'STYLE_INVALID' | 'SYNTHETIC_ASSET_NOT_FOUND' | 'VIDEO_ENCODE_FAILED' | 'VIDEO_ENCODER_UNAVAILABLE' | 'WORLD_RESOLUTION_FAILED';
|
|
146
|
+
type TileflowCapturePhase = 'style-validation' | 'browser-start' | 'resource-load' | 'map-load' | 'theme-blend' | 'map-idle' | 'screenshot' | 'video-encode';
|
|
83
147
|
type TileflowCaptureDiagnostic = {
|
|
84
148
|
message: string;
|
|
85
149
|
path: string;
|
|
@@ -106,11 +170,6 @@ declare class TileflowCaptureError extends Error {
|
|
|
106
170
|
}
|
|
107
171
|
declare function captureErrorMessage(error: unknown, fallback: string): string;
|
|
108
172
|
|
|
109
|
-
declare const tileflowSyntheticAssetOrigin = "https://tileflow.local.invalid";
|
|
110
|
-
type TileflowCaptureAsset = TileflowBuildAsset & {
|
|
111
|
-
url?: string;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
173
|
declare const tileflowWorldCurrentTileJsonUrl: "https://api.tileflow.dev/tiles/world/tiles.json";
|
|
115
174
|
type FetchTileJson = (input: string, init: {
|
|
116
175
|
cache: 'no-store';
|
|
@@ -365,4 +424,4 @@ declare const tileflowVisualReviewLimits: Readonly<{
|
|
|
365
424
|
maximumAggregatePngBytes: number;
|
|
366
425
|
}>;
|
|
367
426
|
|
|
368
|
-
export { type CreateTileflowCaptureSessionOptions, type PreparedTileflowCaptureStyle, type TileflowBrowserInstallProgress, type TileflowBrowserLaunchOptions, type TileflowCapture, TileflowCaptureBrowserManager, TileflowCaptureDataInput, type TileflowCaptureDiagnostic, TileflowCaptureError, type TileflowCaptureErrorCode, type TileflowCaptureErrorDetails, type TileflowCaptureOptions, type TileflowCapturePhase, TileflowCaptureReceipt, TileflowCaptureRendererIdentity, type TileflowCaptureResolvedWorldV1, type TileflowCaptureResourceDiagnostic, type TileflowCaptureResourceKind, type TileflowCaptureResult, type TileflowCaptureSession, TileflowCaptureWorldIdentity, TileflowCaptureWorldSession, type TileflowVisualAppearanceDelta, type TileflowVisualAppearanceProfile, type TileflowVisualAppearanceStatistics, type TileflowVisualBaseline, type TileflowVisualComparison, type TileflowVisualComparisonDocument, type TileflowVisualComparisonStatus, type TileflowVisualImageIdentity, type TileflowVisualPaletteColor, type TileflowVisualPixelMetric, type TileflowVisualReferenceAnalysis, type TileflowVisualReferenceAnalysisDocument, type TileflowVisualReferenceAnalysisOptions, type TileflowVisualReferenceAppearance, type TileflowVisualRegion, type TileflowVisualReviewAppearance, type TileflowVisualReviewCapture, type TileflowVisualReviewComparison, type TileflowVisualReviewDefinition, type TileflowVisualReviewDocument, TileflowVisualReviewError, type TileflowVisualReviewFrameIdentity, type TileflowVisualReviewOptions, type TileflowVisualReviewSide, type TileflowVisualReviewStatus, type TileflowVisualRuntimeIdentity, type TileflowVisualSceneIdentity, analyzeTileflowCaptureReference, captureErrorMessage, captureTileflowScenes, compareTileflowCaptureToBaseline, compareTileflowCapturesForReview, createTileflowCaptureBrowserEnvironment, createTileflowCaptureSession, createTileflowVisualComparisonDocument, createTileflowVisualReferenceAnalysisDocument, createTileflowVisualReviewDocument, launchTileflowCaptureBrowser, resolveTileflowApplicationUrl, resolveTileflowCaptureWorldTileJson, serializeTileflowVisualComparison, setupTileflowCaptureBrowser, tileflowCaptureResultSchemaVersion, tileflowSyntheticAssetOrigin, tileflowVisualArtifactLimits, tileflowVisualComparisonSchemaVersion, tileflowVisualEdgeThreshold, tileflowVisualPerceptualThreshold, tileflowVisualReviewLimits, tileflowVisualReviewSchemaVersion, tileflowWorldCurrentTileJsonUrl, validateTileflowVisualReferencePng };
|
|
427
|
+
export { type CaptureTileflowShotOptions, type CreateTileflowCaptureSessionOptions, type PreparedTileflowCaptureStyle, type TileflowBrowserInstallProgress, type TileflowBrowserLaunchOptions, type TileflowCapture, TileflowCaptureBrowserManager, TileflowCaptureDataInput, type TileflowCaptureDiagnostic, TileflowCaptureError, type TileflowCaptureErrorCode, type TileflowCaptureErrorDetails, type TileflowCaptureOptions, type TileflowCapturePhase, TileflowCaptureReceipt, TileflowCaptureRendererIdentity, type TileflowCaptureResolvedWorldV1, type TileflowCaptureResourceDiagnostic, type TileflowCaptureResourceKind, type TileflowCaptureResult, type TileflowCaptureSession, TileflowCaptureWorldIdentity, TileflowCaptureWorldSession, type TileflowShotCapture, type TileflowShotCaptureOptions, type TileflowShotCaptureResult, type TileflowShotFormat, type TileflowShotFrame, TileflowShotReceiptV1, type TileflowShotVideoCodec, type TileflowVisualAppearanceDelta, type TileflowVisualAppearanceProfile, type TileflowVisualAppearanceStatistics, type TileflowVisualBaseline, type TileflowVisualComparison, type TileflowVisualComparisonDocument, type TileflowVisualComparisonStatus, type TileflowVisualImageIdentity, type TileflowVisualPaletteColor, type TileflowVisualPixelMetric, type TileflowVisualReferenceAnalysis, type TileflowVisualReferenceAnalysisDocument, type TileflowVisualReferenceAnalysisOptions, type TileflowVisualReferenceAppearance, type TileflowVisualRegion, type TileflowVisualReviewAppearance, type TileflowVisualReviewCapture, type TileflowVisualReviewComparison, type TileflowVisualReviewDefinition, type TileflowVisualReviewDocument, TileflowVisualReviewError, type TileflowVisualReviewFrameIdentity, type TileflowVisualReviewOptions, type TileflowVisualReviewSide, type TileflowVisualReviewStatus, type TileflowVisualRuntimeIdentity, type TileflowVisualSceneIdentity, analyzeTileflowCaptureReference, captureErrorMessage, captureTileflowScenes, captureTileflowShot, compareTileflowCaptureToBaseline, compareTileflowCapturesForReview, createTileflowCaptureBrowserEnvironment, createTileflowCaptureSession, createTileflowVisualComparisonDocument, createTileflowVisualReferenceAnalysisDocument, createTileflowVisualReviewDocument, launchTileflowCaptureBrowser, resolveTileflowApplicationUrl, resolveTileflowCaptureWorldTileJson, serializeTileflowVisualComparison, setupTileflowCaptureBrowser, tileflowCaptureResultSchemaVersion, tileflowShotFramesSha256, tileflowSyntheticAssetOrigin, tileflowVisualArtifactLimits, tileflowVisualComparisonSchemaVersion, tileflowVisualEdgeThreshold, tileflowVisualPerceptualThreshold, tileflowVisualReviewLimits, tileflowVisualReviewSchemaVersion, tileflowWorldCurrentTileJsonUrl, validateTileflowVisualReferencePng };
|