@forgeax/engine-import 0.1.3 → 0.1.4

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 (76) hide show
  1. package/README.md +6 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
  4. package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
  5. package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +2 -0
  6. package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +1 -0
  7. package/dist/browser.d.ts +5 -0
  8. package/dist/browser.d.ts.map +1 -0
  9. package/dist/browser.mjs +782 -0
  10. package/dist/browser.mjs.map +1 -0
  11. package/dist/build-production.d.ts +48 -0
  12. package/dist/build-production.d.ts.map +1 -0
  13. package/dist/catalog-importer-policy.d.ts +13 -0
  14. package/dist/catalog-importer-policy.d.ts.map +1 -0
  15. package/dist/catalog-inventory.d.ts +4 -0
  16. package/dist/catalog-inventory.d.ts.map +1 -0
  17. package/dist/import-product.d.ts +16 -25
  18. package/dist/import-product.d.ts.map +1 -1
  19. package/dist/import-runner.d.ts +6 -3
  20. package/dist/import-runner.d.ts.map +1 -1
  21. package/dist/importer-registry.d.ts +9 -1
  22. package/dist/importer-registry.d.ts.map +1 -1
  23. package/dist/index.d.ts +11 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.mjs +1315 -155
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/mesh-bin.d.ts +1 -1
  28. package/dist/mesh-bin.d.ts.map +1 -1
  29. package/dist/mesh-bin.mjs +182 -0
  30. package/dist/mesh-bin.mjs.map +1 -0
  31. package/dist/pack-projection.d.ts +5 -0
  32. package/dist/pack-projection.d.ts.map +1 -0
  33. package/dist/scriptable-pack-host.d.ts +64 -0
  34. package/dist/scriptable-pack-host.d.ts.map +1 -0
  35. package/dist/scriptable-pack-output-producers.d.ts +3 -2
  36. package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
  37. package/dist/scriptable-pack-staged-snapshot.d.ts +1 -2
  38. package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -1
  39. package/dist/scriptable-pack.d.ts +6 -18
  40. package/dist/scriptable-pack.d.ts.map +1 -1
  41. package/dist/scriptable-source-package.d.ts +14 -0
  42. package/dist/scriptable-source-package.d.ts.map +1 -0
  43. package/dist/source-package-errors.d.ts +52 -0
  44. package/dist/source-package-errors.d.ts.map +1 -0
  45. package/dist/source-package-publication.d.ts +63 -0
  46. package/dist/source-package-publication.d.ts.map +1 -0
  47. package/dist/source-package.d.ts +60 -0
  48. package/dist/source-package.d.ts.map +1 -0
  49. package/package.json +13 -7
  50. package/src/__tests__/import-contract-migration.test.ts +33 -0
  51. package/src/__tests__/import-local-artifacts.test.ts +3 -1
  52. package/src/__tests__/owner-chain.integration.test.ts +37 -0
  53. package/src/__tests__/scriptable-pack-product.contract.test.ts +47 -0
  54. package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +78 -19
  55. package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +52 -20
  56. package/src/browser.ts +14 -0
  57. package/src/build-production.ts +487 -0
  58. package/src/catalog-importer-policy.ts +58 -0
  59. package/src/catalog-inventory.ts +24 -0
  60. package/src/import-product.ts +37 -69
  61. package/src/import-runner.ts +36 -7
  62. package/src/importer-registry.ts +24 -1
  63. package/src/index.ts +79 -8
  64. package/src/mesh-bin.ts +2 -2
  65. package/src/pack-projection.ts +21 -0
  66. package/src/scriptable-pack-host.ts +461 -0
  67. package/src/scriptable-pack-output-producers.ts +48 -58
  68. package/src/scriptable-pack-staged-snapshot.ts +17 -6
  69. package/src/scriptable-pack.ts +37 -26
  70. package/src/scriptable-source-package.ts +88 -0
  71. package/src/source-package-errors.ts +201 -0
  72. package/src/source-package-publication.ts +295 -0
  73. package/src/source-package.ts +187 -0
  74. package/dist/__tests__/material-import-product.unit.test.d.ts +0 -2
  75. package/dist/__tests__/material-import-product.unit.test.d.ts.map +0 -1
  76. package/src/__tests__/material-import-product.unit.test.ts +0 -56
@@ -0,0 +1,782 @@
1
+ import { validateSourceOverrideMap, ImportError, IMPORT_ERROR_HINTS, canonicalizeSourceOverrides, err, ok } from '@forgeax/engine-types';
2
+ export { IMPORT_ERROR_HINTS, ImportError } from '@forgeax/engine-types';
3
+ import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
4
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
5
+ import { MESH_BIN_HEADER_V4_BYTES, writeMeshBinHeader } from '@forgeax/engine-pack/mesh-bin-contract';
6
+
7
+ // src/browser.ts
8
+ function invalidProduct(field) {
9
+ return err({
10
+ code: "import-product-invalid",
11
+ expected: "a complete terminal import product with source identity",
12
+ hint: "preserve refs, artifacts, receipts, diagnostics, and source revision at the product boundary",
13
+ detail: { field }
14
+ });
15
+ }
16
+ function createImportProduct(input) {
17
+ if (!Array.isArray(input.assets)) return invalidProduct("assets");
18
+ if (!Array.isArray(input.sourceDependencies)) return invalidProduct("sourceDependencies");
19
+ if (input.sourceRevision.trim().length === 0) return invalidProduct("sourceRevision");
20
+ if (!Array.isArray(input.refs)) return invalidProduct("refs");
21
+ if (input.artifacts === null || typeof input.artifacts !== "object") {
22
+ return invalidProduct("artifacts");
23
+ }
24
+ if (!Array.isArray(input.receipts)) return invalidProduct("receipts");
25
+ if (!Array.isArray(input.diagnostics)) return invalidProduct("diagnostics");
26
+ return ok({ ...input });
27
+ }
28
+ async function sha256Hex(bytes) {
29
+ const subtle = globalThis.crypto?.subtle;
30
+ if (subtle === void 0) throw new Error("Web Crypto API is required for importer digests");
31
+ const owned = new Uint8Array(bytes.byteLength);
32
+ owned.set(bytes);
33
+ const digest = await subtle.digest("SHA-256", owned.buffer);
34
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
35
+ }
36
+ async function artifactDigest(bytes) {
37
+ return `sha256:${await sha256Hex(bytes)}`;
38
+ }
39
+ function concatBytes(chunks) {
40
+ const encoder = new TextEncoder();
41
+ const encoded = chunks.map(
42
+ (chunk) => typeof chunk === "string" ? encoder.encode(chunk) : chunk
43
+ );
44
+ const totalLength = encoded.reduce((total, chunk) => total + chunk.byteLength, 0);
45
+ const bytes = new Uint8Array(totalLength);
46
+ let offset = 0;
47
+ for (const chunk of encoded) {
48
+ bytes.set(chunk, offset);
49
+ offset += chunk.byteLength;
50
+ }
51
+ return bytes;
52
+ }
53
+ async function productDigest(asset, artifacts) {
54
+ const chunks = [
55
+ JSON.stringify(projectImportedAssetPayload(asset)),
56
+ JSON.stringify(asset.refs.map((ref) => ref.guid)),
57
+ JSON.stringify(artifacts)
58
+ ];
59
+ for (const [key, artifact] of Object.entries(asset.artifacts).sort(
60
+ ([left], [right]) => left.localeCompare(right)
61
+ )) {
62
+ chunks.push(key, artifact.bytes);
63
+ }
64
+ return `sha256:${await sha256Hex(concatBytes(chunks))}`;
65
+ }
66
+ async function artifactDescriptors(artifacts) {
67
+ const entries = [];
68
+ for (const [path, artifact] of Object.entries(artifacts)) {
69
+ entries.push([
70
+ path,
71
+ {
72
+ path,
73
+ mediaType: artifact.mediaType,
74
+ byteLength: artifact.bytes.byteLength,
75
+ integrity: { algorithm: "sha256", digest: await artifactDigest(artifact.bytes) },
76
+ ...artifact.assetCodec === void 0 ? {} : { assetCodec: artifact.assetCodec }
77
+ }
78
+ ]);
79
+ }
80
+ return Object.fromEntries(entries);
81
+ }
82
+ function projectImportedAssetPayload(asset) {
83
+ const payload = asset.payload;
84
+ if (Object.keys(asset.artifacts).length === 0) return payload;
85
+ if (asset.kind === "mesh") return { kind: "mesh" };
86
+ if (asset.kind === "texture" || asset.kind === "equirect") {
87
+ const { data: _runtimeBytes, ...metadata } = payload;
88
+ return metadata;
89
+ }
90
+ return payload;
91
+ }
92
+ function finalizeImportProducts(product, inputFingerprint) {
93
+ return (async () => {
94
+ const products = [];
95
+ for (const asset of product.assets) {
96
+ const artifacts = await artifactDescriptors(asset.artifacts);
97
+ const digest = await productDigest(asset, artifacts);
98
+ products.push({
99
+ guid: asset.guid,
100
+ payload: asset.payload,
101
+ refs: asset.refs.map((ref) => ref.guid),
102
+ artifacts,
103
+ digest,
104
+ receipt: {
105
+ guid: asset.guid,
106
+ origin: "sourceMeta",
107
+ status: "succeeded",
108
+ inputFingerprint,
109
+ outputDigest: digest
110
+ }
111
+ });
112
+ }
113
+ return products;
114
+ })();
115
+ }
116
+
117
+ // src/import-runner.ts
118
+ var SHADER_RESERVED_IMPORTER_KEY = "shader";
119
+ function isModuleLoadFailure(e) {
120
+ if (!(e instanceof Error)) return false;
121
+ const code = e.code;
122
+ if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND" || code === "ERR_DLOPEN_FAILED") {
123
+ return true;
124
+ }
125
+ const msg = e.message;
126
+ return msg.includes("Cannot find module") || msg.includes("native addon") || msg.includes(".node");
127
+ }
128
+ function normaliseForPack(value) {
129
+ if (value === null || value === void 0) return value;
130
+ if (value instanceof Float32Array || value instanceof Float64Array || value instanceof Uint8Array || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array) {
131
+ return Array.from(value);
132
+ }
133
+ if (Array.isArray(value)) {
134
+ return value.map(normaliseForPack);
135
+ }
136
+ if (typeof value === "object") {
137
+ const out = {};
138
+ for (const [k, v] of Object.entries(value)) {
139
+ out[k] = normaliseForPack(v);
140
+ }
141
+ return out;
142
+ }
143
+ return value;
144
+ }
145
+ function declarationFields(declaration) {
146
+ if (declaration === void 0) return {};
147
+ return {
148
+ sourceIndex: declaration.sourceIndex,
149
+ ...declaration.sourceKey !== void 0 ? { sourceKey: declaration.sourceKey } : {},
150
+ ...declaration.relations !== void 0 ? { relations: declaration.relations } : {}
151
+ };
152
+ }
153
+ function joinSiblingPath(sourcePath, uri) {
154
+ const slash = Math.max(sourcePath.lastIndexOf("/"), sourcePath.lastIndexOf("\\"));
155
+ const dir = slash >= 0 ? sourcePath.slice(0, slash + 1) : "";
156
+ return `${dir}${uri}`;
157
+ }
158
+ function normalizeDependencyPath(path) {
159
+ const slash = path.replaceAll("\\", "/");
160
+ const parts = [];
161
+ for (const part of slash.split("/")) {
162
+ if (part === "" || part === ".") continue;
163
+ if (part === "..") {
164
+ parts.pop();
165
+ } else {
166
+ parts.push(part);
167
+ }
168
+ }
169
+ return parts.join("/");
170
+ }
171
+ function errResult(error) {
172
+ return { ok: false, error };
173
+ }
174
+ function sourceKeyActual(value) {
175
+ if (typeof value === "string") return JSON.stringify(value);
176
+ if (value === void 0) return "missing";
177
+ return typeof value;
178
+ }
179
+ function sourceReadFailureReason(error) {
180
+ if (typeof error !== "object" || error === null) {
181
+ return typeof error === "string" ? error : "unknown";
182
+ }
183
+ const metadata = error;
184
+ const token = typeof metadata.code === "string" ? metadata.code : typeof metadata.name === "string" ? metadata.name : void 0;
185
+ switch (token) {
186
+ case "ENOENT":
187
+ case "NotFoundError":
188
+ return "not-found";
189
+ case "EACCES":
190
+ case "EPERM":
191
+ case "PermissionDeniedError":
192
+ case "NotAllowedError":
193
+ case "SecurityError":
194
+ return "permission-denied";
195
+ case "EAGAIN":
196
+ case "EBUSY":
197
+ case "EINTR":
198
+ case "ECONNABORTED":
199
+ case "ECONNREFUSED":
200
+ case "ECONNRESET":
201
+ case "EHOSTUNREACH":
202
+ case "ENETDOWN":
203
+ case "ENETUNREACH":
204
+ case "ETIMEDOUT":
205
+ case "EPIPE":
206
+ case "ABORT_ERR":
207
+ case "AbortError":
208
+ case "TimeoutError":
209
+ return "transient";
210
+ default:
211
+ if (typeof metadata.detail === "object" && metadata.detail !== null) {
212
+ const reason = metadata.detail.reason;
213
+ if (typeof reason === "string" && reason.length > 0) return reason;
214
+ }
215
+ if (typeof metadata.message === "string" && metadata.message.length > 0) {
216
+ return metadata.message;
217
+ }
218
+ return "unknown";
219
+ }
220
+ }
221
+ function validateOutputSourceKeys(meta) {
222
+ if (meta.subAssets.length <= 1) return void 0;
223
+ const diagnostics = [];
224
+ const seen = /* @__PURE__ */ new Map();
225
+ for (const [index, declaration] of meta.subAssets.entries()) {
226
+ const sourcePath = `${meta.source}#subAssets[${index}]`;
227
+ const sourceRange = { start: 0, end: 0, line: 1, column: 1 };
228
+ const sourceKey = declaration.sourceKey;
229
+ if (typeof sourceKey !== "string" || sourceKey.trim().length === 0) {
230
+ diagnostics.push({
231
+ code: "source-key-required",
232
+ severity: "error",
233
+ sourcePath,
234
+ sourceRange,
235
+ rule: "import-output-source-key",
236
+ expected: "a non-empty producer-owned sourceKey",
237
+ actual: sourceKeyActual(sourceKey),
238
+ hint: "publish a stable semantic sourceKey; sourceIndex is only a locator"
239
+ });
240
+ continue;
241
+ }
242
+ const prior = seen.get(sourceKey);
243
+ if (prior !== void 0) {
244
+ diagnostics.push({
245
+ code: "duplicate-source-key",
246
+ severity: "error",
247
+ sourcePath,
248
+ sourceRange,
249
+ rule: "import-output-source-key-unique",
250
+ expected: "sourceKey to be unique within one imported package",
251
+ actual: `${JSON.stringify(sourceKey)} duplicates subAssets[${prior}]`,
252
+ hint: "rename the duplicate semantic output before writing Meta"
253
+ });
254
+ continue;
255
+ }
256
+ seen.set(sourceKey, index);
257
+ }
258
+ if (diagnostics.length === 0) return void 0;
259
+ return new ImportError({
260
+ code: "source-validation-failed",
261
+ expected: "every writable imported output to declare a unique non-empty sourceKey",
262
+ hint: IMPORT_ERROR_HINTS["source-validation-failed"],
263
+ detail: { diagnostics }
264
+ });
265
+ }
266
+ async function runImport(meta, registry, fs) {
267
+ if (meta.importer === SHADER_RESERVED_IMPORTER_KEY) {
268
+ return { ok: true, value: { skipped: "shader" } };
269
+ }
270
+ const sourceKeyError = validateOutputSourceKeys(meta);
271
+ if (sourceKeyError !== void 0) return errResult(sourceKeyError);
272
+ const declaredSourceKeys = meta.subAssets.flatMap(
273
+ (subAsset) => subAsset.sourceKey === void 0 ? [] : [subAsset.sourceKey]
274
+ );
275
+ const sourceOverridesResult = validateSourceOverrideMap(meta.sourceOverrides, declaredSourceKeys);
276
+ if (!sourceOverridesResult.ok) {
277
+ const error = new ImportError({
278
+ code: sourceOverridesResult.error.code,
279
+ expected: sourceOverridesResult.error.expected,
280
+ hint: IMPORT_ERROR_HINTS[sourceOverridesResult.error.code],
281
+ detail: {
282
+ sourceKey: sourceOverridesResult.error.actual,
283
+ declaredSourceKeys,
284
+ reason: sourceOverridesResult.error.hint
285
+ }
286
+ });
287
+ if (sourceOverridesResult.error.actual !== void 0) {
288
+ Object.assign(error, { actual: sourceOverridesResult.error.actual });
289
+ }
290
+ return errResult(error);
291
+ }
292
+ const importer = registry.get(meta.importer);
293
+ if (importer === void 0) {
294
+ return errResult(
295
+ new ImportError({
296
+ code: "importer-not-registered",
297
+ expected: `an importer registered for meta.importer "${meta.importer}"`,
298
+ hint: IMPORT_ERROR_HINTS["importer-not-registered"],
299
+ detail: {
300
+ importer: meta.importer,
301
+ registeredImporters: registry.registeredImporters()
302
+ }
303
+ })
304
+ );
305
+ }
306
+ const dependencies = /* @__PURE__ */ new Set();
307
+ const readSource = async (sourcePath) => {
308
+ dependencies.add(normalizeDependencyPath(sourcePath));
309
+ try {
310
+ return await fs.readSource(sourcePath);
311
+ } catch (error) {
312
+ return { ok: false, error };
313
+ }
314
+ };
315
+ const readSibling = async (uri) => {
316
+ let inner;
317
+ try {
318
+ if (fs.readSibling) {
319
+ dependencies.add(normalizeDependencyPath(joinSiblingPath(meta.source, uri)));
320
+ inner = await fs.readSibling(meta.source, uri);
321
+ } else {
322
+ inner = await readSource(joinSiblingPath(meta.source, uri));
323
+ }
324
+ } catch (error) {
325
+ inner = { ok: false, error };
326
+ }
327
+ if (inner.ok) {
328
+ return { ok: true, value: inner.value };
329
+ }
330
+ return {
331
+ ok: false,
332
+ error: new ImportError({
333
+ code: "source-read-failed",
334
+ expected: `readable sibling file "${uri}" co-located with meta.source "${meta.source}"`,
335
+ hint: IMPORT_ERROR_HINTS["source-read-failed"],
336
+ detail: {
337
+ source: uri,
338
+ reason: sourceReadFailureReason(inner.error)
339
+ }
340
+ })
341
+ };
342
+ };
343
+ const decodeImage = fs.decodeImage ?? (async () => {
344
+ throw new Error(
345
+ "ImportRunnerFs.decodeImage was not provided; gltfImporter texture extraction requires the host (vite-plugin-pack / cli-gltf / test) to bind decodeImage when constructing the ImportRunnerFs"
346
+ );
347
+ });
348
+ const canonicalSourceOverrides = canonicalizeSourceOverrides(sourceOverridesResult.value);
349
+ const ctx = {
350
+ source: meta.source,
351
+ readSource: () => readSource(meta.source),
352
+ readSibling,
353
+ decodeImage,
354
+ subAssets: meta.subAssets.map(({ guid, sourceIndex, sourceKey, kind }) => ({
355
+ guid,
356
+ sourceIndex,
357
+ ...sourceKey === void 0 ? {} : { sourceKey },
358
+ kind
359
+ })),
360
+ importSettings: meta.importSettings ?? {},
361
+ ...canonicalSourceOverrides === void 0 ? {} : { sourceOverrides: canonicalSourceOverrides }
362
+ };
363
+ const sourceProbe = await readSource(meta.source);
364
+ if (!sourceProbe.ok) {
365
+ return errResult(
366
+ new ImportError({
367
+ code: "source-read-failed",
368
+ expected: `readable source file at meta.source "${meta.source}"`,
369
+ hint: IMPORT_ERROR_HINTS["source-read-failed"],
370
+ detail: {
371
+ source: meta.source,
372
+ reason: sourceReadFailureReason(sourceProbe.error)
373
+ }
374
+ })
375
+ );
376
+ }
377
+ let product;
378
+ try {
379
+ const imported = await importer.import(ctx);
380
+ if (imported === void 0 || imported === null || typeof imported !== "object" || !("ok" in imported)) {
381
+ throw new Error(
382
+ "importer returned a legacy or malformed result; expected ImportResult<ImportProduct>"
383
+ );
384
+ } else {
385
+ if (!imported.ok) {
386
+ if (imported.error === void 0)
387
+ throw new Error("importer returned an invalid failure result");
388
+ if (imported.error instanceof ImportError) return errResult(imported.error);
389
+ return errResult(
390
+ new ImportError({
391
+ code: "import-internal-error",
392
+ expected: `importer "${meta.importer}" to return a structured ImportError`,
393
+ hint: IMPORT_ERROR_HINTS["import-internal-error"],
394
+ detail: { reason: String(imported.error) }
395
+ })
396
+ );
397
+ }
398
+ if (imported.value === void 0)
399
+ throw new Error("importer returned an invalid success result");
400
+ const value = imported.value;
401
+ if (value === void 0 || !Array.isArray(value.assets) || !Array.isArray(value.sourceDependencies) || "artifacts" in value) {
402
+ throw new Error("importer returned an invalid ImportProduct");
403
+ }
404
+ for (const asset of value.assets) {
405
+ if (asset === null || typeof asset !== "object" || !("artifacts" in asset) || asset.artifacts === null || typeof asset.artifacts !== "object" || Array.isArray(asset.artifacts)) {
406
+ throw new Error("importer returned an asset without local artifacts");
407
+ }
408
+ }
409
+ product = value;
410
+ }
411
+ } catch (e) {
412
+ if (e instanceof ImportError) return errResult(e);
413
+ const message = e instanceof Error ? e.message : String(e);
414
+ if (isModuleLoadFailure(e)) {
415
+ return errResult(
416
+ new ImportError({
417
+ code: "import-internal-error",
418
+ expected: `importer module "${meta.importer}" to load (module + native addon present)`,
419
+ hint: IMPORT_ERROR_HINTS["import-internal-error"],
420
+ detail: { loadError: message }
421
+ })
422
+ );
423
+ }
424
+ return errResult(
425
+ new ImportError({
426
+ code: "import-internal-error",
427
+ expected: `importer "${meta.importer}" to convert the source without throwing`,
428
+ hint: IMPORT_ERROR_HINTS["import-internal-error"],
429
+ detail: { reason: message }
430
+ })
431
+ );
432
+ }
433
+ const produced = product.assets;
434
+ const declared = new Set(meta.subAssets.map((s) => s.guid));
435
+ const producedGuids = new Set(produced.map((a) => a.guid));
436
+ const unexpectedGuids = [...producedGuids].filter((g) => !declared.has(g));
437
+ if (unexpectedGuids.length > 0) {
438
+ return errResult(
439
+ new ImportError({
440
+ code: "guid-mismatch",
441
+ expected: "every produced GUID to be declared in meta.subAssets[]",
442
+ hint: IMPORT_ERROR_HINTS["guid-mismatch"],
443
+ detail: { unexpectedGuids }
444
+ })
445
+ );
446
+ }
447
+ const missingGuids = [...declared].filter((g) => !producedGuids.has(g));
448
+ if (produced.length === 0 || missingGuids.length > 0) {
449
+ return errResult(
450
+ new ImportError({
451
+ code: "import-produced-no-assets",
452
+ expected: produced.length === 0 ? "the importer to produce at least one ImportedAsset" : "the produced GUID set to be a superset of meta.subAssets[]",
453
+ hint: IMPORT_ERROR_HINTS["import-produced-no-assets"],
454
+ detail: { missingGuids }
455
+ })
456
+ );
457
+ }
458
+ const declarations = new Map(
459
+ meta.subAssets.map((declaration) => [declaration.guid, declaration])
460
+ );
461
+ const productWithDependencies = {
462
+ ...product,
463
+ sourceDependencies: [...dependencies]
464
+ };
465
+ const inputFingerprint = `source:${[...dependencies].sort().join("|")}`;
466
+ let cookProducts;
467
+ try {
468
+ cookProducts = await finalizeImportProducts(productWithDependencies, inputFingerprint);
469
+ } catch (e) {
470
+ const reason = e instanceof Error ? e.message : String(e);
471
+ return errResult(
472
+ new ImportError({
473
+ code: "import-internal-error",
474
+ expected: `importer "${meta.importer}" finalization to produce complete CookProduct digests`,
475
+ hint: IMPORT_ERROR_HINTS["import-internal-error"],
476
+ detail: { reason: `finalization/digest: ${reason}` }
477
+ })
478
+ );
479
+ }
480
+ const terminalProduct = createImportProduct({
481
+ ...productWithDependencies,
482
+ refs: productWithDependencies.assets.flatMap((asset) => asset.refs),
483
+ artifacts: Object.fromEntries(
484
+ productWithDependencies.assets.flatMap(
485
+ (asset) => Object.entries(asset.artifacts).map(([key, artifact]) => [
486
+ `${asset.guid}/${key}`,
487
+ artifact
488
+ ])
489
+ )
490
+ ),
491
+ receipts: cookProducts.map((product2) => product2.receipt),
492
+ diagnostics: meta.diagnostics ?? [],
493
+ sourceRevision: inputFingerprint
494
+ });
495
+ if (!terminalProduct.ok) {
496
+ return errResult(
497
+ new ImportError({
498
+ code: "import-internal-error",
499
+ expected: "the import product to retain complete terminal producer facts",
500
+ hint: "preserve source identity and producer evidence when returning the import product",
501
+ detail: { reason: terminalProduct.error.detail.field }
502
+ })
503
+ );
504
+ }
505
+ if (meta.buildPack === false) {
506
+ return {
507
+ ok: true,
508
+ value: { product: terminalProduct.value, cookProducts }
509
+ };
510
+ }
511
+ const assets = produced.map((a) => {
512
+ const outputFields = declarationFields(declarations.get(a.guid));
513
+ return {
514
+ guid: a.guid,
515
+ kind: a.kind,
516
+ ...outputFields,
517
+ ...a.name !== void 0 ? { name: a.name } : {},
518
+ // bug-20260610: mesh / scene / animation-clip payloads carry Float32Array
519
+ // / Uint16Array / Uint32Array fields. JSON.stringify on a typed array
520
+ // serialises to `{ "0": v0, "1": v1, ... }` (a plain object), which the
521
+ // runtime mesh / animation loaders reject (`vertexData instanceof
522
+ // Float32Array` and `Array.isArray(vertexData)` both fail). Convert
523
+ // every typed-array field to a plain Array here so the pack is JSON-
524
+ // roundtrip safe end-to-end. This matches the convention every
525
+ // existing pack-fixture test uses (`vertices: Array.from(...)`).
526
+ payload: normaliseForPack(a.payload),
527
+ refs: a.refs.map((r) => r.guid),
528
+ artifacts: a.artifacts
529
+ };
530
+ });
531
+ const pack = {
532
+ schemaVersion: "2.0.0",
533
+ kind: "internal-text-package",
534
+ ...meta.packageId !== void 0 ? { packageId: meta.packageId } : {},
535
+ ...meta.provenance !== void 0 ? { provenance: meta.provenance } : {},
536
+ ...meta.revision !== void 0 ? { revision: meta.revision } : {},
537
+ ...meta.diagnostics !== void 0 ? { diagnostics: meta.diagnostics } : {},
538
+ assets
539
+ };
540
+ return {
541
+ ok: true,
542
+ value: {
543
+ product: terminalProduct.value,
544
+ cookProducts,
545
+ pack
546
+ }
547
+ };
548
+ }
549
+
550
+ // src/importer-registry.ts
551
+ var ImporterRegistry = class {
552
+ importers = /* @__PURE__ */ new Map();
553
+ /**
554
+ * Register an importer for its `importer.key`. Fail-fast on a malformed
555
+ * importer (charter P3); idempotent on a repeated key (last write wins).
556
+ *
557
+ * @param importer the `{ key, import }` object to register.
558
+ * @throws TypeError when `importer.key` is empty or `importer.import` is not
559
+ * a function - a wire-time misconfiguration the host must fix.
560
+ */
561
+ register(importer) {
562
+ if (typeof importer.key !== "string" || importer.key.length === 0) {
563
+ throw new TypeError(
564
+ `ImporterRegistry.register: importer.key must be a non-empty string (got ${JSON.stringify(importer.key)})`
565
+ );
566
+ }
567
+ if (typeof importer.import !== "function") {
568
+ throw new TypeError(
569
+ `ImporterRegistry.register: importer.import must be a function for key "${importer.key}"`
570
+ );
571
+ }
572
+ this.importers.set(importer.key, importer);
573
+ }
574
+ /**
575
+ * Look up the importer registered for `key`. Returns `undefined` when no
576
+ * importer is wired - the import runner maps that to a structured
577
+ * `ImportError(code='importer-not-registered')` with the registered keys in
578
+ * `.detail.registeredImporters` (charter P3).
579
+ */
580
+ get(key) {
581
+ return this.importers.get(key);
582
+ }
583
+ /**
584
+ * The importer keys currently wired, in insertion order. Fed into the
585
+ * `importer-not-registered` error `.detail.registeredImporters` so AI users
586
+ * see exactly what is injectable.
587
+ */
588
+ registeredImporters() {
589
+ return [...this.importers.keys()];
590
+ }
591
+ /** Project the first registered producer capability into the runner context. */
592
+ contextCapabilities() {
593
+ for (const importer of this.importers.values()) {
594
+ const decoder = importer.capabilities?.decodeImage;
595
+ if (decoder !== void 0) return { decodeImage: decoder };
596
+ }
597
+ return {};
598
+ }
599
+ /** Ask the registered producer whether a declaration has a Catalog product. */
600
+ shouldPublishCatalog(input) {
601
+ return this.get(input.importer)?.capabilities?.catalog?.publish?.({
602
+ importSettings: input.importSettings,
603
+ subAssets: input.subAssets
604
+ }) ?? true;
605
+ }
606
+ };
607
+ function failure(sourceKey, expected, actual) {
608
+ return {
609
+ code: "mesh-bin-payload-invalid",
610
+ subject: "mesh-bin",
611
+ sourceKey,
612
+ expected,
613
+ actual,
614
+ recovery: "re-cook the source with its Meta sidecar through the build-time importer"
615
+ };
616
+ }
617
+ function asAttributeMap(value) {
618
+ return value ?? {};
619
+ }
620
+ function jsonValue(value) {
621
+ if (value instanceof Float32Array || value instanceof Uint16Array) return Array.from(value);
622
+ if (Array.isArray(value)) return value.map(jsonValue);
623
+ if (value !== null && typeof value === "object") {
624
+ return Object.fromEntries(
625
+ Object.entries(value).map(([key, nested]) => [key, jsonValue(nested)])
626
+ );
627
+ }
628
+ return value;
629
+ }
630
+ function refsMeta(payload, refs) {
631
+ const materialSlots = (payload.materialSlots ?? [{ slotName: "Default" }]).map(
632
+ (slot, slotIndex) => {
633
+ const defaultMaterial = slot.defaultMaterial;
634
+ let defaultMaterialRef;
635
+ if (defaultMaterial !== void 0) {
636
+ const guid = AssetGuid.format(defaultMaterial);
637
+ defaultMaterialRef = refs.findIndex((candidate) => candidate.toLowerCase() === guid);
638
+ if (defaultMaterialRef < 0) {
639
+ throw new Error(
640
+ `material slot ${slotIndex} default material ${guid} is absent from refs`
641
+ );
642
+ }
643
+ }
644
+ return {
645
+ slotName: slot.slotName,
646
+ ...slot.sourceKey === void 0 ? {} : { sourceKey: slot.sourceKey },
647
+ ...defaultMaterialRef === void 0 ? {} : { defaultMaterialRef }
648
+ };
649
+ }
650
+ );
651
+ return {
652
+ submeshes: payload.submeshes === void 0 || payload.submeshes.length === 0 ? [{ indexOffset: 0, indexCount: payload.indices?.length ?? 0, materialSlot: 0 }] : payload.submeshes,
653
+ materialSlots,
654
+ ...payload.aabb === void 0 ? {} : { aabb: jsonValue(payload.aabb) },
655
+ ...payload.morphTargets === void 0 ? {} : { morphTargets: jsonValue(payload.morphTargets) },
656
+ ...payload.morphWeights === void 0 ? {} : { morphWeights: jsonValue(payload.morphWeights) }
657
+ };
658
+ }
659
+ function packMeshBinV4(payload, sourceKey, refs = []) {
660
+ try {
661
+ const vertices = payload.vertices;
662
+ const indices = payload.indices;
663
+ if (!(vertices instanceof Float32Array)) {
664
+ return err(
665
+ failure(sourceKey, "Float32Array interleaved vertices", "vertices is not Float32Array")
666
+ );
667
+ }
668
+ if (indices !== void 0 && !(indices instanceof Uint16Array || indices instanceof Uint32Array)) {
669
+ return err(
670
+ failure(sourceKey, "Uint16Array or Uint32Array indices", "indices has an unsupported type")
671
+ );
672
+ }
673
+ const attributes = asAttributeMap(payload.attributes);
674
+ const projection = deriveVertexLayoutProjection(attributes);
675
+ if (projection.attributes.length === 0 || projection.arrayStride === 0) {
676
+ return err(
677
+ failure(
678
+ sourceKey,
679
+ "a non-empty canonical geometry projection",
680
+ "projection has no attributes"
681
+ )
682
+ );
683
+ }
684
+ const vertexCount = payload.vertexCount ?? vertices.byteLength / projection.arrayStride;
685
+ if (!Number.isSafeInteger(vertexCount) || vertexCount < 0) {
686
+ return err(
687
+ failure(sourceKey, "a non-negative safe vertex cardinality", `vertexCount=${vertexCount}`)
688
+ );
689
+ }
690
+ if (vertices.byteLength !== vertexCount * projection.arrayStride) {
691
+ return err(
692
+ failure(
693
+ sourceKey,
694
+ `vertices.byteLength=${vertexCount * projection.arrayStride}`,
695
+ `vertices.byteLength=${vertices.byteLength}; stride=${projection.arrayStride}`
696
+ )
697
+ );
698
+ }
699
+ for (const attribute of projection.attributes) {
700
+ const value = attributes[attribute.key];
701
+ const components = attribute.byteLength / (attribute.format === "uint16x4" ? 2 : 4);
702
+ if (value === void 0 || !(value instanceof Float32Array) && !(value instanceof Uint16Array) || value.length !== vertexCount * components) {
703
+ return err(
704
+ failure(
705
+ sourceKey,
706
+ `${attribute.key} cardinality=${vertexCount * components}`,
707
+ `${attribute.key} cardinality=${value?.byteLength ?? "missing"}`
708
+ )
709
+ );
710
+ }
711
+ }
712
+ const interleaved = new Uint8Array(vertexCount * projection.arrayStride);
713
+ const interleavedView = new DataView(interleaved.buffer);
714
+ for (const attribute of projection.attributes) {
715
+ const value = attributes[attribute.key];
716
+ if (value === void 0) continue;
717
+ const components = attribute.byteLength / (attribute.format === "uint16x4" ? 2 : 4);
718
+ for (let vertex = 0; vertex < vertexCount; vertex++) {
719
+ for (let component = 0; component < components; component++) {
720
+ const sourceIndex = vertex * components + component;
721
+ const targetOffset = vertex * projection.arrayStride + attribute.offset + component * (attribute.format === "uint16x4" ? 2 : 4);
722
+ if (attribute.format === "uint16x4") {
723
+ interleavedView.setUint16(targetOffset, value[sourceIndex] ?? 0, true);
724
+ } else {
725
+ interleavedView.setFloat32(
726
+ targetOffset,
727
+ value[sourceIndex] ?? 0,
728
+ true
729
+ );
730
+ }
731
+ }
732
+ }
733
+ }
734
+ const indexCount = indices?.length ?? 0;
735
+ const indexWidth = indices === void 0 || indexCount === 0 ? 0 : indices.BYTES_PER_ELEMENT;
736
+ const indexBytes = indexCount * indexWidth;
737
+ if (!Number.isSafeInteger(indexBytes) || indexBytes > 4294967295) {
738
+ return err(failure(sourceKey, "safe index payload byte length", `indexBytes=${indexBytes}`));
739
+ }
740
+ const meta = new TextEncoder().encode(JSON.stringify(refsMeta(payload, refs)));
741
+ const header = {
742
+ version: 4,
743
+ projectionVersion: projection.schemaVersion,
744
+ mask: projection.mask,
745
+ digest: projection.digest,
746
+ stride: projection.arrayStride,
747
+ vertexCount,
748
+ vertexBytes: interleaved.byteLength,
749
+ indexCount,
750
+ indexWidth,
751
+ indexBytes,
752
+ jsonBytes: meta.byteLength
753
+ };
754
+ const total = MESH_BIN_HEADER_V4_BYTES + interleaved.byteLength + indexBytes + meta.byteLength;
755
+ if (!Number.isSafeInteger(total) || total > 4294967295) {
756
+ return err(failure(sourceKey, "safe mesh binary byte length", `total=${total}`));
757
+ }
758
+ const out = new Uint8Array(total);
759
+ writeMeshBinHeader(header, out);
760
+ let offset = MESH_BIN_HEADER_V4_BYTES;
761
+ out.set(interleaved, offset);
762
+ offset += interleaved.byteLength;
763
+ if (indices !== void 0 && indexBytes > 0) {
764
+ out.set(new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength), offset);
765
+ offset += indexBytes;
766
+ }
767
+ out.set(meta, offset);
768
+ return ok(out);
769
+ } catch (error) {
770
+ return err(
771
+ failure(
772
+ sourceKey,
773
+ "valid canonical mesh payload",
774
+ error instanceof Error ? error.message : String(error)
775
+ )
776
+ );
777
+ }
778
+ }
779
+
780
+ export { ImporterRegistry, SHADER_RESERVED_IMPORTER_KEY, normaliseForPack, packMeshBinV4, runImport };
781
+ //# sourceMappingURL=browser.mjs.map
782
+ //# sourceMappingURL=browser.mjs.map