@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,1544 +1,1589 @@
1
- import { coerceCarbonMathInto, exportCarbonValue, normalizeCarbonValue } from "../types/index.js";
2
- import { CjsSchema } from "../schema/index.js";
3
- import { getRuntimeState } from "../runtime/CjsRuntimeState.js";
4
- import { CjsModelState } from "./CjsModelState.js";
5
- import { CjsEventEmitter } from "./CjsEventEmitter.js";
6
-
7
- const MAX_UPDATE_PASSES = 32;
8
-
9
- /**
10
- * Shared base for schema-backed CarbonEngineJS runtime classes.
11
- *
12
- * Source fields are exported; runtime caches and bookkeeping are not.
13
- */
14
- export class CjsModel extends CjsEventEmitter
15
- {
16
- /**
17
- * Creates a schema-backed model with initialized runtime state.
18
- */
19
- constructor()
20
- {
21
- super();
22
- const className = CjsSchema.getClassName(this.constructor);
23
- if (!className)
24
- {
25
- throw new TypeError("CjsModel subclasses require an explicit CjsSchema className.");
26
- }
27
- initializeModelState(this);
28
- }
29
-
30
- /**
31
- * Exports the model's schema fields to a new plain object.
32
- *
33
- * @param {object} [options={}]
34
- * @returns {object}
35
- */
36
- GetValues(options = {})
37
- {
38
- return CjsModel.get(this, {}, options);
39
- }
40
-
41
- /**
42
- * Applies a plain value bag through the canonical schema-backed setter.
43
- *
44
- * @param {object} [values={}]
45
- * @param {object} [options={}]
46
- * @returns {Set<string>|boolean} The changed fields, or a boolean result.
47
- */
48
- SetValues(values = {}, options = {})
49
- {
50
- return CjsModel.set(this, values, options);
51
- }
52
-
53
- /**
54
- * Copies the exported fields of another model into this model.
55
- *
56
- * @param {CjsModel} value
57
- * @param {object} [options={}]
58
- * @returns {CjsModel} This model.
59
- */
60
- Copy(value, options = {})
61
- {
62
- return CjsModel.copy(this, value, options);
63
- }
64
-
65
- /**
66
- * Constructs a new model of this instance's class from its schema values.
67
- *
68
- * @param {object} [options={}]
69
- * @returns {CjsModel}
70
- */
71
- Clone(options = {})
72
- {
73
- return this.constructor.clone(this, options);
74
- }
75
-
76
- /**
77
- * Deep-merges ordered value sources and applies the result once.
78
- *
79
- * Plain objects merge recursively; arrays, typed arrays, and other values
80
- * replace the preceding value.
81
- *
82
- * @param {Array<Object|CjsModel>} [values=[]]
83
- * @param {object} [options={}]
84
- * @returns {Set<string>|boolean} The result returned by {@link CjsModel.set}.
85
- */
86
- Merge(values = [], options = {})
87
- {
88
- return CjsModel.merge(this, values, options);
89
- }
90
-
91
- /**
92
- * Applies pending changes: drives the OnModified hook until the model
93
- * settles, clears the dirty mark, and emits one final modified event.
94
- *
95
- * Calling this IS the "I made changes, apply please" contract: it always
96
- * runs at least one hook pass, dirty or not, so direct/untracked
97
- * mutations (the cooperative-pipeline reality) can be applied
98
- * explicitly. Class Update/per-frame methods typically gate on
99
- * `__state.IsDirty()` before calling.
100
- *
101
- * @param {object} [options={}]
102
- * @param {string|Iterable<string>} [options.property] Fields the caller changed directly; their declared flag/rebuild tokens are added first.
103
- * @param {string|Iterable<string>} [options.properties] Alias of `property`.
104
- * @param {*} [options.source=this] Origin forwarded to the hook and event (binding feedback control).
105
- * @param {boolean} [options.skipEvents=false] Prevents the final modified event.
106
- * @returns {boolean} False when the hook rejected the update (dirty is retained).
107
- * @throws {Error} If local changes do not settle within the update-pass limit.
108
- */
109
- UpdateValues(options = {})
110
- {
111
- addExplicitUpdateProperties(this, options.property ?? options.properties);
112
- if (this.__state.updating) return true;
113
-
114
- const source = options.source ?? this;
115
- this.__state.updating = true;
116
-
117
- try
118
- {
119
- for (let pass = 0; ; pass++)
120
- {
121
- if (pass >= MAX_UPDATE_PASSES)
122
- {
123
- throw new Error(`${CjsSchema.getClassName(this.constructor)}.UpdateValues exceeded ${MAX_UPDATE_PASSES} local settle passes.`);
124
- }
125
-
126
- this.__state.dirty = false;
127
-
128
- if (this.OnModified({ ...options, source }) === false)
129
- {
130
- this.__state.dirty = true;
131
- return false;
132
- }
133
-
134
- if (!this.__state.dirty) break;
135
- }
136
- }
137
- catch (err)
138
- {
139
- this.__state.dirty = true;
140
- throw err;
141
- }
142
- finally
143
- {
144
- this.__state.updating = false;
145
- }
146
-
147
- if (options.skipEvents !== true && this.__state.suppressEvents === 0)
148
- {
149
- this.EmitEvent("modified", this, { source });
150
- }
151
-
152
- return true;
153
- }
154
-
155
- /**
156
- * The settle hook: reproduces the meaningful consequences of the
157
- * corresponding Carbon INotify::OnModified implementation.
158
- *
159
- * Invoked only by UpdateValues. Receives the mutation options bag
160
- * (source, caller context, skipEvents, ...). There is no changed-property
161
- * list - the pipeline is cooperative and cannot guarantee one - so
162
- * overrides are written broad-safe: consult own state, compare cached
163
- * derivations, and rely on `__state.flags`/`__state.rebuild` tokens for
164
- * targeted signals. Returning `false` rejects the update and retains the
165
- * dirty mark.
166
- *
167
- * @param {object} [options={}]
168
- * @returns {boolean} Whether the update may complete.
169
- */
170
- OnModified(options = {})
171
- {
172
- return true;
173
- }
174
-
175
- /**
176
- * Visits this model and its schema-backed child models without revisiting cycles.
177
- *
178
- * In pre-order traversal, returning `false` prunes that model's descendants.
179
- * Visitor return values are ignored in post-order traversal.
180
- *
181
- * @param {function(CjsModel): (boolean|void)} visitor
182
- * @param {object} [options={}]
183
- * @param {Set<CjsModel>} [options.visited] Existing cycle-detection set.
184
- * @param {"pre"|"post"} [options.order="pre"]
185
- * @param {boolean} [options.reverse=false] Reverses field and list-item order.
186
- * @param {boolean} [options.ownedOnly=false] Traverses only owned relationships.
187
- * @returns {CjsModel} This model.
188
- * @throws {TypeError} If `visitor` is not a function.
189
- */
190
- Traverse(visitor, options = {})
191
- {
192
- if (typeof visitor !== "function")
193
- {
194
- throw new TypeError("CjsModel.Traverse requires a visitor function.");
195
- }
196
-
197
- const visited = options.visited instanceof Set ? options.visited : new Set();
198
- const order = options.order === "post" ? "post" : "pre";
199
- const reverse = options.reverse === true;
200
-
201
- const visit = model =>
202
- {
203
- if (!(model instanceof CjsModel) || visited.has(model)) return;
204
- visited.add(model);
205
-
206
- let descend = true;
207
- if (order === "pre") descend = visitor(model) !== false;
208
-
209
- if (descend)
210
- {
211
- const fields = getModelFields(model);
212
- const start = reverse ? fields.length - 1 : 0;
213
- const end = reverse ? -1 : fields.length;
214
- const step = reverse ? -1 : 1;
215
-
216
- for (let i = start; i !== end; i += step)
217
- {
218
- const field = fields[i];
219
- if (options.ownedOnly === true && field.io?.ownership !== "owned") continue;
220
- const value = model[field.name];
221
-
222
- if (Array.isArray(value))
223
- {
224
- const itemStart = reverse ? value.length - 1 : 0;
225
- const itemEnd = reverse ? -1 : value.length;
226
- for (let j = itemStart; j !== itemEnd; j += step) visit(value[j]);
227
- }
228
- else
229
- {
230
- visit(value);
231
- }
232
- }
233
- }
234
-
235
- if (order === "post") visitor(model);
236
- };
237
-
238
- visit(this);
239
- return this;
240
- }
241
-
242
- /**
243
- * Collects unique resources reported by this model graph into an array.
244
- *
245
- * @param {Array<*>} [out=[]] Output array, whose contents are replaced.
246
- * @returns {Array<*>} The supplied output array.
247
- */
248
- GetResources(out = [])
249
- {
250
- const resources = new Set();
251
- AddResources(resources, out);
252
-
253
- this.Traverse(model =>
254
- {
255
- if (typeof model.OnGetResources !== "function") return true;
256
- AddResources(resources, model.OnGetResources(resources));
257
- return false;
258
- });
259
-
260
- out.length = 0;
261
- out.push(...resources);
262
- return out;
263
- }
264
-
265
- /**
266
- * Marks the model as changed; the next settle applies it.
267
- *
268
- * The cooperative-pipeline contract: anything mutating outside
269
- * `SetValues` (direct writes, Object.assign, reader adapters) owes this
270
- * call or an explicit `UpdateValues()`.
271
- *
272
- * @returns {CjsModel} This model.
273
- */
274
- MarkDirty()
275
- {
276
- this.__state.MarkDirty();
277
- return this;
278
- }
279
-
280
- /**
281
- * Clears the dirty mark without settling. Rarely correct outside tests
282
- * and teardown - the settle clears it itself.
283
- *
284
- * @returns {CjsModel} This model.
285
- */
286
- ClearDirty()
287
- {
288
- this.__state.ClearDirty();
289
- return this;
290
- }
291
-
292
- /**
293
- * Checks whether a settle is owed.
294
- *
295
- * @returns {boolean}
296
- */
297
- IsDirty()
298
- {
299
- return this.__state.IsDirty();
300
- }
301
-
302
- /**
303
- * Gets the shared schema registry and decorator facade.
304
- *
305
- * @returns {typeof CjsSchema}
306
- */
307
- static get schema()
308
- {
309
- return CjsSchema;
310
- }
311
-
312
- /**
313
- * Exports a model's schema fields into an output object.
314
- *
315
- * All options default off, leaving the plain output identical to the
316
- * historical shape. Options propagate recursively to nested models.
317
- *
318
- * @param {CjsModel} value
319
- * @param {object} [out={}]
320
- * @param {object} [options={}]
321
- * @param {boolean} [options.persistOnly] Exports only persisted fields.
322
- * @param {boolean} [options.typeTags] Emits `_type` only where the concrete
323
- * class is not derivable from the declared field type (the root and
324
- * polymorphic slots).
325
- * @param {boolean} [options.forceTypeTags] Emits `_type` on every model.
326
- * @param {boolean} [options.refs] Tracks shared models: repeats export as
327
- * `{ _ref }` and their first occurrence carries `_id`. Also guards
328
- * against cyclic graphs.
329
- * @param {boolean} [options.forceIDs] Emits `_id` on every model.
330
- * @param {boolean} [options.keyedLists] Exports a list as a name-keyed
331
- * object when every item is a model with a unique non-empty `name`;
332
- * the redundant item `name` field is dropped in that form. Empty
333
- * lists stay arrays.
334
- * @param {string} [options.enumFormat] Enum-backed field emission:
335
- * "values" (default, numeric), "names" (exact member-name strings),
336
- * or "identity" (`[name, "OwnerClass.EnumName"]` tuples). Unknown
337
- * numeric values export as raw numbers in every mode.
338
- * @returns {object} The supplied output object.
339
- * @throws {TypeError} If the source or output target is invalid.
340
- */
341
- static get(value, out = {}, options = {})
342
- {
343
- if (!(value instanceof CjsModel))
344
- {
345
- throw new TypeError("CjsModel.get requires a CjsModel source.");
346
- }
347
-
348
- if (!out || typeof out !== "object" || Array.isArray(out) || ArrayBuffer.isView(out))
349
- {
350
- throw new TypeError("CjsModel.get requires an object output target.");
351
- }
352
-
353
- if (!hasAdvancedExportOptions(options))
354
- {
355
- for (const field of getModelFields(value))
356
- {
357
- out[field.name] = exportSourceValue(value[field.name], options);
358
- }
359
-
360
- return out;
361
- }
362
-
363
- return exportModelInto(value, out, null, options, createExportContext(value, options));
364
- }
365
-
366
- /**
367
- * Applies schema-backed values to a model and processes resulting updates.
368
- *
369
- * Reserved metadata keys are honored, never treated as fields: a string
370
- * `values._type` must name the target's class or one of its base classes;
371
- * `values._id` registers the target for `{ _ref }` resolution; a
372
- * `{ _ref }` incoming field value resolves to the registered instance
373
- * (shared identity) and throws when the id never resolves.
374
- *
375
- * @param {CjsModel} out
376
- * @param {object} [values={}]
377
- * @param {object} [options={}]
378
- * @param {boolean} [options.markDirty=true] Tracks changed properties and notification flags.
379
- * @param {boolean} [options.notify=true] Tracks schema notification flags.
380
- * @param {boolean} [options.skipUpdate=false] Leaves dirty changes unsettled.
381
- * @param {boolean} [options.skipEvents=false] Suppresses direct modified events.
382
- * @param {boolean} [options.returnBoolean=false] Returns a boolean instead of changed fields.
383
- * @param {*} [options.source=out] Origin included in update callbacks and events.
384
- * @returns {Set<string>|boolean} Changed fields, or a boolean result.
385
- * @throws {TypeError} If the target is not a model.
386
- */
387
- static set(out, values = {}, options = {})
388
- {
389
- if (!(out instanceof CjsModel))
390
- {
391
- throw new TypeError("CjsModel.set requires a CjsModel target.");
392
- }
393
-
394
- if (!values || typeof values !== "object") return false;
395
-
396
- if (typeof values._type === "string")
397
- {
398
- assertTargetTypeMatches(out, values._type, options);
399
- }
400
-
401
- // One import operation context is shared across the whole call tree so
402
- // `_id` registrations and `{ _ref }` resolutions see the same identity
403
- // table. The outermost call owns finalization of forward references.
404
- const ownsImportContext = !options.importContext;
405
- const importOptions = {
406
- ...options,
407
- importContext: options.importContext ?? createImportContext(),
408
- ownerConstructor: out.constructor
409
- };
410
-
411
- if (values._id !== undefined && values._id !== null)
412
- {
413
- importOptions.importContext.register(values._id, out);
414
- }
415
-
416
- const enumTranslations = validateEnumInputs(out, values);
417
-
418
- const changed = new Set();
419
- for (const field of getModelFields(out))
420
- {
421
- if (!isWritableModelField(field)) continue;
422
-
423
- const key = findIncomingKey(values, field);
424
- if (key !== null)
425
- {
426
- const oldValue = out[field.name];
427
- const incoming = enumTranslations.has(field.name) ? enumTranslations.get(field.name) : values[key];
428
-
429
- let didChange;
430
- if (isReferenceValue(incoming))
431
- {
432
- didChange = applyIncomingReference(out, field, incoming, importOptions);
433
- }
434
- else
435
- {
436
- // Registered struct fields have value semantics. Constructors may
437
- // install their canonical struct instance up front; populate that
438
- // instance rather than replacing it with an imported object.
439
- const structChanged = applyIncomingStructInPlace(oldValue, incoming, field, importOptions);
440
-
441
- if (structChanged !== null)
442
- {
443
- didChange = field.io?.always === true || structChanged;
444
- }
445
- else
446
- {
447
- // Fast path: a math field with an existing compatible typed array
448
- // is coerced IN PLACE (no allocation, buffer reference preserved).
449
- const mathChanged = coerceCarbonMathInto(oldValue, incoming, field);
450
-
451
- if (mathChanged !== null)
452
- {
453
- didChange = field.io?.always === true || mathChanged;
454
- }
455
- else
456
- {
457
- const newValue = importSourceValue(incoming, field, importOptions);
458
- didChange = field.io?.always === true || !areEquivalentSourceValues(oldValue, newValue);
459
- if (didChange) out[field.name] = newValue;
460
- }
461
- }
462
- }
463
-
464
- if (didChange)
465
- {
466
- changed.add(field.name);
467
- if (options.markDirty !== false)
468
- {
469
- out.__state.dirty = true;
470
- // Write-time token adds: the knowledge of WHICH field
471
- // changed lives here, so declared consequences land
472
- // here (props are not tracked in state).
473
- if (options.notify !== false)
474
- {
475
- addDeclaredFieldTokens(out, field);
476
- }
477
- }
478
- }
479
- }
480
- }
481
-
482
- if (ownsImportContext)
483
- {
484
- importOptions.importContext.finalize();
485
- importOptions.importContext.initializeCreated(importOptions);
486
- }
487
-
488
- if (changed.size && options.markDirty === false)
489
- {
490
- if (options.skipUpdate !== true && options.skipEvents !== true && out.__state.suppressEvents === 0)
491
- {
492
- out.EmitEvent("modified", out, createModifiedPayload(changed, options.source ?? out));
493
- }
494
- }
495
- else if (changed.size && options.skipUpdate !== true && !out.__state.updating)
496
- {
497
- out.UpdateValues(options);
498
- }
499
-
500
- return options.returnBoolean === true ? changed.size > 0 : changed.size ? changed : false;
501
- }
502
-
503
- /**
504
- * Copies all exported fields from one model into another.
505
- *
506
- * @param {CjsModel} out
507
- * @param {CjsModel} value
508
- * @param {object} [options={}]
509
- * @returns {CjsModel} The target model.
510
- * @throws {TypeError} If either argument is not a model.
511
- */
512
- static copy(out, value, options = {})
513
- {
514
- if (!(out instanceof CjsModel))
515
- {
516
- throw new TypeError("CjsModel.copy requires a CjsModel target.");
517
- }
518
-
519
- if (!(value instanceof CjsModel))
520
- {
521
- throw new TypeError("CjsModel.copy requires a CjsModel source.");
522
- }
523
-
524
- CjsModel.set(out, CjsModel.get(value, {}, options), options);
525
- return out;
526
- }
527
-
528
- /**
529
- * Deep-merges ordered value sources and applies the result with one set call.
530
- *
531
- * @param {CjsModel} out
532
- * @param {Array<object|CjsModel>} [values=[]]
533
- * @param {object} [options={}]
534
- * @returns {Set<string>|boolean} The result returned by {@link CjsModel.set}.
535
- * @throws {TypeError} If the target, source array, or options are invalid.
536
- */
537
- static merge(out, values = [], options = {})
538
- {
539
- if (!(out instanceof CjsModel))
540
- {
541
- throw new TypeError("CjsModel.merge requires a CjsModel target.");
542
- }
543
-
544
- if (!Array.isArray(values))
545
- {
546
- throw new TypeError("CjsModel.merge requires an array of value sources.");
547
- }
548
-
549
- if (!options || typeof options !== "object" || Array.isArray(options) || ArrayBuffer.isView(options))
550
- {
551
- throw new TypeError("CjsModel.merge requires an options object.");
552
- }
553
-
554
- const merged = {};
555
- for (const value of values) mergeValueBag(merged, value, options);
556
- return CjsModel.set(out, merged, options);
557
- }
558
-
559
- /**
560
- * Constructs, populates, initializes, and cleans an owned model graph.
561
- *
562
- * The invoked constructor must support zero arguments. Initial population
563
- * suppresses updates and events; owned children initialize before parents.
564
- *
565
- * A string `values._type` selects the concrete constructor: it must name
566
- * this class or a registered subclass, otherwise a TypeError is thrown. A
567
- * `values._id` registers the instance in the import operation context
568
- * before any field descends, so `{ _ref }` values elsewhere in the same
569
- * operation — including cycles and self-references — resolve to this
570
- * instance. The outermost call finalizes forward references before
571
- * initialization; an unresolved `_ref` throws.
572
- *
573
- * @param {object} [values={}]
574
- * @param {object} [options={}]
575
- * @returns {CjsModel} An instance of the invoked model constructor.
576
- * @throws {Error} If any owned model explicitly fails initialization.
577
- */
578
- static from(values = {}, options = {})
579
- {
580
- if (isReferenceValue(values))
581
- {
582
- throw new TypeError(`${CjsSchema.getClassName(this) || this.name}.from cannot construct from a { _ref } value; references resolve only inside the owning import operation.`);
583
- }
584
-
585
- if (values && typeof values === "object" && typeof values._type === "string")
586
- {
587
- const Constructor = resolveRegisteredModelClass(values._type, options);
588
- if (Constructor !== this)
589
- {
590
- if (!(Constructor.prototype instanceof this))
591
- {
592
- throw new TypeError(`_type "${values._type}" is not ${CjsSchema.getClassName(this) || this.name} or one of its registered subclasses.`);
593
- }
594
- return Constructor.from(values, options);
595
- }
596
- }
597
-
598
- const ownsImportContext = !options.importContext;
599
- const importOptions = ownsImportContext
600
- ? { ...options, importContext: createImportContext() }
601
- : options;
602
-
603
- const result = new this();
604
-
605
- importOptions.importContext.registerCreated(result);
606
-
607
- // Register-before-descent: the instance is visible to `_ref` lookups
608
- // before its own fields import, so back-references and cycles work.
609
- if (values && typeof values === "object" && values._id !== undefined && values._id !== null)
610
- {
611
- importOptions.importContext.register(values._id, result);
612
- }
613
-
614
- result.__state.suppressEvents++;
615
- try
616
- {
617
- result.SetValues(values, {
618
- ...importOptions,
619
- skipEvents: true,
620
- skipUpdate: true
621
- });
622
-
623
- if (ownsImportContext)
624
- {
625
- importOptions.importContext.finalize();
626
- importOptions.importContext.initializeCreated({
627
- ...importOptions,
628
- initChildren: true
629
- });
630
- }
631
- }
632
- finally
633
- {
634
- result.__state.suppressEvents--;
635
- }
636
- return result;
637
- }
638
-
639
- /**
640
- * Constructs a model from another model-like value or a raw value bag.
641
- *
642
- * @param {CjsModel|object|null} value
643
- * @param {object} [options={}]
644
- * @returns {CjsModel} An instance of the invoked model constructor.
645
- */
646
- static clone(value, options = {})
647
- {
648
- if (!value || typeof value.GetValues !== "function")
649
- {
650
- return this.from(value || {}, options);
651
- }
652
-
653
- return this.from(value.GetValues(options), options);
654
- }
655
-
656
- }
657
-
658
- CjsSchema.define(CjsModel, { className: "CjsModel" });
659
-
660
- export const carbon = CjsSchema.carbon;
661
- export { CjsSchema };
662
- export const impl = CjsSchema.impl;
663
- export const io = CjsSchema.io;
664
- export const jessica = CjsSchema.jessica;
665
- export const schema = CjsSchema;
666
- export const type = CjsSchema.type;
667
-
668
- function mergeValueBag(out, value, options = {})
669
- {
670
- const source = value instanceof CjsModel ? CjsModel.get(value, {}, options) : value;
671
- if (!isPlainRecord(source)) return out;
672
-
673
- for (const [key, incoming] of Object.entries(source))
674
- {
675
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
676
-
677
- const normalized = incoming instanceof CjsModel ? CjsModel.get(incoming, {}, options) : incoming;
678
- if (isPlainRecord(normalized))
679
- {
680
- if (!isPlainRecord(out[key])) out[key] = {};
681
- mergeValueBag(out[key], normalized, options);
682
- }
683
- else
684
- {
685
- out[key] = normalized;
686
- }
687
- }
688
- return out;
689
- }
690
-
691
- function isPlainRecord(value)
692
- {
693
- if (!value || typeof value !== "object" || Array.isArray(value) || ArrayBuffer.isView(value)) return false;
694
- const prototype = Object.getPrototypeOf(value);
695
- return prototype === Object.prototype || prototype === null;
696
- }
697
-
698
- function getModelFields(target)
699
- {
700
- const schema = CjsSchema.getSchema(target.constructor);
701
- return schema.fields.map(schemaFieldToModelField);
702
- }
703
-
704
- function initializeModelState(target)
705
- {
706
- // Models own their runtime-state shape: __state is a CjsModelState,
707
- // created at construction before anything else (the event emitter's
708
- // lazily-added `events` map lives on the same instance as an expando).
709
- const existing = getRuntimeState(target);
710
- if (existing instanceof CjsModelState) return existing;
711
- if (existing)
712
- {
713
- throw new TypeError("CjsModel requires __state to be a CjsModelState.");
714
- }
715
-
716
- const state = new CjsModelState();
717
- Object.defineProperty(target, "__state", {
718
- value: state,
719
- enumerable: false,
720
- configurable: false,
721
- writable: false
722
- });
723
- return state;
724
- }
725
-
726
- function initializeOwnedGraph(root, options = {})
727
- {
728
- root.Traverse(value =>
729
- {
730
- value.__state.suppressEvents++;
731
-
732
- try
733
- {
734
- if (value.__state instanceof CjsModelState)
735
- {
736
- // Construction: everything is new, so every declared consequence
737
- // applies - all flag/rebuild tokens are added, and the object is
738
- // marked for one settle.
739
- addAllDeclaredTokens(value);
740
- value.__state.dirty = true;
741
- }
742
-
743
- // Initialize arguments belong to the class's Carbon/adapted contract.
744
- // Owned-graph traversal is coordinated here and must not occupy arg 0.
745
- // A conforming Initialize performs its own final
746
- // UpdateValues({ skipEvents: true }), leaving nothing dirty.
747
- if (typeof value.Initialize === "function")
748
- {
749
- if (value.Initialize() === false)
750
- {
751
- throw new Error(`${CjsSchema.getClassName(value.constructor)}.from initialization failed.`);
752
- }
753
- }
754
-
755
- // Settle anything Initialize did not (including the no-Initialize
756
- // case): the one construction settle, events suppressed.
757
- if (value.__state instanceof CjsModelState && value.__state.dirty)
758
- {
759
- value.UpdateValues({
760
- ...options,
761
- source: options.source ?? value,
762
- skipEvents: true
763
- });
764
- }
765
- }
766
- finally
767
- {
768
- value.__state.suppressEvents--;
769
- }
770
- }, {
771
- order: "post",
772
- reverse: true,
773
- ownedOnly: true,
774
- visited: options.visited
775
- });
776
- return root;
777
- }
778
-
779
- function AddResources(target, values)
780
- {
781
- if (values === null || values === undefined) return;
782
- if (values?.isResource === true)
783
- {
784
- target.add(values);
785
- return;
786
- }
787
- if (typeof values !== "string" && typeof values[Symbol.iterator] === "function")
788
- {
789
- for (const value of values) AddResources(target, value);
790
- }
791
- }
792
-
793
- function schemaFieldToModelField(field)
794
- {
795
- return {
796
- ...field,
797
- jsType: field.type || field.jsType || null
798
- };
799
- }
800
-
801
- function isWritableModelField(field)
802
- {
803
- const io = field?.io;
804
- if (!io) return true;
805
- if (io.write || io.persist || io.persistOnly) return true;
806
- if (io.read && !io.write) return false;
807
- return true;
808
- }
809
-
810
- function findIncomingKey(values, field)
811
- {
812
- for (const key of incomingKeyCandidates(field))
813
- {
814
- if (Object.prototype.hasOwnProperty.call(values, key)) return key;
815
- }
816
-
817
- return null;
818
- }
819
-
820
- function incomingKeyCandidates(field)
821
- {
822
- const aliases = field.aliases === undefined
823
- ? field.alias === undefined ? [] : [field.alias]
824
- : Array.isArray(field.aliases) ? field.aliases : [field.aliases];
825
- return [field.name, ...aliases].filter(value => typeof value === "string" && value.length);
826
- }
827
-
828
- // Adds one field's declared @io.flag / @io.rebuild tokens to their stores.
829
- // Duplicate adds are no-ops (Sets). Nothing in the model layer ever clears
830
- // these stores - getters clear flags, work methods clear rebuild tokens.
831
- function addDeclaredFieldTokens(target, field)
832
- {
833
- const io = field?.io;
834
- if (!io) return;
835
- if (io.flag) for (const token of io.flag) target.__state.flags.add(token);
836
- if (io.rebuild) for (const token of io.rebuild) target.__state.rebuild.add(token);
837
- }
838
-
839
- // Construction / broad invalidation: every declared token applies.
840
- function addAllDeclaredTokens(target)
841
- {
842
- const fields = CjsSchema.getSchema(target.constructor)?.fields || [];
843
- for (const field of fields) addDeclaredFieldTokens(target, field);
844
- }
845
-
846
- // Direct-mutation courtesy: a caller that knows which fields it touched
847
- // (bindings) passes them so declared consequences stay precise.
848
- function addExplicitUpdateProperties(target, properties)
849
- {
850
- if (properties === null || properties === undefined) return;
851
- target.__state.dirty = true;
852
- for (const property of typeof properties === "string" ? [properties] : properties)
853
- {
854
- const field = CjsSchema.getField(target.constructor, property);
855
- if (field) addDeclaredFieldTokens(target, field);
856
- }
857
- }
858
-
859
- function createModifiedPayload(properties, source)
860
- {
861
- return Object.freeze({
862
- properties: new Set(properties),
863
- source
864
- });
865
- }
866
-
867
- function areEquivalentSourceValues(a, b)
868
- {
869
- if (Object.is(a, b)) return true;
870
-
871
- if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b))
872
- {
873
- if (a.constructor !== b.constructor || a.length !== b.length) return false;
874
- for (let i = 0; i < a.length; i++)
875
- {
876
- if (!Object.is(a[i], b[i])) return false;
877
- }
878
- return true;
879
- }
880
-
881
- if (Array.isArray(a) && Array.isArray(b))
882
- {
883
- if (a.length !== b.length) return false;
884
- for (let i = 0; i < a.length; i++)
885
- {
886
- if (!areEquivalentSourceValues(a[i], b[i])) return false;
887
- }
888
- return true;
889
- }
890
-
891
- return false;
892
- }
893
-
894
- export function exportSourceValue(value, options = {})
895
- {
896
- if (value instanceof CjsModel) return value.GetValues(options);
897
- return exportCarbonValue(value);
898
- }
899
-
900
- function hasAdvancedExportOptions(options)
901
- {
902
- return !!(options && (options.persistOnly || options.typeTags || options.forceTypeTags
903
- || options.refs || options.forceIDs || options.keyedLists
904
- || (options.enumFormat && options.enumFormat !== "values")));
905
- }
906
-
907
- // Enum-aware value handling: @schema.enum("X") resolves through the owning
908
- // class's PascalCase static `Constructor.X`, lazily and leaf-first.
909
- const ENUM_REVERSE_CACHE = new WeakMap();
910
-
911
- function resolveEnumStaticForField(Constructor, field)
912
- {
913
- const name = field?.enum?.enumType;
914
- if (!name) return null;
915
- const members = Constructor?.[name];
916
- if (!members || typeof members !== "object") return null;
917
- return { name, members };
918
- }
919
-
920
- function enumMemberName(members, value)
921
- {
922
- let reverse = ENUM_REVERSE_CACHE.get(members);
923
- if (!reverse)
924
- {
925
- reverse = new Map();
926
- for (const key of Object.keys(members))
927
- {
928
- // Duplicate values: first-declared key wins.
929
- if (!reverse.has(members[key])) reverse.set(members[key], key);
930
- }
931
- ENUM_REVERSE_CACHE.set(members, reverse);
932
- }
933
- return reverse.get(value);
934
- }
935
-
936
- function enumIdentity(Constructor, name)
937
- {
938
- let current = Constructor;
939
- while (typeof current === "function")
940
- {
941
- if (Object.hasOwn(current, name))
942
- {
943
- return `${CjsSchema.getClassName(current) || current.name}.${name}`;
944
- }
945
- current = Object.getPrototypeOf(current);
946
- }
947
- return `${CjsSchema.getClassName(Constructor) || Constructor.name}.${name}`;
948
- }
949
-
950
- // Returns the validated numeric member value, or undefined when the input is
951
- // not a member. Accepts numeric values, exact member-name strings, and arrays
952
- // (element 0 only, so identity tuples round-trip).
953
- function translateEnumInput(value, members)
954
- {
955
- if (Array.isArray(value))
956
- {
957
- if (!value.length) return undefined;
958
- return translateEnumInput(value[0], members);
959
- }
960
- if (typeof value === "string")
961
- {
962
- return Object.hasOwn(members, value) ? members[value] : undefined;
963
- }
964
- if (typeof value === "number")
965
- {
966
- return enumMemberName(members, value) === undefined ? undefined : value;
967
- }
968
- return undefined;
969
- }
970
-
971
- // Atomic pre-validation: every enum-backed incoming value is checked before
972
- // any mutation; one TypeError reports every invalid property.
973
- function validateEnumInputs(out, values)
974
- {
975
- const translations = new Map();
976
- let issues = null;
977
- for (const field of getModelFields(out))
978
- {
979
- if (!isWritableModelField(field)) continue;
980
- const key = findIncomingKey(values, field);
981
- if (key === null) continue;
982
- const spec = resolveEnumStaticForField(out.constructor, field);
983
- if (!spec) continue;
984
- const raw = values[key];
985
- if (raw === null || raw === undefined || raw instanceof CjsModel) continue;
986
- const translated = translateEnumInput(raw, spec.members);
987
- if (translated === undefined)
988
- {
989
- issues = issues || [];
990
- issues.push(`${field.name}: ${JSON.stringify(raw)} is not a member of ${enumIdentity(out.constructor, spec.name)}`);
991
- }
992
- else
993
- {
994
- translations.set(field.name, translated);
995
- }
996
- }
997
- if (issues)
998
- {
999
- throw new TypeError(`Invalid enum values for ${CjsSchema.getClassName(out.constructor) || "model"} - ${issues.join("; ")}`);
1000
- }
1001
- return translations;
1002
- }
1003
-
1004
- function exportEnumFieldValue(value, spec, Constructor, options)
1005
- {
1006
- if (typeof value !== "number") return value;
1007
- const memberName = enumMemberName(spec.members, value);
1008
- if (memberName === undefined) return value;
1009
- if (options.enumFormat === "names") return memberName;
1010
- return [memberName, enumIdentity(Constructor, spec.name)];
1011
- }
1012
-
1013
- function isPersistedModelField(field)
1014
- {
1015
- const io = field?.io;
1016
- return !!(io && (io.persist || io.persistOnly));
1017
- }
1018
-
1019
- function declaredExportClassName(fieldType)
1020
- {
1021
- if (!fieldType) return null;
1022
- if (typeof fieldType === "string") return fieldType;
1023
- if (fieldType.kind === "array" || fieldType.kind === "list")
1024
- {
1025
- const item = fieldType.itemType ?? null;
1026
- return typeof item === "string" ? item : item?.className ?? null;
1027
- }
1028
- return fieldType.className ?? null;
1029
- }
1030
-
1031
- function createExportContext(root, options)
1032
- {
1033
- if (!options.refs && !options.forceIDs) return null;
1034
-
1035
- let nextId = 1;
1036
- const idByModel = new Map();
1037
- const context = {
1038
- emitted: new Set(),
1039
- idByModel,
1040
- getId(model)
1041
- {
1042
- let id = idByModel.get(model);
1043
- if (id === undefined)
1044
- {
1045
- id = nextId++;
1046
- idByModel.set(model, id);
1047
- }
1048
- return id;
1049
- }
1050
- };
1051
-
1052
- if (options.refs)
1053
- {
1054
- // Pre-count occurrences so only genuinely shared models receive ids.
1055
- const counts = new Map();
1056
- (function walk(value)
1057
- {
1058
- if (Array.isArray(value))
1059
- {
1060
- for (const item of value) walk(item);
1061
- return;
1062
- }
1063
- if (!(value instanceof CjsModel)) return;
1064
- const count = (counts.get(value) ?? 0) + 1;
1065
- counts.set(value, count);
1066
- if (count > 1) return;
1067
- for (const field of getModelFields(value))
1068
- {
1069
- if (options.persistOnly && !isPersistedModelField(field)) continue;
1070
- walk(value[field.name]);
1071
- }
1072
- })(root);
1073
- for (const [model, count] of counts)
1074
- {
1075
- if (count > 1) idByModel.set(model, nextId++);
1076
- }
1077
- }
1078
-
1079
- return context;
1080
- }
1081
-
1082
- function exportModelInto(model, out, declaredClassName, options, context)
1083
- {
1084
- if (context)
1085
- {
1086
- context.emitted.add(model);
1087
- }
1088
-
1089
- const className = CjsSchema.getClassName(model.constructor);
1090
- if (options.forceTypeTags || (options.typeTags && className && className !== declaredClassName))
1091
- {
1092
- out._type = className;
1093
- }
1094
- if (context && (options.forceIDs || context.idByModel.has(model)))
1095
- {
1096
- out._id = context.getId(model);
1097
- }
1098
-
1099
- const enumMode = options.enumFormat && options.enumFormat !== "values";
1100
- for (const field of getModelFields(model))
1101
- {
1102
- if (options.persistOnly && !isPersistedModelField(field)) continue;
1103
- if (enumMode)
1104
- {
1105
- const spec = resolveEnumStaticForField(model.constructor, field);
1106
- if (spec)
1107
- {
1108
- out[field.name] = exportEnumFieldValue(model[field.name], spec, model.constructor, options);
1109
- continue;
1110
- }
1111
- }
1112
- out[field.name] = exportAdvancedValue(
1113
- model[field.name],
1114
- declaredExportClassName(field.jsType || field.type || null),
1115
- options,
1116
- context
1117
- );
1118
- }
1119
-
1120
- return out;
1121
- }
1122
-
1123
- function exportAdvancedValue(value, declaredClassName, options, context)
1124
- {
1125
- if (value instanceof CjsModel)
1126
- {
1127
- if (context && options.refs && context.emitted.has(value))
1128
- {
1129
- return { _ref: context.getId(value) };
1130
- }
1131
- return exportModelInto(value, {}, declaredClassName, options, context);
1132
- }
1133
- if (Array.isArray(value))
1134
- {
1135
- if (options.keyedLists)
1136
- {
1137
- const keyed = exportKeyedList(value, declaredClassName, options, context);
1138
- if (keyed) return keyed;
1139
- }
1140
- return value.map(item => exportAdvancedValue(item, declaredClassName, options, context));
1141
- }
1142
- return exportCarbonValue(value);
1143
- }
1144
-
1145
- function exportKeyedList(list, declaredClassName, options, context)
1146
- {
1147
- if (!list.length) return null;
1148
-
1149
- const seen = new Set();
1150
- for (const item of list)
1151
- {
1152
- if (!(item instanceof CjsModel)) return null;
1153
- const name = item.name;
1154
- if (typeof name !== "string" || name === "" || seen.has(name)) return null;
1155
- seen.add(name);
1156
- }
1157
-
1158
- const out = {};
1159
- for (const item of list)
1160
- {
1161
- const exported = exportAdvancedValue(item, declaredClassName, options, context);
1162
- if (exported && typeof exported === "object" && exported._ref === undefined)
1163
- {
1164
- delete exported.name;
1165
- }
1166
- out[item.name] = exported;
1167
- }
1168
- return out;
1169
- }
1170
-
1171
- export function importSourceValue(value, field = null, options = {})
1172
- {
1173
- if (value instanceof CjsModel) return value;
1174
-
1175
- if (isReferenceValue(value))
1176
- {
1177
- const resolved = resolveIncomingReference(value, options);
1178
- if (resolved instanceof CjsPendingReference)
1179
- {
1180
- throw new TypeError(`Forward { _ref: ${JSON.stringify(value._ref)} } cannot be deferred in this position.`);
1181
- }
1182
- return resolved;
1183
- }
1184
-
1185
- const schemaType = getSchemaType(options.ownerConstructor, field?.name);
1186
- const declaredClassName = getSchemaClassName(schemaType, options);
1187
- if (value && typeof value === "object" && !(value instanceof CjsModel) && !Array.isArray(value) && !ArrayBuffer.isView(value))
1188
- {
1189
- // A registered `_type` selects the concrete class in singular
1190
- // schema-typed positions. Carbon contracts may be declared through
1191
- // interface names with no runtime inheritance, so the declared name
1192
- // is a fallback, not a constraint the concrete class must extend.
1193
- const explicitClassName = isSingularSchemaKind(schemaType) && typeof value._type === "string"
1194
- ? getSchemaClassName(value._type, options)
1195
- : null;
1196
- const className = explicitClassName || declaredClassName;
1197
- if (className)
1198
- {
1199
- return createModelValue(className, value, options);
1200
- }
1201
- }
1202
-
1203
- if ((schemaType?.kind === "array" || schemaType?.kind === "list") && schemaType.itemType && Array.isArray(value))
1204
- {
1205
- const itemClassName = getSchemaClassName(schemaType.itemType, options);
1206
- const result = [];
1207
- for (let i = 0; i < value.length; i++)
1208
- {
1209
- const item = value[i];
1210
- if (isReferenceValue(item))
1211
- {
1212
- result.push(importReferenceInto(item, options, result, i));
1213
- continue;
1214
- }
1215
- if (!item || typeof item !== "object" || item instanceof CjsModel || ArrayBuffer.isView(item))
1216
- {
1217
- result.push(importSourceValue(item, null, options));
1218
- continue;
1219
- }
1220
-
1221
- const explicitClassName = typeof item._type === "string"
1222
- ? getSchemaClassName(item._type, options)
1223
- : null;
1224
- if (explicitClassName)
1225
- {
1226
- result.push(createModelValue(explicitClassName, item, options));
1227
- continue;
1228
- }
1229
-
1230
- result.push(itemClassName
1231
- ? createModelValue(itemClassName, item, options)
1232
- : importSourceValue(item, null, options));
1233
- }
1234
- return result;
1235
- }
1236
-
1237
- // List fields also accept name-keyed object maps for unique-named items;
1238
- // the map is a wholesale list replacement, mirroring array semantics.
1239
- const effectiveType = field?.jsType || field?.type || schemaType;
1240
- if ((effectiveType?.kind === "array" || effectiveType?.kind === "list")
1241
- && value && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value))
1242
- {
1243
- return importListMapValue(value, effectiveType, options);
1244
- }
1245
-
1246
- if (field) return normalizeCarbonValue(value, field);
1247
- if (ArrayBuffer.isView(value)) return normalizeCarbonValue(value, { jsType: { kind: "typedArray", js: value.constructor.name } });
1248
- if (typeof value === "bigint") return value;
1249
- if (Array.isArray(value))
1250
- {
1251
- const result = [];
1252
- for (let i = 0; i < value.length; i++)
1253
- {
1254
- const item = value[i];
1255
- result.push(isReferenceValue(item)
1256
- ? importReferenceInto(item, options, result, i)
1257
- : importSourceValue(item, null, options));
1258
- }
1259
- return result;
1260
- }
1261
- if (value && typeof value === "object" && !(value instanceof CjsModel))
1262
- {
1263
- const result = {};
1264
- for (const [key, item] of Object.entries(value))
1265
- {
1266
- result[key] = isReferenceValue(item)
1267
- ? importReferenceInto(item, options, result, key)
1268
- : importSourceValue(item, null, options);
1269
- }
1270
- return result;
1271
- }
1272
- return value;
1273
- }
1274
-
1275
- function applyIncomingStructInPlace(current, incoming, field, options)
1276
- {
1277
- const schemaType = getSchemaType(options.ownerConstructor, field?.name) || field?.type || field?.jsType;
1278
- if (schemaType?.kind !== "struct" || !(current instanceof CjsModel)) return null;
1279
- if (incoming === null || incoming === undefined) return false;
1280
- if (typeof incoming !== "object" || Array.isArray(incoming) || ArrayBuffer.isView(incoming))
1281
- {
1282
- throw new TypeError(`${field.name} requires an object value for registered struct ${schemaType.className || "unknown"}.`);
1283
- }
1284
-
1285
- const values = incoming instanceof CjsModel ? incoming.GetValues() : incoming;
1286
- const changed = current.SetValues(values, options);
1287
- return changed instanceof Set ? changed.size > 0 : changed === true;
1288
- }
1289
-
1290
- function importListMapValue(value, schemaType, options)
1291
- {
1292
- const itemClassName = schemaType.itemType ? getSchemaClassName(schemaType.itemType, options) : null;
1293
- const result = [];
1294
- for (const key of Object.keys(value))
1295
- {
1296
- const item = value[key];
1297
- if (item === undefined || item === null) continue;
1298
-
1299
- if (item instanceof CjsModel)
1300
- {
1301
- if (typeof item.name === "string" && item.name === "")
1302
- {
1303
- item.SetValues({ name: key });
1304
- }
1305
- result.push(item);
1306
- continue;
1307
- }
1308
-
1309
- if (isReferenceValue(item))
1310
- {
1311
- // Shared items keep their own name; the map key is not restamped
1312
- // onto an instance owned by another position in the graph.
1313
- result.push(importReferenceInto(item, options, result, result.length));
1314
- continue;
1315
- }
1316
-
1317
- if (typeof item !== "object" || Array.isArray(item) || ArrayBuffer.isView(item))
1318
- {
1319
- throw new TypeError(`List field maps require object or model values; "${key}" cannot become a list item.`);
1320
- }
1321
-
1322
- const explicitClassName = typeof item._type === "string"
1323
- ? getSchemaClassName(item._type, options)
1324
- : null;
1325
- const className = explicitClassName || itemClassName;
1326
- if (!className)
1327
- {
1328
- throw new TypeError(`List field maps cannot resolve a model class for "${key}".`);
1329
- }
1330
-
1331
- const values = item.name === undefined ? { ...item, name: key } : item;
1332
- result.push(createModelValue(className, values, options));
1333
- }
1334
- return result;
1335
- }
1336
-
1337
- function getSchemaType(Constructor, fieldName)
1338
- {
1339
- if (!Constructor || !fieldName) return null;
1340
- return CjsSchema.getField(Constructor, fieldName)?.type || null;
1341
- }
1342
-
1343
- function getSchemaClassName(schemaType, options = {})
1344
- {
1345
- if (!schemaType) return null;
1346
- if (typeof schemaType === "string")
1347
- {
1348
- const Schema = options.registry || CjsModel.schema;
1349
- return Schema.GetConstructor(schemaType) ? schemaType : null;
1350
- }
1351
- if (schemaType.kind === "model")
1352
- {
1353
- return schemaType.className || null;
1354
- }
1355
- if (schemaType.kind === "objectRef" || schemaType.kind === "struct")
1356
- {
1357
- const Schema = options.registry || CjsModel.schema;
1358
- return schemaType.className && Schema.GetConstructor(schemaType.className)
1359
- ? schemaType.className
1360
- : null;
1361
- }
1362
- return null;
1363
- }
1364
-
1365
- function createModelValue(className, values, options)
1366
- {
1367
- const Schema = options.registry || CjsModel.schema;
1368
- const Constructor = Schema.GetConstructor(className);
1369
- if (!Constructor)
1370
- {
1371
- throw new TypeError(`No CjsModel class is registered for schema type ${className}.`);
1372
- }
1373
- if (Constructor !== CjsModel && !(Constructor.prototype instanceof CjsModel))
1374
- {
1375
- throw new TypeError(`Registered schema type ${className} is not a CjsModel.`);
1376
- }
1377
- if (typeof Constructor.from !== "function")
1378
- {
1379
- throw new TypeError(`Registered CjsModel ${className} does not provide from().`);
1380
- }
1381
- return Constructor.from(values, options);
1382
- }
1383
-
1384
- // --- Import operation context: `_id`/`_ref` identity across one call tree ---
1385
-
1386
- /** Represents one unresolved model reference during a single import operation. */
1387
- class CjsPendingReference
1388
- {
1389
-
1390
- constructor(id, expectedClassName = null)
1391
- {
1392
- this.id = id;
1393
- this.expectedClassName = expectedClassName;
1394
- }
1395
-
1396
- }
1397
-
1398
- function createImportContext()
1399
- {
1400
- const byId = new Map();
1401
- const created = [];
1402
- const pending = [];
1403
- return {
1404
- byId,
1405
- registerCreated(instance)
1406
- {
1407
- created.push(instance);
1408
- },
1409
- register(id, instance)
1410
- {
1411
- const existing = byId.get(id);
1412
- if (existing === instance) return;
1413
- if (existing !== undefined)
1414
- {
1415
- throw new TypeError(`Duplicate _id ${JSON.stringify(id)} in imported values.`);
1416
- }
1417
- byId.set(id, instance);
1418
- },
1419
- defer(id, assign)
1420
- {
1421
- pending.push({ id, assign });
1422
- },
1423
- finalize()
1424
- {
1425
- const unresolved = new Set();
1426
- for (const entry of pending)
1427
- {
1428
- const instance = byId.get(entry.id);
1429
- if (instance === undefined)
1430
- {
1431
- unresolved.add(entry.id);
1432
- continue;
1433
- }
1434
- entry.assign(instance);
1435
- }
1436
- pending.length = 0;
1437
- if (unresolved.size)
1438
- {
1439
- throw new TypeError(`Unresolved _ref ids: ${Array.from(unresolved, id => JSON.stringify(id)).join(", ")}. Every { _ref } must match a { _id } in the same import operation.`);
1440
- }
1441
- },
1442
- initializeCreated(options)
1443
- {
1444
- const visited = new Set();
1445
-
1446
- for (let index = created.length - 1; index >= 0; index--)
1447
- {
1448
- initializeOwnedGraph(created[index], { ...options, visited });
1449
- }
1450
-
1451
- created.length = 0;
1452
- }
1453
- };
1454
- }
1455
-
1456
- function isReferenceValue(value)
1457
- {
1458
- return !!value && typeof value === "object" && !Array.isArray(value)
1459
- && !ArrayBuffer.isView(value) && !(value instanceof CjsModel)
1460
- && value._ref !== undefined;
1461
- }
1462
-
1463
- // Resolves a `{ _ref }` immediately when the target is registered, or returns
1464
- // a CjsPendingReference for the finalize pass (forward references). Resolved
1465
- // references assign like direct instances: no declared-type constraint, since
1466
- // Carbon contracts may be declared through interface names that have no
1467
- // runtime inheritance relationship with the concrete class.
1468
- function resolveIncomingReference(value, options)
1469
- {
1470
- const id = value._ref;
1471
- const context = options.importContext;
1472
- if (!context)
1473
- {
1474
- throw new TypeError(`Cannot resolve { _ref: ${JSON.stringify(id)} } outside an import operation; import the graph through SetValues/from so identity is tracked.`);
1475
- }
1476
- const resolved = context.byId.get(id);
1477
- if (resolved === undefined) return new CjsPendingReference(id);
1478
- return resolved;
1479
- }
1480
-
1481
- // Resolves a reference into a container slot, deferring forward references to
1482
- // the owning operation's finalize pass. Deferred slots hold null until then.
1483
- function importReferenceInto(value, options, target, key)
1484
- {
1485
- const resolved = resolveIncomingReference(value, options);
1486
- if (resolved instanceof CjsPendingReference)
1487
- {
1488
- options.importContext.defer(resolved.id, instance =>
1489
- {
1490
- target[key] = instance;
1491
- });
1492
- return null;
1493
- }
1494
- return resolved;
1495
- }
1496
-
1497
- // Applies a `{ _ref }` incoming value to a model field, returning whether the
1498
- // field changed. Forward references keep the current value until finalize.
1499
- function applyIncomingReference(out, field, incoming, options)
1500
- {
1501
- const fieldName = field.name;
1502
- const resolved = resolveIncomingReference(incoming, options);
1503
-
1504
- if (resolved instanceof CjsPendingReference)
1505
- {
1506
- options.importContext.defer(resolved.id, instance =>
1507
- {
1508
- out[fieldName] = instance;
1509
- });
1510
- return true;
1511
- }
1512
-
1513
- if (field.io?.always !== true && Object.is(out[fieldName], resolved)) return false;
1514
- out[fieldName] = resolved;
1515
- return true;
1516
- }
1517
-
1518
- function resolveRegisteredModelClass(typeName, options = {})
1519
- {
1520
- const Schema = options.registry || CjsModel.schema;
1521
- const Constructor = Schema.GetConstructor(typeName);
1522
- if (!Constructor)
1523
- {
1524
- throw new TypeError(`No CjsModel class is registered for _type "${typeName}".`);
1525
- }
1526
- return Constructor;
1527
- }
1528
-
1529
- function isSingularSchemaKind(schemaType)
1530
- {
1531
- if (!schemaType) return false;
1532
- if (typeof schemaType === "string") return true;
1533
- return schemaType.kind !== "array" && schemaType.kind !== "list"
1534
- && schemaType.kind !== "map" && schemaType.kind !== "set";
1535
- }
1536
-
1537
- function assertTargetTypeMatches(out, typeName, options = {})
1538
- {
1539
- const Constructor = resolveRegisteredModelClass(typeName, options);
1540
- if (!(out instanceof Constructor))
1541
- {
1542
- throw new TypeError(`Values with _type "${typeName}" cannot apply to a ${CjsSchema.getClassName(out.constructor) || "model"} target.`);
1543
- }
1544
- }
1
+ import { coerceCarbonMathInto, exportCarbonValue, normalizeCarbonValue } from "../types/index.js";
2
+ import { CjsSchema } from "../schema/index.js";
3
+ import { getRuntimeState } from "../runtime/CjsRuntimeState.js";
4
+ import { CjsModelState } from "./CjsModelState.js";
5
+ import { CjsEventEmitter } from "./CjsEventEmitter.js";
6
+
7
+ const MAX_UPDATE_PASSES = 32;
8
+
9
+ /**
10
+ * Shared base for schema-backed CarbonEngineJS runtime classes.
11
+ *
12
+ * Source fields are exported; runtime caches and bookkeeping are not.
13
+ */
14
+ export class CjsModel extends CjsEventEmitter
15
+ {
16
+ /**
17
+ * Identifies this class as a schema-backed model.
18
+ *
19
+ * Declared statically so CjsSchema can recognise a model class from a field
20
+ * declaration alone, without importing CjsModel - which it cannot do, since
21
+ * this module already imports CjsSchema. Mirrors `CjsResource.isResource`.
22
+ */
23
+ static isModel = true;
24
+
25
+ /**
26
+ * Creates a schema-backed model with initialized runtime state.
27
+ */
28
+ constructor()
29
+ {
30
+ super();
31
+ const className = CjsSchema.getClassName(this.constructor);
32
+ if (!className)
33
+ {
34
+ throw new TypeError("CjsModel subclasses require an explicit CjsSchema className.");
35
+ }
36
+ initializeModelState(this);
37
+ }
38
+
39
+ /**
40
+ * Exports the model's schema fields to a new plain object.
41
+ *
42
+ * @param {object} [options={}]
43
+ * @returns {object}
44
+ */
45
+ GetValues(options = {})
46
+ {
47
+ return CjsModel.get(this, {}, options);
48
+ }
49
+
50
+ /**
51
+ * Applies a plain value bag through the canonical schema-backed setter.
52
+ *
53
+ * @param {object} [values={}]
54
+ * @param {object} [options={}]
55
+ * @returns {Set<string>|boolean} The changed fields, or a boolean result.
56
+ */
57
+ SetValues(values = {}, options = {})
58
+ {
59
+ return CjsModel.set(this, values, options);
60
+ }
61
+
62
+ /**
63
+ * Copies the exported fields of another model into this model.
64
+ *
65
+ * @param {CjsModel} value
66
+ * @param {object} [options={}]
67
+ * @returns {CjsModel} This model.
68
+ */
69
+ Copy(value, options = {})
70
+ {
71
+ return CjsModel.copy(this, value, options);
72
+ }
73
+
74
+ /**
75
+ * Constructs a new model of this instance's class from its schema values.
76
+ *
77
+ * @param {object} [options={}]
78
+ * @returns {CjsModel}
79
+ */
80
+ Clone(options = {})
81
+ {
82
+ return this.constructor.clone(this, options);
83
+ }
84
+
85
+ /**
86
+ * Deep-merges ordered value sources and applies the result once.
87
+ *
88
+ * Plain objects merge recursively; arrays, typed arrays, and other values
89
+ * replace the preceding value.
90
+ *
91
+ * @param {Array<Object|CjsModel>} [values=[]]
92
+ * @param {object} [options={}]
93
+ * @returns {Set<string>|boolean} The result returned by {@link CjsModel.set}.
94
+ */
95
+ Merge(values = [], options = {})
96
+ {
97
+ return CjsModel.merge(this, values, options);
98
+ }
99
+
100
+ /**
101
+ * Applies pending changes: drives the OnModified hook until the model
102
+ * settles, clears the dirty mark, and emits one final modified event.
103
+ *
104
+ * Calling this IS the "I made changes, apply please" contract: it always
105
+ * runs at least one hook pass, dirty or not, so direct/untracked
106
+ * mutations (the cooperative-pipeline reality) can be applied
107
+ * explicitly. Class Update/per-frame methods typically gate on
108
+ * `__state.IsDirty()` before calling.
109
+ *
110
+ * @param {object} [options={}]
111
+ * @param {string|Iterable<string>} [options.property] Fields the caller changed directly; their declared flag/rebuild tokens are added first.
112
+ * @param {string|Iterable<string>} [options.properties] Alias of `property`.
113
+ * @param {*} [options.source=this] Origin forwarded to the hook and event (binding feedback control).
114
+ * @param {boolean} [options.skipEvents=false] Prevents the final modified event.
115
+ * @returns {boolean} False when the hook rejected the update (dirty is retained).
116
+ * @throws {Error} If local changes do not settle within the update-pass limit.
117
+ */
118
+ UpdateValues(options = {})
119
+ {
120
+ addExplicitUpdateProperties(this, options.property ?? options.properties);
121
+ if (this.__state.updating) return true;
122
+
123
+ const source = options.source ?? this;
124
+ this.__state.updating = true;
125
+
126
+ try
127
+ {
128
+ for (let pass = 0; ; pass++)
129
+ {
130
+ if (pass >= MAX_UPDATE_PASSES)
131
+ {
132
+ throw new Error(`${CjsSchema.getClassName(this.constructor)}.UpdateValues exceeded ${MAX_UPDATE_PASSES} local settle passes.`);
133
+ }
134
+
135
+ this.__state.dirty = false;
136
+
137
+ if (this.OnModified({ ...options, source }) === false)
138
+ {
139
+ this.__state.dirty = true;
140
+ return false;
141
+ }
142
+
143
+ if (!this.__state.dirty) break;
144
+ }
145
+ }
146
+ catch (err)
147
+ {
148
+ this.__state.dirty = true;
149
+ throw err;
150
+ }
151
+ finally
152
+ {
153
+ this.__state.updating = false;
154
+ }
155
+
156
+ if (options.skipEvents !== true && this.__state.suppressEvents === 0)
157
+ {
158
+ this.EmitEvent("modified", this, { source });
159
+ }
160
+
161
+ return true;
162
+ }
163
+
164
+ /**
165
+ * The settle hook: reproduces the meaningful consequences of the
166
+ * corresponding Carbon INotify::OnModified implementation.
167
+ *
168
+ * Invoked only by UpdateValues. Receives the mutation options bag
169
+ * (source, caller context, skipEvents, ...). There is no changed-property
170
+ * list - the pipeline is cooperative and cannot guarantee one - so
171
+ * overrides are written broad-safe: consult own state, compare cached
172
+ * derivations, and rely on `__state.flags`/`__state.rebuild` tokens for
173
+ * targeted signals. Returning `false` rejects the update and retains the
174
+ * dirty mark.
175
+ *
176
+ * @param {object} [options={}]
177
+ * @returns {boolean} Whether the update may complete.
178
+ */
179
+ OnModified(options = {})
180
+ {
181
+ return true;
182
+ }
183
+
184
+ /**
185
+ * Visits this model and its schema-backed child models without revisiting cycles.
186
+ *
187
+ * In pre-order traversal, returning `false` prunes that model's descendants.
188
+ * Visitor return values are ignored in post-order traversal.
189
+ *
190
+ * @param {function(CjsModel): (boolean|void)} visitor
191
+ * @param {object} [options={}]
192
+ * @param {Set<CjsModel>} [options.visited] Existing cycle-detection set.
193
+ * @param {"pre"|"post"} [options.order="pre"]
194
+ * @param {boolean} [options.reverse=false] Reverses field and list-item order.
195
+ * @param {boolean} [options.ownedOnly=false] Traverses only owned relationships.
196
+ * @returns {CjsModel} This model.
197
+ * @throws {TypeError} If `visitor` is not a function.
198
+ */
199
+ Traverse(visitor, options = {})
200
+ {
201
+ if (typeof visitor !== "function")
202
+ {
203
+ throw new TypeError("CjsModel.Traverse requires a visitor function.");
204
+ }
205
+
206
+ const visited = options.visited instanceof Set ? options.visited : new Set();
207
+ const order = options.order === "post" ? "post" : "pre";
208
+ const reverse = options.reverse === true;
209
+
210
+ const visit = model =>
211
+ {
212
+ if (!(model instanceof CjsModel) || visited.has(model)) return;
213
+ visited.add(model);
214
+
215
+ let descend = true;
216
+ if (order === "pre") descend = visitor(model) !== false;
217
+
218
+ if (descend)
219
+ {
220
+ // Only the fields declared to hold child models, precomputed per
221
+ // class - not every field, type-tested per value per visit.
222
+ const children = CjsSchema.getSchema(model.constructor).children;
223
+ const start = reverse ? children.length - 1 : 0;
224
+ const end = reverse ? -1 : children.length;
225
+ const step = reverse ? -1 : 1;
226
+
227
+ for (let i = start; i !== end; i += step)
228
+ {
229
+ const child = children[i];
230
+ if (options.ownedOnly === true && !child.owned) continue;
231
+ const value = model[child.name];
232
+
233
+ if (Array.isArray(value))
234
+ {
235
+ const itemStart = reverse ? value.length - 1 : 0;
236
+ const itemEnd = reverse ? -1 : value.length;
237
+ for (let j = itemStart; j !== itemEnd; j += step) visit(value[j]);
238
+ }
239
+ else
240
+ {
241
+ visit(value);
242
+ }
243
+ }
244
+ }
245
+
246
+ if (order === "post") visitor(model);
247
+ };
248
+
249
+ visit(this);
250
+ return this;
251
+ }
252
+
253
+ /**
254
+ * Collects unique resources reported by this model graph into an array.
255
+ *
256
+ * Every model in the graph is visited: reporting resources does not hide a
257
+ * model's descendants, because an under-reported dependency set would let
258
+ * readiness checks pass while a child's resources were still loading.
259
+ *
260
+ * Resources held in schema fields are collected automatically - they are
261
+ * already declared, as `@type.objectRef("TriGeometryRes")` and friends, so
262
+ * restating them in a hook would be the hand-written relay chain this
263
+ * traversal exists to replace.
264
+ *
265
+ * `OnGetResources()` is the escape hatch for resources a model holds
266
+ * outside its schema, such as private fields. It takes no arguments and
267
+ * always returns an iterable of resources - never a bare resource and never
268
+ * nothing. Most models do not implement it.
269
+ *
270
+ * @param {Array<*>} [out=[]] Output array, whose contents are replaced.
271
+ * @returns {Array<*>} The supplied output array.
272
+ */
273
+ GetResources(out = [])
274
+ {
275
+ const resources = new Set();
276
+
277
+ this.Traverse(model =>
278
+ {
279
+ for (const field of CjsSchema.getSchema(model.constructor).resources)
280
+ {
281
+ const value = model[field.name];
282
+ if (Array.isArray(value))
283
+ {
284
+ for (const item of value) AddResource(resources, item);
285
+ }
286
+ else
287
+ {
288
+ AddResource(resources, value);
289
+ }
290
+ }
291
+
292
+ if (typeof model.OnGetResources === "function")
293
+ {
294
+ AddResources(resources, model.OnGetResources());
295
+ }
296
+ return true;
297
+ });
298
+
299
+ out.length = 0;
300
+ out.push(...resources);
301
+ return out;
302
+ }
303
+
304
+ /**
305
+ * Marks the model as changed; the next settle applies it.
306
+ *
307
+ * The cooperative-pipeline contract: anything mutating outside
308
+ * `SetValues` (direct writes, Object.assign, reader adapters) owes this
309
+ * call or an explicit `UpdateValues()`.
310
+ *
311
+ * @returns {CjsModel} This model.
312
+ */
313
+ MarkDirty()
314
+ {
315
+ this.__state.MarkDirty();
316
+ return this;
317
+ }
318
+
319
+ /**
320
+ * Clears the dirty mark without settling. Rarely correct outside tests
321
+ * and teardown - the settle clears it itself.
322
+ *
323
+ * @returns {CjsModel} This model.
324
+ */
325
+ ClearDirty()
326
+ {
327
+ this.__state.ClearDirty();
328
+ return this;
329
+ }
330
+
331
+ /**
332
+ * Checks whether a settle is owed.
333
+ *
334
+ * @returns {boolean}
335
+ */
336
+ IsDirty()
337
+ {
338
+ return this.__state.IsDirty();
339
+ }
340
+
341
+ /**
342
+ * Gets the shared schema registry and decorator facade.
343
+ *
344
+ * @returns {typeof CjsSchema}
345
+ */
346
+ static get schema()
347
+ {
348
+ return CjsSchema;
349
+ }
350
+
351
+ /**
352
+ * Exports a model's schema fields into an output object.
353
+ *
354
+ * All options default off, leaving the plain output identical to the
355
+ * historical shape. Options propagate recursively to nested models.
356
+ *
357
+ * @param {CjsModel} value
358
+ * @param {object} [out={}]
359
+ * @param {object} [options={}]
360
+ * @param {boolean} [options.persistOnly] Exports only persisted fields.
361
+ * @param {boolean} [options.typeTags] Emits `_type` only where the concrete
362
+ * class is not derivable from the declared field type (the root and
363
+ * polymorphic slots).
364
+ * @param {boolean} [options.forceTypeTags] Emits `_type` on every model.
365
+ * @param {boolean} [options.refs] Tracks shared models: repeats export as
366
+ * `{ _ref }` and their first occurrence carries `_id`. Also guards
367
+ * against cyclic graphs.
368
+ * @param {boolean} [options.forceIDs] Emits `_id` on every model.
369
+ * @param {boolean} [options.keyedLists] Exports a list as a name-keyed
370
+ * object when every item is a model with a unique non-empty `name`;
371
+ * the redundant item `name` field is dropped in that form. Empty
372
+ * lists stay arrays.
373
+ * @param {string} [options.enumFormat] Enum-backed field emission:
374
+ * "values" (default, numeric), "names" (exact member-name strings),
375
+ * or "identity" (`[name, "OwnerClass.EnumName"]` tuples). Unknown
376
+ * numeric values export as raw numbers in every mode.
377
+ * @returns {object} The supplied output object.
378
+ * @throws {TypeError} If the source or output target is invalid.
379
+ */
380
+ static get(value, out = {}, options = {})
381
+ {
382
+ if (!(value instanceof CjsModel))
383
+ {
384
+ throw new TypeError("CjsModel.get requires a CjsModel source.");
385
+ }
386
+
387
+ if (!out || typeof out !== "object" || Array.isArray(out) || ArrayBuffer.isView(out))
388
+ {
389
+ throw new TypeError("CjsModel.get requires an object output target.");
390
+ }
391
+
392
+ if (!hasAdvancedExportOptions(options))
393
+ {
394
+ for (const field of getModelFields(value))
395
+ {
396
+ out[field.name] = exportSourceValue(value[field.name], options);
397
+ }
398
+
399
+ return out;
400
+ }
401
+
402
+ return exportModelInto(value, out, null, options, createExportContext(value, options));
403
+ }
404
+
405
+ /**
406
+ * Applies schema-backed values to a model and processes resulting updates.
407
+ *
408
+ * Reserved metadata keys are honored, never treated as fields: a string
409
+ * `values._type` must name the target's class or one of its base classes;
410
+ * `values._id` registers the target for `{ _ref }` resolution; a
411
+ * `{ _ref }` incoming field value resolves to the registered instance
412
+ * (shared identity) and throws when the id never resolves.
413
+ *
414
+ * @param {CjsModel} out
415
+ * @param {object} [values={}]
416
+ * @param {object} [options={}]
417
+ * @param {boolean} [options.markDirty=true] Tracks changed properties and notification flags.
418
+ * @param {boolean} [options.notify=true] Tracks schema notification flags.
419
+ * @param {boolean} [options.skipUpdate=false] Leaves dirty changes unsettled.
420
+ * @param {boolean} [options.skipEvents=false] Suppresses direct modified events.
421
+ * @param {boolean} [options.returnBoolean=false] Returns a boolean instead of changed fields.
422
+ * @param {*} [options.source=out] Origin included in update callbacks and events.
423
+ * @returns {Set<string>|boolean} Changed fields, or a boolean result.
424
+ * @throws {TypeError} If the target is not a model.
425
+ */
426
+ static set(out, values = {}, options = {})
427
+ {
428
+ if (!(out instanceof CjsModel))
429
+ {
430
+ throw new TypeError("CjsModel.set requires a CjsModel target.");
431
+ }
432
+
433
+ if (!values || typeof values !== "object") return false;
434
+
435
+ if (typeof values._type === "string")
436
+ {
437
+ assertTargetTypeMatches(out, values._type, options);
438
+ }
439
+
440
+ // One import operation context is shared across the whole call tree so
441
+ // `_id` registrations and `{ _ref }` resolutions see the same identity
442
+ // table. The outermost call owns finalization of forward references.
443
+ const ownsImportContext = !options.importContext;
444
+ const importOptions = {
445
+ ...options,
446
+ importContext: options.importContext ?? createImportContext(),
447
+ ownerConstructor: out.constructor
448
+ };
449
+
450
+ if (values._id !== undefined && values._id !== null)
451
+ {
452
+ importOptions.importContext.register(values._id, out);
453
+ }
454
+
455
+ const enumTranslations = validateEnumInputs(out, values);
456
+
457
+ const changed = new Set();
458
+ for (const field of getModelFields(out))
459
+ {
460
+ if (!isWritableModelField(field)) continue;
461
+
462
+ const key = findIncomingKey(values, field);
463
+ if (key !== null)
464
+ {
465
+ const oldValue = out[field.name];
466
+ const incoming = enumTranslations.has(field.name) ? enumTranslations.get(field.name) : values[key];
467
+
468
+ let didChange;
469
+ if (isReferenceValue(incoming))
470
+ {
471
+ didChange = applyIncomingReference(out, field, incoming, importOptions);
472
+ }
473
+ else
474
+ {
475
+ // Registered struct fields have value semantics. Constructors may
476
+ // install their canonical struct instance up front; populate that
477
+ // instance rather than replacing it with an imported object.
478
+ const structChanged = applyIncomingStructInPlace(oldValue, incoming, field, importOptions);
479
+
480
+ if (structChanged !== null)
481
+ {
482
+ didChange = field.io?.always === true || structChanged;
483
+ }
484
+ else
485
+ {
486
+ // Fast path: a math field with an existing compatible typed array
487
+ // is coerced IN PLACE (no allocation, buffer reference preserved).
488
+ const mathChanged = coerceCarbonMathInto(oldValue, incoming, field);
489
+
490
+ if (mathChanged !== null)
491
+ {
492
+ didChange = field.io?.always === true || mathChanged;
493
+ }
494
+ else
495
+ {
496
+ const newValue = importSourceValue(incoming, field, importOptions);
497
+ didChange = field.io?.always === true || !areEquivalentSourceValues(oldValue, newValue);
498
+ if (didChange) out[field.name] = newValue;
499
+ }
500
+ }
501
+ }
502
+
503
+ if (didChange)
504
+ {
505
+ changed.add(field.name);
506
+ if (options.markDirty !== false)
507
+ {
508
+ out.__state.dirty = true;
509
+ // Write-time token adds: the knowledge of WHICH field
510
+ // changed lives here, so declared consequences land
511
+ // here (props are not tracked in state).
512
+ if (options.notify !== false)
513
+ {
514
+ addDeclaredFieldTokens(out, field);
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+
521
+ if (ownsImportContext)
522
+ {
523
+ importOptions.importContext.finalize();
524
+ importOptions.importContext.initializeCreated(importOptions);
525
+ }
526
+
527
+ if (changed.size && options.markDirty === false)
528
+ {
529
+ if (options.skipUpdate !== true && options.skipEvents !== true && out.__state.suppressEvents === 0)
530
+ {
531
+ out.EmitEvent("modified", out, createModifiedPayload(changed, options.source ?? out));
532
+ }
533
+ }
534
+ else if (changed.size && options.skipUpdate !== true && !out.__state.updating)
535
+ {
536
+ out.UpdateValues(options);
537
+ }
538
+
539
+ return options.returnBoolean === true ? changed.size > 0 : changed.size ? changed : false;
540
+ }
541
+
542
+ /**
543
+ * Copies all exported fields from one model into another.
544
+ *
545
+ * @param {CjsModel} out
546
+ * @param {CjsModel} value
547
+ * @param {object} [options={}]
548
+ * @returns {CjsModel} The target model.
549
+ * @throws {TypeError} If either argument is not a model.
550
+ */
551
+ static copy(out, value, options = {})
552
+ {
553
+ if (!(out instanceof CjsModel))
554
+ {
555
+ throw new TypeError("CjsModel.copy requires a CjsModel target.");
556
+ }
557
+
558
+ if (!(value instanceof CjsModel))
559
+ {
560
+ throw new TypeError("CjsModel.copy requires a CjsModel source.");
561
+ }
562
+
563
+ CjsModel.set(out, CjsModel.get(value, {}, options), options);
564
+ return out;
565
+ }
566
+
567
+ /**
568
+ * Deep-merges ordered value sources and applies the result with one set call.
569
+ *
570
+ * @param {CjsModel} out
571
+ * @param {Array<object|CjsModel>} [values=[]]
572
+ * @param {object} [options={}]
573
+ * @returns {Set<string>|boolean} The result returned by {@link CjsModel.set}.
574
+ * @throws {TypeError} If the target, source array, or options are invalid.
575
+ */
576
+ static merge(out, values = [], options = {})
577
+ {
578
+ if (!(out instanceof CjsModel))
579
+ {
580
+ throw new TypeError("CjsModel.merge requires a CjsModel target.");
581
+ }
582
+
583
+ if (!Array.isArray(values))
584
+ {
585
+ throw new TypeError("CjsModel.merge requires an array of value sources.");
586
+ }
587
+
588
+ if (!options || typeof options !== "object" || Array.isArray(options) || ArrayBuffer.isView(options))
589
+ {
590
+ throw new TypeError("CjsModel.merge requires an options object.");
591
+ }
592
+
593
+ const merged = {};
594
+ for (const value of values) mergeValueBag(merged, value, options);
595
+ return CjsModel.set(out, merged, options);
596
+ }
597
+
598
+ /**
599
+ * Constructs, populates, initializes, and cleans an owned model graph.
600
+ *
601
+ * The invoked constructor must support zero arguments. Initial population
602
+ * suppresses updates and events; owned children initialize before parents.
603
+ *
604
+ * A string `values._type` selects the concrete constructor: it must name
605
+ * this class or a registered subclass, otherwise a TypeError is thrown. A
606
+ * `values._id` registers the instance in the import operation context
607
+ * before any field descends, so `{ _ref }` values elsewhere in the same
608
+ * operation including cycles and self-references resolve to this
609
+ * instance. The outermost call finalizes forward references before
610
+ * initialization; an unresolved `_ref` throws.
611
+ *
612
+ * @param {object} [values={}]
613
+ * @param {object} [options={}]
614
+ * @returns {CjsModel} An instance of the invoked model constructor.
615
+ * @throws {Error} If any owned model explicitly fails initialization.
616
+ */
617
+ static from(values = {}, options = {})
618
+ {
619
+ if (isReferenceValue(values))
620
+ {
621
+ throw new TypeError(`${CjsSchema.getClassName(this) || this.name}.from cannot construct from a { _ref } value; references resolve only inside the owning import operation.`);
622
+ }
623
+
624
+ if (values && typeof values === "object" && typeof values._type === "string")
625
+ {
626
+ const Constructor = resolveRegisteredModelClass(values._type, options);
627
+ if (Constructor !== this)
628
+ {
629
+ if (!(Constructor.prototype instanceof this))
630
+ {
631
+ throw new TypeError(`_type "${values._type}" is not ${CjsSchema.getClassName(this) || this.name} or one of its registered subclasses.`);
632
+ }
633
+ return Constructor.from(values, options);
634
+ }
635
+ }
636
+
637
+ const ownsImportContext = !options.importContext;
638
+ const importOptions = ownsImportContext
639
+ ? { ...options, importContext: createImportContext() }
640
+ : options;
641
+
642
+ const result = new this();
643
+
644
+ importOptions.importContext.registerCreated(result);
645
+
646
+ // Register-before-descent: the instance is visible to `_ref` lookups
647
+ // before its own fields import, so back-references and cycles work.
648
+ if (values && typeof values === "object" && values._id !== undefined && values._id !== null)
649
+ {
650
+ importOptions.importContext.register(values._id, result);
651
+ }
652
+
653
+ result.__state.suppressEvents++;
654
+ try
655
+ {
656
+ result.SetValues(values, {
657
+ ...importOptions,
658
+ skipEvents: true,
659
+ skipUpdate: true
660
+ });
661
+
662
+ if (ownsImportContext)
663
+ {
664
+ importOptions.importContext.finalize();
665
+ importOptions.importContext.initializeCreated({
666
+ ...importOptions,
667
+ initChildren: true
668
+ });
669
+ }
670
+ }
671
+ finally
672
+ {
673
+ result.__state.suppressEvents--;
674
+ }
675
+ return result;
676
+ }
677
+
678
+ /**
679
+ * Constructs a model from another model-like value or a raw value bag.
680
+ *
681
+ * @param {CjsModel|object|null} value
682
+ * @param {object} [options={}]
683
+ * @returns {CjsModel} An instance of the invoked model constructor.
684
+ */
685
+ static clone(value, options = {})
686
+ {
687
+ if (!value || typeof value.GetValues !== "function")
688
+ {
689
+ return this.from(value || {}, options);
690
+ }
691
+
692
+ return this.from(value.GetValues(options), options);
693
+ }
694
+
695
+ }
696
+
697
+ CjsSchema.define(CjsModel, { className: "CjsModel" });
698
+
699
+ export const carbon = CjsSchema.carbon;
700
+ export { CjsSchema };
701
+ export const impl = CjsSchema.impl;
702
+ export const io = CjsSchema.io;
703
+ export const jessica = CjsSchema.jessica;
704
+ export const schema = CjsSchema;
705
+ export const type = CjsSchema.type;
706
+
707
+ function mergeValueBag(out, value, options = {})
708
+ {
709
+ const source = value instanceof CjsModel ? CjsModel.get(value, {}, options) : value;
710
+ if (!isPlainRecord(source)) return out;
711
+
712
+ for (const [key, incoming] of Object.entries(source))
713
+ {
714
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
715
+
716
+ const normalized = incoming instanceof CjsModel ? CjsModel.get(incoming, {}, options) : incoming;
717
+ if (isPlainRecord(normalized))
718
+ {
719
+ if (!isPlainRecord(out[key])) out[key] = {};
720
+ mergeValueBag(out[key], normalized, options);
721
+ }
722
+ else
723
+ {
724
+ out[key] = normalized;
725
+ }
726
+ }
727
+ return out;
728
+ }
729
+
730
+ function isPlainRecord(value)
731
+ {
732
+ if (!value || typeof value !== "object" || Array.isArray(value) || ArrayBuffer.isView(value)) return false;
733
+ const prototype = Object.getPrototypeOf(value);
734
+ return prototype === Object.prototype || prototype === null;
735
+ }
736
+
737
+ function getModelFields(target)
738
+ {
739
+ const schema = CjsSchema.getSchema(target.constructor);
740
+ return schema.fields.map(schemaFieldToModelField);
741
+ }
742
+
743
+ function initializeModelState(target)
744
+ {
745
+ // Models own their runtime-state shape: __state is a CjsModelState,
746
+ // created at construction before anything else (the event emitter's
747
+ // lazily-added `events` map lives on the same instance as an expando).
748
+ const existing = getRuntimeState(target);
749
+ if (existing instanceof CjsModelState) return existing;
750
+ if (existing)
751
+ {
752
+ throw new TypeError("CjsModel requires __state to be a CjsModelState.");
753
+ }
754
+
755
+ const state = new CjsModelState();
756
+ Object.defineProperty(target, "__state", {
757
+ value: state,
758
+ enumerable: false,
759
+ configurable: false,
760
+ writable: false
761
+ });
762
+ return state;
763
+ }
764
+
765
+ function initializeOwnedGraph(root, options = {})
766
+ {
767
+ root.Traverse(value =>
768
+ {
769
+ value.__state.suppressEvents++;
770
+
771
+ try
772
+ {
773
+ if (value.__state instanceof CjsModelState)
774
+ {
775
+ // Construction: everything is new, so every declared consequence
776
+ // applies - all flag/rebuild tokens are added, and the object is
777
+ // marked for one settle.
778
+ addAllDeclaredTokens(value);
779
+ value.__state.dirty = true;
780
+ }
781
+
782
+ // Initialize arguments belong to the class's Carbon/adapted contract.
783
+ // Owned-graph traversal is coordinated here and must not occupy arg 0.
784
+ // A conforming Initialize performs its own final
785
+ // UpdateValues({ skipEvents: true }), leaving nothing dirty.
786
+ if (typeof value.Initialize === "function")
787
+ {
788
+ if (value.Initialize() === false)
789
+ {
790
+ throw new Error(`${CjsSchema.getClassName(value.constructor)}.from initialization failed.`);
791
+ }
792
+ }
793
+
794
+ // Settle anything Initialize did not (including the no-Initialize
795
+ // case): the one construction settle, events suppressed.
796
+ if (value.__state instanceof CjsModelState && value.__state.dirty)
797
+ {
798
+ value.UpdateValues({
799
+ ...options,
800
+ source: options.source ?? value,
801
+ skipEvents: true
802
+ });
803
+ }
804
+ }
805
+ finally
806
+ {
807
+ value.__state.suppressEvents--;
808
+ }
809
+ }, {
810
+ order: "post",
811
+ reverse: true,
812
+ ownedOnly: true,
813
+ visited: options.visited
814
+ });
815
+ return root;
816
+ }
817
+
818
+ function AddResource(target, value)
819
+ {
820
+ if (value?.isResource === true) target.add(value);
821
+ }
822
+
823
+
824
+ function AddResources(target, values)
825
+ {
826
+ if (typeof values === "string" || typeof values?.[Symbol.iterator] !== "function")
827
+ {
828
+ throw new TypeError("CjsModel.OnGetResources must return an iterable of resources.");
829
+ }
830
+
831
+ // Empty slots are the model's own unset fields, not a contract violation.
832
+ for (const value of values)
833
+ {
834
+ if (value !== null && value !== undefined) target.add(value);
835
+ }
836
+ }
837
+
838
+ function schemaFieldToModelField(field)
839
+ {
840
+ return {
841
+ ...field,
842
+ jsType: field.type || field.jsType || null
843
+ };
844
+ }
845
+
846
+ function isWritableModelField(field)
847
+ {
848
+ const io = field?.io;
849
+ if (!io) return true;
850
+ if (io.write || io.persist || io.persistOnly) return true;
851
+ if (io.read && !io.write) return false;
852
+ return true;
853
+ }
854
+
855
+ function findIncomingKey(values, field)
856
+ {
857
+ for (const key of incomingKeyCandidates(field))
858
+ {
859
+ if (Object.prototype.hasOwnProperty.call(values, key)) return key;
860
+ }
861
+
862
+ return null;
863
+ }
864
+
865
+ function incomingKeyCandidates(field)
866
+ {
867
+ const aliases = field.aliases === undefined
868
+ ? field.alias === undefined ? [] : [field.alias]
869
+ : Array.isArray(field.aliases) ? field.aliases : [field.aliases];
870
+ return [field.name, ...aliases].filter(value => typeof value === "string" && value.length);
871
+ }
872
+
873
+ // Adds one field's declared @io.flag / @io.rebuild tokens to their stores.
874
+ // Duplicate adds are no-ops (Sets). Nothing in the model layer ever clears
875
+ // these stores - getters clear flags, work methods clear rebuild tokens.
876
+ function addDeclaredFieldTokens(target, field)
877
+ {
878
+ const io = field?.io;
879
+ if (!io) return;
880
+ if (io.flag) for (const token of io.flag) target.__state.flags.add(token);
881
+ if (io.rebuild) for (const token of io.rebuild) target.__state.rebuild.add(token);
882
+ }
883
+
884
+ // Construction / broad invalidation: every declared token applies.
885
+ function addAllDeclaredTokens(target)
886
+ {
887
+ const fields = CjsSchema.getSchema(target.constructor)?.fields || [];
888
+ for (const field of fields) addDeclaredFieldTokens(target, field);
889
+ }
890
+
891
+ // Direct-mutation courtesy: a caller that knows which fields it touched
892
+ // (bindings) passes them so declared consequences stay precise.
893
+ function addExplicitUpdateProperties(target, properties)
894
+ {
895
+ if (properties === null || properties === undefined) return;
896
+ target.__state.dirty = true;
897
+ for (const property of typeof properties === "string" ? [properties] : properties)
898
+ {
899
+ const field = CjsSchema.getField(target.constructor, property);
900
+ if (field) addDeclaredFieldTokens(target, field);
901
+ }
902
+ }
903
+
904
+ function createModifiedPayload(properties, source)
905
+ {
906
+ return Object.freeze({
907
+ properties: new Set(properties),
908
+ source
909
+ });
910
+ }
911
+
912
+ function areEquivalentSourceValues(a, b)
913
+ {
914
+ if (Object.is(a, b)) return true;
915
+
916
+ if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b))
917
+ {
918
+ if (a.constructor !== b.constructor || a.length !== b.length) return false;
919
+ for (let i = 0; i < a.length; i++)
920
+ {
921
+ if (!Object.is(a[i], b[i])) return false;
922
+ }
923
+ return true;
924
+ }
925
+
926
+ if (Array.isArray(a) && Array.isArray(b))
927
+ {
928
+ if (a.length !== b.length) return false;
929
+ for (let i = 0; i < a.length; i++)
930
+ {
931
+ if (!areEquivalentSourceValues(a[i], b[i])) return false;
932
+ }
933
+ return true;
934
+ }
935
+
936
+ return false;
937
+ }
938
+
939
+ export function exportSourceValue(value, options = {})
940
+ {
941
+ if (value instanceof CjsModel) return value.GetValues(options);
942
+ return exportCarbonValue(value);
943
+ }
944
+
945
+ function hasAdvancedExportOptions(options)
946
+ {
947
+ return !!(options && (options.persistOnly || options.typeTags || options.forceTypeTags
948
+ || options.refs || options.forceIDs || options.keyedLists
949
+ || (options.enumFormat && options.enumFormat !== "values")));
950
+ }
951
+
952
+ // Enum-aware value handling: @schema.enum("X") resolves through the owning
953
+ // class's PascalCase static `Constructor.X`, lazily and leaf-first.
954
+ const ENUM_REVERSE_CACHE = new WeakMap();
955
+
956
+ function resolveEnumStaticForField(Constructor, field)
957
+ {
958
+ const name = field?.enum?.enumType;
959
+ if (!name) return null;
960
+ const members = Constructor?.[name];
961
+ if (!members || typeof members !== "object") return null;
962
+ return { name, members };
963
+ }
964
+
965
+ function enumMemberName(members, value)
966
+ {
967
+ let reverse = ENUM_REVERSE_CACHE.get(members);
968
+ if (!reverse)
969
+ {
970
+ reverse = new Map();
971
+ for (const key of Object.keys(members))
972
+ {
973
+ // Duplicate values: first-declared key wins.
974
+ if (!reverse.has(members[key])) reverse.set(members[key], key);
975
+ }
976
+ ENUM_REVERSE_CACHE.set(members, reverse);
977
+ }
978
+ return reverse.get(value);
979
+ }
980
+
981
+ function enumIdentity(Constructor, name)
982
+ {
983
+ let current = Constructor;
984
+ while (typeof current === "function")
985
+ {
986
+ if (Object.hasOwn(current, name))
987
+ {
988
+ return `${CjsSchema.getClassName(current) || current.name}.${name}`;
989
+ }
990
+ current = Object.getPrototypeOf(current);
991
+ }
992
+ return `${CjsSchema.getClassName(Constructor) || Constructor.name}.${name}`;
993
+ }
994
+
995
+ // Returns the validated numeric member value, or undefined when the input is
996
+ // not a member. Accepts numeric values, exact member-name strings, and arrays
997
+ // (element 0 only, so identity tuples round-trip).
998
+ function translateEnumInput(value, members)
999
+ {
1000
+ if (Array.isArray(value))
1001
+ {
1002
+ if (!value.length) return undefined;
1003
+ return translateEnumInput(value[0], members);
1004
+ }
1005
+ if (typeof value === "string")
1006
+ {
1007
+ return Object.hasOwn(members, value) ? members[value] : undefined;
1008
+ }
1009
+ if (typeof value === "number")
1010
+ {
1011
+ return enumMemberName(members, value) === undefined ? undefined : value;
1012
+ }
1013
+ return undefined;
1014
+ }
1015
+
1016
+ // Atomic pre-validation: every enum-backed incoming value is checked before
1017
+ // any mutation; one TypeError reports every invalid property.
1018
+ function validateEnumInputs(out, values)
1019
+ {
1020
+ const translations = new Map();
1021
+ let issues = null;
1022
+ for (const field of getModelFields(out))
1023
+ {
1024
+ if (!isWritableModelField(field)) continue;
1025
+ const key = findIncomingKey(values, field);
1026
+ if (key === null) continue;
1027
+ const spec = resolveEnumStaticForField(out.constructor, field);
1028
+ if (!spec) continue;
1029
+ const raw = values[key];
1030
+ if (raw === null || raw === undefined || raw instanceof CjsModel) continue;
1031
+ const translated = translateEnumInput(raw, spec.members);
1032
+ if (translated === undefined)
1033
+ {
1034
+ issues = issues || [];
1035
+ issues.push(`${field.name}: ${JSON.stringify(raw)} is not a member of ${enumIdentity(out.constructor, spec.name)}`);
1036
+ }
1037
+ else
1038
+ {
1039
+ translations.set(field.name, translated);
1040
+ }
1041
+ }
1042
+ if (issues)
1043
+ {
1044
+ throw new TypeError(`Invalid enum values for ${CjsSchema.getClassName(out.constructor) || "model"} - ${issues.join("; ")}`);
1045
+ }
1046
+ return translations;
1047
+ }
1048
+
1049
+ function exportEnumFieldValue(value, spec, Constructor, options)
1050
+ {
1051
+ if (typeof value !== "number") return value;
1052
+ const memberName = enumMemberName(spec.members, value);
1053
+ if (memberName === undefined) return value;
1054
+ if (options.enumFormat === "names") return memberName;
1055
+ return [memberName, enumIdentity(Constructor, spec.name)];
1056
+ }
1057
+
1058
+ function isPersistedModelField(field)
1059
+ {
1060
+ const io = field?.io;
1061
+ return !!(io && (io.persist || io.persistOnly));
1062
+ }
1063
+
1064
+ function declaredExportClassName(fieldType)
1065
+ {
1066
+ if (!fieldType) return null;
1067
+ if (typeof fieldType === "string") return fieldType;
1068
+ if (fieldType.kind === "array" || fieldType.kind === "list")
1069
+ {
1070
+ const item = fieldType.itemType ?? null;
1071
+ return typeof item === "string" ? item : item?.className ?? null;
1072
+ }
1073
+ return fieldType.className ?? null;
1074
+ }
1075
+
1076
+ function createExportContext(root, options)
1077
+ {
1078
+ if (!options.refs && !options.forceIDs) return null;
1079
+
1080
+ let nextId = 1;
1081
+ const idByModel = new Map();
1082
+ const context = {
1083
+ emitted: new Set(),
1084
+ idByModel,
1085
+ getId(model)
1086
+ {
1087
+ let id = idByModel.get(model);
1088
+ if (id === undefined)
1089
+ {
1090
+ id = nextId++;
1091
+ idByModel.set(model, id);
1092
+ }
1093
+ return id;
1094
+ }
1095
+ };
1096
+
1097
+ if (options.refs)
1098
+ {
1099
+ // Pre-count occurrences so only genuinely shared models receive ids.
1100
+ const counts = new Map();
1101
+ (function walk(value)
1102
+ {
1103
+ if (Array.isArray(value))
1104
+ {
1105
+ for (const item of value) walk(item);
1106
+ return;
1107
+ }
1108
+ if (!(value instanceof CjsModel)) return;
1109
+ const count = (counts.get(value) ?? 0) + 1;
1110
+ counts.set(value, count);
1111
+ if (count > 1) return;
1112
+ for (const field of getModelFields(value))
1113
+ {
1114
+ if (options.persistOnly && !isPersistedModelField(field)) continue;
1115
+ walk(value[field.name]);
1116
+ }
1117
+ })(root);
1118
+ for (const [model, count] of counts)
1119
+ {
1120
+ if (count > 1) idByModel.set(model, nextId++);
1121
+ }
1122
+ }
1123
+
1124
+ return context;
1125
+ }
1126
+
1127
+ function exportModelInto(model, out, declaredClassName, options, context)
1128
+ {
1129
+ if (context)
1130
+ {
1131
+ context.emitted.add(model);
1132
+ }
1133
+
1134
+ const className = CjsSchema.getClassName(model.constructor);
1135
+ if (options.forceTypeTags || (options.typeTags && className && className !== declaredClassName))
1136
+ {
1137
+ out._type = className;
1138
+ }
1139
+ if (context && (options.forceIDs || context.idByModel.has(model)))
1140
+ {
1141
+ out._id = context.getId(model);
1142
+ }
1143
+
1144
+ const enumMode = options.enumFormat && options.enumFormat !== "values";
1145
+ for (const field of getModelFields(model))
1146
+ {
1147
+ if (options.persistOnly && !isPersistedModelField(field)) continue;
1148
+ if (enumMode)
1149
+ {
1150
+ const spec = resolveEnumStaticForField(model.constructor, field);
1151
+ if (spec)
1152
+ {
1153
+ out[field.name] = exportEnumFieldValue(model[field.name], spec, model.constructor, options);
1154
+ continue;
1155
+ }
1156
+ }
1157
+ out[field.name] = exportAdvancedValue(
1158
+ model[field.name],
1159
+ declaredExportClassName(field.jsType || field.type || null),
1160
+ options,
1161
+ context
1162
+ );
1163
+ }
1164
+
1165
+ return out;
1166
+ }
1167
+
1168
+ function exportAdvancedValue(value, declaredClassName, options, context)
1169
+ {
1170
+ if (value instanceof CjsModel)
1171
+ {
1172
+ if (context && options.refs && context.emitted.has(value))
1173
+ {
1174
+ return { _ref: context.getId(value) };
1175
+ }
1176
+ return exportModelInto(value, {}, declaredClassName, options, context);
1177
+ }
1178
+ if (Array.isArray(value))
1179
+ {
1180
+ if (options.keyedLists)
1181
+ {
1182
+ const keyed = exportKeyedList(value, declaredClassName, options, context);
1183
+ if (keyed) return keyed;
1184
+ }
1185
+ return value.map(item => exportAdvancedValue(item, declaredClassName, options, context));
1186
+ }
1187
+ return exportCarbonValue(value);
1188
+ }
1189
+
1190
+ function exportKeyedList(list, declaredClassName, options, context)
1191
+ {
1192
+ if (!list.length) return null;
1193
+
1194
+ const seen = new Set();
1195
+ for (const item of list)
1196
+ {
1197
+ if (!(item instanceof CjsModel)) return null;
1198
+ const name = item.name;
1199
+ if (typeof name !== "string" || name === "" || seen.has(name)) return null;
1200
+ seen.add(name);
1201
+ }
1202
+
1203
+ const out = {};
1204
+ for (const item of list)
1205
+ {
1206
+ const exported = exportAdvancedValue(item, declaredClassName, options, context);
1207
+ if (exported && typeof exported === "object" && exported._ref === undefined)
1208
+ {
1209
+ delete exported.name;
1210
+ }
1211
+ out[item.name] = exported;
1212
+ }
1213
+ return out;
1214
+ }
1215
+
1216
+ export function importSourceValue(value, field = null, options = {})
1217
+ {
1218
+ if (value instanceof CjsModel) return value;
1219
+
1220
+ if (isReferenceValue(value))
1221
+ {
1222
+ const resolved = resolveIncomingReference(value, options);
1223
+ if (resolved instanceof CjsPendingReference)
1224
+ {
1225
+ throw new TypeError(`Forward { _ref: ${JSON.stringify(value._ref)} } cannot be deferred in this position.`);
1226
+ }
1227
+ return resolved;
1228
+ }
1229
+
1230
+ const schemaType = getSchemaType(options.ownerConstructor, field?.name);
1231
+ const declaredClassName = getSchemaClassName(schemaType, options);
1232
+ if (value && typeof value === "object" && !(value instanceof CjsModel) && !Array.isArray(value) && !ArrayBuffer.isView(value))
1233
+ {
1234
+ // A registered `_type` selects the concrete class in singular
1235
+ // schema-typed positions. Carbon contracts may be declared through
1236
+ // interface names with no runtime inheritance, so the declared name
1237
+ // is a fallback, not a constraint the concrete class must extend.
1238
+ const explicitClassName = isSingularSchemaKind(schemaType) && typeof value._type === "string"
1239
+ ? getSchemaClassName(value._type, options)
1240
+ : null;
1241
+ const className = explicitClassName || declaredClassName;
1242
+ if (className)
1243
+ {
1244
+ return createModelValue(className, value, options);
1245
+ }
1246
+ }
1247
+
1248
+ if ((schemaType?.kind === "array" || schemaType?.kind === "list") && schemaType.itemType && Array.isArray(value))
1249
+ {
1250
+ const itemClassName = getSchemaClassName(schemaType.itemType, options);
1251
+ const result = [];
1252
+ for (let i = 0; i < value.length; i++)
1253
+ {
1254
+ const item = value[i];
1255
+ if (isReferenceValue(item))
1256
+ {
1257
+ result.push(importReferenceInto(item, options, result, i));
1258
+ continue;
1259
+ }
1260
+ if (!item || typeof item !== "object" || item instanceof CjsModel || ArrayBuffer.isView(item))
1261
+ {
1262
+ result.push(importSourceValue(item, null, options));
1263
+ continue;
1264
+ }
1265
+
1266
+ const explicitClassName = typeof item._type === "string"
1267
+ ? getSchemaClassName(item._type, options)
1268
+ : null;
1269
+ if (explicitClassName)
1270
+ {
1271
+ result.push(createModelValue(explicitClassName, item, options));
1272
+ continue;
1273
+ }
1274
+
1275
+ result.push(itemClassName
1276
+ ? createModelValue(itemClassName, item, options)
1277
+ : importSourceValue(item, null, options));
1278
+ }
1279
+ return result;
1280
+ }
1281
+
1282
+ // List fields also accept name-keyed object maps for unique-named items;
1283
+ // the map is a wholesale list replacement, mirroring array semantics.
1284
+ const effectiveType = field?.jsType || field?.type || schemaType;
1285
+ if ((effectiveType?.kind === "array" || effectiveType?.kind === "list")
1286
+ && value && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value))
1287
+ {
1288
+ return importListMapValue(value, effectiveType, options);
1289
+ }
1290
+
1291
+ if (field) return normalizeCarbonValue(value, field);
1292
+ if (ArrayBuffer.isView(value)) return normalizeCarbonValue(value, { jsType: { kind: "typedArray", js: value.constructor.name } });
1293
+ if (typeof value === "bigint") return value;
1294
+ if (Array.isArray(value))
1295
+ {
1296
+ const result = [];
1297
+ for (let i = 0; i < value.length; i++)
1298
+ {
1299
+ const item = value[i];
1300
+ result.push(isReferenceValue(item)
1301
+ ? importReferenceInto(item, options, result, i)
1302
+ : importSourceValue(item, null, options));
1303
+ }
1304
+ return result;
1305
+ }
1306
+ if (value && typeof value === "object" && !(value instanceof CjsModel))
1307
+ {
1308
+ const result = {};
1309
+ for (const [key, item] of Object.entries(value))
1310
+ {
1311
+ result[key] = isReferenceValue(item)
1312
+ ? importReferenceInto(item, options, result, key)
1313
+ : importSourceValue(item, null, options);
1314
+ }
1315
+ return result;
1316
+ }
1317
+ return value;
1318
+ }
1319
+
1320
+ function applyIncomingStructInPlace(current, incoming, field, options)
1321
+ {
1322
+ const schemaType = getSchemaType(options.ownerConstructor, field?.name) || field?.type || field?.jsType;
1323
+ if (schemaType?.kind !== "struct" || !(current instanceof CjsModel)) return null;
1324
+ if (incoming === null || incoming === undefined) return false;
1325
+ if (typeof incoming !== "object" || Array.isArray(incoming) || ArrayBuffer.isView(incoming))
1326
+ {
1327
+ throw new TypeError(`${field.name} requires an object value for registered struct ${schemaType.className || "unknown"}.`);
1328
+ }
1329
+
1330
+ const values = incoming instanceof CjsModel ? incoming.GetValues() : incoming;
1331
+ const changed = current.SetValues(values, options);
1332
+ return changed instanceof Set ? changed.size > 0 : changed === true;
1333
+ }
1334
+
1335
+ function importListMapValue(value, schemaType, options)
1336
+ {
1337
+ const itemClassName = schemaType.itemType ? getSchemaClassName(schemaType.itemType, options) : null;
1338
+ const result = [];
1339
+ for (const key of Object.keys(value))
1340
+ {
1341
+ const item = value[key];
1342
+ if (item === undefined || item === null) continue;
1343
+
1344
+ if (item instanceof CjsModel)
1345
+ {
1346
+ if (typeof item.name === "string" && item.name === "")
1347
+ {
1348
+ item.SetValues({ name: key });
1349
+ }
1350
+ result.push(item);
1351
+ continue;
1352
+ }
1353
+
1354
+ if (isReferenceValue(item))
1355
+ {
1356
+ // Shared items keep their own name; the map key is not restamped
1357
+ // onto an instance owned by another position in the graph.
1358
+ result.push(importReferenceInto(item, options, result, result.length));
1359
+ continue;
1360
+ }
1361
+
1362
+ if (typeof item !== "object" || Array.isArray(item) || ArrayBuffer.isView(item))
1363
+ {
1364
+ throw new TypeError(`List field maps require object or model values; "${key}" cannot become a list item.`);
1365
+ }
1366
+
1367
+ const explicitClassName = typeof item._type === "string"
1368
+ ? getSchemaClassName(item._type, options)
1369
+ : null;
1370
+ const className = explicitClassName || itemClassName;
1371
+ if (!className)
1372
+ {
1373
+ throw new TypeError(`List field maps cannot resolve a model class for "${key}".`);
1374
+ }
1375
+
1376
+ const values = item.name === undefined ? { ...item, name: key } : item;
1377
+ result.push(createModelValue(className, values, options));
1378
+ }
1379
+ return result;
1380
+ }
1381
+
1382
+ function getSchemaType(Constructor, fieldName)
1383
+ {
1384
+ if (!Constructor || !fieldName) return null;
1385
+ return CjsSchema.getField(Constructor, fieldName)?.type || null;
1386
+ }
1387
+
1388
+ function getSchemaClassName(schemaType, options = {})
1389
+ {
1390
+ if (!schemaType) return null;
1391
+ if (typeof schemaType === "string")
1392
+ {
1393
+ const Schema = options.registry || CjsModel.schema;
1394
+ return Schema.GetConstructor(schemaType) ? schemaType : null;
1395
+ }
1396
+ if (schemaType.kind === "model")
1397
+ {
1398
+ return schemaType.className || null;
1399
+ }
1400
+ if (schemaType.kind === "objectRef" || schemaType.kind === "struct")
1401
+ {
1402
+ const Schema = options.registry || CjsModel.schema;
1403
+ return schemaType.className && Schema.GetConstructor(schemaType.className)
1404
+ ? schemaType.className
1405
+ : null;
1406
+ }
1407
+ return null;
1408
+ }
1409
+
1410
+ function createModelValue(className, values, options)
1411
+ {
1412
+ const Schema = options.registry || CjsModel.schema;
1413
+ const Constructor = Schema.GetConstructor(className);
1414
+ if (!Constructor)
1415
+ {
1416
+ throw new TypeError(`No CjsModel class is registered for schema type ${className}.`);
1417
+ }
1418
+ if (Constructor !== CjsModel && !(Constructor.prototype instanceof CjsModel))
1419
+ {
1420
+ throw new TypeError(`Registered schema type ${className} is not a CjsModel.`);
1421
+ }
1422
+ if (typeof Constructor.from !== "function")
1423
+ {
1424
+ throw new TypeError(`Registered CjsModel ${className} does not provide from().`);
1425
+ }
1426
+ return Constructor.from(values, options);
1427
+ }
1428
+
1429
+ // --- Import operation context: `_id`/`_ref` identity across one call tree ---
1430
+
1431
+ /** Represents one unresolved model reference during a single import operation. */
1432
+ class CjsPendingReference
1433
+ {
1434
+
1435
+ constructor(id, expectedClassName = null)
1436
+ {
1437
+ this.id = id;
1438
+ this.expectedClassName = expectedClassName;
1439
+ }
1440
+
1441
+ }
1442
+
1443
+ function createImportContext()
1444
+ {
1445
+ const byId = new Map();
1446
+ const created = [];
1447
+ const pending = [];
1448
+ return {
1449
+ byId,
1450
+ registerCreated(instance)
1451
+ {
1452
+ created.push(instance);
1453
+ },
1454
+ register(id, instance)
1455
+ {
1456
+ const existing = byId.get(id);
1457
+ if (existing === instance) return;
1458
+ if (existing !== undefined)
1459
+ {
1460
+ throw new TypeError(`Duplicate _id ${JSON.stringify(id)} in imported values.`);
1461
+ }
1462
+ byId.set(id, instance);
1463
+ },
1464
+ defer(id, assign)
1465
+ {
1466
+ pending.push({ id, assign });
1467
+ },
1468
+ finalize()
1469
+ {
1470
+ const unresolved = new Set();
1471
+ for (const entry of pending)
1472
+ {
1473
+ const instance = byId.get(entry.id);
1474
+ if (instance === undefined)
1475
+ {
1476
+ unresolved.add(entry.id);
1477
+ continue;
1478
+ }
1479
+ entry.assign(instance);
1480
+ }
1481
+ pending.length = 0;
1482
+ if (unresolved.size)
1483
+ {
1484
+ throw new TypeError(`Unresolved _ref ids: ${Array.from(unresolved, id => JSON.stringify(id)).join(", ")}. Every { _ref } must match a { _id } in the same import operation.`);
1485
+ }
1486
+ },
1487
+ initializeCreated(options)
1488
+ {
1489
+ const visited = new Set();
1490
+
1491
+ for (let index = created.length - 1; index >= 0; index--)
1492
+ {
1493
+ initializeOwnedGraph(created[index], { ...options, visited });
1494
+ }
1495
+
1496
+ created.length = 0;
1497
+ }
1498
+ };
1499
+ }
1500
+
1501
+ function isReferenceValue(value)
1502
+ {
1503
+ return !!value && typeof value === "object" && !Array.isArray(value)
1504
+ && !ArrayBuffer.isView(value) && !(value instanceof CjsModel)
1505
+ && value._ref !== undefined;
1506
+ }
1507
+
1508
+ // Resolves a `{ _ref }` immediately when the target is registered, or returns
1509
+ // a CjsPendingReference for the finalize pass (forward references). Resolved
1510
+ // references assign like direct instances: no declared-type constraint, since
1511
+ // Carbon contracts may be declared through interface names that have no
1512
+ // runtime inheritance relationship with the concrete class.
1513
+ function resolveIncomingReference(value, options)
1514
+ {
1515
+ const id = value._ref;
1516
+ const context = options.importContext;
1517
+ if (!context)
1518
+ {
1519
+ throw new TypeError(`Cannot resolve { _ref: ${JSON.stringify(id)} } outside an import operation; import the graph through SetValues/from so identity is tracked.`);
1520
+ }
1521
+ const resolved = context.byId.get(id);
1522
+ if (resolved === undefined) return new CjsPendingReference(id);
1523
+ return resolved;
1524
+ }
1525
+
1526
+ // Resolves a reference into a container slot, deferring forward references to
1527
+ // the owning operation's finalize pass. Deferred slots hold null until then.
1528
+ function importReferenceInto(value, options, target, key)
1529
+ {
1530
+ const resolved = resolveIncomingReference(value, options);
1531
+ if (resolved instanceof CjsPendingReference)
1532
+ {
1533
+ options.importContext.defer(resolved.id, instance =>
1534
+ {
1535
+ target[key] = instance;
1536
+ });
1537
+ return null;
1538
+ }
1539
+ return resolved;
1540
+ }
1541
+
1542
+ // Applies a `{ _ref }` incoming value to a model field, returning whether the
1543
+ // field changed. Forward references keep the current value until finalize.
1544
+ function applyIncomingReference(out, field, incoming, options)
1545
+ {
1546
+ const fieldName = field.name;
1547
+ const resolved = resolveIncomingReference(incoming, options);
1548
+
1549
+ if (resolved instanceof CjsPendingReference)
1550
+ {
1551
+ options.importContext.defer(resolved.id, instance =>
1552
+ {
1553
+ out[fieldName] = instance;
1554
+ });
1555
+ return true;
1556
+ }
1557
+
1558
+ if (field.io?.always !== true && Object.is(out[fieldName], resolved)) return false;
1559
+ out[fieldName] = resolved;
1560
+ return true;
1561
+ }
1562
+
1563
+ function resolveRegisteredModelClass(typeName, options = {})
1564
+ {
1565
+ const Schema = options.registry || CjsModel.schema;
1566
+ const Constructor = Schema.GetConstructor(typeName);
1567
+ if (!Constructor)
1568
+ {
1569
+ throw new TypeError(`No CjsModel class is registered for _type "${typeName}".`);
1570
+ }
1571
+ return Constructor;
1572
+ }
1573
+
1574
+ function isSingularSchemaKind(schemaType)
1575
+ {
1576
+ if (!schemaType) return false;
1577
+ if (typeof schemaType === "string") return true;
1578
+ return schemaType.kind !== "array" && schemaType.kind !== "list"
1579
+ && schemaType.kind !== "map" && schemaType.kind !== "set";
1580
+ }
1581
+
1582
+ function assertTargetTypeMatches(out, typeName, options = {})
1583
+ {
1584
+ const Constructor = resolveRegisteredModelClass(typeName, options);
1585
+ if (!(out instanceof Constructor))
1586
+ {
1587
+ throw new TypeError(`Values with _type "${typeName}" cannot apply to a ${CjsSchema.getClassName(out.constructor) || "model"} target.`);
1588
+ }
1589
+ }