@carbonenginejs/runtime-utils 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -7
- package/THIRD-PARTY-NOTICES.md +30 -4
- package/docs/README.md +10 -7
- package/docs/architecture.md +7 -6
- package/docs/concepts/foundation-consolidation.md +20 -21
- package/docs/const-kb.md +18 -11
- package/docs/core-types/DECORATOR-TODOS.md +1 -1
- package/docs/reference/api.md +43 -1
- package/docs/reference/classes/README.md +20 -0
- package/package.json +8 -1
- package/src/constants/index.js +1 -0
- package/src/constants/trinity.js +13 -0
- package/src/errors/CjsError.js +286 -0
- package/src/errors/index.js +5 -0
- package/src/index.js +4 -0
- package/src/is.js +71 -9
- package/src/mesh.js +37 -7
- package/src/model/CjsModel.js +61 -16
- package/src/object.js +39 -0
- package/src/path.js +32 -0
- package/src/schema/CjsSchema.js +416 -297
- package/src/vec3.js +16 -0
package/src/schema/CjsSchema.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
const CLASS_SCHEMA = new WeakMap();
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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");
|
|
6
13
|
|
|
7
14
|
export const CJS_ENUM_NAME = Symbol.for("carbonenginejs.enum.name");
|
|
8
15
|
|
|
@@ -56,32 +63,32 @@ export class CjsSchema
|
|
|
56
63
|
return this;
|
|
57
64
|
}
|
|
58
65
|
|
|
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
|
-
}
|
|
66
|
+
static defineMethod(Constructor, methodName, namespace, value)
|
|
67
|
+
{
|
|
68
|
+
defineMethodMetadata(Constructor, methodName, namespace, value);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static getField(Constructor, fieldName)
|
|
73
|
+
{
|
|
74
|
+
return getEffectiveFields(Constructor).find(field => field.name === fieldName) || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Excludes named inherited fields from the decorated class's schema surface.
|
|
79
|
+
*/
|
|
80
|
+
static hideInherited(fieldNames)
|
|
81
|
+
{
|
|
82
|
+
return hiddenInheritedFieldsDecorator(normalizeHiddenInheritedFields(fieldNames));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks whether a field is hidden from a class by its inheritance chain.
|
|
87
|
+
*/
|
|
88
|
+
static isFieldHidden(Constructor, fieldName)
|
|
89
|
+
{
|
|
90
|
+
return getHiddenInheritedFieldNames(Constructor).has(fieldName);
|
|
91
|
+
}
|
|
85
92
|
|
|
86
93
|
static getMethod(Constructor, methodName)
|
|
87
94
|
{
|
|
@@ -128,6 +135,9 @@ export class CjsSchema
|
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
CONSTRUCTOR_BY_NAME.set(name.trim(), Constructor);
|
|
138
|
+
// Buckets resolve class references by name, so a late registration
|
|
139
|
+
// changes how already-built schemas should have been bucketed.
|
|
140
|
+
SCHEMA_GENERATION += 1;
|
|
131
141
|
return this;
|
|
132
142
|
}
|
|
133
143
|
|
|
@@ -151,49 +161,40 @@ export class CjsSchema
|
|
|
151
161
|
return name ? ENUM_SCHEMA_BY_NAME.get(name) || null : null;
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
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
|
-
}
|
|
164
|
+
/**
|
|
165
|
+
* Return the exported schema for a class.
|
|
166
|
+
*
|
|
167
|
+
* The schema is the precomputed answer - collapsing the inheritance
|
|
168
|
+
* lineage and merging metadata - so building it per call would defeat its
|
|
169
|
+
* purpose. Callers traverse model graphs and ask once per node, so this is
|
|
170
|
+
* memoized per class and rebuilt only when class metadata is defined.
|
|
171
|
+
*
|
|
172
|
+
* Namespace-filtered exports are not memoized: they are a projection of the
|
|
173
|
+
* full schema requested by tooling, not the hot read path.
|
|
174
|
+
*
|
|
175
|
+
* The result is shared, not copied - treat it as read-only. It is not
|
|
176
|
+
* frozen: deep-cloning and freezing every field on the way out cost far
|
|
177
|
+
* more than the mistakes it guarded against.
|
|
178
|
+
*
|
|
179
|
+
* @param {Function} Constructor
|
|
180
|
+
* @param {object} [options={}]
|
|
181
|
+
* @param {string|Array<string>} [options.namespaces] Restricts exported metadata namespaces.
|
|
182
|
+
* @returns {object} Shared schema export.
|
|
183
|
+
*/
|
|
184
|
+
static getSchema(Constructor, options = {})
|
|
185
|
+
{
|
|
186
|
+
const namespaces = normalizeNamespaces(options.namespaces);
|
|
187
|
+
if (namespaces) return buildSchema(Constructor, namespaces);
|
|
191
188
|
|
|
192
|
-
|
|
189
|
+
const memo = SCHEMA_EXPORTS.get(Constructor);
|
|
190
|
+
if (memo && memo.generation === SCHEMA_GENERATION) return memo.schema;
|
|
193
191
|
|
|
194
|
-
|
|
192
|
+
const schema = buildSchema(Constructor, null);
|
|
193
|
+
SCHEMA_EXPORTS.set(Constructor, { generation: SCHEMA_GENERATION, schema });
|
|
194
|
+
return schema;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
|
|
197
198
|
static type = Object.freeze({
|
|
198
199
|
array: itemType => fieldDecorator("type", { kind: "array", itemType }),
|
|
199
200
|
boolean: fieldDecorator("type", { kind: "boolean" }),
|
|
@@ -338,17 +339,17 @@ function createComponentsNamespace()
|
|
|
338
339
|
return Object.freeze(components);
|
|
339
340
|
}
|
|
340
341
|
|
|
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
|
|
342
|
+
function fieldDecorator(namespace, value)
|
|
343
|
+
{
|
|
344
|
+
return function schemaFieldDecorator(targetOrValue, contextOrFieldName)
|
|
345
|
+
{
|
|
346
|
+
if (contextOrFieldName && typeof contextOrFieldName === "object")
|
|
347
|
+
{
|
|
348
|
+
const context = contextOrFieldName;
|
|
349
|
+
if (context.kind !== "field") throw new TypeError("CjsSchema decorators only support class fields.");
|
|
350
|
+
recordStage3FieldMetadata(context, namespace, value);
|
|
351
|
+
|
|
352
|
+
// Register field metadata on instance construction. addInitializer covers
|
|
352
353
|
// spec-compliant runtimes; the returned field initializer covers runtimes (e.g.
|
|
353
354
|
// Deno/SWC) that do NOT fire field-decorator addInitializer. Both register the same
|
|
354
355
|
// metadata (idempotent via mergeNamespace), so whichever the runtime honours, the
|
|
@@ -376,44 +377,44 @@ function fieldDecorator(namespace, value)
|
|
|
376
377
|
};
|
|
377
378
|
}
|
|
378
379
|
|
|
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
|
-
}
|
|
380
|
+
function classDefinitionDecorator(definition)
|
|
381
|
+
{
|
|
382
|
+
return function schemaClassDefinitionDecorator(value, context)
|
|
383
|
+
{
|
|
384
|
+
if (context && typeof context === "object")
|
|
385
|
+
{
|
|
386
|
+
if (context.kind !== "class") throw new TypeError("CjsSchema type.define only supports classes.");
|
|
387
|
+
registerStage3FieldMetadata(value, context.metadata);
|
|
388
|
+
defineClassMetadata(value, normalizeClassDefinition(value, definition));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
390
391
|
|
|
391
392
|
if (typeof value !== "function")
|
|
392
393
|
{
|
|
393
394
|
throw new TypeError("CjsSchema type.define requires a class constructor.");
|
|
394
395
|
}
|
|
395
396
|
|
|
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
|
-
}
|
|
397
|
+
defineClassMetadata(value, normalizeClassDefinition(value, definition));
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function hiddenInheritedFieldsDecorator(fieldNames)
|
|
402
|
+
{
|
|
403
|
+
return function schemaHiddenInheritedFieldsDecorator(value, context)
|
|
404
|
+
{
|
|
405
|
+
if (context && typeof context === "object")
|
|
406
|
+
{
|
|
407
|
+
if (context.kind !== "class") throw new TypeError("CjsSchema.hideInherited only supports classes.");
|
|
408
|
+
registerStage3FieldMetadata(value, context.metadata);
|
|
409
|
+
}
|
|
410
|
+
else if (typeof value !== "function")
|
|
411
|
+
{
|
|
412
|
+
throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
defineHiddenInheritedFields(value, fieldNames);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
417
418
|
|
|
418
419
|
const CONTEXT_FIRST_PARAMETER = /^\(?\s*_?(context|updateContext)\b/;
|
|
419
420
|
|
|
@@ -523,10 +524,10 @@ function defineMethodMetadata(Constructor, methodName, namespace, value)
|
|
|
523
524
|
defineMemberMetadata(Constructor, "methods", "methodsByName", methodName, namespace, value);
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, value)
|
|
527
|
-
{
|
|
528
|
-
const schema = getOrCreateClassSchema(Constructor);
|
|
529
|
-
let item = schema[mapKey].get(name);
|
|
527
|
+
function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, value)
|
|
528
|
+
{
|
|
529
|
+
const schema = getOrCreateClassSchema(Constructor);
|
|
530
|
+
let item = schema[mapKey].get(name);
|
|
530
531
|
|
|
531
532
|
if (!item)
|
|
532
533
|
{
|
|
@@ -534,188 +535,307 @@ function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, val
|
|
|
534
535
|
schema[listKey].push(item);
|
|
535
536
|
schema[mapKey].set(name, item);
|
|
536
537
|
}
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
538
|
+
|
|
539
|
+
item[namespace] = mergeNamespace(item[namespace], value);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function defineHiddenInheritedFields(Constructor, fieldNames)
|
|
543
|
+
{
|
|
544
|
+
if (typeof Constructor !== "function")
|
|
545
|
+
{
|
|
546
|
+
throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const Parent = Object.getPrototypeOf(Constructor);
|
|
550
|
+
const inheritedFields = new Set(getEffectiveFields(Parent).map(field => field.name));
|
|
551
|
+
// Declared name only: Constructor.name does not survive minification, and a
|
|
552
|
+
// mangled name in an error reads as a real one and sends you chasing it.
|
|
553
|
+
const className = CLASS_SCHEMA.get(Constructor)?.className || "<undeclared>";
|
|
554
|
+
|
|
555
|
+
for (const fieldName of fieldNames)
|
|
556
|
+
{
|
|
557
|
+
if (!inheritedFields.has(fieldName))
|
|
558
|
+
{
|
|
559
|
+
throw new TypeError(
|
|
560
|
+
`CjsSchema.hideInherited cannot hide "${fieldName}" on ${className}: ` +
|
|
561
|
+
"the parent schema does not expose that field."
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const schema = getOrCreateClassSchema(Constructor);
|
|
567
|
+
for (const fieldName of fieldNames)
|
|
568
|
+
{
|
|
569
|
+
schema.hiddenInherited.add(fieldName);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function getEffectiveFields(Constructor)
|
|
574
|
+
{
|
|
575
|
+
const ordered = [];
|
|
576
|
+
const byName = new Map();
|
|
577
|
+
const hidden = new Set();
|
|
578
|
+
|
|
579
|
+
for (const current of getSchemaLineage(Constructor))
|
|
580
|
+
{
|
|
581
|
+
const schema = CLASS_SCHEMA.get(current);
|
|
582
|
+
for (const field of schema?.fields || [])
|
|
583
|
+
{
|
|
584
|
+
const existing = byName.get(field.name);
|
|
585
|
+
if (existing)
|
|
586
|
+
{
|
|
587
|
+
mergeMemberMetadata(existing, field);
|
|
588
|
+
}
|
|
589
|
+
else
|
|
590
|
+
{
|
|
591
|
+
const merged = mergeMemberMetadata({ name: field.name }, field);
|
|
592
|
+
ordered.push(merged);
|
|
593
|
+
byName.set(field.name, merged);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
for (const fieldName of schema?.hiddenInherited || [])
|
|
598
|
+
{
|
|
599
|
+
hidden.add(fieldName);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return ordered.filter(field => !hidden.has(field.name));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getHiddenInheritedFieldNames(Constructor)
|
|
607
|
+
{
|
|
608
|
+
const hidden = new Set();
|
|
609
|
+
for (const current of getSchemaLineage(Constructor))
|
|
610
|
+
{
|
|
611
|
+
for (const fieldName of CLASS_SCHEMA.get(current)?.hiddenInherited || [])
|
|
612
|
+
{
|
|
613
|
+
hidden.add(fieldName);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return hidden;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function getSchemaLineage(Constructor)
|
|
620
|
+
{
|
|
621
|
+
const lineage = [];
|
|
622
|
+
let current = Constructor;
|
|
623
|
+
while (typeof current === "function")
|
|
624
|
+
{
|
|
625
|
+
if (CLASS_SCHEMA.has(current)) lineage.push(current);
|
|
626
|
+
current = Object.getPrototypeOf(current);
|
|
627
|
+
}
|
|
628
|
+
return lineage.reverse();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function mergeMemberMetadata(target, source)
|
|
632
|
+
{
|
|
633
|
+
for (const [namespace, value] of Object.entries(source))
|
|
634
|
+
{
|
|
635
|
+
if (namespace === "name") continue;
|
|
636
|
+
target[namespace] = mergeNamespace(target[namespace], value);
|
|
637
|
+
}
|
|
638
|
+
return target;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function buildSchema(Constructor, namespaces)
|
|
642
|
+
{
|
|
643
|
+
const schema = CLASS_SCHEMA.get(Constructor);
|
|
644
|
+
const fields = [];
|
|
645
|
+
const methods = [];
|
|
646
|
+
|
|
647
|
+
for (const field of getEffectiveFields(Constructor))
|
|
648
|
+
{
|
|
649
|
+
fields.push(enrichEnumField(exportField(field, namespaces), Constructor));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
for (const method of schema?.methods || [])
|
|
653
|
+
{
|
|
654
|
+
methods.push(exportField(method, namespaces));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const result = {
|
|
658
|
+
className: CjsSchema.getClassName(Constructor),
|
|
659
|
+
fields
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const family = schema?.family || CjsSchema.getClassFamily(Constructor);
|
|
663
|
+
if (family)
|
|
664
|
+
{
|
|
665
|
+
result.family = family;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (schema?.sourceClass && schema.sourceClass !== result.className)
|
|
669
|
+
{
|
|
670
|
+
result.sourceClass = schema.sourceClass;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (schema?.aliases?.length)
|
|
674
|
+
{
|
|
675
|
+
result.aliases = [ ...schema.aliases ];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (methods.length) result.methods = methods;
|
|
679
|
+
|
|
680
|
+
addSchemaBuckets(result);
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
// Kinds that hold many values rather than one. `map` and `set` are included
|
|
686
|
+
// because they are iterable collections of the referenced class, same as a list.
|
|
687
|
+
const MANY_KINDS = new Set([ "list", "array", "set", "map" ]);
|
|
688
|
+
|
|
689
|
+
// Kinds that can hold a child model. Bucketing on the KIND rather than on a
|
|
690
|
+
// resolvable class reference keeps traversal conservative: `type.struct(Class)`
|
|
691
|
+
// drops the reference during normalization, and a raw defineField may omit the
|
|
692
|
+
// item type, so requiring a reference would silently stop visiting those.
|
|
693
|
+
// Scalar and math kinds are excluded, which is where the saving comes from.
|
|
694
|
+
const MODEL_KINDS = new Set([
|
|
695
|
+
"struct", "model", "rawStruct", "objectRef", "unknown",
|
|
696
|
+
"list", "array", "set", "map"
|
|
697
|
+
]);
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Precompute the answers consumers would otherwise recompute per traversal.
|
|
702
|
+
*
|
|
703
|
+
* Graph walks ask the same two questions of every node - which fields hold
|
|
704
|
+
* child models, which hold resources - and answering them by scanning the field
|
|
705
|
+
* list and type-testing each value costs more than the walk itself. The class
|
|
706
|
+
* cannot change without rebuilding its schema, so this is solved once.
|
|
707
|
+
*/
|
|
708
|
+
function addSchemaBuckets(schema)
|
|
709
|
+
{
|
|
710
|
+
const byName = new Map();
|
|
711
|
+
const children = [];
|
|
712
|
+
const resources = [];
|
|
713
|
+
|
|
714
|
+
for (const field of schema.fields)
|
|
715
|
+
{
|
|
716
|
+
byName.set(field.name, field);
|
|
717
|
+
|
|
718
|
+
const type = field.type;
|
|
719
|
+
// An undeclared field could hold anything, so it stays traversable.
|
|
720
|
+
const kind = type?.kind;
|
|
721
|
+
if (kind && !MODEL_KINDS.has(kind)) continue;
|
|
722
|
+
|
|
723
|
+
const entry = { name: field.name, many: MANY_KINDS.has(kind) };
|
|
724
|
+
|
|
725
|
+
if (type && resolveFieldClass(type)?.isResource === true)
|
|
726
|
+
{
|
|
727
|
+
resources.push(entry);
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
entry.owned = field.io?.ownership === "owned";
|
|
732
|
+
children.push(entry);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
schema.byName = byName;
|
|
736
|
+
schema.children = children;
|
|
737
|
+
schema.resources = resources;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
// Only string references survive normalization - type.struct(SomeClass) drops
|
|
742
|
+
// the reference entirely - so a field can only be bucketed when it names a
|
|
743
|
+
// class. The one field in the tree that named nothing was a missing
|
|
744
|
+
// declaration, not a deliberate escape.
|
|
745
|
+
function resolveFieldClass(type)
|
|
746
|
+
{
|
|
747
|
+
const ref = type.className || type.itemType || type.valueType;
|
|
748
|
+
return typeof ref === "string" && ref ? CONSTRUCTOR_BY_NAME.get(ref) || null : null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
function getOrCreateClassSchema(Constructor)
|
|
753
|
+
{
|
|
754
|
+
// The only route by which class metadata is mutated, and therefore the only
|
|
755
|
+
// place exported schemas can go stale. A single global counter rather than
|
|
756
|
+
// per-class invalidation because a base class change invalidates every
|
|
757
|
+
// subclass, and lineage is not tracked in reverse.
|
|
758
|
+
SCHEMA_GENERATION += 1;
|
|
759
|
+
|
|
760
|
+
let schema = CLASS_SCHEMA.get(Constructor);
|
|
761
|
+
if (!schema)
|
|
642
762
|
{
|
|
643
763
|
schema = {
|
|
644
764
|
className: null,
|
|
645
765
|
family: null,
|
|
646
766
|
sourceClass: null,
|
|
647
|
-
aliases: null,
|
|
648
|
-
fields: [],
|
|
649
|
-
fieldsByName: new Map(),
|
|
650
|
-
hiddenInherited: new Set(),
|
|
651
|
-
methods: [],
|
|
652
|
-
methodsByName: new Map()
|
|
653
|
-
};
|
|
767
|
+
aliases: null,
|
|
768
|
+
fields: [],
|
|
769
|
+
fieldsByName: new Map(),
|
|
770
|
+
hiddenInherited: new Set(),
|
|
771
|
+
methods: [],
|
|
772
|
+
methodsByName: new Map()
|
|
773
|
+
};
|
|
654
774
|
CLASS_SCHEMA.set(Constructor, schema);
|
|
655
775
|
}
|
|
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
|
-
{
|
|
776
|
+
return schema;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function normalizeHiddenInheritedFields(fieldNames)
|
|
780
|
+
{
|
|
781
|
+
if (!Array.isArray(fieldNames) || !fieldNames.length)
|
|
782
|
+
{
|
|
783
|
+
throw new TypeError("CjsSchema.hideInherited requires a non-empty array of field names.");
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const normalized = fieldNames.map((fieldName, index) =>
|
|
787
|
+
{
|
|
788
|
+
if (typeof fieldName !== "string" || !fieldName.trim())
|
|
789
|
+
{
|
|
790
|
+
throw new TypeError(`CjsSchema.hideInherited fieldNames[${index}] must be a non-empty string.`);
|
|
791
|
+
}
|
|
792
|
+
return fieldName.trim();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
return Object.freeze([...new Set(normalized)]);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function recordStage3FieldMetadata(context, namespace, value)
|
|
799
|
+
{
|
|
800
|
+
const metadata = context?.metadata;
|
|
801
|
+
if (!metadata || typeof metadata !== "object") return;
|
|
802
|
+
|
|
803
|
+
let fields;
|
|
804
|
+
if (Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA))
|
|
805
|
+
{
|
|
806
|
+
fields = metadata[STAGE3_FIELD_METADATA];
|
|
807
|
+
}
|
|
808
|
+
else
|
|
809
|
+
{
|
|
810
|
+
fields = [];
|
|
811
|
+
Object.defineProperty(metadata, STAGE3_FIELD_METADATA, {
|
|
812
|
+
configurable: false,
|
|
813
|
+
enumerable: false,
|
|
814
|
+
value: fields,
|
|
815
|
+
writable: false
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
fields.push({
|
|
820
|
+
name: context.name,
|
|
821
|
+
namespace,
|
|
822
|
+
value
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function registerStage3FieldMetadata(Constructor, metadata)
|
|
827
|
+
{
|
|
828
|
+
if (!metadata || typeof metadata !== "object") return;
|
|
829
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA)) return;
|
|
830
|
+
|
|
831
|
+
for (const field of metadata[STAGE3_FIELD_METADATA])
|
|
832
|
+
{
|
|
833
|
+
defineFieldMetadata(Constructor, field.name, field.namespace, field.value);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function normalizeClassDefinition(Constructor, definition)
|
|
838
|
+
{
|
|
719
839
|
if (typeof definition === "string")
|
|
720
840
|
{
|
|
721
841
|
definition = { className: definition };
|
|
@@ -937,9 +1057,8 @@ function componentIndex(char)
|
|
|
937
1057
|
|
|
938
1058
|
function mergeNamespace(existing, value)
|
|
939
1059
|
{
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
return cloneSchemaValue(value);
|
|
1060
|
+
if (isPlainObject(existing) && isPlainObject(value)) return { ...existing, ...value };
|
|
1061
|
+
return value;
|
|
943
1062
|
}
|
|
944
1063
|
|
|
945
1064
|
// Resolves @schema.enum("X") through the owning class's PascalCase static so
|
|
@@ -984,10 +1103,10 @@ function exportField(field, namespaces)
|
|
|
984
1103
|
{
|
|
985
1104
|
if (key === "name") continue;
|
|
986
1105
|
if (namespaces && !namespaces.has(key)) continue;
|
|
987
|
-
result[key] =
|
|
1106
|
+
result[key] = value;
|
|
988
1107
|
}
|
|
989
1108
|
|
|
990
|
-
return
|
|
1109
|
+
return result;
|
|
991
1110
|
}
|
|
992
1111
|
|
|
993
1112
|
function normalizeNamespaces(namespaces)
|