@carbonenginejs/runtime-utils 0.1.2 → 0.1.4

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