fundus 0.0.2

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.
Files changed (61) hide show
  1. package/dist/build-CZYFLLvl.js +191 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +1420 -0
  4. package/dist/config.d.ts +2 -0
  5. package/dist/config.js +2 -0
  6. package/dist/core/image-Dqmz8FXS.js +54 -0
  7. package/dist/core/index.d.ts +499 -0
  8. package/dist/core/index.js +962 -0
  9. package/dist/core/reference-graph-CjuHbLDw.d.ts +280 -0
  10. package/dist/core/testing.d.ts +27 -0
  11. package/dist/core/testing.js +80 -0
  12. package/dist/editor/200.html +37 -0
  13. package/dist/editor/_app/immutable/assets/0.DLhIsYA_.css +1 -0
  14. package/dist/editor/_app/immutable/assets/2.DIbbW7U1.css +1 -0
  15. package/dist/editor/_app/immutable/assets/3.C-9vFQ9A.css +1 -0
  16. package/dist/editor/_app/immutable/assets/4.BS8ElpOV.css +1 -0
  17. package/dist/editor/_app/immutable/assets/ConfirmDialog.CHEISE-L.css +1 -0
  18. package/dist/editor/_app/immutable/assets/LibraryContextMenu.CDjCmdV-.css +1 -0
  19. package/dist/editor/_app/immutable/chunks/BfPE5wdt.js +1 -0
  20. package/dist/editor/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  21. package/dist/editor/_app/immutable/chunks/CNRz3sSw.js +3 -0
  22. package/dist/editor/_app/immutable/chunks/CO4RBJfa.js +1 -0
  23. package/dist/editor/_app/immutable/chunks/Cx6qtxJg.js +1 -0
  24. package/dist/editor/_app/immutable/chunks/DjdrdRzT.js +1 -0
  25. package/dist/editor/_app/immutable/chunks/DjvOiDmq.js +1 -0
  26. package/dist/editor/_app/immutable/chunks/n7mvYc42.js +5 -0
  27. package/dist/editor/_app/immutable/chunks/xihTtKlq.js +1 -0
  28. package/dist/editor/_app/immutable/entry/app.BkqbfI8n.js +2 -0
  29. package/dist/editor/_app/immutable/entry/start.C8HHOLly.js +1 -0
  30. package/dist/editor/_app/immutable/nodes/0.xdMI5GEU.js +2 -0
  31. package/dist/editor/_app/immutable/nodes/1.D9p2tfoJ.js +1 -0
  32. package/dist/editor/_app/immutable/nodes/2.BrA0Hyzu.js +3 -0
  33. package/dist/editor/_app/immutable/nodes/3.WO7tZH_d.js +104 -0
  34. package/dist/editor/_app/immutable/nodes/4.DQtx17aW.js +2 -0
  35. package/dist/editor/_app/version.json +1 -0
  36. package/dist/index.d.ts +370 -0
  37. package/dist/index.js +4 -0
  38. package/dist/operations-DYM-5KVl.js +1797 -0
  39. package/dist/runtime/chroma-key-gl.d.ts +23 -0
  40. package/dist/runtime/chroma-key-gl.js +290 -0
  41. package/dist/runtime/components/ChromaKeyVideo.svelte +225 -0
  42. package/dist/runtime/components/Image.svelte +18 -0
  43. package/dist/runtime/components/Slice.svelte +35 -0
  44. package/dist/runtime/components/SliceCanvas.svelte +162 -0
  45. package/dist/runtime/components/SliceDom.svelte +104 -0
  46. package/dist/runtime/components/Video.svelte +60 -0
  47. package/dist/runtime/geometry.d.ts +75 -0
  48. package/dist/runtime/geometry.js +209 -0
  49. package/dist/runtime/index.d.ts +9 -0
  50. package/dist/runtime/index.js +9 -0
  51. package/dist/runtime/kind-handlers.d.ts +46 -0
  52. package/dist/runtime/kind-handlers.js +86 -0
  53. package/dist/runtime/manifest.d.ts +60 -0
  54. package/dist/runtime/manifest.js +117 -0
  55. package/dist/runtime/observe-border-box-size.d.ts +5 -0
  56. package/dist/runtime/observe-border-box-size.js +12 -0
  57. package/dist/runtime/preload-registry.d.ts +26 -0
  58. package/dist/runtime/preload-registry.js +68 -0
  59. package/dist/start-Bhnqt9Dc.js +279 -0
  60. package/dist/start-D2IXOrni.js +2 -0
  61. package/package.json +65 -0
@@ -0,0 +1,962 @@
1
+ import { a as isAssetReference, i as validateImagePreset, n as RASTER_IMAGE_EXTENSIONS, r as imagePlugin, t as IMAGE_BUILT_IN_PRESETS } from "./image-Dqmz8FXS.js";
2
+ //#region src/folder-path.ts
3
+ /** The implicit raw/proxy root. Explicit folders are always non-empty paths. */
4
+ const ROOT_FOLDER = "";
5
+ /** Fundus-owned file that preserves explicit empty folders in source control. */
6
+ const FOLDER_SENTINEL = ".gitkeep";
7
+ const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
8
+ const WINDOWS_FORBIDDEN_CHARACTERS = /[<>:"|?*]/;
9
+ const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
10
+ /** Validate one human-facing folder name (a single path segment). */
11
+ function folderNameError(name) {
12
+ if (name.length === 0) return "Folder names must not be empty.";
13
+ if (name !== name.trim()) return "Folder names must not start or end with whitespace.";
14
+ if (name === "." || name === "..") return `"${name}" is not a valid folder name.`;
15
+ if (name.includes("/") || name.includes("\\")) return "Folder names must not contain path separators.";
16
+ if (CONTROL_CHARACTERS.test(name)) return "Folder names must not contain control characters.";
17
+ if (WINDOWS_FORBIDDEN_CHARACTERS.test(name)) return "Folder names must not contain Windows-reserved characters (: * ? \" < > |).";
18
+ if (name.endsWith(".")) return "Folder names must not end with a dot.";
19
+ if (name.toLocaleLowerCase("en-US") === "root") return `"${name}" is reserved for the implicit Fundus root folder.`;
20
+ if (name.toLocaleLowerCase("en-US") === ".gitkeep") return `"${name}" is reserved for Fundus folder management.`;
21
+ if (WINDOWS_RESERVED_NAME.test(name)) return `"${name}" is a reserved filesystem name.`;
22
+ return null;
23
+ }
24
+ /** Normalize a validated, portable relative folder path to NFC + POSIX separators. */
25
+ function normalizeFolderPath(path) {
26
+ return path.split("/").map((segment) => segment.normalize("NFC")).join("/");
27
+ }
28
+ /** Validate a non-root, POSIX-relative folder path. */
29
+ function folderPathError(path) {
30
+ if (path.length === 0) return "The root is implicit and cannot be registered as a folder.";
31
+ if (path.startsWith("/") || path.endsWith("/")) return "Folder paths must be relative and must not start or end with a slash.";
32
+ for (const segment of path.split("/")) {
33
+ const problem = folderNameError(segment);
34
+ if (problem) return `Invalid folder path "${path}": ${problem}`;
35
+ }
36
+ return null;
37
+ }
38
+ /** Case-insensitive, Unicode-normalized key used to prevent portable path collisions. */
39
+ function portableFolderPathKey(path) {
40
+ return normalizeFolderPath(path).toLocaleLowerCase("en-US");
41
+ }
42
+ function parentFolderPath(path) {
43
+ const separator = path.lastIndexOf("/");
44
+ return separator === -1 ? "" : path.slice(0, separator);
45
+ }
46
+ function folderNameOf(path) {
47
+ const separator = path.lastIndexOf("/");
48
+ return separator === -1 ? path : path.slice(separator + 1);
49
+ }
50
+ function joinFolderPath(parent, name) {
51
+ return parent === "" ? name : `${parent}/${name}`;
52
+ }
53
+ function folderOfRawPath(rawPath) {
54
+ return parentFolderPath(rawPath);
55
+ }
56
+ function rawFileNameOfPath(rawPath) {
57
+ return folderNameOf(rawPath);
58
+ }
59
+ function isFolderOrDescendant(path, ancestor) {
60
+ return path === ancestor || path.startsWith(`${ancestor}/`);
61
+ }
62
+ function replaceFolderPrefix(path, from, to) {
63
+ if (path === from) return to;
64
+ return `${to}${path.slice(from.length)}`;
65
+ }
66
+ //#endregion
67
+ //#region src/library.ts
68
+ /** A fresh, empty library. */
69
+ function createEmptyLibrary() {
70
+ return {
71
+ version: 1,
72
+ folders: [],
73
+ assets: {},
74
+ manifests: {}
75
+ };
76
+ }
77
+ function isRecord(value) {
78
+ return typeof value === "object" && value !== null && !Array.isArray(value);
79
+ }
80
+ /**
81
+ * Keys that are prototype-pollution hazards as record keys: assigning
82
+ * `assets["__proto__"] = …` on a plain object replaces its prototype instead
83
+ * of adding an entry. Rejected outright at parse time — id *validity* beyond
84
+ * this is a validator concern (reported as issues, not parse failures).
85
+ */
86
+ const DANGEROUS_KEYS = [
87
+ "__proto__",
88
+ "constructor",
89
+ "prototype"
90
+ ];
91
+ function assertSafeKey(kind, key) {
92
+ if (DANGEROUS_KEYS.includes(key)) throw new Error(`Library ${kind} key "${key}" is not allowed.`);
93
+ }
94
+ function parseAssetRecord(id, value) {
95
+ if (!isRecord(value)) throw new Error(`Library asset "${id}" must be an object.`);
96
+ if (typeof value.type !== "string" || value.type.length === 0) throw new Error(`Library asset "${id}" is missing a string "type".`);
97
+ if (typeof value.raw !== "string" || value.raw.length === 0) throw new Error(`Library asset "${id}" is missing a string "raw" file name.`);
98
+ if (typeof value.rawHash !== "string" || value.rawHash.length === 0) throw new Error(`Library asset "${id}" is missing a string "rawHash".`);
99
+ if (!("parameters" in value)) throw new Error(`Library asset "${id}" is missing "parameters".`);
100
+ if (!Array.isArray(value.manifests) || value.manifests.some((entry) => typeof entry !== "string")) throw new Error(`Library asset "${id}" must have a "manifests" array of strings.`);
101
+ return {
102
+ type: value.type,
103
+ raw: value.raw,
104
+ rawHash: value.rawHash,
105
+ parameters: value.parameters,
106
+ manifests: value.manifests
107
+ };
108
+ }
109
+ function parseManifestRecord(name, value) {
110
+ const record = {};
111
+ if ("color" in value && value.color !== void 0) {
112
+ if (typeof value.color !== "string") throw new Error(`Library manifest "${name}" has a non-string "color".`);
113
+ record.color = value.color;
114
+ }
115
+ if ("emoji" in value && value.emoji !== void 0) {
116
+ if (typeof value.emoji !== "string") throw new Error(`Library manifest "${name}" has a non-string "emoji".`);
117
+ record.emoji = value.emoji;
118
+ }
119
+ return record;
120
+ }
121
+ /**
122
+ * Parse and structurally validate a library file's JSON text. Throws with a
123
+ * clear message on any malformed part; plugin parameter validation is a
124
+ * separate, plugin-owned step.
125
+ */
126
+ function parseLibrary(json) {
127
+ let value;
128
+ try {
129
+ value = JSON.parse(json);
130
+ } catch (cause) {
131
+ throw new Error(`Library file is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
132
+ }
133
+ if (!isRecord(value)) throw new Error("Library file must contain a JSON object.");
134
+ if (value.version !== 1) throw new Error(`Unsupported library version ${JSON.stringify(value.version)} (expected 1).`);
135
+ if (!isRecord(value.assets)) throw new Error("Library \"assets\" must be an object.");
136
+ if (!isRecord(value.manifests)) throw new Error("Library \"manifests\" must be an object.");
137
+ if (value.folders !== void 0 && (!Array.isArray(value.folders) || value.folders.some((entry) => typeof entry !== "string"))) throw new Error("Library \"folders\" must be an array of strings.");
138
+ const folders = value.folders === void 0 ? [] : value.folders;
139
+ for (const folder of folders) {
140
+ const problem = folderPathError(folder);
141
+ if (problem) throw new Error(`Invalid library folder: ${problem}`);
142
+ }
143
+ const assets = {};
144
+ for (const [id, record] of Object.entries(value.assets)) {
145
+ assertSafeKey("asset", id);
146
+ assets[id] = parseAssetRecord(id, record);
147
+ }
148
+ const manifests = {};
149
+ for (const [name, record] of Object.entries(value.manifests)) {
150
+ assertSafeKey("manifest", name);
151
+ if (!isRecord(record)) throw new Error(`Library manifest "${name}" must be an object.`);
152
+ manifests[name] = parseManifestRecord(name, record);
153
+ }
154
+ return {
155
+ version: 1,
156
+ folders,
157
+ assets,
158
+ manifests
159
+ };
160
+ }
161
+ /** Recursively sort object keys so serialization is order-independent. */
162
+ function sortValue(value) {
163
+ if (Array.isArray(value)) return value.map(sortValue);
164
+ if (typeof value === "object" && value !== null) {
165
+ const sorted = {};
166
+ for (const key of Object.keys(value).sort()) {
167
+ const entry = value[key];
168
+ if (entry !== void 0) sorted[key] = sortValue(entry);
169
+ }
170
+ return sorted;
171
+ }
172
+ return value;
173
+ }
174
+ /**
175
+ * Serialize a library deterministically: recursively sorted keys, tab-indented,
176
+ * trailing newline. Same library → byte-identical file, whatever the in-memory
177
+ * insertion order was.
178
+ */
179
+ function serializeLibrary(library) {
180
+ return `${JSON.stringify(sortValue({
181
+ ...library,
182
+ folders: [...library.folders].sort()
183
+ }), null, " ")}\n`;
184
+ }
185
+ //#endregion
186
+ //#region src/asset-id.ts
187
+ /**
188
+ * Asset ids are the SSOT keys and the generated property names on manifests
189
+ * are stable, human-readable camelCase slugs that must be valid JS
190
+ * identifiers and must not collide with `Manifest` class members.
191
+ */
192
+ /**
193
+ * Names an asset id may never take: current and future members of the runtime
194
+ * `Manifest` class (entries become direct properties on it), plus inherited
195
+ * `Object.prototype` members that would shadow-collide, plus keys that are
196
+ * prototype-pollution hazards when used as record keys (`prototype`;
197
+ * `__proto__` already fails the camelCase pattern).
198
+ */
199
+ const RESERVED_ASSET_IDS = [
200
+ "preload",
201
+ "release",
202
+ "entries",
203
+ "constructor",
204
+ "prototype",
205
+ "hasOwnProperty",
206
+ "isPrototypeOf",
207
+ "propertyIsEnumerable",
208
+ "toLocaleString",
209
+ "toString",
210
+ "valueOf"
211
+ ];
212
+ const ASSET_ID_PATTERN = /^[a-z][A-Za-z0-9]*$/;
213
+ /**
214
+ * Whether `id` is a valid asset id: a camelCase JS identifier (lowercase
215
+ * letter, then letters/digits) that is not a reserved `Manifest` member name.
216
+ */
217
+ function isValidAssetId(id) {
218
+ return ASSET_ID_PATTERN.test(id) && !RESERVED_ASSET_IDS.includes(id);
219
+ }
220
+ /**
221
+ * Why an id is invalid, as a human-readable message — or `null` when valid.
222
+ * Lets the editor and the validator surface the precise reason.
223
+ */
224
+ function assetIdError(id) {
225
+ if (!ASSET_ID_PATTERN.test(id)) return `"${id}" is not a valid asset id — use a camelCase identifier starting with a lowercase letter (e.g. "navigationPanel").`;
226
+ if (RESERVED_ASSET_IDS.includes(id)) return `"${id}" is reserved (it collides with a Manifest member name).`;
227
+ return null;
228
+ }
229
+ /**
230
+ * Manifest names deliberately share the asset-id grammar because each name
231
+ * becomes a generated JavaScript module export.
232
+ */
233
+ function manifestNameError(name) {
234
+ if (!ASSET_ID_PATTERN.test(name)) return `"${name}" is not a valid manifest name — use a camelCase identifier starting with a lowercase letter (e.g. "mainCatalog").`;
235
+ if (RESERVED_ASSET_IDS.includes(name)) return `"${name}" is reserved (it collides with a Manifest member name).`;
236
+ return null;
237
+ }
238
+ //#endregion
239
+ //#region src/manifest-appearance.ts
240
+ /**
241
+ * The fixed color palette. Values are the SSOT stored in the library; names are
242
+ * an ergonomic CLI alias. A Figma-like spread of distinct, saturated hues.
243
+ */
244
+ const MANIFEST_COLORS = [
245
+ {
246
+ name: "slate",
247
+ value: "#64748b"
248
+ },
249
+ {
250
+ name: "red",
251
+ value: "#ef4444"
252
+ },
253
+ {
254
+ name: "orange",
255
+ value: "#f97316"
256
+ },
257
+ {
258
+ name: "amber",
259
+ value: "#f59e0b"
260
+ },
261
+ {
262
+ name: "yellow",
263
+ value: "#eab308"
264
+ },
265
+ {
266
+ name: "lime",
267
+ value: "#84cc16"
268
+ },
269
+ {
270
+ name: "green",
271
+ value: "#22c55e"
272
+ },
273
+ {
274
+ name: "teal",
275
+ value: "#14b8a6"
276
+ },
277
+ {
278
+ name: "cyan",
279
+ value: "#06b6d4"
280
+ },
281
+ {
282
+ name: "blue",
283
+ value: "#3b82f6"
284
+ },
285
+ {
286
+ name: "indigo",
287
+ value: "#6366f1"
288
+ },
289
+ {
290
+ name: "violet",
291
+ value: "#8b5cf6"
292
+ },
293
+ {
294
+ name: "pink",
295
+ value: "#ec4899"
296
+ }
297
+ ];
298
+ /** The fixed emoji set — 63 visually distinct choices (an 8×8 picker grid with a clear cell). */
299
+ const MANIFEST_EMOJIS = [
300
+ "🗂️",
301
+ "🏠",
302
+ "🎯",
303
+ "⭐",
304
+ "🔥",
305
+ "💎",
306
+ "🎨",
307
+ "🧩",
308
+ "🚀",
309
+ "🎵",
310
+ "🏆",
311
+ "⚡",
312
+ "🌟",
313
+ "🍀",
314
+ "🎁",
315
+ "📦",
316
+ "🗺️",
317
+ "🔑",
318
+ "🛒",
319
+ "❤️",
320
+ "🐙",
321
+ "🎲",
322
+ "🌈",
323
+ "🧪",
324
+ "🎪",
325
+ "🎭",
326
+ "🎬",
327
+ "🎤",
328
+ "🎧",
329
+ "🎸",
330
+ "🥁",
331
+ "🔔",
332
+ "💡",
333
+ "🔮",
334
+ "🧭",
335
+ "⏰",
336
+ "📸",
337
+ "🖼️",
338
+ "📚",
339
+ "✏️",
340
+ "🖌️",
341
+ "🧲",
342
+ "⚙️",
343
+ "🛠️",
344
+ "🔧",
345
+ "🧰",
346
+ "🔌",
347
+ "🔋",
348
+ "🌍",
349
+ "🌙",
350
+ "☀️",
351
+ "❄️",
352
+ "🌊",
353
+ "🍄",
354
+ "🌵",
355
+ "🌸",
356
+ "🍎",
357
+ "🍕",
358
+ "🍩",
359
+ "☕",
360
+ "🐳",
361
+ "🦄",
362
+ "🐝"
363
+ ];
364
+ /** Whether `value` is one of the fixed color values (`#rrggbb`, lowercased). */
365
+ function isManifestColor(value) {
366
+ return MANIFEST_COLORS.some((color) => color.value === value);
367
+ }
368
+ /** Whether `emoji` is one of the fixed emojis. */
369
+ function isManifestEmoji(emoji) {
370
+ return MANIFEST_EMOJIS.includes(emoji);
371
+ }
372
+ /**
373
+ * Resolve a CLI-supplied color token (either a palette name like `blue` or a
374
+ * hex value like `#3b82f6`, case-insensitively) to its stored hex value, or
375
+ * `null` when it matches no palette entry.
376
+ */
377
+ function resolveManifestColor(token) {
378
+ const normalized = token.trim().toLowerCase();
379
+ const match = MANIFEST_COLORS.find((color) => color.value === normalized || color.name === normalized);
380
+ return match ? match.value : null;
381
+ }
382
+ /** Why a color value is invalid, as a human-readable message — or `null` when valid. */
383
+ function manifestColorError(value) {
384
+ if (isManifestColor(value)) return null;
385
+ return `"${value}" is not a selectable manifest color — choose one of: ${MANIFEST_COLORS.map((color) => `${color.name} (${color.value})`).join(", ")}.`;
386
+ }
387
+ /** Why an emoji is invalid, as a human-readable message — or `null` when valid. */
388
+ function manifestEmojiError(emoji) {
389
+ if (isManifestEmoji(emoji)) return null;
390
+ return `"${emoji}" is not a selectable manifest emoji — choose one of: ${MANIFEST_EMOJIS.join(" ")}.`;
391
+ }
392
+ //#endregion
393
+ //#region src/plugins/audio.ts
394
+ /** Built-in presets, always available; host config merges over them. */
395
+ const AUDIO_BUILT_IN_PRESETS = { default: {
396
+ codec: "opus",
397
+ bitrateKbps: 96,
398
+ container: "webm"
399
+ } };
400
+ /** Each codec's muxable container — a mismatch is a config error, not an ffmpeg one. */
401
+ const CONTAINER_BY_CODEC = {
402
+ opus: "webm",
403
+ aac: "m4a"
404
+ };
405
+ function validateAudioPreset(name, value) {
406
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`Audio preset "${name}" must be an object.`);
407
+ const record = value;
408
+ if (record.codec !== "opus" && record.codec !== "aac") throw new Error(`Audio preset "${name}" needs a "codec" of 'opus' | 'aac'.`);
409
+ if (typeof record.bitrateKbps !== "number" || !Number.isInteger(record.bitrateKbps) || record.bitrateKbps <= 0) throw new Error(`Audio preset "${name}" needs a positive integer "bitrateKbps".`);
410
+ const expectedContainer = CONTAINER_BY_CODEC[record.codec];
411
+ if (record.container !== expectedContainer) throw new Error(`Audio preset "${name}": codec '${record.codec}' requires container '${expectedContainer}'.`);
412
+ return {
413
+ codec: record.codec,
414
+ bitrateKbps: record.bitrateKbps,
415
+ container: expectedContainer
416
+ };
417
+ }
418
+ const AUDIO_EXTENSIONS = [
419
+ "mp3",
420
+ "wav",
421
+ "m4a",
422
+ "ogg",
423
+ "flac"
424
+ ];
425
+ const audioPlugin = {
426
+ type: "audio",
427
+ accepts: [...AUDIO_EXTENSIONS],
428
+ defaultParameters: () => ({ preset: "default" }),
429
+ validateParameters(value) {
430
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Audio parameters must be an object.");
431
+ const record = value;
432
+ if (typeof record.preset !== "string" || record.preset.length === 0) throw new Error("Audio parameters need a non-empty string \"preset\".");
433
+ return { preset: record.preset };
434
+ }
435
+ };
436
+ //#endregion
437
+ //#region src/chroma-key.ts
438
+ /** The upstream shader's property defaults — a freshly ingested asset starts here. */
439
+ const DEFAULT_CHROMA_KEY_PARAMETERS = {
440
+ keyColor: [
441
+ 0,
442
+ 1,
443
+ 0
444
+ ],
445
+ colorCutoff: .2,
446
+ colorFeathering: .33,
447
+ maskFeathering: 1,
448
+ sharpening: .5,
449
+ despill: 1,
450
+ despillLuminanceAdd: .2
451
+ };
452
+ const SCALAR_KEYS = [
453
+ "colorCutoff",
454
+ "colorFeathering",
455
+ "maskFeathering",
456
+ "sharpening",
457
+ "despill",
458
+ "despillLuminanceAdd"
459
+ ];
460
+ function clampUnit(value) {
461
+ return Math.min(1, Math.max(0, value));
462
+ }
463
+ /**
464
+ * Validate an unknown chroma-key blob: every field required, every channel and
465
+ * scalar a finite number. Values are clamped into 0–1 (slider float artifacts
466
+ * must not invalidate a library), non-numbers throw.
467
+ */
468
+ function validateChromaKeyParameters(value) {
469
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("\"chromaKey\" must be an object.");
470
+ const record = value;
471
+ const keyColor = record.keyColor;
472
+ if (!Array.isArray(keyColor) || keyColor.length !== 3 || keyColor.some((channel) => typeof channel !== "number" || !Number.isFinite(channel))) throw new Error("\"chromaKey.keyColor\" must be an array of three numbers (RGB, 0–1 each).");
473
+ const result = { keyColor: [
474
+ clampUnit(keyColor[0]),
475
+ clampUnit(keyColor[1]),
476
+ clampUnit(keyColor[2])
477
+ ] };
478
+ for (const key of SCALAR_KEYS) {
479
+ const scalar = record[key];
480
+ if (typeof scalar !== "number" || !Number.isFinite(scalar)) throw new Error(`"chromaKey.${key}" must be a number (0–1).`);
481
+ result[key] = clampUnit(scalar);
482
+ }
483
+ return result;
484
+ }
485
+ //#endregion
486
+ //#region src/plugins/video.ts
487
+ /**
488
+ * Built-in presets, always available; host config merges over them. The
489
+ * default is the proven committed-video formula: H.264, slow preset, animation
490
+ * tune, CRF 28, yuv420p, faststart MP4.
491
+ */
492
+ const VIDEO_BUILT_IN_PRESETS = { default: {
493
+ codec: "h264",
494
+ crf: 28,
495
+ encoderPreset: "slow",
496
+ tune: "animation",
497
+ pixelFormat: "yuv420p",
498
+ container: "mp4"
499
+ } };
500
+ function validateVideoPreset(name, value) {
501
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`Video preset "${name}" must be an object.`);
502
+ const record = value;
503
+ if (record.codec !== "h264") throw new Error(`Video preset "${name}" needs a "codec" of 'h264'.`);
504
+ if (typeof record.crf !== "number" || !Number.isInteger(record.crf) || record.crf < 0) throw new Error(`Video preset "${name}" needs an integer "crf" ≥ 0.`);
505
+ if (typeof record.encoderPreset !== "string" || record.encoderPreset.length === 0) throw new Error(`Video preset "${name}" needs a non-empty string "encoderPreset".`);
506
+ if (record.tune !== void 0 && (typeof record.tune !== "string" || record.tune.length === 0)) throw new Error(`Video preset "${name}" has a non-string "tune".`);
507
+ if (typeof record.pixelFormat !== "string" || record.pixelFormat.length === 0) throw new Error(`Video preset "${name}" needs a non-empty string "pixelFormat".`);
508
+ if (record.container !== "mp4") throw new Error(`Video preset "${name}" needs a "container" of 'mp4'.`);
509
+ return {
510
+ codec: "h264",
511
+ crf: record.crf,
512
+ encoderPreset: record.encoderPreset,
513
+ ...record.tune !== void 0 ? { tune: record.tune } : {},
514
+ pixelFormat: record.pixelFormat,
515
+ container: "mp4"
516
+ };
517
+ }
518
+ const VIDEO_EXTENSIONS = [
519
+ "mp4",
520
+ "mov",
521
+ "webm",
522
+ "m4v"
523
+ ];
524
+ /** Shared by the video and chroma-key-video plugins (identical transcode inputs). */
525
+ function validateVideoParameters(label, value) {
526
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} parameters must be an object.`);
527
+ const record = value;
528
+ if (typeof record.preset !== "string" || record.preset.length === 0) throw new Error(`${label} parameters need a non-empty string "preset".`);
529
+ if (record.maxWidth !== void 0) {
530
+ if (typeof record.maxWidth !== "number" || !Number.isInteger(record.maxWidth) || record.maxWidth <= 0) throw new Error(`${label} "maxWidth" must be a positive integer when set.`);
531
+ }
532
+ return {
533
+ preset: record.preset,
534
+ ...record.maxWidth !== void 0 ? { maxWidth: record.maxWidth } : {}
535
+ };
536
+ }
537
+ const videoPlugin = {
538
+ type: "video",
539
+ accepts: [...VIDEO_EXTENSIONS],
540
+ defaultParameters: () => ({ preset: "default" }),
541
+ validateParameters(value) {
542
+ return validateVideoParameters("Video", value);
543
+ }
544
+ };
545
+ //#endregion
546
+ //#region src/plugins/chroma-key-video.ts
547
+ /**
548
+ * Chroma-key-video plugin (core part): a greenscreen video delivered through
549
+ * the video plugin's transcode pipeline, plus the keying data the runtime's
550
+ * WebGL renderer needs — chroma-key parameters and two optional image
551
+ * references: a grayscale shape `mask` multiplied onto the keyed
552
+ * alpha, and a static `fallback` image shown when WebGL2 is unavailable.
553
+ *
554
+ * The keying values and references are entry-only data: they travel into the
555
+ * generated manifest entry but never touch the ffmpeg job, so
556
+ * `processingParameters` narrows the job-hash input to the transcode inputs
557
+ * (preset + maxWidth) — retuning a slider must not re-transcode the proxy.
558
+ */
559
+ function validateOptionalReference(label, value) {
560
+ if (value === void 0) return void 0;
561
+ if (!isAssetReference(value)) throw new Error(`"${label}" must be an asset reference ({ "$asset": "<id>" }).`);
562
+ return { $asset: value.$asset };
563
+ }
564
+ const chromaKeyVideoPlugin = {
565
+ type: "chroma-key-video",
566
+ accepts: [...VIDEO_EXTENSIONS],
567
+ defaultParameters: () => ({
568
+ preset: "default",
569
+ chromaKey: {
570
+ ...DEFAULT_CHROMA_KEY_PARAMETERS,
571
+ keyColor: [...DEFAULT_CHROMA_KEY_PARAMETERS.keyColor]
572
+ }
573
+ }),
574
+ validateParameters(value) {
575
+ const videoParameters = validateVideoParameters("Chroma-key-video", value);
576
+ const record = value;
577
+ const chromaKey = validateChromaKeyParameters(record.chromaKey);
578
+ const mask = validateOptionalReference("mask", record.mask);
579
+ const fallback = validateOptionalReference("fallback", record.fallback);
580
+ return {
581
+ ...videoParameters,
582
+ chromaKey,
583
+ ...mask !== void 0 ? { mask } : {},
584
+ ...fallback !== void 0 ? { fallback } : {}
585
+ };
586
+ },
587
+ collectReferences(parameters) {
588
+ const references = [];
589
+ if (parameters.mask) references.push({
590
+ field: "mask",
591
+ assetId: parameters.mask.$asset,
592
+ expectedTypes: ["image"]
593
+ });
594
+ if (parameters.fallback) references.push({
595
+ field: "fallback",
596
+ assetId: parameters.fallback.$asset,
597
+ expectedTypes: ["image"]
598
+ });
599
+ return references;
600
+ },
601
+ processingParameters(parameters) {
602
+ return {
603
+ preset: parameters.preset,
604
+ ...parameters.maxWidth !== void 0 ? { maxWidth: parameters.maxWidth } : {}
605
+ };
606
+ }
607
+ };
608
+ //#endregion
609
+ //#region src/slice-data.ts
610
+ /**
611
+ * The slice data model keeps the source image separate from slicing metadata
612
+ * (Fundus tracks raw files and
613
+ * proxy dimensions separately), so there is no `SliceSource` here.
614
+ *
615
+ * The geometry is stored as **insets** (border thickness in source pixels), the
616
+ * canonical, render-ready form — equivalent to CSS `border-image-slice`.
617
+ * `slicedAxesFor` is the single source of truth for which axes each mode uses.
618
+ */
619
+ const SLICE_MODES = [
620
+ "nine",
621
+ "three-horizontal",
622
+ "three-vertical",
623
+ "one"
624
+ ];
625
+ /** All-zero insets — the "no overdraw" value. */
626
+ function createZeroInsets() {
627
+ return {
628
+ top: 0,
629
+ right: 0,
630
+ bottom: 0,
631
+ left: 0
632
+ };
633
+ }
634
+ /**
635
+ * Default insets for a freshly ingested image: each border a quarter of the
636
+ * corresponding dimension (at least 1px where the image is large enough),
637
+ * always clamped so a center of at least one source pixel remains on each
638
+ * axis. For an axis too small for insets + a 1px center (width/height ≤ 2)
639
+ * the insets on that axis are 0 — structurally valid slicing for any size ≥ 1.
640
+ */
641
+ function defaultInsetsForSize(width, height) {
642
+ const horizontal = Math.min(Math.max(1, Math.floor(width / 4)), Math.floor((width - 1) / 2));
643
+ const vertical = Math.min(Math.max(1, Math.floor(height / 4)), Math.floor((height - 1) / 2));
644
+ return {
645
+ top: vertical,
646
+ right: horizontal,
647
+ bottom: vertical,
648
+ left: horizontal
649
+ };
650
+ }
651
+ /** Which source-image axes are sliced for a mode. */
652
+ function slicedAxesFor(mode) {
653
+ return {
654
+ horizontal: mode === "nine" || mode === "three-horizontal",
655
+ vertical: mode === "nine" || mode === "three-vertical"
656
+ };
657
+ }
658
+ /** Maximum inset on one side while preserving the opposite inset. */
659
+ function maxInset(insets, side, width, height) {
660
+ if (side === "left") return width - insets.right;
661
+ if (side === "right") return width - insets.left;
662
+ if (side === "top") return height - insets.bottom;
663
+ return height - insets.top;
664
+ }
665
+ /** Maximum overdraw on one side while preserving at least one core source pixel. */
666
+ function maxOverdraw(overdraw, side, width, height) {
667
+ return maxInset(overdraw, side, width, height) - 1;
668
+ }
669
+ //#endregion
670
+ //#region src/plugins/slice.ts
671
+ /** Built-in presets, always available; host config merges over them. */
672
+ const SLICE_BUILT_IN_PRESETS = { default: {
673
+ format: "webp",
674
+ quality: 82
675
+ } };
676
+ function validateInsets(label, value) {
677
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`Slice ${label} must be an object with top/right/bottom/left.`);
678
+ const record = value;
679
+ const sides = [
680
+ "top",
681
+ "right",
682
+ "bottom",
683
+ "left"
684
+ ];
685
+ const result = createZeroInsets();
686
+ for (const side of sides) {
687
+ const sideValue = record[side];
688
+ if (typeof sideValue !== "number" || !Number.isInteger(sideValue) || sideValue < 0) throw new Error(`Slice ${label}.${side} must be a non-negative integer.`);
689
+ result[side] = sideValue;
690
+ }
691
+ return result;
692
+ }
693
+ const slicePlugin = {
694
+ type: "slice",
695
+ accepts: [...RASTER_IMAGE_EXTENSIONS],
696
+ defaultParameters: () => ({
697
+ mode: "nine",
698
+ insets: createZeroInsets(),
699
+ pixelRatio: 1,
700
+ preset: "default"
701
+ }),
702
+ validateParameters(value) {
703
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Slice parameters must be an object.");
704
+ const record = value;
705
+ if (typeof record.mode !== "string" || !SLICE_MODES.includes(record.mode)) throw new Error(`Slice "mode" must be one of ${SLICE_MODES.map((mode) => `'${mode}'`).join(" | ")}.`);
706
+ const insets = validateInsets("insets", record.insets);
707
+ if (typeof record.pixelRatio !== "number" || !Number.isInteger(record.pixelRatio) || record.pixelRatio < 1) throw new Error("Slice \"pixelRatio\" must be an integer ≥ 1.");
708
+ const overdraw = record.overdraw === void 0 ? void 0 : validateInsets("overdraw", record.overdraw);
709
+ if (typeof record.preset !== "string" || record.preset.length === 0) throw new Error("Slice parameters need a non-empty string \"preset\".");
710
+ return {
711
+ mode: record.mode,
712
+ insets,
713
+ pixelRatio: record.pixelRatio,
714
+ ...overdraw !== void 0 ? { overdraw } : {},
715
+ preset: record.preset
716
+ };
717
+ }
718
+ };
719
+ //#endregion
720
+ //#region src/ingest.ts
721
+ /**
722
+ * Ingestion helpers shared by every ingest surface: the editor's
723
+ * drag & drop dialog and the CLI's `asset ingest` command both resolve a
724
+ * dropped file name to its candidate asset types and propose an asset id the
725
+ * same way — one implementation, in core.
726
+ */
727
+ /** The built-in descriptors, in registration order (image before slice, video before chroma-key-video). */
728
+ const BUILT_IN_DESCRIPTORS = [
729
+ imagePlugin,
730
+ slicePlugin,
731
+ videoPlugin,
732
+ chromaKeyVideoPlugin,
733
+ audioPlugin
734
+ ];
735
+ /** Unique HTML file-picker accept extensions derived from descriptor claims. */
736
+ function acceptedExtensions(descriptors = BUILT_IN_DESCRIPTORS) {
737
+ return [...new Set(descriptors.flatMap((descriptor) => descriptor.accepts))].sort().map((extension) => `.${extension}`);
738
+ }
739
+ /**
740
+ * Types an ingested file of this extension could become — derived from the
741
+ * descriptors' `accepts` claims. Raster images and video containers are
742
+ * ambiguous (image/slice, video/chroma-key-video) — the editor dialog prompts
743
+ * once, the CLI requires `--type`; audio is unambiguous.
744
+ */
745
+ function typesForFileName(fileName, descriptors = BUILT_IN_DESCRIPTORS) {
746
+ const extension = fileName.slice(fileName.lastIndexOf(".") + 1).toLowerCase();
747
+ return descriptors.filter((descriptor) => descriptor.accepts.includes(extension)).map((descriptor) => descriptor.type);
748
+ }
749
+ /**
750
+ * Propose a camelCase asset id from a file name: split on non-alphanumeric
751
+ * runs and case boundaries, drop a `@2x`-style scale suffix, lowercase the
752
+ * first word, capitalize the rest, strip leading digits.
753
+ */
754
+ function proposeAssetId(fileName) {
755
+ const words = fileName.replace(/\.[^.]+$/, "").replace(/(?:@|[-_])\dx$/i, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
756
+ if (words.length === 0) return "asset";
757
+ const withoutLeadingDigits = words.map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("").replace(/^[0-9]+/, "");
758
+ if (!withoutLeadingDigits) return "asset";
759
+ return withoutLeadingDigits.charAt(0).toLowerCase() + withoutLeadingDigits.slice(1);
760
+ }
761
+ //#endregion
762
+ //#region src/canonicalize.ts
763
+ /**
764
+ * Stable JSON for hash inputs: recursively sorted keys, no whitespace,
765
+ * `undefined` object values dropped. Two parameter objects that differ only in
766
+ * key order (or in explicitly-undefined optionals) canonicalize identically,
767
+ * so the derived job hash — and thus the proxy file name — stays stable.
768
+ */
769
+ function canonicalizeParameters(parameters) {
770
+ return stringify(parameters);
771
+ }
772
+ /** Deterministic UTF-16 code-unit ordering, independent of host locale. */
773
+ function compareCodeUnits(left, right) {
774
+ return left < right ? -1 : left > right ? 1 : 0;
775
+ }
776
+ function stringify(value) {
777
+ if (value === void 0) return "null";
778
+ if (value === null || typeof value === "number" || typeof value === "boolean") return JSON.stringify(value);
779
+ if (typeof value === "string") return JSON.stringify(value);
780
+ if (Array.isArray(value)) return `[${value.map(stringify).join(",")}]`;
781
+ if (typeof value === "object") {
782
+ const record = value;
783
+ const parts = [];
784
+ for (const key of Object.keys(record).sort()) {
785
+ if (record[key] === void 0) continue;
786
+ parts.push(`${JSON.stringify(key)}:${stringify(record[key])}`);
787
+ }
788
+ return `{${parts.join(",")}}`;
789
+ }
790
+ throw new Error(`Cannot canonicalize a ${typeof value} value.`);
791
+ }
792
+ //#endregion
793
+ //#region src/config.ts
794
+ /** Identity helper so the host config file gets full typing + completion. */
795
+ function defineConfig(config) {
796
+ return config;
797
+ }
798
+ //#endregion
799
+ //#region src/reference-graph.ts
800
+ /**
801
+ * Ask each asset's owning plugin for the references in its parameters. Assets
802
+ * whose type is unknown or whose parameters do not validate contribute no
803
+ * references — those problems are reported by the regular validation pass.
804
+ */
805
+ function collectAllReferences(library, plugins) {
806
+ const referencesByAsset = {};
807
+ for (const [assetId, record] of Object.entries(library.assets)) {
808
+ referencesByAsset[assetId] = [];
809
+ const plugin = plugins[record.type];
810
+ if (!plugin?.collectReferences) continue;
811
+ try {
812
+ referencesByAsset[assetId] = plugin.collectReferences(plugin.validateParameters(record.parameters));
813
+ } catch {}
814
+ }
815
+ return referencesByAsset;
816
+ }
817
+ /**
818
+ * Reference *errors* (they block build/codegen): a reference to an unknown
819
+ * asset, a target whose type is not among the field's expected types, and
820
+ * cycles (composition is acyclic by nature).
821
+ */
822
+ function referenceErrors(library, referencesByAsset) {
823
+ const issues = [];
824
+ for (const [assetId, references] of Object.entries(referencesByAsset)) for (const reference of references) {
825
+ if (!Object.hasOwn(library.assets, reference.assetId)) {
826
+ issues.push({
827
+ assetId,
828
+ message: `Reference "${reference.field}" points at unknown asset "${reference.assetId}".`
829
+ });
830
+ continue;
831
+ }
832
+ const targetType = library.assets[reference.assetId].type;
833
+ if (reference.expectedTypes.length > 0 && !reference.expectedTypes.includes(targetType)) issues.push({
834
+ assetId,
835
+ message: `Reference "${reference.field}" points at "${reference.assetId}" of type "${targetType}" — expected ${reference.expectedTypes.map((type) => `"${type}"`).join(" or ")}.`
836
+ });
837
+ }
838
+ issues.push(...cycleErrors(referencesByAsset));
839
+ return issues;
840
+ }
841
+ /** DFS cycle detection with an explicit stack path; each cycle reported once. */
842
+ function cycleErrors(referencesByAsset) {
843
+ const issues = [];
844
+ const finished = /* @__PURE__ */ new Set();
845
+ const reportedCycles = /* @__PURE__ */ new Set();
846
+ const stack = [];
847
+ const onStack = /* @__PURE__ */ new Set();
848
+ function visit(assetId) {
849
+ if (finished.has(assetId)) return;
850
+ if (onStack.has(assetId)) {
851
+ const cyclePath = [...stack.slice(stack.indexOf(assetId)), assetId];
852
+ const cycleKey = [...new Set(cyclePath)].sort().join("\0");
853
+ if (!reportedCycles.has(cycleKey)) {
854
+ reportedCycles.add(cycleKey);
855
+ issues.push({
856
+ assetId: cyclePath[0],
857
+ message: `Reference cycle: ${cyclePath.map((id) => `"${id}"`).join(" → ")} — references express composition and must stay acyclic.`
858
+ });
859
+ }
860
+ return;
861
+ }
862
+ stack.push(assetId);
863
+ onStack.add(assetId);
864
+ for (const reference of referencesByAsset[assetId] ?? []) if (Object.hasOwn(referencesByAsset, reference.assetId)) visit(reference.assetId);
865
+ stack.pop();
866
+ onStack.delete(assetId);
867
+ finished.add(assetId);
868
+ }
869
+ for (const assetId of Object.keys(referencesByAsset)) visit(assetId);
870
+ return issues;
871
+ }
872
+ /**
873
+ * The transitive reference closure of a set of assets, including the starting
874
+ * set — the assets that must be delivered so the starting set renders as
875
+ * authored. Tolerates cycles and ignores dangling targets (both are reported
876
+ * as errors by `referenceErrors`).
877
+ */
878
+ function referenceClosure(assetIds, referencesByAsset) {
879
+ const closure = /* @__PURE__ */ new Set();
880
+ const pending = [...assetIds];
881
+ while (pending.length > 0) {
882
+ const assetId = pending.pop();
883
+ if (closure.has(assetId) || !Object.hasOwn(referencesByAsset, assetId)) continue;
884
+ closure.add(assetId);
885
+ for (const reference of referencesByAsset[assetId]) pending.push(reference.assetId);
886
+ }
887
+ return closure;
888
+ }
889
+ /**
890
+ * A dependencies-first order over `assetIds` and everything they transitively
891
+ * reference — the build order that lets each entry inline its references from
892
+ * already-built entries. Throws on a cycle (with the cycle path).
893
+ */
894
+ function topologicalReferenceOrder(assetIds, referencesByAsset) {
895
+ const order = [];
896
+ const finished = /* @__PURE__ */ new Set();
897
+ const stack = [];
898
+ const onStack = /* @__PURE__ */ new Set();
899
+ function visit(assetId) {
900
+ if (finished.has(assetId) || !Object.hasOwn(referencesByAsset, assetId)) return;
901
+ if (onStack.has(assetId)) {
902
+ const cyclePath = [...stack.slice(stack.indexOf(assetId)), assetId];
903
+ throw new Error(`Reference cycle: ${cyclePath.map((id) => `"${id}"`).join(" → ")} — cannot order entries.`);
904
+ }
905
+ stack.push(assetId);
906
+ onStack.add(assetId);
907
+ for (const reference of referencesByAsset[assetId]) visit(reference.assetId);
908
+ stack.pop();
909
+ onStack.delete(assetId);
910
+ finished.add(assetId);
911
+ order.push(assetId);
912
+ }
913
+ for (const assetId of assetIds) visit(assetId);
914
+ return order;
915
+ }
916
+ //#endregion
917
+ //#region src/manifest-membership.ts
918
+ /**
919
+ * Compute the full membership picture from a library and its collected
920
+ * references: for each manifest the explicit members, the passive members
921
+ * (`referenceClosure(explicit) − explicit`) with their via-attribution, and
922
+ * the resulting delivery set.
923
+ */
924
+ function computeLibraryMembership(library, referencesByAsset) {
925
+ const manifestNames = new Set(Object.keys(library.manifests));
926
+ for (const record of Object.values(library.assets)) for (const manifestName of record.manifests) manifestNames.add(manifestName);
927
+ const membership = {};
928
+ for (const manifestName of [...manifestNames].sort(compareCodeUnits)) {
929
+ const explicitMemberIds = Object.entries(library.assets).filter(([, record]) => record.manifests.includes(manifestName)).map(([assetId]) => assetId).sort(compareCodeUnits);
930
+ const explicitMemberSet = new Set(explicitMemberIds);
931
+ const deliveredAssetIds = new Set(explicitMemberIds);
932
+ const viaByPassiveAssetId = /* @__PURE__ */ new Map();
933
+ for (const explicitMemberId of explicitMemberIds) for (const reachedAssetId of referenceClosure([explicitMemberId], referencesByAsset)) {
934
+ deliveredAssetIds.add(reachedAssetId);
935
+ if (explicitMemberSet.has(reachedAssetId)) continue;
936
+ const via = viaByPassiveAssetId.get(reachedAssetId) ?? [];
937
+ via.push(explicitMemberId);
938
+ viaByPassiveAssetId.set(reachedAssetId, via);
939
+ }
940
+ membership[manifestName] = {
941
+ explicitMemberIds,
942
+ passiveMembers: [...viaByPassiveAssetId.entries()].map(([assetId, via]) => ({
943
+ assetId,
944
+ via
945
+ })).sort((a, b) => compareCodeUnits(a.assetId, b.assetId)),
946
+ deliveredAssetIds
947
+ };
948
+ }
949
+ return membership;
950
+ }
951
+ /**
952
+ * Every asset delivered by at least one manifest, explicitly or passively —
953
+ * the processing set (assets outside it are inert). An asset is
954
+ * *unreachable* (nothing delivers it) exactly when it is missing here.
955
+ */
956
+ function allDeliveredAssetIds(membership) {
957
+ const delivered = /* @__PURE__ */ new Set();
958
+ for (const manifestMembership of Object.values(membership)) for (const assetId of manifestMembership.deliveredAssetIds) delivered.add(assetId);
959
+ return delivered;
960
+ }
961
+ //#endregion
962
+ export { AUDIO_BUILT_IN_PRESETS, AUDIO_EXTENSIONS, DEFAULT_CHROMA_KEY_PARAMETERS, FOLDER_SENTINEL, IMAGE_BUILT_IN_PRESETS, MANIFEST_COLORS, MANIFEST_EMOJIS, RASTER_IMAGE_EXTENSIONS, RESERVED_ASSET_IDS, ROOT_FOLDER, SLICE_BUILT_IN_PRESETS, VIDEO_BUILT_IN_PRESETS, VIDEO_EXTENSIONS, acceptedExtensions, allDeliveredAssetIds, assetIdError, audioPlugin, canonicalizeParameters, chromaKeyVideoPlugin, collectAllReferences, compareCodeUnits, computeLibraryMembership, createEmptyLibrary, createZeroInsets, defaultInsetsForSize, defineConfig, folderNameError, folderNameOf, folderOfRawPath, folderPathError, imagePlugin, isAssetReference, isFolderOrDescendant, isManifestColor, isManifestEmoji, isValidAssetId, joinFolderPath, manifestColorError, manifestEmojiError, manifestNameError, maxInset, maxOverdraw, normalizeFolderPath, parentFolderPath, parseLibrary, portableFolderPathKey, proposeAssetId, rawFileNameOfPath, referenceClosure, referenceErrors, replaceFolderPrefix, resolveManifestColor, serializeLibrary, slicePlugin, slicedAxesFor, topologicalReferenceOrder, typesForFileName, validateAudioPreset, validateChromaKeyParameters, validateImagePreset, validateVideoParameters, validateVideoPreset, videoPlugin };