@carbonenginejs/runtime-utils 0.1.1 → 0.1.3

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 (104) hide show
  1. package/LICENSE +21 -21
  2. package/NOTICE +23 -23
  3. package/README.md +69 -69
  4. package/THIRD-PARTY-NOTICES.md +64 -64
  5. package/docs/README.md +74 -73
  6. package/docs/architecture.md +101 -101
  7. package/docs/concepts/foundation-consolidation.md +85 -85
  8. package/docs/concepts/model-lifecycle.md +111 -0
  9. package/docs/const-kb.md +85 -87
  10. package/docs/core-types/DECORATOR-TODOS.md +25 -25
  11. package/docs/core-types/README.md +229 -168
  12. package/docs/reference/api.md +105 -102
  13. package/docs/reference/classes/README.md +135 -135
  14. package/package.json +139 -139
  15. package/src/arrays.js +5 -5
  16. package/src/audio/audioFormats.js +34 -34
  17. package/src/audio/index.js +1 -1
  18. package/src/box3.js +331 -331
  19. package/src/bytes.js +56 -56
  20. package/src/compression.js +56 -56
  21. package/src/constants/index.js +7 -7
  22. package/src/constants/trinity.js +13 -13
  23. package/src/constants.js +15 -15
  24. package/src/curve.js +79 -79
  25. package/src/d3d/dxgiFormats.js +46 -46
  26. package/src/d3d/index.js +2 -2
  27. package/src/d3d/primitiveTopology.js +11 -11
  28. package/src/document/CjsCarbonDocument.js +212 -212
  29. package/src/document/CjsClassRegistry.js +373 -373
  30. package/src/document/CjsDocumentDehydrator.js +142 -142
  31. package/src/document/CjsDocumentHydrator.js +156 -156
  32. package/src/document/CjsStructRegistry.js +348 -348
  33. package/src/document/hydrationAdapter.js +129 -129
  34. package/src/document/index.js +6 -6
  35. package/src/errors/CjsError.js +286 -286
  36. package/src/errors/index.js +5 -5
  37. package/src/geometry/box.js +22 -22
  38. package/src/geometry/cylinder.js +22 -22
  39. package/src/geometry/helpers/earcut.js +1 -1
  40. package/src/geometry/helpers/misc.js +3 -3
  41. package/src/geometry/index.js +8 -8
  42. package/src/geometry/json.js +67 -67
  43. package/src/geometry/lathe.js +44 -44
  44. package/src/geometry/plane.js +14 -14
  45. package/src/geometry/shape.js +4 -4
  46. package/src/geometry/sphere.js +24 -24
  47. package/src/geometry/torus.js +14 -14
  48. package/src/graphics/colorSpaces.js +22 -22
  49. package/src/graphics/index.js +4 -4
  50. package/src/graphics/pixelFormats.js +158 -158
  51. package/src/graphics/textureDimensions.js +22 -22
  52. package/src/graphics/trinityEnums.js +87 -87
  53. package/src/index.js +62 -62
  54. package/src/is.js +108 -46
  55. package/src/json.js +23 -23
  56. package/src/lifecycle/CjsLifecycleState.js +77 -77
  57. package/src/lifecycle/index.js +1 -1
  58. package/src/lne3.js +70 -70
  59. package/src/lookup.js +48 -48
  60. package/src/mat3.js +51 -51
  61. package/src/mat4.js +699 -699
  62. package/src/math/index.js +25 -25
  63. package/src/math/scalar.js +63 -63
  64. package/src/media/index.js +1 -1
  65. package/src/media/mediaTypes.js +50 -50
  66. package/src/mesh.js +424 -424
  67. package/src/model/CjsEventEmitter.js +333 -333
  68. package/src/model/CjsModel.js +1589 -1544
  69. package/src/model/CjsModelState.js +72 -72
  70. package/src/model/index.js +4 -4
  71. package/src/model/sourceRecordUtils.js +54 -54
  72. package/src/noise.js +310 -310
  73. package/src/num.js +827 -827
  74. package/src/object.js +39 -39
  75. package/src/path.js +53 -53
  76. package/src/pln.js +125 -125
  77. package/src/pool.js +9 -9
  78. package/src/quat.js +144 -144
  79. package/src/ray3.js +188 -188
  80. package/src/renderContext/formats.js +145 -145
  81. package/src/renderContext/index.js +5 -5
  82. package/src/renderContext/presentation.js +125 -125
  83. package/src/renderContext/resources.js +27 -27
  84. package/src/renderContext/upscaling.js +22 -22
  85. package/src/renderContext/window.js +20 -20
  86. package/src/runtime/CjsRuntimeState.js +50 -50
  87. package/src/schema/CjsSchema.js +555 -318
  88. package/src/schema/index.js +4 -4
  89. package/src/shader/index.js +1 -1
  90. package/src/shader/shaderStages.js +37 -37
  91. package/src/sph3.js +181 -181
  92. package/src/tangent.js +288 -288
  93. package/src/text.js +40 -40
  94. package/src/tri3.js +98 -98
  95. package/src/types/carbonTypes.js +635 -635
  96. package/src/types/index.js +2 -2
  97. package/src/utils.js +58 -58
  98. package/src/validation.js +46 -46
  99. package/src/vec2.js +229 -229
  100. package/src/vec3.js +1188 -1172
  101. package/src/vec4.js +347 -347
  102. package/src/vertex.js +108 -108
  103. package/src/webgpu/index.js +1 -1
  104. package/src/webgpu/textureFormats.js +121 -121
@@ -1,8 +1,20 @@
1
- const CLASS_SCHEMA = new WeakMap();
2
- const CONSTRUCTOR_BY_NAME = new Map();
3
- const ENUM_SCHEMA_BY_NAME = new Map();
4
- const ENUM_SCHEMA_BY_OBJECT = new WeakMap();
5
- const STAGE3_FIELD_METADATA = Symbol("carbonenginejs.schema.stage3Fields");
1
+ const CLASS_SCHEMA = new WeakMap();
2
+
3
+ // Exported schemas, memoized per class. SCHEMA_GENERATION is bumped by every
4
+ // metadata definition (see getOrCreateClassSchema), which is what makes a stale
5
+ // memo detectable without tracking which subclasses a base class change reaches.
6
+ const SCHEMA_EXPORTS = new WeakMap();
7
+ let SCHEMA_GENERATION = 0;
8
+
9
+ const CONSTRUCTOR_BY_NAME = new Map();
10
+ const ENUM_SCHEMA_BY_NAME = new Map();
11
+ const ENUM_SCHEMA_BY_OBJECT = new WeakMap();
12
+ const STAGE3_FIELD_METADATA = Symbol("carbonenginejs.schema.stage3Fields");
13
+
14
+ // Declared here rather than beside describeDecorator: the CjsSchema class body
15
+ // builds every decorator namespace in its static initializer, which runs before
16
+ // any const declared after the class is initialized.
17
+ const DECORATOR_METADATA = Symbol("carbonenginejs.schema.decoratorMetadata");
6
18
 
7
19
  export const CJS_ENUM_NAME = Symbol.for("carbonenginejs.enum.name");
8
20
 
@@ -56,32 +68,32 @@ export class CjsSchema
56
68
  return this;
57
69
  }
58
70
 
59
- static defineMethod(Constructor, methodName, namespace, value)
60
- {
61
- defineMethodMetadata(Constructor, methodName, namespace, value);
62
- return this;
63
- }
64
-
65
- static getField(Constructor, fieldName)
66
- {
67
- return getEffectiveFields(Constructor).find(field => field.name === fieldName) || null;
68
- }
69
-
70
- /**
71
- * Excludes named inherited fields from the decorated class's schema surface.
72
- */
73
- static hideInherited(fieldNames)
74
- {
75
- return hiddenInheritedFieldsDecorator(normalizeHiddenInheritedFields(fieldNames));
76
- }
77
-
78
- /**
79
- * Checks whether a field is hidden from a class by its inheritance chain.
80
- */
81
- static isFieldHidden(Constructor, fieldName)
82
- {
83
- return getHiddenInheritedFieldNames(Constructor).has(fieldName);
84
- }
71
+ static defineMethod(Constructor, methodName, namespace, value)
72
+ {
73
+ defineMethodMetadata(Constructor, methodName, namespace, value);
74
+ return this;
75
+ }
76
+
77
+ static getField(Constructor, fieldName)
78
+ {
79
+ return getEffectiveFields(Constructor).find(field => field.name === fieldName) || null;
80
+ }
81
+
82
+ /**
83
+ * Excludes named inherited fields from the decorated class's schema surface.
84
+ */
85
+ static hideInherited(fieldNames)
86
+ {
87
+ return hiddenInheritedFieldsDecorator(normalizeHiddenInheritedFields(fieldNames));
88
+ }
89
+
90
+ /**
91
+ * Checks whether a field is hidden from a class by its inheritance chain.
92
+ */
93
+ static isFieldHidden(Constructor, fieldName)
94
+ {
95
+ return getHiddenInheritedFieldNames(Constructor).has(fieldName);
96
+ }
85
97
 
86
98
  static getMethod(Constructor, methodName)
87
99
  {
@@ -128,6 +140,9 @@ export class CjsSchema
128
140
  }
129
141
 
130
142
  CONSTRUCTOR_BY_NAME.set(name.trim(), Constructor);
143
+ // Buckets resolve class references by name, so a late registration
144
+ // changes how already-built schemas should have been bucketed.
145
+ SCHEMA_GENERATION += 1;
131
146
  return this;
132
147
  }
133
148
 
@@ -151,49 +166,40 @@ export class CjsSchema
151
166
  return name ? ENUM_SCHEMA_BY_NAME.get(name) || null : null;
152
167
  }
153
168
 
154
- static getSchema(Constructor, options = {})
155
- {
156
- const schema = CLASS_SCHEMA.get(Constructor);
157
- const namespaces = normalizeNamespaces(options.namespaces);
158
- const fields = [];
159
- const methods = [];
160
-
161
- for (const field of getEffectiveFields(Constructor))
162
- {
163
- fields.push(enrichEnumField(exportField(field, namespaces), Constructor));
164
- }
165
-
166
- for (const method of schema?.methods || [])
167
- {
168
- methods.push(exportField(method, namespaces));
169
- }
170
-
171
- const result = {
172
- className: CjsSchema.getClassName(Constructor),
173
- fields: Object.freeze(fields)
174
- };
175
-
176
- const family = schema?.family || CjsSchema.getClassFamily(Constructor);
177
- if (family)
178
- {
179
- result.family = family;
180
- }
181
-
182
- if (schema?.sourceClass && schema.sourceClass !== result.className)
183
- {
184
- result.sourceClass = schema.sourceClass;
185
- }
186
-
187
- if (schema?.aliases?.length)
188
- {
189
- result.aliases = Object.freeze([...schema.aliases]);
190
- }
169
+ /**
170
+ * Return the exported schema for a class.
171
+ *
172
+ * The schema is the precomputed answer - collapsing the inheritance
173
+ * lineage and merging metadata - so building it per call would defeat its
174
+ * purpose. Callers traverse model graphs and ask once per node, so this is
175
+ * memoized per class and rebuilt only when class metadata is defined.
176
+ *
177
+ * Namespace-filtered exports are not memoized: they are a projection of the
178
+ * full schema requested by tooling, not the hot read path.
179
+ *
180
+ * The result is shared, not copied - treat it as read-only. It is not
181
+ * frozen: deep-cloning and freezing every field on the way out cost far
182
+ * more than the mistakes it guarded against.
183
+ *
184
+ * @param {Function} Constructor
185
+ * @param {object} [options={}]
186
+ * @param {string|Array<string>} [options.namespaces] Restricts exported metadata namespaces.
187
+ * @returns {object} Shared schema export.
188
+ */
189
+ static getSchema(Constructor, options = {})
190
+ {
191
+ const namespaces = normalizeNamespaces(options.namespaces);
192
+ if (namespaces) return buildSchema(Constructor, namespaces);
191
193
 
192
- if (methods.length) result.methods = Object.freeze(methods);
194
+ const memo = SCHEMA_EXPORTS.get(Constructor);
195
+ if (memo && memo.generation === SCHEMA_GENERATION) return memo.schema;
193
196
 
194
- return Object.freeze(result);
197
+ const schema = buildSchema(Constructor, null);
198
+ SCHEMA_EXPORTS.set(Constructor, { generation: SCHEMA_GENERATION, schema });
199
+ return schema;
195
200
  }
196
201
 
202
+
197
203
  static type = Object.freeze({
198
204
  array: itemType => fieldDecorator("type", { kind: "array", itemType }),
199
205
  boolean: fieldDecorator("type", { kind: "boolean" }),
@@ -307,7 +313,8 @@ export class CjsSchema
307
313
  contextual: true,
308
314
  contextTiers: Object.freeze(normalized)
309
315
  });
310
- return function contextualMethodDecorator(targetOrValue, contextOrMethodName)
316
+ const described = getDecoratorMetadata(base);
317
+ return describeDecorator(function contextualMethodDecorator(targetOrValue, contextOrMethodName)
311
318
  {
312
319
  // Contextual methods are validated context-first at decoration
313
320
  // time: the first declared parameter must be the frame context.
@@ -320,7 +327,7 @@ export class CjsSchema
320
327
  assertContextFirstMethod(targetOrValue[contextOrMethodName], contextOrMethodName);
321
328
  }
322
329
  return base(targetOrValue, contextOrMethodName);
323
- };
330
+ }, described.namespace, described.value);
324
331
  }
325
332
  });
326
333
 
@@ -338,22 +345,46 @@ function createComponentsNamespace()
338
345
  return Object.freeze(components);
339
346
  }
340
347
 
341
- function fieldDecorator(namespace, value)
342
- {
343
- return function schemaFieldDecorator(targetOrValue, contextOrFieldName)
344
- {
345
- if (contextOrFieldName && typeof contextOrFieldName === "object")
346
- {
347
- const context = contextOrFieldName;
348
- if (context.kind !== "field") throw new TypeError("CjsSchema decorators only support class fields.");
349
- recordStage3FieldMetadata(context, namespace, value);
350
-
351
- // Register field metadata on instance construction. addInitializer covers
352
- // spec-compliant runtimes; the returned field initializer covers runtimes (e.g.
353
- // Deno/SWC) that do NOT fire field-decorator addInitializer. Both register the same
354
- // metadata (idempotent via mergeNamespace), so whichever the runtime honours, the
355
- // schema is populated. Registration is lazy (first construction); the class decorator
356
- // still registers the class eagerly at definition time.
348
+ // Every decorator carries the namespace and value it would install. That is
349
+ // what lets one vocabulary serve both forms: `type.uint32` applied as a
350
+ // decorator, and `type.uint32` written as data in a CjsSchema.define() member.
351
+ // Without it the object form would need its own spelling of every namespace
352
+ // value, and two spellings of `impl.adapted` will eventually disagree.
353
+ function describeDecorator(decorator, namespace, value)
354
+ {
355
+ Object.defineProperty(decorator, DECORATOR_METADATA, {
356
+ value: { namespace, value }
357
+ });
358
+ return decorator;
359
+ }
360
+
361
+ /**
362
+ * The namespace/value a decorator installs, or null when it is not one.
363
+ *
364
+ * @param {*} candidate Possible decorator.
365
+ * @returns {{namespace:string, value:*}|null} Installed metadata.
366
+ */
367
+ function getDecoratorMetadata(candidate)
368
+ {
369
+ return typeof candidate === "function" ? candidate[DECORATOR_METADATA] || null : null;
370
+ }
371
+
372
+ function fieldDecorator(namespace, value)
373
+ {
374
+ return describeDecorator(function schemaFieldDecorator(targetOrValue, contextOrFieldName)
375
+ {
376
+ if (contextOrFieldName && typeof contextOrFieldName === "object")
377
+ {
378
+ const context = contextOrFieldName;
379
+ if (context.kind !== "field") throw new TypeError("CjsSchema decorators only support class fields.");
380
+ recordStage3FieldMetadata(context, namespace, value);
381
+
382
+ // The class decorator replays the Stage-3 metadata recorded above, so
383
+ // schema inspection works before the first instance is constructed.
384
+ // Keep both runtime initializers as fallbacks: addInitializer covers
385
+ // spec-compliant runtimes, while the returned field initializer covers
386
+ // runtimes that do not fire field-decorator addInitializer. All paths
387
+ // register the same metadata idempotently through mergeNamespace.
357
388
  context.addInitializer(function initializeSchemaField()
358
389
  {
359
390
  defineFieldMetadata(this.constructor, context.name, namespace, value);
@@ -373,47 +404,47 @@ function fieldDecorator(namespace, value)
373
404
  }
374
405
 
375
406
  defineFieldMetadata(Constructor, contextOrFieldName, namespace, value);
376
- };
407
+ }, namespace, value);
377
408
  }
378
409
 
379
- function classDefinitionDecorator(definition)
380
- {
381
- return function schemaClassDefinitionDecorator(value, context)
382
- {
383
- if (context && typeof context === "object")
384
- {
385
- if (context.kind !== "class") throw new TypeError("CjsSchema type.define only supports classes.");
386
- registerStage3FieldMetadata(value, context.metadata);
387
- defineClassMetadata(value, normalizeClassDefinition(value, definition));
388
- return;
389
- }
410
+ function classDefinitionDecorator(definition)
411
+ {
412
+ return function schemaClassDefinitionDecorator(value, context)
413
+ {
414
+ if (context && typeof context === "object")
415
+ {
416
+ if (context.kind !== "class") throw new TypeError("CjsSchema type.define only supports classes.");
417
+ registerStage3FieldMetadata(value, context.metadata);
418
+ defineClassMetadata(value, normalizeClassDefinition(value, definition));
419
+ return;
420
+ }
390
421
 
391
422
  if (typeof value !== "function")
392
423
  {
393
424
  throw new TypeError("CjsSchema type.define requires a class constructor.");
394
425
  }
395
426
 
396
- defineClassMetadata(value, normalizeClassDefinition(value, definition));
397
- };
398
- }
399
-
400
- function hiddenInheritedFieldsDecorator(fieldNames)
401
- {
402
- return function schemaHiddenInheritedFieldsDecorator(value, context)
403
- {
404
- if (context && typeof context === "object")
405
- {
406
- if (context.kind !== "class") throw new TypeError("CjsSchema.hideInherited only supports classes.");
407
- registerStage3FieldMetadata(value, context.metadata);
408
- }
409
- else if (typeof value !== "function")
410
- {
411
- throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
412
- }
413
-
414
- defineHiddenInheritedFields(value, fieldNames);
415
- };
416
- }
427
+ defineClassMetadata(value, normalizeClassDefinition(value, definition));
428
+ };
429
+ }
430
+
431
+ function hiddenInheritedFieldsDecorator(fieldNames)
432
+ {
433
+ return function schemaHiddenInheritedFieldsDecorator(value, context)
434
+ {
435
+ if (context && typeof context === "object")
436
+ {
437
+ if (context.kind !== "class") throw new TypeError("CjsSchema.hideInherited only supports classes.");
438
+ registerStage3FieldMetadata(value, context.metadata);
439
+ }
440
+ else if (typeof value !== "function")
441
+ {
442
+ throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
443
+ }
444
+
445
+ defineHiddenInheritedFields(value, fieldNames);
446
+ };
447
+ }
417
448
 
418
449
  const CONTEXT_FIRST_PARAMETER = /^\(?\s*_?(context|updateContext)\b/;
419
450
 
@@ -436,7 +467,7 @@ function assertContextFirstMethod(fn, methodName)
436
467
 
437
468
  function methodDecorator(namespace, value)
438
469
  {
439
- return function schemaMethodDecorator(targetOrValue, contextOrMethodName)
470
+ return describeDecorator(function schemaMethodDecorator(targetOrValue, contextOrMethodName)
440
471
  {
441
472
  if (contextOrMethodName && typeof contextOrMethodName === "object")
442
473
  {
@@ -458,14 +489,14 @@ function methodDecorator(namespace, value)
458
489
  }
459
490
 
460
491
  defineMethodMetadata(Constructor, contextOrMethodName, namespace, value);
461
- };
492
+ }, namespace, value);
462
493
  }
463
494
 
464
495
  function memberDecorator(namespace, value)
465
496
  {
466
497
  const forMethods = methodDecorator(namespace, value);
467
498
  const forFields = fieldDecorator(namespace, value);
468
- return function schemaMemberDecorator(targetOrValue, contextOrMemberName)
499
+ return describeDecorator(function schemaMemberDecorator(targetOrValue, contextOrMemberName)
469
500
  {
470
501
  if (contextOrMemberName && typeof contextOrMemberName === "object")
471
502
  {
@@ -479,7 +510,7 @@ function memberDecorator(namespace, value)
479
510
  return targetOrValue && contextOrMemberName && typeof targetOrValue[contextOrMemberName] === "function"
480
511
  ? forMethods(targetOrValue, contextOrMemberName)
481
512
  : forFields(targetOrValue, contextOrMemberName);
482
- };
513
+ }, namespace, value);
483
514
  }
484
515
 
485
516
  function defineFieldMetadata(Constructor, fieldName, namespace, value)
@@ -523,10 +554,10 @@ function defineMethodMetadata(Constructor, methodName, namespace, value)
523
554
  defineMemberMetadata(Constructor, "methods", "methodsByName", methodName, namespace, value);
524
555
  }
525
556
 
526
- function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, value)
527
- {
528
- const schema = getOrCreateClassSchema(Constructor);
529
- let item = schema[mapKey].get(name);
557
+ function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, value)
558
+ {
559
+ const schema = getOrCreateClassSchema(Constructor);
560
+ let item = schema[mapKey].get(name);
530
561
 
531
562
  if (!item)
532
563
  {
@@ -534,188 +565,324 @@ function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, val
534
565
  schema[listKey].push(item);
535
566
  schema[mapKey].set(name, item);
536
567
  }
537
-
538
- item[namespace] = mergeNamespace(item[namespace], value);
539
- }
540
-
541
- function defineHiddenInheritedFields(Constructor, fieldNames)
542
- {
543
- if (typeof Constructor !== "function")
544
- {
545
- throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
546
- }
547
-
548
- const Parent = Object.getPrototypeOf(Constructor);
549
- const inheritedFields = new Set(getEffectiveFields(Parent).map(field => field.name));
550
- const className = CLASS_SCHEMA.get(Constructor)?.className || Constructor.name || "<anonymous>";
551
-
552
- for (const fieldName of fieldNames)
553
- {
554
- if (!inheritedFields.has(fieldName))
555
- {
556
- throw new TypeError(
557
- `CjsSchema.hideInherited cannot hide "${fieldName}" on ${className}: ` +
558
- "the parent schema does not expose that field."
559
- );
560
- }
561
- }
562
-
563
- const schema = getOrCreateClassSchema(Constructor);
564
- for (const fieldName of fieldNames)
565
- {
566
- schema.hiddenInherited.add(fieldName);
567
- }
568
- }
569
-
570
- function getEffectiveFields(Constructor)
571
- {
572
- const ordered = [];
573
- const byName = new Map();
574
- const hidden = new Set();
575
-
576
- for (const current of getSchemaLineage(Constructor))
577
- {
578
- const schema = CLASS_SCHEMA.get(current);
579
- for (const field of schema?.fields || [])
580
- {
581
- const existing = byName.get(field.name);
582
- if (existing)
583
- {
584
- mergeMemberMetadata(existing, field);
585
- }
586
- else
587
- {
588
- const merged = mergeMemberMetadata({ name: field.name }, field);
589
- ordered.push(merged);
590
- byName.set(field.name, merged);
591
- }
592
- }
593
-
594
- for (const fieldName of schema?.hiddenInherited || [])
595
- {
596
- hidden.add(fieldName);
597
- }
598
- }
599
-
600
- return ordered.filter(field => !hidden.has(field.name));
601
- }
602
-
603
- function getHiddenInheritedFieldNames(Constructor)
604
- {
605
- const hidden = new Set();
606
- for (const current of getSchemaLineage(Constructor))
607
- {
608
- for (const fieldName of CLASS_SCHEMA.get(current)?.hiddenInherited || [])
609
- {
610
- hidden.add(fieldName);
611
- }
612
- }
613
- return hidden;
614
- }
615
-
616
- function getSchemaLineage(Constructor)
617
- {
618
- const lineage = [];
619
- let current = Constructor;
620
- while (typeof current === "function")
621
- {
622
- if (CLASS_SCHEMA.has(current)) lineage.push(current);
623
- current = Object.getPrototypeOf(current);
624
- }
625
- return lineage.reverse();
626
- }
627
-
628
- function mergeMemberMetadata(target, source)
629
- {
630
- for (const [namespace, value] of Object.entries(source))
631
- {
632
- if (namespace === "name") continue;
633
- target[namespace] = mergeNamespace(target[namespace], value);
634
- }
635
- return target;
636
- }
637
-
638
- function getOrCreateClassSchema(Constructor)
639
- {
640
- let schema = CLASS_SCHEMA.get(Constructor);
641
- if (!schema)
568
+
569
+ item[namespace] = mergeNamespace(item[namespace], value);
570
+ }
571
+
572
+ function defineHiddenInheritedFields(Constructor, fieldNames)
573
+ {
574
+ if (typeof Constructor !== "function")
575
+ {
576
+ throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
577
+ }
578
+
579
+ const Parent = Object.getPrototypeOf(Constructor);
580
+ const inheritedFields = new Set(getEffectiveFields(Parent).map(field => field.name));
581
+ // Declared name only: Constructor.name does not survive minification, and a
582
+ // mangled name in an error reads as a real one and sends you chasing it.
583
+ const className = CLASS_SCHEMA.get(Constructor)?.className || "<undeclared>";
584
+
585
+ for (const fieldName of fieldNames)
586
+ {
587
+ if (!inheritedFields.has(fieldName))
588
+ {
589
+ throw new TypeError(
590
+ `CjsSchema.hideInherited cannot hide "${fieldName}" on ${className}: ` +
591
+ "the parent schema does not expose that field."
592
+ );
593
+ }
594
+ }
595
+
596
+ const schema = getOrCreateClassSchema(Constructor);
597
+ for (const fieldName of fieldNames)
598
+ {
599
+ schema.hiddenInherited.add(fieldName);
600
+ }
601
+ }
602
+
603
+ function getEffectiveFields(Constructor)
604
+ {
605
+ const ordered = [];
606
+ const byName = new Map();
607
+ const hidden = new Set();
608
+
609
+ for (const current of getSchemaLineage(Constructor))
610
+ {
611
+ const schema = CLASS_SCHEMA.get(current);
612
+ for (const field of schema?.fields || [])
613
+ {
614
+ const existing = byName.get(field.name);
615
+ if (existing)
616
+ {
617
+ mergeMemberMetadata(existing, field);
618
+ }
619
+ else
620
+ {
621
+ const merged = mergeMemberMetadata({ name: field.name }, field);
622
+ ordered.push(merged);
623
+ byName.set(field.name, merged);
624
+ }
625
+ }
626
+
627
+ for (const fieldName of schema?.hiddenInherited || [])
628
+ {
629
+ hidden.add(fieldName);
630
+ }
631
+ }
632
+
633
+ return ordered.filter(field => !hidden.has(field.name));
634
+ }
635
+
636
+ function getHiddenInheritedFieldNames(Constructor)
637
+ {
638
+ const hidden = new Set();
639
+ for (const current of getSchemaLineage(Constructor))
640
+ {
641
+ for (const fieldName of CLASS_SCHEMA.get(current)?.hiddenInherited || [])
642
+ {
643
+ hidden.add(fieldName);
644
+ }
645
+ }
646
+ return hidden;
647
+ }
648
+
649
+ function getSchemaLineage(Constructor)
650
+ {
651
+ const lineage = [];
652
+ let current = Constructor;
653
+ while (typeof current === "function")
654
+ {
655
+ if (CLASS_SCHEMA.has(current)) lineage.push(current);
656
+ current = Object.getPrototypeOf(current);
657
+ }
658
+ return lineage.reverse();
659
+ }
660
+
661
+ function mergeMemberMetadata(target, source)
662
+ {
663
+ for (const [namespace, value] of Object.entries(source))
664
+ {
665
+ if (namespace === "name") continue;
666
+ target[namespace] = mergeNamespace(target[namespace], value);
667
+ }
668
+ return target;
669
+ }
670
+
671
+ function buildSchema(Constructor, namespaces)
672
+ {
673
+ const schema = CLASS_SCHEMA.get(Constructor);
674
+ const fields = [];
675
+ const methods = [];
676
+
677
+ for (const field of getEffectiveFields(Constructor))
678
+ {
679
+ fields.push(enrichEnumField(exportField(field, namespaces), Constructor));
680
+ }
681
+
682
+ // KNOWN DEFECT: methods are read from this class only, while fields resolve
683
+ // through the whole lineage above. A subclass therefore reports no inherited
684
+ // methods, and the decorated form hides that behind a second bug that
685
+ // cancels it out: method decorators register through addInitializer, where
686
+ // `this.constructor` is the *instance's* class, so constructing one
687
+ // Tr2LightProfileRes writes CjsResource's methods onto Tr2LightProfileRes.
688
+ // Which class owns which methods then depends on construction order, and
689
+ // before any instance exists a class reports no methods at all.
690
+ //
691
+ // Declaring metadata as data (CjsSchema.define fields/methods) registers on
692
+ // the declaring class at module load, so it is deterministic - and it makes
693
+ // the missing inheritance visible rather than accidentally papered over.
694
+ //
695
+ // Left unfixed deliberately: nothing reads .methods off a runtime schema
696
+ // today (tools-core classTool parses source documents, not these), and the
697
+ // fix - walking the lineage here as getEffectiveFields does - changes
698
+ // exported schemas for every decorator-using class in the org.
699
+ for (const method of schema?.methods || [])
700
+ {
701
+ methods.push(exportField(method, namespaces));
702
+ }
703
+
704
+ const result = {
705
+ className: CjsSchema.getClassName(Constructor),
706
+ fields
707
+ };
708
+
709
+ const family = schema?.family || CjsSchema.getClassFamily(Constructor);
710
+ if (family)
711
+ {
712
+ result.family = family;
713
+ }
714
+
715
+ if (schema?.sourceClass && schema.sourceClass !== result.className)
716
+ {
717
+ result.sourceClass = schema.sourceClass;
718
+ }
719
+
720
+ if (schema?.aliases?.length)
721
+ {
722
+ result.aliases = [ ...schema.aliases ];
723
+ }
724
+
725
+ if (methods.length) result.methods = methods;
726
+
727
+ addSchemaBuckets(result);
728
+ return result;
729
+ }
730
+
731
+
732
+ // Kinds that hold many values rather than one. `map` and `set` are included
733
+ // because they are iterable collections of the referenced class, same as a list.
734
+ const MANY_KINDS = new Set([ "list", "array", "set", "map" ]);
735
+
736
+ // Kinds that can hold a child model. Bucketing on the KIND rather than on a
737
+ // resolvable class reference keeps traversal conservative: `type.struct(Class)`
738
+ // drops the reference during normalization, and a raw defineField may omit the
739
+ // item type, so requiring a reference would silently stop visiting those.
740
+ // Scalar and math kinds are excluded, which is where the saving comes from.
741
+ const MODEL_KINDS = new Set([
742
+ "struct", "model", "rawStruct", "objectRef", "unknown",
743
+ "list", "array", "set", "map"
744
+ ]);
745
+
746
+
747
+ /**
748
+ * Precompute the answers consumers would otherwise recompute per traversal.
749
+ *
750
+ * Graph walks ask the same two questions of every node - which fields hold
751
+ * child models, which hold resources - and answering them by scanning the field
752
+ * list and type-testing each value costs more than the walk itself. The class
753
+ * cannot change without rebuilding its schema, so this is solved once.
754
+ */
755
+ function addSchemaBuckets(schema)
756
+ {
757
+ const byName = new Map();
758
+ const children = [];
759
+ const resources = [];
760
+
761
+ for (const field of schema.fields)
762
+ {
763
+ byName.set(field.name, field);
764
+
765
+ const type = field.type;
766
+ // An undeclared field could hold anything, so it stays traversable.
767
+ const kind = type?.kind;
768
+ if (kind && !MODEL_KINDS.has(kind)) continue;
769
+
770
+ const entry = { name: field.name, many: MANY_KINDS.has(kind) };
771
+
772
+ if (type && resolveFieldClass(type)?.isResource === true)
773
+ {
774
+ resources.push(entry);
775
+ continue;
776
+ }
777
+
778
+ entry.owned = field.io?.ownership === "owned";
779
+ children.push(entry);
780
+ }
781
+
782
+ schema.byName = byName;
783
+ schema.children = children;
784
+ schema.resources = resources;
785
+ }
786
+
787
+
788
+ // Only string references survive normalization - type.struct(SomeClass) drops
789
+ // the reference entirely - so a field can only be bucketed when it names a
790
+ // class. The one field in the tree that named nothing was a missing
791
+ // declaration, not a deliberate escape.
792
+ function resolveFieldClass(type)
793
+ {
794
+ const ref = type.className || type.itemType || type.valueType;
795
+ return typeof ref === "string" && ref ? CONSTRUCTOR_BY_NAME.get(ref) || null : null;
796
+ }
797
+
798
+
799
+ function getOrCreateClassSchema(Constructor)
800
+ {
801
+ // The only route by which class metadata is mutated, and therefore the only
802
+ // place exported schemas can go stale. A single global counter rather than
803
+ // per-class invalidation because a base class change invalidates every
804
+ // subclass, and lineage is not tracked in reverse.
805
+ SCHEMA_GENERATION += 1;
806
+
807
+ let schema = CLASS_SCHEMA.get(Constructor);
808
+ if (!schema)
642
809
  {
643
810
  schema = {
644
811
  className: null,
645
812
  family: null,
646
813
  sourceClass: null,
647
- aliases: null,
648
- fields: [],
649
- fieldsByName: new Map(),
650
- hiddenInherited: new Set(),
651
- methods: [],
652
- methodsByName: new Map()
653
- };
814
+ aliases: null,
815
+ fields: [],
816
+ fieldsByName: new Map(),
817
+ hiddenInherited: new Set(),
818
+ methods: [],
819
+ methodsByName: new Map()
820
+ };
654
821
  CLASS_SCHEMA.set(Constructor, schema);
655
822
  }
656
- return schema;
657
- }
658
-
659
- function normalizeHiddenInheritedFields(fieldNames)
660
- {
661
- if (!Array.isArray(fieldNames) || !fieldNames.length)
662
- {
663
- throw new TypeError("CjsSchema.hideInherited requires a non-empty array of field names.");
664
- }
665
-
666
- const normalized = fieldNames.map((fieldName, index) =>
667
- {
668
- if (typeof fieldName !== "string" || !fieldName.trim())
669
- {
670
- throw new TypeError(`CjsSchema.hideInherited fieldNames[${index}] must be a non-empty string.`);
671
- }
672
- return fieldName.trim();
673
- });
674
-
675
- return Object.freeze([...new Set(normalized)]);
676
- }
677
-
678
- function recordStage3FieldMetadata(context, namespace, value)
679
- {
680
- const metadata = context?.metadata;
681
- if (!metadata || typeof metadata !== "object") return;
682
-
683
- let fields;
684
- if (Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA))
685
- {
686
- fields = metadata[STAGE3_FIELD_METADATA];
687
- }
688
- else
689
- {
690
- fields = [];
691
- Object.defineProperty(metadata, STAGE3_FIELD_METADATA, {
692
- configurable: false,
693
- enumerable: false,
694
- value: fields,
695
- writable: false
696
- });
697
- }
698
-
699
- fields.push({
700
- name: context.name,
701
- namespace,
702
- value
703
- });
704
- }
705
-
706
- function registerStage3FieldMetadata(Constructor, metadata)
707
- {
708
- if (!metadata || typeof metadata !== "object") return;
709
- if (!Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA)) return;
710
-
711
- for (const field of metadata[STAGE3_FIELD_METADATA])
712
- {
713
- defineFieldMetadata(Constructor, field.name, field.namespace, field.value);
714
- }
715
- }
716
-
717
- function normalizeClassDefinition(Constructor, definition)
718
- {
823
+ return schema;
824
+ }
825
+
826
+ function normalizeHiddenInheritedFields(fieldNames)
827
+ {
828
+ if (!Array.isArray(fieldNames) || !fieldNames.length)
829
+ {
830
+ throw new TypeError("CjsSchema.hideInherited requires a non-empty array of field names.");
831
+ }
832
+
833
+ const normalized = fieldNames.map((fieldName, index) =>
834
+ {
835
+ if (typeof fieldName !== "string" || !fieldName.trim())
836
+ {
837
+ throw new TypeError(`CjsSchema.hideInherited fieldNames[${index}] must be a non-empty string.`);
838
+ }
839
+ return fieldName.trim();
840
+ });
841
+
842
+ return Object.freeze([...new Set(normalized)]);
843
+ }
844
+
845
+ function recordStage3FieldMetadata(context, namespace, value)
846
+ {
847
+ const metadata = context?.metadata;
848
+ if (!metadata || typeof metadata !== "object") return;
849
+
850
+ let fields;
851
+ if (Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA))
852
+ {
853
+ fields = metadata[STAGE3_FIELD_METADATA];
854
+ }
855
+ else
856
+ {
857
+ fields = [];
858
+ Object.defineProperty(metadata, STAGE3_FIELD_METADATA, {
859
+ configurable: false,
860
+ enumerable: false,
861
+ value: fields,
862
+ writable: false
863
+ });
864
+ }
865
+
866
+ fields.push({
867
+ name: context.name,
868
+ namespace,
869
+ value
870
+ });
871
+ }
872
+
873
+ function registerStage3FieldMetadata(Constructor, metadata)
874
+ {
875
+ if (!metadata || typeof metadata !== "object") return;
876
+ if (!Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA)) return;
877
+
878
+ for (const field of metadata[STAGE3_FIELD_METADATA])
879
+ {
880
+ defineFieldMetadata(Constructor, field.name, field.namespace, field.value);
881
+ }
882
+ }
883
+
884
+ function normalizeClassDefinition(Constructor, definition)
885
+ {
719
886
  if (typeof definition === "string")
720
887
  {
721
888
  definition = { className: definition };
@@ -740,27 +907,98 @@ function normalizeClassDefinition(Constructor, definition)
740
907
  return result;
741
908
  }
742
909
 
910
+ /**
911
+ * Normalize a definition's `fields`/`methods` into internal member records.
912
+ *
913
+ * Two spellings are accepted. A name-keyed object is the one to write:
914
+ * declaration order is key order, which is what drives GetValues() export
915
+ * order, and the name appears once rather than as a property of its own record.
916
+ * The array of `{name, ...}` records predates it and still parses, because it
917
+ * is what the internal schema stores.
918
+ *
919
+ * @param {object|Array<object>|null} members Declared members.
920
+ * @param {string} memberType Either "fields" or "methods", for messages.
921
+ * @returns {Array<object>} Internal member records.
922
+ */
743
923
  function normalizeManualMembers(members, memberType)
744
924
  {
745
925
  if (members === undefined || members === null) return [];
746
- if (!Array.isArray(members))
926
+
927
+ if (Array.isArray(members))
747
928
  {
748
- throw new TypeError(`CjsSchema.define ${memberType} must be an array.`);
929
+ return members.map((member, index) =>
930
+ {
931
+ if (!isPlainObject(member) || typeof member.name !== "string" || !member.name.trim())
932
+ {
933
+ throw new TypeError(`CjsSchema.define ${memberType}[${index}] requires a non-empty name.`);
934
+ }
935
+ const { name, ...namespaces } = member;
936
+ return normalizeManualMember(name.trim(), namespaces, memberType);
937
+ });
938
+ }
939
+
940
+ if (!isPlainObject(members))
941
+ {
942
+ throw new TypeError(
943
+ `CjsSchema.define ${memberType} must be a name-keyed object or an array of named records.`
944
+ );
749
945
  }
750
946
 
751
- return members.map((member, index) =>
947
+ return Object.entries(members).map(([ name, definition ]) =>
752
948
  {
753
- if (!isPlainObject(member) || typeof member.name !== "string" || !member.name.trim())
949
+ if (!name.trim())
754
950
  {
755
- throw new TypeError(`CjsSchema.define ${memberType}[${index}] requires a non-empty name.`);
951
+ throw new TypeError(`CjsSchema.define ${memberType} requires a non-empty name.`);
756
952
  }
757
- return {
758
- ...member,
759
- name: member.name.trim()
760
- };
953
+ return normalizeManualMember(name.trim(), definition, memberType);
761
954
  });
762
955
  }
763
956
 
957
+ /**
958
+ * Collapse one member's declaration into a namespace map.
959
+ *
960
+ * A declaration is a decorator, a namespace object, or an array mixing both.
961
+ * Decorators are accepted as data so the object form reuses the vocabulary the
962
+ * decorators already define rather than restating it: `impl.adapted` written
963
+ * twice in two spellings is two things that can disagree.
964
+ *
965
+ * @param {string} name Member name.
966
+ * @param {*} definition Declared metadata.
967
+ * @param {string} memberType Either "fields" or "methods", for messages.
968
+ * @returns {object} Internal member record.
969
+ */
970
+ function normalizeManualMember(name, definition, memberType)
971
+ {
972
+ const member = { name };
973
+
974
+ for (const entry of Array.isArray(definition) ? definition : [ definition ])
975
+ {
976
+ if (entry === undefined || entry === null) continue;
977
+
978
+ const decorator = getDecoratorMetadata(entry);
979
+ if (decorator)
980
+ {
981
+ member[decorator.namespace] = mergeNamespace(member[decorator.namespace], decorator.value);
982
+ continue;
983
+ }
984
+
985
+ if (!isPlainObject(entry))
986
+ {
987
+ throw new TypeError(
988
+ `CjsSchema.define ${memberType} "${name}" accepts schema decorators, ` +
989
+ "namespace objects, or an array of them."
990
+ );
991
+ }
992
+
993
+ for (const [ namespace, value ] of Object.entries(entry))
994
+ {
995
+ member[namespace] = mergeNamespace(member[namespace], value);
996
+ }
997
+ }
998
+
999
+ return member;
1000
+ }
1001
+
764
1002
  function registerClassMetadata(Constructor, schema)
765
1003
  {
766
1004
  if (!Constructor || !schema?.className) return;
@@ -937,9 +1175,8 @@ function componentIndex(char)
937
1175
 
938
1176
  function mergeNamespace(existing, value)
939
1177
  {
940
- if (!existing) return cloneSchemaValue(value);
941
- if (isPlainObject(existing) && isPlainObject(value)) return Object.freeze({ ...existing, ...value });
942
- return cloneSchemaValue(value);
1178
+ if (isPlainObject(existing) && isPlainObject(value)) return { ...existing, ...value };
1179
+ return value;
943
1180
  }
944
1181
 
945
1182
  // Resolves @schema.enum("X") through the owning class's PascalCase static so
@@ -984,10 +1221,10 @@ function exportField(field, namespaces)
984
1221
  {
985
1222
  if (key === "name") continue;
986
1223
  if (namespaces && !namespaces.has(key)) continue;
987
- result[key] = cloneSchemaValue(value);
1224
+ result[key] = value;
988
1225
  }
989
1226
 
990
- return Object.freeze(result);
1227
+ return result;
991
1228
  }
992
1229
 
993
1230
  function normalizeNamespaces(namespaces)