@forgeax/engine-pack 0.1.20 → 0.1.23

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 (128) hide show
  1. package/README.md +159 -46
  2. package/dist/__tests__/inventory-schema.test.d.ts +2 -0
  3. package/dist/__tests__/inventory-schema.test.d.ts.map +1 -0
  4. package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts +2 -0
  5. package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts +2 -0
  7. package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/parameterized-pack.unit.test.d.ts +2 -0
  9. package/dist/__tests__/parameterized-pack.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/scanner-blacklist.test.d.ts +2 -0
  11. package/dist/__tests__/scanner-blacklist.test.d.ts.map +1 -0
  12. package/dist/build.d.ts +9 -4
  13. package/dist/build.d.ts.map +1 -1
  14. package/dist/build.mjs +3317 -804
  15. package/dist/build.mjs.map +1 -1
  16. package/dist/builtin.mjs +37 -4
  17. package/dist/builtin.mjs.map +1 -1
  18. package/dist/catalog-builder.d.ts.map +1 -1
  19. package/dist/cli-asset.d.ts.map +1 -1
  20. package/dist/cli-asset.mjs +1368 -850
  21. package/dist/cli-asset.mjs.map +1 -1
  22. package/dist/deriveAssetName.d.ts +11 -7
  23. package/dist/deriveAssetName.d.ts.map +1 -1
  24. package/dist/evidence/material-cook.d.ts.map +1 -1
  25. package/dist/evidence/source-inventory.d.ts.map +1 -1
  26. package/dist/guid.d.ts +15 -0
  27. package/dist/guid.d.ts.map +1 -1
  28. package/dist/guid.mjs +53 -5
  29. package/dist/guid.mjs.map +1 -1
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.mjs +1255 -561
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/inventory/binding.d.ts +6 -0
  35. package/dist/inventory/binding.d.ts.map +1 -0
  36. package/dist/inventory/declaration.d.ts +24 -0
  37. package/dist/inventory/declaration.d.ts.map +1 -0
  38. package/dist/inventory/index.d.ts +3 -0
  39. package/dist/inventory/index.d.ts.map +1 -0
  40. package/dist/inventory/sync.d.ts +5 -0
  41. package/dist/inventory/sync.d.ts.map +1 -0
  42. package/dist/material-cook.mjs +3 -1
  43. package/dist/material-cook.mjs.map +1 -1
  44. package/dist/name.mjs +4 -7
  45. package/dist/name.mjs.map +1 -1
  46. package/dist/package-finalizer.d.ts +2 -20
  47. package/dist/package-finalizer.d.ts.map +1 -1
  48. package/dist/parameterized-pack-node.d.ts +26 -0
  49. package/dist/parameterized-pack-node.d.ts.map +1 -0
  50. package/dist/parameterized-pack-node.mjs +11093 -0
  51. package/dist/parameterized-pack-node.mjs.map +1 -0
  52. package/dist/parameterized-pack.d.ts +233 -0
  53. package/dist/parameterized-pack.d.ts.map +1 -0
  54. package/dist/resolve-asset-source.d.ts +1 -3
  55. package/dist/resolve-asset-source.d.ts.map +1 -1
  56. package/dist/resolve-asset-source.mjs +6 -81
  57. package/dist/resolve-asset-source.mjs.map +1 -1
  58. package/dist/runtime.mjs +77 -7
  59. package/dist/runtime.mjs.map +1 -1
  60. package/dist/scanner.d.ts +19 -8
  61. package/dist/scanner.d.ts.map +1 -1
  62. package/dist/scanner.mjs +1169 -690
  63. package/dist/scanner.mjs.map +1 -1
  64. package/dist/schema-compiled.d.ts.map +1 -1
  65. package/dist/schema.mjs +79 -7
  66. package/dist/schema.mjs.map +1 -1
  67. package/dist/scriptable-pack-node.d.ts +8 -32
  68. package/dist/scriptable-pack-node.d.ts.map +1 -1
  69. package/dist/scriptable-pack-node.mjs +576 -1021
  70. package/dist/scriptable-pack-node.mjs.map +1 -1
  71. package/dist/scriptable-pack-worker.mjs +29 -11
  72. package/dist/scriptable-pack-worker.mjs.map +1 -1
  73. package/dist/scriptable-pack.d.ts +19 -532
  74. package/dist/scriptable-pack.d.ts.map +1 -1
  75. package/dist/scriptable-pack.mjs +1042 -527
  76. package/dist/scriptable-pack.mjs.map +1 -1
  77. package/package.json +7 -7
  78. package/schema/meta.schema.json +1 -1
  79. package/schema/pack.schema.json +76 -6
  80. package/src/__tests__/guid-collision.unit.test.ts +18 -9
  81. package/src/__tests__/inventory-schema.test.ts +109 -0
  82. package/src/__tests__/pack.unit.test.ts +1 -238
  83. package/src/__tests__/package-finalizer.contract.test.ts +55 -0
  84. package/src/__tests__/parameterized-pack-authoring-gateway.unit.test.ts +249 -0
  85. package/src/__tests__/parameterized-pack-scanner.unit.test.ts +123 -0
  86. package/src/__tests__/parameterized-pack.unit.test.ts +266 -0
  87. package/src/__tests__/resolve-asset-source.test.ts +15 -98
  88. package/src/__tests__/scanner-blacklist.test.ts +55 -0
  89. package/src/__tests__/scanner-inventory.contract.test.ts +14 -15
  90. package/src/__tests__/scanner-inventory.test.ts +24 -12
  91. package/src/__tests__/scriptable-pack-cli.integration.test.ts +4 -9
  92. package/src/__tests__/topology.unit.test.ts +23 -0
  93. package/src/build.ts +28 -18
  94. package/src/catalog-builder.ts +61 -34
  95. package/src/cli-asset.ts +70 -85
  96. package/src/deriveAssetName.ts +14 -13
  97. package/src/evidence/material-cook.ts +3 -1
  98. package/src/evidence/source-inventory.ts +34 -21
  99. package/src/guid.ts +65 -4
  100. package/src/index.ts +12 -11
  101. package/src/inventory/binding.ts +25 -0
  102. package/src/inventory/declaration.ts +168 -0
  103. package/src/inventory/index.ts +18 -0
  104. package/src/inventory/sync.ts +22 -0
  105. package/src/package-finalizer.ts +2 -42
  106. package/src/parameterized-pack-node.ts +1968 -0
  107. package/src/parameterized-pack.ts +1493 -0
  108. package/src/resolve-asset-source.ts +4 -76
  109. package/src/scanner.ts +241 -59
  110. package/src/schema-compiled.ts +2 -0
  111. package/src/scriptable-pack-node.ts +111 -722
  112. package/src/scriptable-pack-worker.ts +48 -16
  113. package/src/scriptable-pack.ts +27 -939
  114. package/dist/.tsbuildinfo +0 -1
  115. package/dist/__tests__/load-asset-config.test.d.ts +0 -2
  116. package/dist/__tests__/load-asset-config.test.d.ts.map +0 -1
  117. package/dist/__tests__/scriptable-pack.test-d.d.ts +0 -2
  118. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +0 -1
  119. package/dist/__tests__/scriptable-pack.unit.test.d.ts +0 -2
  120. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +0 -1
  121. package/dist/config.d.ts +0 -6
  122. package/dist/config.d.ts.map +0 -1
  123. package/dist/config.mjs +0 -29
  124. package/dist/config.mjs.map +0 -1
  125. package/src/__tests__/load-asset-config.test.ts +0 -121
  126. package/src/__tests__/scriptable-pack.test-d.ts +0 -131
  127. package/src/__tests__/scriptable-pack.unit.test.ts +0 -789
  128. package/src/config.ts +0 -36
@@ -1,12 +1,13 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { existsSync } from 'fs';
3
- import { realpath, readFile, stat, mkdtemp, rm, mkdir, writeFile, rename } from 'fs/promises';
3
+ import { realpath, readFile, stat, mkdtemp, rm } from 'fs/promises';
4
4
  import { tmpdir } from 'os';
5
- import { resolve, dirname, extname, relative, sep, isAbsolute } from 'path';
5
+ import { resolve, dirname, extname, relative, sep } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { Worker } from 'worker_threads';
8
- import { ok, err, AssetError, ImportError } from '@forgeax/engine-types';
8
+ import { err, ok, AssetError, ImportError } from '@forgeax/engine-types';
9
9
  import ts from 'typescript';
10
+ import { sha1 } from '@noble/hashes/legacy.js';
10
11
  import { uuidv7obj } from 'uuidv7';
11
12
 
12
13
  // src/scriptable-pack-node.ts
@@ -26,6 +27,8 @@ var PackError = class extends Error {
26
27
  this.detail = args.detail;
27
28
  }
28
29
  };
30
+
31
+ // src/guid.ts
29
32
  function brand(bytes) {
30
33
  return bytes;
31
34
  }
@@ -35,6 +38,10 @@ function bytesToDashForm(bytes) {
35
38
  return `${h[bytes[0]]}${h[bytes[1]]}${h[bytes[2]]}${h[bytes[3]]}-${h[bytes[4]]}${h[bytes[5]]}-${h[bytes[6]]}${h[bytes[7]]}-${h[bytes[8]]}${h[bytes[9]]}-${h[bytes[10]]}${h[bytes[11]]}${h[bytes[12]]}${h[bytes[13]]}${h[bytes[14]]}${h[bytes[15]]}`;
36
39
  }
37
40
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41
+ var PACK_SOURCE_KEY_RE = /^[a-z0-9][a-z0-9._-]*(\/[a-z0-9][a-z0-9._-]*)*$/;
42
+ function isValidPackSourceKey(value) {
43
+ return typeof value === "string" && PACK_SOURCE_KEY_RE.test(value);
44
+ }
38
45
  function isValidAssetGuidString(value) {
39
46
  return typeof value === "string" && UUID_RE.test(value);
40
47
  }
@@ -46,6 +53,33 @@ function dashFormToBytes(dashForm) {
46
53
  }
47
54
  return bytes;
48
55
  }
56
+ function randomUuidBytes() {
57
+ const uuid = uuidv7obj();
58
+ const bytes = new Uint8Array(16);
59
+ bytes.set(uuid.bytes);
60
+ return bytes;
61
+ }
62
+ function derivedGuid(namespace, sourceKey) {
63
+ if (!(namespace instanceof Uint8Array) || namespace.byteLength !== 16) {
64
+ throw new TypeError("AssetGuid.derive requires a 16-byte PackageId");
65
+ }
66
+ if (!isValidPackSourceKey(sourceKey)) {
67
+ const error = new TypeError(
68
+ `AssetGuid.derive received invalid sourceKey ${JSON.stringify(sourceKey)}`
69
+ );
70
+ Object.defineProperty(error, "code", { value: "pack-source-key-invalid" });
71
+ throw error;
72
+ }
73
+ const name = new TextEncoder().encode(sourceKey);
74
+ const input = new Uint8Array(namespace.byteLength + name.byteLength);
75
+ input.set(namespace, 0);
76
+ input.set(name, namespace.byteLength);
77
+ const digest = sha1(input);
78
+ const result = digest.slice(0, 16);
79
+ result[6] = (result[6] ?? 0) & 15 | 80;
80
+ result[8] = (result[8] ?? 0) & 63 | 128;
81
+ return brand(result);
82
+ }
49
83
  var AssetGuid = {
50
84
  /**
51
85
  * Parse a 36-char RFC 4122 dash-form UUID string into an AssetGuid.
@@ -89,473 +123,515 @@ var AssetGuid = {
89
123
  * Works in both Node.js and browser environments.
90
124
  */
91
125
  random() {
92
- const uuid = uuidv7obj();
93
- const bytes = new Uint8Array(16);
94
- bytes.set(uuid.bytes);
95
- return brand(bytes);
126
+ return brand(randomUuidBytes());
127
+ },
128
+ /** Derive the stable UUIDv5 projection for one Pack subject and sourceKey. */
129
+ derive(namespace, sourceKey) {
130
+ return derivedGuid(namespace, sourceKey);
96
131
  }
97
132
  };
98
- var SCRIPTABLE_PACK_ASSET_KINDS = [
99
- "mesh",
100
- "material",
101
- "scene",
102
- "texture",
103
- "equirect",
104
- "sampler",
105
- "font",
106
- "render-pipeline",
107
- "tileset",
108
- "video",
109
- "skeleton",
110
- "skin",
111
- "animation-clip",
112
- "animation-graph",
113
- "audio",
114
- "particle-effect"
133
+
134
+ // src/parameterized-pack.ts
135
+ var PACK_PARAMETER_TYPES = [
136
+ "bool",
137
+ "u32",
138
+ "i32",
139
+ "f32",
140
+ "f64",
141
+ "string",
142
+ "enum",
143
+ "vec2",
144
+ "vec3",
145
+ "vec4",
146
+ "color",
147
+ "asset-guid"
115
148
  ];
116
- function isScriptablePackAssetKind(value) {
117
- return SCRIPTABLE_PACK_ASSET_KINDS.includes(value);
118
- }
119
- function sceneComponentFieldType(value) {
120
- if (typeof value === "string") return value;
121
- if (isRecord(value) && typeof value.type === "string") return value.type;
122
- return void 0;
123
- }
124
- function projectScriptablePackSceneComponents(components) {
125
- return (components ?? []).map((component) => ({
126
- name: component.name,
127
- fields: Object.fromEntries(
128
- Object.entries(component.fields).map(([fieldName, field]) => [
129
- fieldName,
130
- sceneComponentFieldType(field)
131
- ])
132
- )
133
- }));
134
- }
135
- var SCRIPTABLE_PACK_REQUEST_ID_SCHEMA = {
136
- type: "string",
137
- minLength: 1,
138
- maxLength: 128,
139
- pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$",
140
- description: "Caller-minted identity for one idempotent ScriptablePack operation."
141
- };
142
- var SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA = {
143
- type: "string",
144
- minLength: 8,
145
- pattern: "^[^/].*\\.pack\\.ts$",
146
- description: "Game-root-relative ScriptablePack source path."
147
- };
148
- var SCRIPTABLE_PACK_OWNER_GUID_SCHEMA = {
149
- type: "string",
150
- format: "uuid",
151
- description: "Catalog GUID used to resolve the producer-owned source subject."
152
- };
153
- var SCRIPTABLE_PACK_REVISION_SCHEMA = {
154
- type: "string",
155
- minLength: 64,
156
- maxLength: 64,
157
- pattern: "^[a-f0-9]{64}$",
158
- description: "SHA-256 source revision returned by preflight."
159
- };
160
- var SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA = { type: "string", minLength: 1 };
161
- var SCRIPTABLE_PACK_ASSET_KIND_SCHEMA = {
162
- enum: SCRIPTABLE_PACK_ASSET_KINDS,
163
- description: "Ordinary Asset kind supported by the ScriptablePack producer/loader matrix."
164
- };
165
- var SCRIPTABLE_PACK_NAME_SCHEMA = { type: "string", minLength: 1 };
166
- function scriptablePackArgsSchema(properties, required, options = {}) {
167
- const subjectRequired = ["requestId", ...required];
149
+ function authoringError(code, expected, hint, detail = {}, actual) {
168
150
  return {
169
- type: "object",
170
- properties: {
171
- sourcePath: SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA,
172
- ownerGuid: SCRIPTABLE_PACK_OWNER_GUID_SCHEMA,
173
- requestId: SCRIPTABLE_PACK_REQUEST_ID_SCHEMA,
174
- ...properties
175
- },
176
- required: subjectRequired,
177
- ...options.subject === false ? {} : {
178
- anyOf: [
179
- { required: [...subjectRequired, "sourcePath"] },
180
- { required: [...subjectRequired, "ownerGuid"] },
181
- ...options.guidSubject === false ? [] : [{ required: [...subjectRequired, "guid"] }]
182
- ]
183
- },
184
- additionalProperties: false
151
+ code,
152
+ expected,
153
+ hint,
154
+ ...actual === void 0 ? {} : { actual },
155
+ detail
185
156
  };
186
157
  }
187
- [
188
- {
189
- id: "asset-source.create",
190
- kind: "create",
191
- domain: "session",
192
- title: "Create Asset Source",
193
- argsSchema: scriptablePackArgsSchema(
194
- {
195
- name: SCRIPTABLE_PACK_NAME_SCHEMA,
196
- initialOutput: {
197
- type: "object",
198
- properties: {
199
- sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
200
- kind: SCRIPTABLE_PACK_ASSET_KIND_SCHEMA,
201
- name: SCRIPTABLE_PACK_NAME_SCHEMA
202
- },
203
- required: ["sourceKey", "kind"],
204
- additionalProperties: false
205
- }
206
- },
207
- ["sourcePath", "initialOutput"],
208
- { subject: false }
209
- )
210
- },
211
- {
212
- id: "asset-source.add-output",
213
- kind: "add-output",
214
- domain: "session",
215
- title: "Add Asset Source Output",
216
- argsSchema: scriptablePackArgsSchema(
217
- {
218
- sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
219
- assetKind: SCRIPTABLE_PACK_ASSET_KIND_SCHEMA,
220
- name: SCRIPTABLE_PACK_NAME_SCHEMA,
221
- expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
222
- },
223
- ["sourceKey", "assetKind", "expectedRevision"]
224
- )
225
- },
226
- {
227
- id: "asset-source.add-external-asset",
228
- kind: "add-external-asset",
229
- domain: "session",
230
- title: "Add Asset Source External Asset",
231
- argsSchema: scriptablePackArgsSchema(
232
- {
233
- alias: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
234
- guid: { type: "string", format: "uuid" },
235
- expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
236
- },
237
- ["alias", "guid", "expectedRevision"],
238
- { guidSubject: false }
239
- )
240
- },
241
- {
242
- id: "asset-source.rename",
243
- kind: "rename",
244
- domain: "session",
245
- title: "Rename Asset Source Display Name",
246
- argsSchema: scriptablePackArgsSchema(
247
- {
248
- target: {
249
- oneOf: [
250
- {
251
- type: "object",
252
- properties: { kind: { const: "package" } },
253
- required: ["kind"],
254
- additionalProperties: false
255
- },
256
- {
257
- type: "object",
258
- properties: {
259
- kind: { const: "output" },
260
- sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA
261
- },
262
- required: ["kind", "sourceKey"],
263
- additionalProperties: false
264
- }
265
- ]
266
- },
267
- name: SCRIPTABLE_PACK_NAME_SCHEMA,
268
- expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
269
- },
270
- ["target", "name", "expectedRevision"]
271
- )
272
- },
273
- {
274
- id: "asset-source.remove-output",
275
- kind: "remove-output",
276
- domain: "session",
277
- title: "Remove Asset Source Output",
278
- destructive: true,
279
- argsSchema: scriptablePackArgsSchema(
280
- {
281
- sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
282
- confirmIncomingRefs: { type: "array", items: { type: "string" } },
283
- expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
284
- },
285
- ["sourceKey", "expectedRevision"]
286
- )
287
- },
288
- {
289
- id: "asset-source.clone",
290
- kind: "clone",
291
- domain: "session",
292
- title: "Clone Asset Source",
293
- argsSchema: scriptablePackArgsSchema(
294
- {
295
- targetPath: SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA,
296
- expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
297
- },
298
- ["targetPath", "expectedRevision"]
299
- )
300
- },
301
- {
302
- id: "asset-source.rebuild",
303
- kind: "rebuild",
304
- domain: "session",
305
- title: "Rebuild Asset Source",
306
- argsSchema: scriptablePackArgsSchema({ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA }, [
307
- "expectedRevision"
308
- ])
309
- },
310
- {
311
- id: "asset-source.cold-cook",
312
- kind: "cold-cook",
313
- domain: "session",
314
- title: "Cold Cook Asset Source",
315
- argsSchema: scriptablePackArgsSchema({ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA }, [
316
- "expectedRevision"
317
- ])
318
- }
319
- ];
320
- function actual(value) {
321
- if (value === null) return "null";
322
- if (Array.isArray(value)) return "array";
323
- if (ArrayBuffer.isView(value)) return value.constructor.name;
324
- return typeof value;
325
- }
326
- function invalid(propertyPath, expected, value, sourcePath) {
327
- return err({
328
- code: "pack-source-definition-invalid",
329
- expected,
330
- hint: "repair the default exported ScriptablePack definition, then inspect Meta again",
331
- detail: {
332
- ...sourcePath === void 0 ? {} : { sourcePath },
333
- propertyPath,
334
- actual: actual(value)
335
- }
336
- });
158
+ function failure(error) {
159
+ return err(error);
337
160
  }
338
161
  function isRecord(value) {
339
162
  return value !== null && typeof value === "object" && !Array.isArray(value);
340
163
  }
341
- function isAssetGuid(value) {
164
+ function validateSceneComponents(value) {
165
+ if (value === void 0) return ok(void 0);
166
+ if (!Array.isArray(value)) {
167
+ return failure(
168
+ parameterFailure(
169
+ "$.sceneComponents",
170
+ "an array of component schema records",
171
+ value,
172
+ "sceneComponents is not an array"
173
+ )
174
+ );
175
+ }
176
+ const components = [];
177
+ const names = /* @__PURE__ */ new Set();
178
+ for (const [componentIndex, candidate] of value.entries()) {
179
+ if (!isRecord(candidate) || typeof candidate.name !== "string" || !isRecord(candidate.fields)) {
180
+ return failure(
181
+ parameterFailure(
182
+ `$.sceneComponents[${componentIndex}]`,
183
+ "a component record with name and fields",
184
+ candidate,
185
+ "scene component schema is malformed"
186
+ )
187
+ );
188
+ }
189
+ if (candidate.name.length === 0 || names.has(candidate.name)) {
190
+ return failure(
191
+ parameterFailure(
192
+ `$.sceneComponents[${componentIndex}].name`,
193
+ "a non-empty unique component name",
194
+ candidate.name,
195
+ "scene component names must be unique"
196
+ )
197
+ );
198
+ }
199
+ names.add(candidate.name);
200
+ const fields = {};
201
+ for (const [fieldName, field] of Object.entries(candidate.fields)) {
202
+ if (typeof field === "string" && field.length === 0 || typeof field !== "string" && (!isRecord(field) || typeof field.type !== "string" || field.type.length === 0)) {
203
+ return failure(
204
+ parameterFailure(
205
+ `$.sceneComponents[${componentIndex}].fields.${fieldName}`,
206
+ "a field type string or { type: string }",
207
+ field,
208
+ "scene component field schema is malformed"
209
+ )
210
+ );
211
+ }
212
+ fields[fieldName] = field;
213
+ }
214
+ components.push({ name: candidate.name, fields });
215
+ }
216
+ return ok(components);
217
+ }
218
+ function isPackageId(value) {
342
219
  return value instanceof Uint8Array && value.byteLength === 16;
343
220
  }
344
- function guidKey(guid) {
345
- return AssetGuid.format(guid);
221
+ function cloneValue(value) {
222
+ if (value instanceof Uint8Array) return value.slice();
223
+ if (Array.isArray(value)) return value.map((item) => cloneValue(item));
224
+ return value;
346
225
  }
347
- function freezeDefinition(definition) {
348
- const assets = Object.fromEntries(
349
- Object.entries(definition.assets).map(([sourceKey, descriptor]) => [
350
- sourceKey,
351
- Object.freeze({ ...descriptor, guid: descriptor.guid.slice() })
352
- ])
353
- );
354
- const externalAssets = Object.fromEntries(
355
- Object.entries(definition.externalAssets).map(([alias, guid]) => [
356
- alias,
357
- guid.slice()
358
- ])
359
- );
360
- const sceneComponents = definition.sceneComponents === void 0 ? void 0 : Object.freeze(
361
- projectScriptablePackSceneComponents(definition.sceneComponents).map(
362
- (component) => Object.freeze({
363
- ...component,
364
- fields: Object.freeze({ ...component.fields })
365
- })
366
- )
226
+ function parameterFailure(propertyPath, expected, actual, reason) {
227
+ return authoringError(
228
+ "pack-parameter-invalid",
229
+ expected,
230
+ "repair the parameter declaration or the instance values, then inspect and rebuild",
231
+ { propertyPath, reason, actual: typeof actual === "string" ? actual : JSON.stringify(actual) }
367
232
  );
368
- return Object.freeze({
369
- ...definition,
370
- packageId: definition.packageId.slice(),
371
- assets: Object.freeze(assets),
372
- externalAssets: Object.freeze(externalAssets),
373
- ...sceneComponents === void 0 ? {} : { sceneComponents }
374
- });
375
233
  }
376
- function validateScriptablePackDefinition(value, sourcePath) {
377
- if (!isRecord(value))
378
- return invalid("$", "a ScriptablePack definition object", value, sourcePath);
379
- if (value.schemaVersion !== "1.0.0") {
380
- return invalid("$.schemaVersion", "the literal '1.0.0'", value.schemaVersion, sourcePath);
234
+ function numericType(type) {
235
+ return type === "u32" || type === "i32" || type === "f32" || type === "f64";
236
+ }
237
+ function vectorLength(type) {
238
+ switch (type) {
239
+ case "vec2":
240
+ return 2;
241
+ case "vec3":
242
+ return 3;
243
+ case "vec4":
244
+ case "color":
245
+ return 4;
246
+ default:
247
+ return void 0;
381
248
  }
382
- if (!isAssetGuid(value.packageId)) {
383
- return invalid("$.packageId", "a 16-byte AssetGuid", value.packageId, sourcePath);
249
+ }
250
+ function parameterValueError(parameter, value, propertyPath) {
251
+ const { type } = parameter;
252
+ if (type === "bool" && typeof value !== "boolean") {
253
+ return parameterFailure(propertyPath, "a boolean", value, "bool value has the wrong type");
384
254
  }
385
- if (value.name !== void 0 && typeof value.name !== "string") {
386
- return invalid("$.name", "a string when present", value.name, sourcePath);
255
+ if (numericType(type)) {
256
+ if (typeof value !== "number" || !Number.isFinite(value)) {
257
+ return parameterFailure(
258
+ propertyPath,
259
+ "a finite number",
260
+ value,
261
+ "numeric value is not finite"
262
+ );
263
+ }
264
+ if ((type === "u32" || type === "i32") && !Number.isInteger(value)) {
265
+ return parameterFailure(
266
+ propertyPath,
267
+ "an integer",
268
+ value,
269
+ "integer parameter received a fraction"
270
+ );
271
+ }
272
+ if (type === "u32" && (value < 0 || value > 4294967295)) {
273
+ return parameterFailure(
274
+ propertyPath,
275
+ "an unsigned 32-bit integer",
276
+ value,
277
+ "u32 value is outside [0, 2^32 - 1]"
278
+ );
279
+ }
280
+ if (type === "i32" && (value < -2147483648 || value > 2147483647)) {
281
+ return parameterFailure(
282
+ propertyPath,
283
+ "a signed 32-bit integer",
284
+ value,
285
+ "i32 value is outside [-2^31, 2^31 - 1]"
286
+ );
287
+ }
288
+ if (type === "f32" && !Number.isFinite(Math.fround(value))) {
289
+ return parameterFailure(
290
+ propertyPath,
291
+ "a finite IEEE-754 32-bit float",
292
+ value,
293
+ "f32 value overflows binary32"
294
+ );
295
+ }
296
+ if (parameter.minimum !== void 0 && value < parameter.minimum) {
297
+ return parameterFailure(
298
+ propertyPath,
299
+ `a number >= ${parameter.minimum}`,
300
+ value,
301
+ "value is below minimum"
302
+ );
303
+ }
304
+ if (parameter.maximum !== void 0 && value > parameter.maximum) {
305
+ return parameterFailure(
306
+ propertyPath,
307
+ `a number <= ${parameter.maximum}`,
308
+ value,
309
+ "value is above maximum"
310
+ );
311
+ }
312
+ return void 0;
387
313
  }
388
- if (!isRecord(value.assets) || Object.keys(value.assets).length === 0) {
389
- return invalid(
390
- "$.assets",
391
- "a non-empty sourceKey to descriptor object",
392
- value.assets,
393
- sourcePath
394
- );
314
+ if (type === "string" && typeof value !== "string") {
315
+ return parameterFailure(propertyPath, "a string", value, "string parameter has the wrong type");
395
316
  }
396
- if (!isRecord(value.externalAssets)) {
397
- return invalid(
398
- "$.externalAssets",
399
- "an alias to AssetGuid object",
400
- value.externalAssets,
401
- sourcePath
402
- );
317
+ if (type === "enum") {
318
+ if (typeof value !== "string") {
319
+ return parameterFailure(
320
+ propertyPath,
321
+ "one of the declared enum strings",
322
+ value,
323
+ "enum value has the wrong type"
324
+ );
325
+ }
326
+ if (parameter.values === void 0 || !parameter.values.includes(value)) {
327
+ return parameterFailure(
328
+ propertyPath,
329
+ "one of the declared enum values",
330
+ value,
331
+ "enum value is not declared"
332
+ );
333
+ }
334
+ return void 0;
403
335
  }
404
- if (value.sceneComponents !== void 0) {
405
- if (!Array.isArray(value.sceneComponents)) {
406
- return invalid(
407
- "$.sceneComponents",
408
- "an array of component tokens or neutral schema rows when present",
409
- value.sceneComponents,
410
- sourcePath
336
+ const length = vectorLength(type);
337
+ if (length !== void 0) {
338
+ if (!Array.isArray(value) || value.length !== length || value.some((component) => typeof component !== "number" || !Number.isFinite(component))) {
339
+ return parameterFailure(
340
+ propertyPath,
341
+ `a finite numeric vector with ${length} components`,
342
+ value,
343
+ "vector value has the wrong shape"
411
344
  );
412
345
  }
413
- const componentNames = /* @__PURE__ */ new Set();
414
- for (const [componentIndex, component] of value.sceneComponents.entries()) {
415
- if (!isRecord(component)) {
416
- return invalid(
417
- `$.sceneComponents[${componentIndex}]`,
418
- "a component token or neutral schema row",
419
- component,
420
- sourcePath
421
- );
422
- }
423
- if (typeof component.name !== "string" || component.name.trim().length === 0) {
424
- return invalid(
425
- `$.sceneComponents[${componentIndex}].name`,
426
- "a non-empty component name",
427
- component.name,
428
- sourcePath
429
- );
430
- }
431
- if (componentNames.has(component.name)) {
432
- return invalid(
433
- `$.sceneComponents[${componentIndex}].name`,
434
- "component names to be unique within one ScriptablePack",
435
- component.name,
436
- sourcePath
437
- );
438
- }
439
- componentNames.add(component.name);
440
- if (!isRecord(component.fields)) {
441
- return invalid(
442
- `$.sceneComponents[${componentIndex}].fields`,
443
- "a field-name to schema-type object",
444
- component.fields,
445
- sourcePath
446
- );
447
- }
448
- for (const [fieldName, field] of Object.entries(component.fields)) {
449
- const type = sceneComponentFieldType(field);
450
- if (fieldName.trim().length === 0 || type === void 0 || type.trim().length === 0) {
451
- return invalid(
452
- `$.sceneComponents[${componentIndex}].fields`,
453
- "field names with non-empty string schema types",
454
- field,
455
- sourcePath
456
- );
457
- }
458
- }
346
+ if (type === "color" && value.some((component) => component < 0 || component > 1)) {
347
+ return parameterFailure(
348
+ propertyPath,
349
+ "an RGBA color with components in [0, 1]",
350
+ value,
351
+ "color component is outside [0, 1]"
352
+ );
459
353
  }
354
+ return void 0;
460
355
  }
461
- if (typeof value.build !== "function") {
462
- return invalid("$.build", "a build(assetReader) function", value.build, sourcePath);
356
+ if (type === "asset-guid") {
357
+ const validString = typeof value === "string" && isValidAssetGuidString(value);
358
+ if (!(value instanceof Uint8Array && value.byteLength === 16) && !validString) {
359
+ return parameterFailure(
360
+ propertyPath,
361
+ "a 16-byte AssetGuid or UUID string",
362
+ value,
363
+ "asset GUID value is malformed"
364
+ );
365
+ }
366
+ return void 0;
463
367
  }
464
- const packageGuid = guidKey(value.packageId);
465
- const localGuids = /* @__PURE__ */ new Set();
466
- for (const [sourceKey, descriptor] of Object.entries(value.assets)) {
467
- if (sourceKey.trim().length === 0) {
468
- return invalid("$.assets", "non-empty sourceKey object keys", sourceKey, sourcePath);
368
+ return parameterFailure(
369
+ propertyPath,
370
+ "a supported Pack parameter type",
371
+ value,
372
+ "unknown parameter type"
373
+ );
374
+ }
375
+ function normalizeParameterValue(parameter, value, propertyPath) {
376
+ const error = parameterValueError(parameter, value, propertyPath);
377
+ if (error !== void 0) return failure(error);
378
+ if (parameter.type === "asset-guid" && typeof value === "string") {
379
+ const parsed = AssetGuid.parse(value);
380
+ if (!parsed.ok) {
381
+ return failure(
382
+ parameterFailure(
383
+ propertyPath,
384
+ "a valid AssetGuid UUID string",
385
+ value,
386
+ "asset GUID parser rejected the value"
387
+ )
388
+ );
469
389
  }
470
- if (!isRecord(descriptor)) {
471
- return invalid(
472
- `$.assets[${JSON.stringify(sourceKey)}]`,
473
- "an asset descriptor",
474
- descriptor,
475
- sourcePath
390
+ return ok(parsed.value);
391
+ }
392
+ return ok(cloneValue(value));
393
+ }
394
+ function validateParameterDefinitions(parameters, propertyPath = "$.parameters") {
395
+ if (!Array.isArray(parameters) || parameters.length === 0) {
396
+ return failure(
397
+ parameterFailure(
398
+ propertyPath,
399
+ "a non-empty parameter descriptor array",
400
+ parameters,
401
+ "parameters must be explicit and non-empty"
402
+ )
403
+ );
404
+ }
405
+ const names = /* @__PURE__ */ new Set();
406
+ const normalized = [];
407
+ for (const [index, candidate] of parameters.entries()) {
408
+ const path = `${propertyPath}[${index}]`;
409
+ if (!isRecord(candidate))
410
+ return failure(
411
+ parameterFailure(
412
+ path,
413
+ "a parameter descriptor object",
414
+ candidate,
415
+ "descriptor is not an object"
416
+ )
417
+ );
418
+ const name = candidate.name;
419
+ if (typeof name !== "string" || !/^[A-Za-z][A-Za-z0-9_.-]*$/.test(name)) {
420
+ return failure(
421
+ parameterFailure(
422
+ `${path}.name`,
423
+ "a unique identifier-like parameter name",
424
+ name,
425
+ "parameter name is invalid"
426
+ )
476
427
  );
477
428
  }
478
- if (!isAssetGuid(descriptor.guid)) {
479
- return invalid(
480
- `$.assets[${JSON.stringify(sourceKey)}].guid`,
481
- "a 16-byte AssetGuid",
482
- descriptor.guid,
483
- sourcePath
429
+ if (names.has(name))
430
+ return failure(
431
+ parameterFailure(
432
+ `${path}.name`,
433
+ "a unique parameter name",
434
+ name,
435
+ "parameter name is duplicated"
436
+ )
437
+ );
438
+ names.add(name);
439
+ const unknown = Object.keys(candidate).find(
440
+ (key) => !["name", "type", "default", "minimum", "maximum", "values", "kind"].includes(key)
441
+ );
442
+ if (unknown !== void 0) {
443
+ return failure(
444
+ parameterFailure(
445
+ `${path}.${unknown}`,
446
+ "only name, type, default, minimum, maximum, values, and kind fields",
447
+ candidate[unknown],
448
+ "parameter descriptors are a closed authoring schema"
449
+ )
484
450
  );
485
451
  }
486
- if (typeof descriptor.kind !== "string" || !isScriptablePackAssetKind(descriptor.kind)) {
487
- return invalid(
488
- `$.assets[${JSON.stringify(sourceKey)}].kind`,
489
- "one of SCRIPTABLE_PACK_ASSET_KINDS",
490
- descriptor.kind,
491
- sourcePath
452
+ const type = candidate.type;
453
+ if (typeof type !== "string" || !PACK_PARAMETER_TYPES.includes(type)) {
454
+ return failure(
455
+ parameterFailure(
456
+ `${path}.type`,
457
+ PACK_PARAMETER_TYPES.join(" | "),
458
+ type,
459
+ "parameter type is not supported"
460
+ )
492
461
  );
493
462
  }
494
- if (descriptor.name !== void 0 && typeof descriptor.name !== "string") {
495
- return invalid(
496
- `$.assets[${JSON.stringify(sourceKey)}].name`,
497
- "a string when present",
498
- descriptor.name,
499
- sourcePath
463
+ const typed = {
464
+ ...candidate,
465
+ name,
466
+ type
467
+ };
468
+ if (typed.values !== void 0) {
469
+ if (typed.type !== "enum" || !Array.isArray(typed.values) || typed.values.length === 0 || typed.values.some((item) => typeof item !== "string") || new Set(typed.values).size !== typed.values.length) {
470
+ return failure(
471
+ parameterFailure(
472
+ `${path}.values`,
473
+ "unique non-empty strings for enum",
474
+ typed.values,
475
+ "enum values are malformed"
476
+ )
477
+ );
478
+ }
479
+ } else if (typed.type === "enum") {
480
+ return failure(
481
+ parameterFailure(
482
+ `${path}.values`,
483
+ "a non-empty enum choices array",
484
+ typed.values,
485
+ "enum has no choices"
486
+ )
500
487
  );
501
488
  }
502
- const guid = guidKey(descriptor.guid);
503
- if (guid === packageGuid || localGuids.has(guid)) {
504
- return invalid(
505
- `$.assets[${JSON.stringify(sourceKey)}].guid`,
506
- "a GUID unique within the package and distinct from packageId",
507
- descriptor.guid,
508
- sourcePath
489
+ if (typed.kind !== void 0 && (typed.type !== "asset-guid" || typeof typed.kind !== "string")) {
490
+ return failure(
491
+ parameterFailure(
492
+ `${path}.kind`,
493
+ "a kind constraint only on asset-guid parameters",
494
+ typed.kind,
495
+ "kind is misplaced"
496
+ )
509
497
  );
510
498
  }
511
- localGuids.add(guid);
512
- }
513
- const externalGuids = /* @__PURE__ */ new Set();
514
- for (const [alias, guidValue] of Object.entries(value.externalAssets)) {
515
- if (alias.trim().length === 0) {
516
- return invalid("$.externalAssets", "non-empty alias object keys", alias, sourcePath);
499
+ for (const field of ["minimum", "maximum"]) {
500
+ const bound = typed[field];
501
+ if (bound !== void 0 && (typeof bound !== "number" || !Number.isFinite(bound))) {
502
+ return failure(
503
+ parameterFailure(
504
+ `${path}.${field}`,
505
+ "a finite numeric bound",
506
+ bound,
507
+ "numeric bound is invalid"
508
+ )
509
+ );
510
+ }
517
511
  }
518
- if (!isAssetGuid(guidValue)) {
519
- return invalid(
520
- `$.externalAssets[${JSON.stringify(alias)}]`,
521
- "a 16-byte AssetGuid",
522
- guidValue,
523
- sourcePath
512
+ if (!numericType(typed.type) && (typed.minimum !== void 0 || typed.maximum !== void 0)) {
513
+ return failure(
514
+ parameterFailure(
515
+ path,
516
+ "minimum/maximum only on numeric parameters",
517
+ typed,
518
+ "numeric bounds are not meaningful for this parameter type"
519
+ )
524
520
  );
525
521
  }
526
- const guid = guidKey(guidValue);
527
- if (guid === packageGuid || localGuids.has(guid) || externalGuids.has(guid)) {
528
- return invalid(
529
- `$.externalAssets[${JSON.stringify(alias)}]`,
530
- "a unique foreign GUID not owned by this package",
531
- guidValue,
532
- sourcePath
522
+ if (typed.minimum !== void 0 && typed.maximum !== void 0 && typed.minimum > typed.maximum) {
523
+ return failure(
524
+ parameterFailure(path, "minimum <= maximum", typed, "numeric bounds are reversed")
533
525
  );
534
526
  }
535
- externalGuids.add(guid);
527
+ const defaultResult = normalizeParameterValue(typed, typed.default, `${path}.default`);
528
+ if (!defaultResult.ok) return defaultResult;
529
+ normalized.push({
530
+ ...typed,
531
+ default: defaultResult.value,
532
+ ...typed.values === void 0 ? {} : { values: Object.freeze([...typed.values]) }
533
+ });
536
534
  }
537
- return ok(freezeDefinition(value));
535
+ return ok(Object.freeze(normalized));
538
536
  }
539
- function projectScriptablePackMeta(definition, source) {
540
- const subAssets = Object.entries(definition.assets).sort(([left], [right]) => left.localeCompare(right)).map(([sourceKey, descriptor], sourceIndex) => ({
541
- guid: guidKey(descriptor.guid),
542
- sourceIndex,
543
- sourceKey,
544
- kind: descriptor.kind,
545
- ...descriptor.name === void 0 ? {} : { name: descriptor.name }
546
- }));
547
- const externalAssets = Object.entries(definition.externalAssets).sort(([left], [right]) => left.localeCompare(right)).map(([alias, guid]) => ({ alias, guid: guidKey(guid) }));
548
- return {
549
- schemaVersion: "1.0.0",
550
- kind: "external-asset-package",
551
- packageId: guidKey(definition.packageId),
552
- ...definition.name === void 0 ? {} : { name: definition.name },
553
- importer: "pack-ts",
554
- source,
555
- importSettings: { contractVersion: "1.0.0", externalAssets },
556
- subAssets
557
- };
537
+ function validatePackDefinition(value, sourcePath) {
538
+ if (!isRecord(value))
539
+ return failure(
540
+ parameterFailure("$", "a Pack definition object", value, "definition is not an object")
541
+ );
542
+ const unknown = Object.keys(value).find(
543
+ (key) => !["schemaVersion", "packageId", "name", "parameters", "sceneComponents", "build"].includes(
544
+ key
545
+ )
546
+ );
547
+ if (unknown !== void 0) {
548
+ return failure(
549
+ parameterFailure(
550
+ `$.${unknown}`,
551
+ "only schemaVersion, packageId, name, parameters, sceneComponents, and build fields",
552
+ value[unknown],
553
+ "legacy static output declarations and external asset tables are not part of v2 authoring"
554
+ )
555
+ );
556
+ }
557
+ if (value.schemaVersion !== "2.0.0") {
558
+ return failure(
559
+ parameterFailure(
560
+ "$.schemaVersion",
561
+ "the literal '2.0.0'",
562
+ value.schemaVersion,
563
+ "authoring source schema is unsupported"
564
+ )
565
+ );
566
+ }
567
+ if (!isPackageId(value.packageId)) {
568
+ return failure(
569
+ authoringError(
570
+ "pack-package-id-invalid",
571
+ "a 16-byte PackageId",
572
+ "use definePackageId() for the stable source identity",
573
+ { sourcePath, propertyPath: "$.packageId" },
574
+ typeof value.packageId === "string" ? value.packageId : void 0
575
+ )
576
+ );
577
+ }
578
+ if (value.name !== void 0 && typeof value.name !== "string") {
579
+ return failure(
580
+ parameterFailure("$.name", "a string when present", value.name, "display name is malformed")
581
+ );
582
+ }
583
+ if (typeof value.build !== "function") {
584
+ return failure(
585
+ parameterFailure("$.build", "a build function", value.build, "build is not callable")
586
+ );
587
+ }
588
+ const sceneComponents = validateSceneComponents(value.sceneComponents);
589
+ if (!sceneComponents.ok) return sceneComponents;
590
+ const hasParameters = Object.hasOwn(value, "parameters");
591
+ if (!hasParameters) {
592
+ return ok(
593
+ Object.freeze({
594
+ schemaVersion: "2.0.0",
595
+ packageId: value.packageId,
596
+ ...value.name === void 0 ? {} : { name: value.name },
597
+ ...sceneComponents.value === void 0 ? {} : { sceneComponents: sceneComponents.value },
598
+ build: value.build
599
+ })
600
+ );
601
+ }
602
+ const parameters = validateParameterDefinitions(value.parameters);
603
+ if (!parameters.ok) return parameters;
604
+ return ok(
605
+ Object.freeze({
606
+ schemaVersion: "2.0.0",
607
+ packageId: value.packageId,
608
+ ...value.name === void 0 ? {} : { name: value.name },
609
+ ...sceneComponents.value === void 0 ? {} : { sceneComponents: sceneComponents.value },
610
+ parameters: parameters.value,
611
+ build: value.build
612
+ })
613
+ );
558
614
  }
615
+ var PARAMETERIZED_PACK_OPERATION_IDS = [
616
+ "asset.list",
617
+ "asset.inspect",
618
+ "asset.resolve",
619
+ "asset.verify",
620
+ "asset-source.create",
621
+ "asset-source.clone",
622
+ "asset-source.create-instance",
623
+ "asset-source.apply-values",
624
+ "asset-source.rebuild",
625
+ "asset-source.cold-cook"
626
+ ];
627
+ PARAMETERIZED_PACK_OPERATION_IDS.map(
628
+ (id) => ({
629
+ id,
630
+ domain: id.startsWith("asset-source.") ? "session" : "asset",
631
+ readOnly: id.startsWith("asset."),
632
+ requiresRevision: !id.startsWith("asset.") && id !== "asset-source.create"
633
+ })
634
+ );
559
635
 
560
636
  // src/evidence/source-inventory.ts
561
637
  function stableValue(value) {
@@ -651,50 +727,51 @@ async function inventoryScriptablePackSource(sourcePath, initialSourceText) {
651
727
  }
652
728
  function serializeFailure(value) {
653
729
  if (value !== null && typeof value === "object") {
654
- const failure = value;
730
+ const failure2 = value;
655
731
  return {
656
- name: failure.name,
657
- code: failure.code,
658
- expected: failure.expected,
659
- actual: failure.actual,
660
- hint: failure.hint,
661
- detail: failure.detail,
662
- message: failure.message
732
+ name: failure2.name,
733
+ code: failure2.code,
734
+ expected: failure2.expected,
735
+ actual: failure2.actual,
736
+ hint: failure2.hint,
737
+ detail: failure2.detail,
738
+ message: failure2.message
663
739
  };
664
740
  }
665
741
  return { message: String(value) };
666
742
  }
667
743
  function hydrateFailure(value) {
668
744
  if (value === null || typeof value !== "object") return value;
669
- const failure = value;
670
- if (failure.name === "AssetError" && typeof failure.code === "string" && typeof failure.expected === "string" && typeof failure.hint === "string") {
745
+ const failure2 = value;
746
+ if (failure2.name === "AssetError" && typeof failure2.code === "string" && typeof failure2.expected === "string" && typeof failure2.hint === "string") {
671
747
  const args = {
672
- code: failure.code,
673
- expected: failure.expected,
674
- hint: failure.hint
748
+ code: failure2.code,
749
+ expected: failure2.expected,
750
+ hint: failure2.hint
675
751
  };
676
- return failure.detail === void 0 ? new AssetError(args) : new AssetError({
752
+ return failure2.detail === void 0 ? new AssetError(args) : new AssetError({
677
753
  ...args,
678
- detail: failure.detail
754
+ detail: failure2.detail
679
755
  });
680
756
  }
681
- if (failure.name === "ImportError" && typeof failure.code === "string" && typeof failure.expected === "string" && typeof failure.hint === "string" && failure.detail !== void 0) {
757
+ if (failure2.name === "ImportError" && typeof failure2.code === "string" && typeof failure2.expected === "string" && typeof failure2.hint === "string" && failure2.detail !== void 0) {
682
758
  return new ImportError({
683
- code: failure.code,
684
- expected: failure.expected,
685
- hint: failure.hint,
686
- detail: failure.detail,
687
- ...typeof failure.actual === "string" ? { actual: failure.actual } : {}
759
+ code: failure2.code,
760
+ expected: failure2.expected,
761
+ hint: failure2.hint,
762
+ detail: failure2.detail,
763
+ ...typeof failure2.actual === "string" ? { actual: failure2.actual } : {}
688
764
  });
689
765
  }
690
- if (typeof failure.message === "string") {
691
- const error = new Error(failure.message);
692
- if (typeof failure.name === "string") error.name = failure.name;
766
+ if (typeof failure2.message === "string") {
767
+ const error = new Error(failure2.message);
768
+ if (typeof failure2.name === "string") error.name = failure2.name;
693
769
  return error;
694
770
  }
695
771
  return value;
696
772
  }
697
773
  var WorkerScriptablePackExecutor = class {
774
+ supportsParameterizedContext = true;
698
775
  worker;
699
776
  compileRoot;
700
777
  nextBuildId = 0;
@@ -722,7 +799,7 @@ var WorkerScriptablePackExecutor = class {
722
799
  resolveLoad({
723
800
  default: value.definition === void 0 ? void 0 : {
724
801
  ...value.definition,
725
- build: (reader) => this.runBuild(reader)
802
+ build: (readByGuid, context) => this.runBuild({ readByGuid }, context)
726
803
  }
727
804
  });
728
805
  } else if (value.kind === "load-threw") {
@@ -754,7 +831,7 @@ var WorkerScriptablePackExecutor = class {
754
831
  })();
755
832
  return this.disposal;
756
833
  }
757
- runBuild(reader) {
834
+ runBuild(reader, context) {
758
835
  const worker = this.worker;
759
836
  if (worker === void 0) return Promise.reject(new Error("ScriptablePack worker is closed"));
760
837
  if (this.build !== void 0)
@@ -762,7 +839,7 @@ var WorkerScriptablePackExecutor = class {
762
839
  const id = this.nextBuildId++;
763
840
  return new Promise((resolveBuild, rejectBuild) => {
764
841
  this.build = { id, reader, resolve: resolveBuild, reject: rejectBuild };
765
- worker.postMessage({ kind: "build", buildId: id });
842
+ worker.postMessage({ kind: "build", buildId: id, context });
766
843
  });
767
844
  }
768
845
  onMessage(message) {
@@ -820,6 +897,7 @@ var ReusableWorkerScriptablePackExecutor = class {
820
897
  }
821
898
  maxTasksPerWorker;
822
899
  release;
900
+ supportsParameterizedContext = true;
823
901
  worker;
824
902
  compileRootReady = mkdtemp(resolve(tmpdir(), "forgeax-scriptable-pack-pool-"));
825
903
  compileRoot;
@@ -903,7 +981,7 @@ var ReusableWorkerScriptablePackExecutor = class {
903
981
  this.build = void 0;
904
982
  building?.reject(error);
905
983
  }
906
- runBuild(reader) {
984
+ runBuild(reader, context) {
907
985
  if (this.released || this.disposal !== void 0) {
908
986
  return Promise.reject(new Error("ScriptablePack executor is not leased"));
909
987
  }
@@ -914,7 +992,7 @@ var ReusableWorkerScriptablePackExecutor = class {
914
992
  return new Promise((resolveBuild, rejectBuild) => {
915
993
  this.build = { id, reader, resolve: resolveBuild, reject: rejectBuild };
916
994
  try {
917
- this.worker.postMessage({ kind: "build", buildId: id });
995
+ this.worker.postMessage({ kind: "build", buildId: id, context });
918
996
  } catch (error) {
919
997
  this.build = void 0;
920
998
  rejectBuild(error);
@@ -931,7 +1009,7 @@ var ReusableWorkerScriptablePackExecutor = class {
931
1009
  loading.resolve({
932
1010
  default: value.definition === void 0 ? void 0 : {
933
1011
  ...value.definition,
934
- build: (reader) => this.runBuild(reader)
1012
+ build: (readByGuid, context) => this.runBuild({ readByGuid }, context)
935
1013
  }
936
1014
  });
937
1015
  return;
@@ -1036,11 +1114,11 @@ var DefaultScriptablePackModuleExecutorPool = class {
1036
1114
  function createScriptablePackModuleExecutorPool(options = {}) {
1037
1115
  return new DefaultScriptablePackModuleExecutorPool(options);
1038
1116
  }
1039
- function loadFailure(sourcePath, reason, phase, diagnostic, timeoutMs) {
1040
- return err({
1041
- code: "pack-source-load-failed",
1042
- expected: "a trusted synchronous module with a valid default ScriptablePack export",
1043
- hint: "repair the module load or initialization failure, then inspect Meta again",
1117
+ function parameterizedLoadFailure(sourcePath, reason, phase, diagnostic, timeoutMs) {
1118
+ return {
1119
+ code: "pack-parameter-invalid",
1120
+ expected: "a trusted Pack v2 authoring module with a valid default export",
1121
+ hint: "repair the source module or its build timeout, then inspect the Pack again",
1044
1122
  detail: {
1045
1123
  sourcePath,
1046
1124
  reason,
@@ -1048,30 +1126,16 @@ function loadFailure(sourcePath, reason, phase, diagnostic, timeoutMs) {
1048
1126
  diagnostic,
1049
1127
  ...timeoutMs === void 0 ? {} : { timeoutMs }
1050
1128
  }
1051
- });
1052
- }
1053
- function buildTimeoutFailure(sourcePath, timeoutMs) {
1054
- return {
1055
- code: "pack-source-load-failed",
1056
- expected: "a ScriptablePack build to settle within the configured build timeout",
1057
- hint: "repair the authored build, then retry through a fresh ScriptablePack generation",
1058
- detail: {
1059
- sourcePath,
1060
- reason: "timeout",
1061
- phase: "build",
1062
- timeoutMs,
1063
- diagnostic: `ScriptablePack build exceeded ${timeoutMs}ms`
1064
- }
1065
1129
  };
1066
1130
  }
1067
- async function loadScriptablePack(sourcePath, options = {}) {
1131
+ async function loadParameterizedScriptablePack(sourcePath, options = {}) {
1068
1132
  const timeoutMs = options.timeoutMs ?? 15e3;
1069
1133
  const buildTimeoutMs = options.buildTimeoutMs ?? 5e3;
1070
1134
  const executor = options.executor ?? new WorkerScriptablePackExecutor();
1071
1135
  let disposeReason;
1072
1136
  let timeout;
1073
1137
  try {
1074
- const timeoutSignal = /* @__PURE__ */ Symbol("scriptable-pack-module-timeout");
1138
+ const timeoutSignal = /* @__PURE__ */ Symbol("parameterized-pack-module-timeout");
1075
1139
  const loaded = await Promise.race([
1076
1140
  executor.load(sourcePath),
1077
1141
  new Promise((resolve3) => {
@@ -1080,34 +1144,49 @@ async function loadScriptablePack(sourcePath, options = {}) {
1080
1144
  ]);
1081
1145
  if (loaded === timeoutSignal) {
1082
1146
  disposeReason = "timeout";
1083
- return loadFailure(
1084
- sourcePath,
1085
- "timeout",
1086
- "module-load",
1087
- `ScriptablePack module initialization exceeded ${timeoutMs}ms`,
1088
- timeoutMs
1147
+ return err(
1148
+ parameterizedLoadFailure(
1149
+ sourcePath,
1150
+ "timeout",
1151
+ "module-load",
1152
+ `Pack module initialization exceeded ${timeoutMs}ms`,
1153
+ timeoutMs
1154
+ )
1089
1155
  );
1090
1156
  }
1091
1157
  const moduleValue = loaded !== null && typeof loaded === "object" && "default" in loaded ? loaded.default : void 0;
1092
- const validated = validateScriptablePackDefinition(moduleValue, sourcePath);
1158
+ const validated = validatePackDefinition(moduleValue, sourcePath);
1093
1159
  if (!validated.ok) {
1094
1160
  disposeReason = "failure";
1095
1161
  return validated;
1096
1162
  }
1097
1163
  if (options.metadataOnly === true) {
1098
1164
  disposeReason = "complete";
1099
- return validated;
1165
+ return ok(validated.value);
1100
1166
  }
1101
1167
  const definition = validated.value;
1168
+ const build = definition.build;
1102
1169
  return ok({
1103
1170
  ...definition,
1104
- build: async (reader) => {
1171
+ build: async (context) => {
1105
1172
  let buildTimedOut = false;
1106
1173
  let buildTimeout;
1107
- const timeoutResult = err(buildTimeoutFailure(sourcePath, buildTimeoutMs));
1174
+ const timeoutResult = err(
1175
+ parameterizedLoadFailure(
1176
+ sourcePath,
1177
+ "timeout",
1178
+ "build",
1179
+ `ScriptablePack build exceeded ${buildTimeoutMs}ms`,
1180
+ buildTimeoutMs
1181
+ )
1182
+ );
1108
1183
  try {
1184
+ const invocation = "supportsParameterizedContext" in executor && executor.supportsParameterizedContext === true ? build(context.readByGuid, {
1185
+ packageId: [...context.packageId],
1186
+ ..."values" in context && context.values !== void 0 ? { values: context.values } : {}
1187
+ }) : definition.build(context);
1109
1188
  const built = await Promise.race([
1110
- Promise.resolve(definition.build(reader)),
1189
+ Promise.resolve(invocation),
1111
1190
  new Promise((resolve3) => {
1112
1191
  buildTimeout = setTimeout(() => {
1113
1192
  buildTimedOut = true;
@@ -1130,545 +1209,21 @@ async function loadScriptablePack(sourcePath, options = {}) {
1130
1209
  }
1131
1210
  });
1132
1211
  } catch (error) {
1133
- const diagnostic = error instanceof Error ? error.message : String(error);
1134
1212
  disposeReason = "failure";
1135
- return loadFailure(sourcePath, "module-load", "module-load", diagnostic);
1213
+ return err(
1214
+ parameterizedLoadFailure(
1215
+ sourcePath,
1216
+ "module-load",
1217
+ "module-load",
1218
+ error instanceof Error ? error.message : String(error)
1219
+ )
1220
+ );
1136
1221
  } finally {
1137
1222
  if (timeout !== void 0) clearTimeout(timeout);
1138
1223
  if (disposeReason !== void 0) await executor.dispose?.(disposeReason);
1139
1224
  }
1140
1225
  }
1141
- var CANONICAL_SCAFFOLD_MARKER = "// @forgeax-scriptable-pack canonical-v1";
1142
- var CANONICAL_MANIFEST_NAME = "canonicalManifest";
1143
- var CANONICAL_MUTABLE_KINDS = /* @__PURE__ */ new Set(["scene", "mesh", "material"]);
1144
- function authoringError(code, input) {
1145
- return {
1146
- code,
1147
- expected: input.expected,
1148
- ...input.actual === void 0 ? {} : { actual: input.actual },
1149
- hint: input.hint,
1150
- retryable: input.retryable,
1151
- recoveryActions: input.recoveryActions,
1152
- detail: {
1153
- ...input.requestId === void 0 ? {} : { requestId: input.requestId },
1154
- ...input.sourcePath === void 0 ? {} : { sourcePath: input.sourcePath },
1155
- ...input.sourceKey === void 0 ? {} : { sourceKey: input.sourceKey },
1156
- ...input.incomingRefs === void 0 ? {} : { incomingRefs: input.incomingRefs }
1157
- }
1158
- };
1159
- }
1160
- function confinedSourcePath(gameRoot, sourcePath) {
1161
- const root = resolve(gameRoot);
1162
- const absolute = resolve(root, sourcePath);
1163
- const rel = relative(root, absolute);
1164
- if (rel.length === 0 || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(sourcePath) || !rel.endsWith(".pack.ts")) {
1165
- return err(
1166
- authoringError("pack-source-path-invalid", {
1167
- expected: "a game-root-relative *.pack.ts path confined to the selected game",
1168
- actual: sourcePath,
1169
- hint: "choose a relative ScriptablePack source path inside the selected game root",
1170
- retryable: false,
1171
- recoveryActions: ["choose-game-relative-source-path"],
1172
- sourcePath
1173
- })
1174
- );
1175
- }
1176
- return ok({ absolute, relative: rel.split(sep).join("/") });
1177
- }
1178
- function revisionOf(source) {
1179
- return createHash("sha256").update(source).digest("hex");
1180
- }
1181
- function unwrapExpression(expression) {
1182
- if (ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isParenthesizedExpression(expression)) {
1183
- return unwrapExpression(expression.expression);
1184
- }
1185
- return expression;
1186
- }
1187
- function literalValue(expression) {
1188
- const value = unwrapExpression(expression);
1189
- if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)) return value.text;
1190
- if (value.kind === ts.SyntaxKind.TrueKeyword) return true;
1191
- if (value.kind === ts.SyntaxKind.FalseKeyword) return false;
1192
- if (value.kind === ts.SyntaxKind.NullKeyword) return null;
1193
- if (ts.isNumericLiteral(value)) return Number(value.text);
1194
- if (ts.isArrayLiteralExpression(value)) return value.elements.map(literalValue);
1195
- if (ts.isObjectLiteralExpression(value)) {
1196
- const result = {};
1197
- for (const property of value.properties) {
1198
- if (!ts.isPropertyAssignment(property))
1199
- throw new TypeError("manifest properties must be data");
1200
- const name = property.name;
1201
- const key = ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) ? name.text : void 0;
1202
- if (key === void 0) throw new TypeError("manifest property names must be static");
1203
- result[key] = literalValue(property.initializer);
1204
- }
1205
- return result;
1206
- }
1207
- throw new TypeError("manifest values must be literal data");
1208
- }
1209
- function parseCanonicalManifest(source, sourcePath) {
1210
- if (!source.startsWith(`${CANONICAL_SCAFFOLD_MARKER}
1211
- `)) return void 0;
1212
- const file = ts.createSourceFile(
1213
- sourcePath,
1214
- source,
1215
- ts.ScriptTarget.Latest,
1216
- true,
1217
- ts.ScriptKind.TS
1218
- );
1219
- for (const statement of file.statements) {
1220
- if (!ts.isVariableStatement(statement)) continue;
1221
- for (const declaration of statement.declarationList.declarations) {
1222
- if (ts.isIdentifier(declaration.name) && declaration.name.text === CANONICAL_MANIFEST_NAME && declaration.initializer !== void 0) {
1223
- const value = literalValue(declaration.initializer);
1224
- if (value === null || typeof value !== "object") return void 0;
1225
- return value;
1226
- }
1227
- }
1228
- }
1229
- return void 0;
1230
- }
1231
- function guidExpression(value) {
1232
- return `parseGuid(${JSON.stringify(value)})`;
1233
- }
1234
- function renderCanonicalScriptablePack(manifest) {
1235
- const data = JSON.stringify(manifest, null, 2);
1236
- return `${CANONICAL_SCAFFOLD_MARKER}
1237
- import { AssetGuid } from '@forgeax/engine-pack/guid';
1238
- import { buildMeshAttributeMapForUvSets } from '@forgeax/engine-geometry';
1239
- import type { ScriptablePackDefinition } from '@forgeax/engine-pack/source';
1240
- import type { Asset, AssetGuid as AssetGuidType } from '@forgeax/engine-types';
1241
- import { ok } from '@forgeax/engine-types';
1242
-
1243
- const ${CANONICAL_MANIFEST_NAME} = ${data} as const;
1244
-
1245
- function parseGuid(value: string): AssetGuidType {
1246
- const parsed = AssetGuid.parse(value);
1247
- if (!parsed.ok) throw parsed.error;
1248
- return parsed.value;
1249
- }
1250
-
1251
- function createOutput(kind: string): Asset {
1252
- switch (kind) {
1253
- case 'scene':
1254
- return { kind: 'scene', entities: [], mounts: [] };
1255
- case 'material':
1256
- return { kind: 'material', values: {} };
1257
- case 'mesh': {
1258
- const vertices = new Float32Array([
1259
- 0, 0.7, 0, 0, 0, 1, 0.5, 1, 0, 0, 0, 1,
1260
- -0.7, -0.6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
1261
- 0.7, -0.6, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1,
1262
- ]);
1263
- return {
1264
- kind: 'mesh',
1265
- vertices,
1266
- indices: new Uint16Array([0, 1, 2]),
1267
- attributes: { ...buildMeshAttributeMapForUvSets(1), position: vertices },
1268
- aabb: new Float32Array([-0.7, -0.6, 0, 0.7, 0.7, 0]),
1269
- submeshes: [{ indexOffset: 0, indexCount: 3, vertexCount: 3, topology: 'triangle-list', materialSlot: 0 }],
1270
- materialSlots: [{ slotName: 'Default' }],
1271
- };
1272
- }
1273
- default:
1274
- throw new Error(\`canonical ScriptablePack output kind is not mutable: \${kind}\`);
1275
- }
1276
- }
1277
-
1278
- const assets = Object.fromEntries(
1279
- Object.entries(${CANONICAL_MANIFEST_NAME}.assets).map(([sourceKey, asset]) => [
1280
- sourceKey,
1281
- { ...asset, guid: parseGuid(asset.guid) },
1282
- ]),
1283
- );
1284
- const externalAssets = Object.fromEntries(
1285
- Object.entries(${CANONICAL_MANIFEST_NAME}.externalAssets).map(([alias, guid]) => [alias, parseGuid(guid)]),
1286
- );
1287
-
1288
- export default {
1289
- schemaVersion: '1.0.0',
1290
- packageId: ${guidExpression(manifest.packageId)},
1291
- ${manifest.name === void 0 ? "" : `name: ${JSON.stringify(manifest.name)},
1292
- `}assets,
1293
- externalAssets,
1294
- build: () => ok(Object.fromEntries(Object.entries(assets).map(([sourceKey, asset]) => [sourceKey, createOutput(asset.kind)])) as Record<string, Asset>),
1295
- } satisfies ScriptablePackDefinition;
1296
- `;
1297
- }
1298
- function definitionFromCanonical(manifest) {
1299
- const packageId = AssetGuid.parse(manifest.packageId);
1300
- if (!packageId.ok) return invalidCanonicalGuid("$.packageId", manifest.packageId);
1301
- const assets = {};
1302
- for (const [sourceKey, output] of Object.entries(manifest.assets)) {
1303
- const guid = AssetGuid.parse(output.guid);
1304
- if (!guid.ok)
1305
- return invalidCanonicalGuid(`$.assets[${JSON.stringify(sourceKey)}].guid`, output.guid);
1306
- assets[sourceKey] = {
1307
- guid: guid.value,
1308
- kind: output.kind,
1309
- ...output.name === void 0 ? {} : { name: output.name }
1310
- };
1311
- }
1312
- const externalAssets = {};
1313
- for (const [alias, value] of Object.entries(manifest.externalAssets)) {
1314
- const guid = AssetGuid.parse(value);
1315
- if (!guid.ok) return invalidCanonicalGuid(`$.externalAssets[${JSON.stringify(alias)}]`, value);
1316
- externalAssets[alias] = guid.value;
1317
- }
1318
- return validateScriptablePackDefinition({
1319
- schemaVersion: "1.0.0",
1320
- packageId: packageId.value,
1321
- ...manifest.name === void 0 ? {} : { name: manifest.name },
1322
- assets,
1323
- externalAssets,
1324
- build: () => ok({})
1325
- });
1326
- }
1327
- function invalidCanonicalGuid(propertyPath, actual2) {
1328
- return err({
1329
- code: "pack-source-definition-invalid",
1330
- expected: "a canonical UUID string",
1331
- hint: "repair the canonical scaffold manifest GUID and inspect Meta again",
1332
- detail: { propertyPath, actual: actual2 }
1333
- });
1334
- }
1335
- function invalidCanonicalSource(sourcePath, error) {
1336
- return err({
1337
- code: "pack-source-definition-invalid",
1338
- expected: "a literal canonical-v1 manifest with the required package/assets/external fields",
1339
- hint: "repair the canonical scaffold or open it as custom source without structured mutation",
1340
- detail: {
1341
- sourcePath,
1342
- propertyPath: "$.canonicalManifest",
1343
- actual: error instanceof Error ? error.message : String(error)
1344
- }
1345
- });
1346
- }
1347
- async function atomicWrite(path, source) {
1348
- await mkdir(dirname(path), { recursive: true });
1349
- const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
1350
- try {
1351
- await writeFile(temporary, source, { encoding: "utf8", flag: "wx" });
1352
- await rename(temporary, path);
1353
- } catch (error) {
1354
- await rm(temporary, { force: true });
1355
- throw error;
1356
- }
1357
- }
1358
- function sameStrings(left, right) {
1359
- return [...left].sort().join("\0") === [...right].sort().join("\0");
1360
- }
1361
- function createFileSystemScriptablePackAuthoringPort(options) {
1362
- const resolvePath = (sourcePath) => confinedSourcePath(options.gameRoot, sourcePath);
1363
- async function readSource(sourcePath) {
1364
- const path = resolvePath(sourcePath);
1365
- if (!path.ok) return path;
1366
- try {
1367
- const source = await readFile(path.value.absolute, "utf8");
1368
- return ok({ ...path.value, source, revision: revisionOf(source) });
1369
- } catch (error) {
1370
- return err(
1371
- authoringError("pack-source-write-failed", {
1372
- expected: "a readable ScriptablePack source file",
1373
- actual: error instanceof Error ? error.message : String(error),
1374
- hint: "restore the source file or create it through the ScriptablePack gateway",
1375
- retryable: true,
1376
- recoveryActions: ["inspect-source-path", "retry"],
1377
- sourcePath
1378
- })
1379
- );
1380
- }
1381
- }
1382
- async function inspect(sourcePath) {
1383
- const read = await readSource(sourcePath);
1384
- if (!read.ok) return read;
1385
- let canonical;
1386
- try {
1387
- canonical = parseCanonicalManifest(read.value.source, read.value.relative);
1388
- } catch (error) {
1389
- return invalidCanonicalSource(read.value.relative, error);
1390
- }
1391
- const loaded = canonical === void 0 ? await loadScriptablePack(read.value.absolute, { metadataOnly: true }) : definitionFromCanonical(canonical);
1392
- if (!loaded.ok) return loaded;
1393
- return ok({
1394
- revision: read.value.revision,
1395
- meta: projectScriptablePackMeta(loaded.value, read.value.relative)
1396
- });
1397
- }
1398
- async function preflight(sourcePath) {
1399
- const read = await readSource(sourcePath);
1400
- if (!read.ok) return read;
1401
- const inspected = await inspect(sourcePath);
1402
- if (!inspected.ok) return inspected;
1403
- let canonical;
1404
- try {
1405
- canonical = parseCanonicalManifest(read.value.source, read.value.relative);
1406
- } catch (error) {
1407
- return invalidCanonicalSource(read.value.relative, error);
1408
- }
1409
- const incomingRefs = await options.incomingRefs?.(read.value.relative) ?? [];
1410
- return ok({
1411
- revision: inspected.value.revision,
1412
- meta: inspected.value.meta,
1413
- capabilities: canonical === void 0 ? {
1414
- inspect: true,
1415
- rebuild: true,
1416
- coldCook: true,
1417
- reason: "structured mutation is available only for canonical-v1 scaffolds"
1418
- } : {
1419
- inspect: true,
1420
- rebuild: true,
1421
- coldCook: true,
1422
- addOutput: true,
1423
- addExternalAsset: true,
1424
- renameDisplay: true,
1425
- removeOutput: true,
1426
- clone: true
1427
- },
1428
- incomingRefs
1429
- });
1430
- }
1431
- async function mutate(operation) {
1432
- const targetPath = operation.kind === "clone-scriptable-pack" ? operation.targetPath : operation.sourcePath;
1433
- const target = resolvePath(targetPath);
1434
- if (!target.ok) return target;
1435
- let manifest;
1436
- let currentRevision;
1437
- if (operation.kind === "create-scriptable-pack") {
1438
- if (!CANONICAL_MUTABLE_KINDS.has(operation.initialOutput.kind)) {
1439
- return err(
1440
- authoringError("pack-source-mutation-unsupported", {
1441
- expected: "a canonical scene, mesh, or material initial output",
1442
- actual: operation.initialOutput.kind,
1443
- hint: "create one of the canonical output kinds, then hand-author advanced outputs",
1444
- retryable: false,
1445
- recoveryActions: ["choose-supported-output-kind"],
1446
- requestId: operation.requestId,
1447
- sourcePath: operation.sourcePath
1448
- })
1449
- );
1450
- }
1451
- try {
1452
- await stat(target.value.absolute);
1453
- return err(
1454
- authoringError("pack-source-revision-conflict", {
1455
- expected: "an unused target source path",
1456
- actual: "source already exists",
1457
- hint: "inspect the existing source or choose a new path",
1458
- retryable: false,
1459
- recoveryActions: ["asset.preflight", "choose-new-source-path"],
1460
- requestId: operation.requestId,
1461
- sourcePath: operation.sourcePath
1462
- })
1463
- );
1464
- } catch {
1465
- }
1466
- manifest = {
1467
- schemaVersion: "1.0.0",
1468
- packageId: AssetGuid.format(operation.packageId),
1469
- ...operation.name === void 0 ? {} : { name: operation.name },
1470
- assets: {
1471
- [operation.initialOutput.sourceKey]: {
1472
- guid: AssetGuid.format(operation.initialOutput.guid),
1473
- kind: operation.initialOutput.kind,
1474
- ...operation.initialOutput.name === void 0 ? {} : { name: operation.initialOutput.name }
1475
- }
1476
- },
1477
- externalAssets: {}
1478
- };
1479
- } else {
1480
- const read = await readSource(operation.sourcePath);
1481
- if (!read.ok) return read;
1482
- currentRevision = read.value.revision;
1483
- if (operation.expectedRevision !== void 0 && operation.expectedRevision !== currentRevision) {
1484
- return err(
1485
- authoringError("pack-source-revision-conflict", {
1486
- expected: operation.expectedRevision,
1487
- actual: currentRevision,
1488
- hint: "inspect the current source revision, reconcile the edit, then retry with a new requestId",
1489
- retryable: true,
1490
- recoveryActions: ["asset.preflight", "mint-request-id"],
1491
- requestId: operation.requestId,
1492
- sourcePath: operation.sourcePath
1493
- })
1494
- );
1495
- }
1496
- let parsed;
1497
- try {
1498
- parsed = parseCanonicalManifest(read.value.source, read.value.relative);
1499
- } catch (error) {
1500
- return invalidCanonicalSource(read.value.relative, error);
1501
- }
1502
- if (parsed === void 0) {
1503
- return err(
1504
- authoringError("pack-source-mutation-unsupported", {
1505
- expected: "a canonical-v1 scaffold for structured source mutation",
1506
- actual: "custom ScriptablePack source",
1507
- hint: "clone into a canonical scaffold or edit custom source with a code editor",
1508
- retryable: false,
1509
- recoveryActions: ["clone-scriptable-pack", "open-code-editor"],
1510
- requestId: operation.requestId,
1511
- sourcePath: operation.sourcePath
1512
- })
1513
- );
1514
- }
1515
- manifest = parsed;
1516
- }
1517
- if (operation.kind === "clone-scriptable-pack") {
1518
- try {
1519
- await stat(target.value.absolute);
1520
- return err(
1521
- authoringError("pack-source-revision-conflict", {
1522
- expected: "an unused clone target source path",
1523
- actual: "source already exists",
1524
- hint: "inspect the existing target or choose a new path",
1525
- retryable: false,
1526
- recoveryActions: ["asset.preflight", "choose-new-source-path"],
1527
- requestId: operation.requestId,
1528
- sourcePath: target.value.relative
1529
- })
1530
- );
1531
- } catch {
1532
- }
1533
- }
1534
- if (operation.kind === "add-output") {
1535
- if (!CANONICAL_MUTABLE_KINDS.has(operation.assetKind)) {
1536
- return err(
1537
- authoringError("pack-source-mutation-unsupported", {
1538
- expected: "a canonical scene, mesh, or material output",
1539
- actual: operation.assetKind,
1540
- hint: "hand-author advanced output kinds in the source module",
1541
- retryable: false,
1542
- recoveryActions: ["open-code-editor"],
1543
- requestId: operation.requestId,
1544
- sourcePath: operation.sourcePath,
1545
- sourceKey: operation.sourceKey
1546
- })
1547
- );
1548
- }
1549
- manifest = {
1550
- ...manifest,
1551
- assets: {
1552
- ...manifest.assets,
1553
- [operation.sourceKey]: {
1554
- guid: AssetGuid.format(operation.guid),
1555
- kind: operation.assetKind,
1556
- ...operation.name === void 0 ? {} : { name: operation.name }
1557
- }
1558
- }
1559
- };
1560
- } else if (operation.kind === "add-external-asset") {
1561
- manifest = {
1562
- ...manifest,
1563
- externalAssets: {
1564
- ...manifest.externalAssets,
1565
- [operation.alias]: AssetGuid.format(operation.guid)
1566
- }
1567
- };
1568
- } else if (operation.kind === "rename-display") {
1569
- if (operation.target.kind === "package") manifest = { ...manifest, name: operation.name };
1570
- else {
1571
- const output = manifest.assets[operation.target.sourceKey];
1572
- if (output === void 0)
1573
- return err(
1574
- authoringError("pack-source-mutation-unsupported", {
1575
- expected: "an existing sourceKey",
1576
- actual: operation.target.sourceKey,
1577
- hint: "inspect Meta and choose a current output",
1578
- retryable: true,
1579
- recoveryActions: ["asset.preflight"],
1580
- requestId: operation.requestId,
1581
- sourcePath: operation.sourcePath,
1582
- sourceKey: operation.target.sourceKey
1583
- })
1584
- );
1585
- manifest = {
1586
- ...manifest,
1587
- assets: {
1588
- ...manifest.assets,
1589
- [operation.target.sourceKey]: { ...output, name: operation.name }
1590
- }
1591
- };
1592
- }
1593
- } else if (operation.kind === "remove-output") {
1594
- const incomingRefs = await options.incomingRefs?.(operation.sourcePath, operation.sourceKey) ?? [];
1595
- if (incomingRefs.length > 0 && !sameStrings(incomingRefs, operation.confirmIncomingRefs ?? [])) {
1596
- return err(
1597
- authoringError("pack-source-reference-conflict", {
1598
- expected: "explicit acknowledgement of every current incoming reference",
1599
- actual: `${incomingRefs.length} incoming references`,
1600
- hint: "inspect dependents, then confirm the exact incoming reference identities or cancel",
1601
- retryable: true,
1602
- recoveryActions: ["inspect-incoming-refs", "confirm-remove-output"],
1603
- requestId: operation.requestId,
1604
- sourcePath: operation.sourcePath,
1605
- sourceKey: operation.sourceKey,
1606
- incomingRefs
1607
- })
1608
- );
1609
- }
1610
- const { [operation.sourceKey]: removed, ...assets } = manifest.assets;
1611
- if (removed === void 0 || Object.keys(assets).length === 0) {
1612
- return err(
1613
- authoringError("pack-source-mutation-unsupported", {
1614
- expected: "an existing output while retaining at least one package output",
1615
- actual: operation.sourceKey,
1616
- hint: "keep one output or remove the entire source file through an explicit file operation",
1617
- retryable: false,
1618
- recoveryActions: ["asset.preflight"],
1619
- requestId: operation.requestId,
1620
- sourcePath: operation.sourcePath,
1621
- sourceKey: operation.sourceKey
1622
- })
1623
- );
1624
- }
1625
- manifest = { ...manifest, assets };
1626
- } else if (operation.kind === "clone-scriptable-pack") {
1627
- manifest = {
1628
- ...manifest,
1629
- packageId: AssetGuid.format(operation.packageId),
1630
- assets: Object.fromEntries(
1631
- Object.entries(manifest.assets).map(([sourceKey, output]) => [
1632
- sourceKey,
1633
- {
1634
- ...output,
1635
- guid: AssetGuid.format(operation.outputGuids[sourceKey])
1636
- }
1637
- ])
1638
- )
1639
- };
1640
- }
1641
- const source = renderCanonicalScriptablePack(manifest);
1642
- try {
1643
- await atomicWrite(target.value.absolute, source);
1644
- return ok({ sourcePath: target.value.relative, revision: revisionOf(source) });
1645
- } catch (error) {
1646
- return err(
1647
- authoringError("pack-source-write-failed", {
1648
- expected: "an atomic source-file replacement",
1649
- actual: error instanceof Error ? error.message : String(error),
1650
- hint: "repair filesystem permissions or disk capacity, then retry with a new requestId",
1651
- retryable: true,
1652
- recoveryActions: ["inspect-filesystem", "mint-request-id"],
1653
- requestId: operation.requestId,
1654
- sourcePath: target.value.relative
1655
- })
1656
- );
1657
- }
1658
- }
1659
- return {
1660
- preflight,
1661
- mutate,
1662
- inspect,
1663
- async rebuild(sourcePath, mode) {
1664
- const path = resolvePath(sourcePath);
1665
- if (!path.ok) return path;
1666
- const rebuilt = await options.rebuild?.(path.value.relative, mode) ?? ok(void 0);
1667
- return rebuilt.ok ? inspect(path.value.relative) : rebuilt;
1668
- }
1669
- };
1670
- }
1671
1226
 
1672
- export { createFileSystemScriptablePackAuthoringPort, createScriptablePackModuleExecutorPool, inventoryScriptablePackSource, loadScriptablePack, readOptionalBuildCache, renderCanonicalScriptablePack, scriptablePackDdcKey };
1227
+ export { createScriptablePackModuleExecutorPool, inventoryScriptablePackSource, loadParameterizedScriptablePack, readOptionalBuildCache, scriptablePackDdcKey };
1673
1228
  //# sourceMappingURL=scriptable-pack-node.mjs.map
1674
1229
  //# sourceMappingURL=scriptable-pack-node.mjs.map