@carbonenginejs/runtime-utils 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/concepts/model-lifecycle.md +43 -0
- package/docs/core-types/README.md +12 -0
- package/package.json +139 -139
- package/src/model/CjsModel.js +295 -14
- package/src/schema/CjsSchema.js +13 -1
|
@@ -44,6 +44,49 @@ the ordinary settled event. Callers that need changed names use the
|
|
|
44
44
|
may emit an immediate payload containing `properties`; that is not the normal
|
|
45
45
|
settled-event shape.
|
|
46
46
|
|
|
47
|
+
## Schema-backed child collections
|
|
48
|
+
|
|
49
|
+
Domain containers expose named methods such as `CreateAttachment`,
|
|
50
|
+
`AddAttachment`, `RemoveAttachment`, and `DeleteAttachment`. Those methods may
|
|
51
|
+
delegate to the programmatic static helpers `CjsModel.createChild(owner, ...)`,
|
|
52
|
+
`addChild`, `removeChild`, `deleteChild`, and `clearChildren`. Model instances do
|
|
53
|
+
not inherit generic property-string child methods; the named methods explicitly
|
|
54
|
+
defined by their domain class are their child-mutation API.
|
|
55
|
+
|
|
56
|
+
The helpers accept only schema `array` and `list` fields backed by ordinary
|
|
57
|
+
JavaScript arrays. They do not operate on typed arrays, maps, sets, or
|
|
58
|
+
undeclared properties. `createChild` hydrates one value using the collection's
|
|
59
|
+
declared item type before adding it.
|
|
60
|
+
|
|
61
|
+
A collection mutation follows the same state rules as `SetValues`:
|
|
62
|
+
|
|
63
|
+
- it marks the parent dirty unless `markDirty: false`;
|
|
64
|
+
- it adds that collection field's declared `@io.flag(...)` and
|
|
65
|
+
`@io.rebuild(...)` tokens unless `notify: false`;
|
|
66
|
+
- it settles the parent unless `skipUpdate: true`; and
|
|
67
|
+
- it suppresses child and modified events when `skipEvents: true`.
|
|
68
|
+
|
|
69
|
+
When the parent implements Carbon-shaped `OnListModified`, insertion and
|
|
70
|
+
removal callbacks receive the mutated list. Clearing sends unload-start while
|
|
71
|
+
the list is still populated, then empties it. Generic `childadded`,
|
|
72
|
+
`childremoved`, `childdeleted`, and `childrencleared` events carry the property
|
|
73
|
+
and affected child or count; named wrappers may also supply `onAdded`,
|
|
74
|
+
`onRemoved`, `onDeleted`, or `onCleared` callbacks.
|
|
75
|
+
|
|
76
|
+
Remove only detaches. Delete also emits the deletion event and may run an
|
|
77
|
+
explicit domain-owned `delete` callback; it never guesses a generic `Destroy`
|
|
78
|
+
operation. JavaScript lifetime management remains ordinary garbage collection
|
|
79
|
+
when no teardown callback is supplied.
|
|
80
|
+
|
|
81
|
+
Child-owned flags and rebuild tokens are deliberately not interpreted by these
|
|
82
|
+
helpers. A child property may declare a token such as a deferred deletion
|
|
83
|
+
request, but the current runtime context decides whether and when to consume
|
|
84
|
+
it and which named parent method to call. That context must retain the exact
|
|
85
|
+
relationship it owns: the same child may be reached through multiple parents,
|
|
86
|
+
properties, or nested contexts, and graph traversal does not make those
|
|
87
|
+
contexts interchangeable. No global deletion queue or child-management
|
|
88
|
+
decorator is implied.
|
|
89
|
+
|
|
47
90
|
## Initialization
|
|
48
91
|
|
|
49
92
|
`CjsModel.from()` constructs or imports the graph, resolves references, then
|
|
@@ -187,6 +187,18 @@ They return the same changed-set, boolean, or `false` result as `SetValues`.
|
|
|
187
187
|
`Copy`/`copy` instead require an instantiated
|
|
188
188
|
`CjsModel` source and forward the supplied `SetValues` options.
|
|
189
189
|
|
|
190
|
+
Schema-backed containers expose domain-named child methods and delegate their
|
|
191
|
+
ordinary `array` or `list` mutations to the programmatic static
|
|
192
|
+
`CjsModel.createChild(owner, ...)`, `addChild`, `removeChild`, `deleteChild`, and
|
|
193
|
+
`clearChildren` helpers. Instances expose only child methods explicitly defined
|
|
194
|
+
by their domain class. The static helpers hydrate declared item types, preserve
|
|
195
|
+
Carbon `OnListModified` notifications, and apply the collection field's normal
|
|
196
|
+
flag/rebuild tokens. They do not manage typed arrays or interpret tokens owned
|
|
197
|
+
by a child; the active domain context remains responsible for consuming child
|
|
198
|
+
work requests.
|
|
199
|
+
See [Model lifecycle](../concepts/model-lifecycle.md#schema-backed-child-collections)
|
|
200
|
+
for removal, deletion, event, and nested-context rules.
|
|
201
|
+
|
|
190
202
|
### Enum-backed fields
|
|
191
203
|
|
|
192
204
|
Enum metadata resolves lazily from the concrete model constructor's PascalCase
|
package/package.json
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@carbonenginejs/runtime-utils",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Browser-safe shared utilities, math, constants, Carbon types, schemas, documents, and runtime model primitives.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "src/index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./src/index.js",
|
|
9
|
-
"./arrays": "./src/arrays.js",
|
|
10
|
-
"./bytes": "./src/bytes.js",
|
|
11
|
-
"./compression": "./src/compression.js",
|
|
12
|
-
"./const": "./src/constants/index.js",
|
|
13
|
-
"./const/trinity": "./src/constants/trinity.js",
|
|
14
|
-
"./const/media": "./src/media/index.js",
|
|
15
|
-
"./const/graphics": "./src/graphics/index.js",
|
|
16
|
-
"./const/render-context": "./src/renderContext/index.js",
|
|
17
|
-
"./const/audio": "./src/audio/index.js",
|
|
18
|
-
"./const/shader": "./src/shader/index.js",
|
|
19
|
-
"./const/d3d": "./src/d3d/index.js",
|
|
20
|
-
"./const/webgpu": "./src/webgpu/index.js",
|
|
21
|
-
"./media": "./src/media/index.js",
|
|
22
|
-
"./graphics": "./src/graphics/index.js",
|
|
23
|
-
"./render-context": "./src/renderContext/index.js",
|
|
24
|
-
"./audio": "./src/audio/index.js",
|
|
25
|
-
"./shader": "./src/shader/index.js",
|
|
26
|
-
"./d3d": "./src/d3d/index.js",
|
|
27
|
-
"./webgpu": "./src/webgpu/index.js",
|
|
28
|
-
"./document": "./src/document/index.js",
|
|
29
|
-
"./errors": "./src/errors/index.js",
|
|
30
|
-
"./hydration": "./src/document/hydrationAdapter.js",
|
|
31
|
-
"./lifecycle": "./src/lifecycle/index.js",
|
|
32
|
-
"./schema": "./src/schema/index.js",
|
|
33
|
-
"./types": "./src/types/index.js",
|
|
34
|
-
"./model": "./src/model/index.js",
|
|
35
|
-
"./is": "./src/is.js",
|
|
36
|
-
"./json": "./src/json.js",
|
|
37
|
-
"./lookup": "./src/lookup.js",
|
|
38
|
-
"./object": "./src/object.js",
|
|
39
|
-
"./math": "./src/math/index.js",
|
|
40
|
-
"./math/scalar": "./src/math/scalar.js",
|
|
41
|
-
"./math/num": "./src/num.js",
|
|
42
|
-
"./math/vec2": "./src/vec2.js",
|
|
43
|
-
"./math/vec3": "./src/vec3.js",
|
|
44
|
-
"./math/vec4": "./src/vec4.js",
|
|
45
|
-
"./math/quat": "./src/quat.js",
|
|
46
|
-
"./math/mat3": "./src/mat3.js",
|
|
47
|
-
"./math/mat4": "./src/mat4.js",
|
|
48
|
-
"./math/box3": "./src/box3.js",
|
|
49
|
-
"./math/tri3": "./src/tri3.js",
|
|
50
|
-
"./math/lne3": "./src/lne3.js",
|
|
51
|
-
"./math/pln": "./src/pln.js",
|
|
52
|
-
"./math/ray3": "./src/ray3.js",
|
|
53
|
-
"./math/sph3": "./src/sph3.js",
|
|
54
|
-
"./math/pool": "./src/pool.js",
|
|
55
|
-
"./math/utils": "./src/utils.js",
|
|
56
|
-
"./math/noise": "./src/noise.js",
|
|
57
|
-
"./math/curve": "./src/curve.js",
|
|
58
|
-
"./math/vertex": "./src/vertex.js",
|
|
59
|
-
"./math/mesh": "./src/mesh.js",
|
|
60
|
-
"./math/tangent": "./src/tangent.js",
|
|
61
|
-
"./math/geometry": "./src/geometry/index.js",
|
|
62
|
-
"./num": "./src/num.js",
|
|
63
|
-
"./vec2": "./src/vec2.js",
|
|
64
|
-
"./vec3": "./src/vec3.js",
|
|
65
|
-
"./vec4": "./src/vec4.js",
|
|
66
|
-
"./quat": "./src/quat.js",
|
|
67
|
-
"./mat3": "./src/mat3.js",
|
|
68
|
-
"./mat4": "./src/mat4.js",
|
|
69
|
-
"./box3": "./src/box3.js",
|
|
70
|
-
"./tri3": "./src/tri3.js",
|
|
71
|
-
"./lne3": "./src/lne3.js",
|
|
72
|
-
"./pln": "./src/pln.js",
|
|
73
|
-
"./ray3": "./src/ray3.js",
|
|
74
|
-
"./sph3": "./src/sph3.js",
|
|
75
|
-
"./pool": "./src/pool.js",
|
|
76
|
-
"./utils": "./src/utils.js",
|
|
77
|
-
"./noise": "./src/noise.js",
|
|
78
|
-
"./curve": "./src/curve.js",
|
|
79
|
-
"./vertex": "./src/vertex.js",
|
|
80
|
-
"./mesh": "./src/mesh.js",
|
|
81
|
-
"./tangent": "./src/tangent.js",
|
|
82
|
-
"./geometry": "./src/geometry/index.js",
|
|
83
|
-
"./path": "./src/path.js",
|
|
84
|
-
"./text": "./src/text.js",
|
|
85
|
-
"./validation": "./src/validation.js"
|
|
86
|
-
},
|
|
87
|
-
"sideEffects": false,
|
|
88
|
-
"engines": {
|
|
89
|
-
"node": ">=18"
|
|
90
|
-
},
|
|
91
|
-
"scripts": {
|
|
92
|
-
"test": "node --test",
|
|
93
|
-
"lint": "eslint --ext .js,.mjs src test",
|
|
94
|
-
"lint:fix": "eslint --fix --ext .js,.mjs src test",
|
|
95
|
-
"check": "npm run lint && npm test"
|
|
96
|
-
},
|
|
97
|
-
"keywords": [
|
|
98
|
-
"carbonenginejs",
|
|
99
|
-
"runtime",
|
|
100
|
-
"utilities",
|
|
101
|
-
"gl-matrix",
|
|
102
|
-
"constants",
|
|
103
|
-
"schema",
|
|
104
|
-
"types",
|
|
105
|
-
"errors",
|
|
106
|
-
"geometry",
|
|
107
|
-
"tangent",
|
|
108
|
-
"browser"
|
|
109
|
-
],
|
|
110
|
-
"files": [
|
|
111
|
-
"src",
|
|
112
|
-
"docs",
|
|
113
|
-
"README.md",
|
|
114
|
-
"LICENSE",
|
|
115
|
-
"NOTICE",
|
|
116
|
-
"THIRD-PARTY-NOTICES.md"
|
|
117
|
-
],
|
|
118
|
-
"license": "MIT",
|
|
119
|
-
"dependencies": {
|
|
120
|
-
"gl-matrix": "^3.4.4"
|
|
121
|
-
},
|
|
122
|
-
"devDependencies": {
|
|
123
|
-
"eslint": "^8.57.1"
|
|
124
|
-
},
|
|
125
|
-
"repository": {
|
|
126
|
-
"type": "git",
|
|
127
|
-
"url": "git+https://github.com/carbonenginejs/runtime-utils.git"
|
|
128
|
-
},
|
|
129
|
-
"homepage": "https://github.com/carbonenginejs/runtime-utils#readme",
|
|
130
|
-
"bugs": {
|
|
131
|
-
"url": "https://github.com/carbonenginejs/runtime-utils/issues"
|
|
132
|
-
},
|
|
133
|
-
"publishConfig": {
|
|
134
|
-
"access": "public"
|
|
135
|
-
},
|
|
136
|
-
"overrides": {
|
|
137
|
-
"brace-expansion": "^5.0.8"
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@carbonenginejs/runtime-utils",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Browser-safe shared utilities, math, constants, Carbon types, schemas, documents, and runtime model primitives.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./arrays": "./src/arrays.js",
|
|
10
|
+
"./bytes": "./src/bytes.js",
|
|
11
|
+
"./compression": "./src/compression.js",
|
|
12
|
+
"./const": "./src/constants/index.js",
|
|
13
|
+
"./const/trinity": "./src/constants/trinity.js",
|
|
14
|
+
"./const/media": "./src/media/index.js",
|
|
15
|
+
"./const/graphics": "./src/graphics/index.js",
|
|
16
|
+
"./const/render-context": "./src/renderContext/index.js",
|
|
17
|
+
"./const/audio": "./src/audio/index.js",
|
|
18
|
+
"./const/shader": "./src/shader/index.js",
|
|
19
|
+
"./const/d3d": "./src/d3d/index.js",
|
|
20
|
+
"./const/webgpu": "./src/webgpu/index.js",
|
|
21
|
+
"./media": "./src/media/index.js",
|
|
22
|
+
"./graphics": "./src/graphics/index.js",
|
|
23
|
+
"./render-context": "./src/renderContext/index.js",
|
|
24
|
+
"./audio": "./src/audio/index.js",
|
|
25
|
+
"./shader": "./src/shader/index.js",
|
|
26
|
+
"./d3d": "./src/d3d/index.js",
|
|
27
|
+
"./webgpu": "./src/webgpu/index.js",
|
|
28
|
+
"./document": "./src/document/index.js",
|
|
29
|
+
"./errors": "./src/errors/index.js",
|
|
30
|
+
"./hydration": "./src/document/hydrationAdapter.js",
|
|
31
|
+
"./lifecycle": "./src/lifecycle/index.js",
|
|
32
|
+
"./schema": "./src/schema/index.js",
|
|
33
|
+
"./types": "./src/types/index.js",
|
|
34
|
+
"./model": "./src/model/index.js",
|
|
35
|
+
"./is": "./src/is.js",
|
|
36
|
+
"./json": "./src/json.js",
|
|
37
|
+
"./lookup": "./src/lookup.js",
|
|
38
|
+
"./object": "./src/object.js",
|
|
39
|
+
"./math": "./src/math/index.js",
|
|
40
|
+
"./math/scalar": "./src/math/scalar.js",
|
|
41
|
+
"./math/num": "./src/num.js",
|
|
42
|
+
"./math/vec2": "./src/vec2.js",
|
|
43
|
+
"./math/vec3": "./src/vec3.js",
|
|
44
|
+
"./math/vec4": "./src/vec4.js",
|
|
45
|
+
"./math/quat": "./src/quat.js",
|
|
46
|
+
"./math/mat3": "./src/mat3.js",
|
|
47
|
+
"./math/mat4": "./src/mat4.js",
|
|
48
|
+
"./math/box3": "./src/box3.js",
|
|
49
|
+
"./math/tri3": "./src/tri3.js",
|
|
50
|
+
"./math/lne3": "./src/lne3.js",
|
|
51
|
+
"./math/pln": "./src/pln.js",
|
|
52
|
+
"./math/ray3": "./src/ray3.js",
|
|
53
|
+
"./math/sph3": "./src/sph3.js",
|
|
54
|
+
"./math/pool": "./src/pool.js",
|
|
55
|
+
"./math/utils": "./src/utils.js",
|
|
56
|
+
"./math/noise": "./src/noise.js",
|
|
57
|
+
"./math/curve": "./src/curve.js",
|
|
58
|
+
"./math/vertex": "./src/vertex.js",
|
|
59
|
+
"./math/mesh": "./src/mesh.js",
|
|
60
|
+
"./math/tangent": "./src/tangent.js",
|
|
61
|
+
"./math/geometry": "./src/geometry/index.js",
|
|
62
|
+
"./num": "./src/num.js",
|
|
63
|
+
"./vec2": "./src/vec2.js",
|
|
64
|
+
"./vec3": "./src/vec3.js",
|
|
65
|
+
"./vec4": "./src/vec4.js",
|
|
66
|
+
"./quat": "./src/quat.js",
|
|
67
|
+
"./mat3": "./src/mat3.js",
|
|
68
|
+
"./mat4": "./src/mat4.js",
|
|
69
|
+
"./box3": "./src/box3.js",
|
|
70
|
+
"./tri3": "./src/tri3.js",
|
|
71
|
+
"./lne3": "./src/lne3.js",
|
|
72
|
+
"./pln": "./src/pln.js",
|
|
73
|
+
"./ray3": "./src/ray3.js",
|
|
74
|
+
"./sph3": "./src/sph3.js",
|
|
75
|
+
"./pool": "./src/pool.js",
|
|
76
|
+
"./utils": "./src/utils.js",
|
|
77
|
+
"./noise": "./src/noise.js",
|
|
78
|
+
"./curve": "./src/curve.js",
|
|
79
|
+
"./vertex": "./src/vertex.js",
|
|
80
|
+
"./mesh": "./src/mesh.js",
|
|
81
|
+
"./tangent": "./src/tangent.js",
|
|
82
|
+
"./geometry": "./src/geometry/index.js",
|
|
83
|
+
"./path": "./src/path.js",
|
|
84
|
+
"./text": "./src/text.js",
|
|
85
|
+
"./validation": "./src/validation.js"
|
|
86
|
+
},
|
|
87
|
+
"sideEffects": false,
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=18"
|
|
90
|
+
},
|
|
91
|
+
"scripts": {
|
|
92
|
+
"test": "node --test",
|
|
93
|
+
"lint": "eslint --ext .js,.mjs src test",
|
|
94
|
+
"lint:fix": "eslint --fix --ext .js,.mjs src test",
|
|
95
|
+
"check": "npm run lint && npm test"
|
|
96
|
+
},
|
|
97
|
+
"keywords": [
|
|
98
|
+
"carbonenginejs",
|
|
99
|
+
"runtime",
|
|
100
|
+
"utilities",
|
|
101
|
+
"gl-matrix",
|
|
102
|
+
"constants",
|
|
103
|
+
"schema",
|
|
104
|
+
"types",
|
|
105
|
+
"errors",
|
|
106
|
+
"geometry",
|
|
107
|
+
"tangent",
|
|
108
|
+
"browser"
|
|
109
|
+
],
|
|
110
|
+
"files": [
|
|
111
|
+
"src",
|
|
112
|
+
"docs",
|
|
113
|
+
"README.md",
|
|
114
|
+
"LICENSE",
|
|
115
|
+
"NOTICE",
|
|
116
|
+
"THIRD-PARTY-NOTICES.md"
|
|
117
|
+
],
|
|
118
|
+
"license": "MIT",
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"gl-matrix": "^3.4.4"
|
|
121
|
+
},
|
|
122
|
+
"devDependencies": {
|
|
123
|
+
"eslint": "^8.57.1"
|
|
124
|
+
},
|
|
125
|
+
"repository": {
|
|
126
|
+
"type": "git",
|
|
127
|
+
"url": "git+https://github.com/carbonenginejs/runtime-utils.git"
|
|
128
|
+
},
|
|
129
|
+
"homepage": "https://github.com/carbonenginejs/runtime-utils#readme",
|
|
130
|
+
"bugs": {
|
|
131
|
+
"url": "https://github.com/carbonenginejs/runtime-utils/issues"
|
|
132
|
+
},
|
|
133
|
+
"publishConfig": {
|
|
134
|
+
"access": "public"
|
|
135
|
+
},
|
|
136
|
+
"overrides": {
|
|
137
|
+
"brace-expansion": "^5.0.8"
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/model/CjsModel.js
CHANGED
|
@@ -4,7 +4,13 @@ import { getRuntimeState } from "../runtime/CjsRuntimeState.js";
|
|
|
4
4
|
import { CjsModelState } from "./CjsModelState.js";
|
|
5
5
|
import { CjsEventEmitter } from "./CjsEventEmitter.js";
|
|
6
6
|
|
|
7
|
-
const MAX_UPDATE_PASSES = 32;
|
|
7
|
+
const MAX_UPDATE_PASSES = 32;
|
|
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
|
+
};
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Shared base for schema-backed CarbonEngineJS runtime classes.
|
|
@@ -92,13 +98,180 @@ export class CjsModel extends CjsEventEmitter
|
|
|
92
98
|
* @param {object} [options={}]
|
|
93
99
|
* @returns {Set<string>|boolean} The result returned by {@link CjsModel.set}.
|
|
94
100
|
*/
|
|
95
|
-
Merge(values = [], options = {})
|
|
96
|
-
{
|
|
97
|
-
return CjsModel.merge(this, values, options);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
101
|
+
Merge(values = [], options = {})
|
|
102
|
+
{
|
|
103
|
+
return CjsModel.merge(this, values, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Constructs one item from a schema-backed child collection's declared
|
|
108
|
+
* item type, then adds it through the ordinary child-mutation path.
|
|
109
|
+
*
|
|
110
|
+
* Domain classes expose named factories such as `CreateAttachment`; this
|
|
111
|
+
* programmatic helper keeps property-string mutation out of their instance
|
|
112
|
+
* API.
|
|
113
|
+
*
|
|
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.
|
|
119
|
+
*/
|
|
120
|
+
static createChild(target, property, values = {}, options = {})
|
|
121
|
+
{
|
|
122
|
+
const { field } = getChildCollection(target, property);
|
|
123
|
+
const imported = importSourceValue([ values ], field, {
|
|
124
|
+
...options,
|
|
125
|
+
ownerConstructor: target.constructor
|
|
126
|
+
});
|
|
127
|
+
const child = imported[0];
|
|
128
|
+
|
|
129
|
+
assertChildObject(child, field.name);
|
|
130
|
+
CjsModel.addChild(target, field.name, child, options);
|
|
131
|
+
return child;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Appends an existing object to a schema-backed child collection.
|
|
136
|
+
*
|
|
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.
|
|
140
|
+
*
|
|
141
|
+
* @param {CjsModel} target Owning model instance.
|
|
142
|
+
* @param {string} property Schema `array` or `list` field name.
|
|
143
|
+
* @param {object} child Existing child object.
|
|
144
|
+
* @param {object} [options={}]
|
|
145
|
+
* @returns {object} The appended child.
|
|
146
|
+
*/
|
|
147
|
+
static addChild(target, property, child, options = {})
|
|
148
|
+
{
|
|
149
|
+
const { field, collection } = getChildCollection(target, property);
|
|
150
|
+
|
|
151
|
+
assertChildObject(child, field.name);
|
|
152
|
+
assertChildCallback(options.onAdded, "onAdded");
|
|
153
|
+
|
|
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);
|
|
158
|
+
|
|
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;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Detaches the first matching object from a schema-backed child collection.
|
|
168
|
+
* Removal never destroys the child.
|
|
169
|
+
*
|
|
170
|
+
* @param {CjsModel} target Owning model instance.
|
|
171
|
+
* @param {string} property Schema `array` or `list` field name.
|
|
172
|
+
* @param {object} child Existing child object.
|
|
173
|
+
* @param {object} [options={}]
|
|
174
|
+
* @returns {boolean} Whether the child was present and removed.
|
|
175
|
+
*/
|
|
176
|
+
static removeChild(target, property, child, options = {})
|
|
177
|
+
{
|
|
178
|
+
const { field, collection } = getChildCollection(target, property);
|
|
179
|
+
const index = collection.indexOf(child);
|
|
180
|
+
|
|
181
|
+
if (index === -1) return false;
|
|
182
|
+
assertChildCallback(options.onRemoved, "onRemoved");
|
|
183
|
+
|
|
184
|
+
collection.splice(index, 1);
|
|
185
|
+
recordChildMutation(target, field, options);
|
|
186
|
+
notifyListModified(target, CHILD_LIST_EVENT.REMOVED, index, 0, child, collection);
|
|
187
|
+
|
|
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;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
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`.
|
|
202
|
+
*
|
|
203
|
+
* @param {CjsModel} target Owning model instance.
|
|
204
|
+
* @param {string} property Schema `array` or `list` field name.
|
|
205
|
+
* @param {object} child Existing child object.
|
|
206
|
+
* @param {object} [options={}]
|
|
207
|
+
* @param {Function} [options.delete] Explicit child teardown callback.
|
|
208
|
+
* @returns {boolean} Whether the child was present and deleted.
|
|
209
|
+
*/
|
|
210
|
+
static deleteChild(target, property, child, options = {})
|
|
211
|
+
{
|
|
212
|
+
const { field, collection } = getChildCollection(target, property);
|
|
213
|
+
const index = collection.indexOf(child);
|
|
214
|
+
|
|
215
|
+
if (index === -1) return false;
|
|
216
|
+
|
|
217
|
+
if (options.delete !== undefined && typeof options.delete !== "function")
|
|
218
|
+
{
|
|
219
|
+
throw new TypeError("CjsModel child delete option must be a function.");
|
|
220
|
+
}
|
|
221
|
+
assertChildCallback(options.onDeleted, "onDeleted");
|
|
222
|
+
|
|
223
|
+
CjsModel.removeChild(target, field.name, child, { ...options, skipUpdate: true });
|
|
224
|
+
|
|
225
|
+
if (typeof options.delete === "function")
|
|
226
|
+
{
|
|
227
|
+
options.delete.call(target, child, options);
|
|
228
|
+
}
|
|
229
|
+
|
|
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;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Detaches every object from a schema-backed child collection without
|
|
239
|
+
* destroying the children.
|
|
240
|
+
*
|
|
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.
|
|
244
|
+
*
|
|
245
|
+
* @param {CjsModel} target Owning model instance.
|
|
246
|
+
* @param {string} property Schema `array` or `list` field name.
|
|
247
|
+
* @param {object} [options={}]
|
|
248
|
+
* @returns {boolean} Whether any children were cleared.
|
|
249
|
+
*/
|
|
250
|
+
static clearChildren(target, property, options = {})
|
|
251
|
+
{
|
|
252
|
+
const { field, collection } = getChildCollection(target, property);
|
|
253
|
+
const count = collection.length;
|
|
254
|
+
|
|
255
|
+
if (!count) return false;
|
|
256
|
+
assertChildCallback(options.onCleared, "onCleared");
|
|
257
|
+
|
|
258
|
+
recordChildMutation(target, field, options);
|
|
259
|
+
notifyListModified(target, CHILD_LIST_EVENT.UNLOAD_START, 0, 0, null, collection);
|
|
260
|
+
collection.length = 0;
|
|
261
|
+
|
|
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;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Applies pending changes: drives the OnModified hook until the model
|
|
102
275
|
* settles, clears the dirty mark, and emits one final modified event.
|
|
103
276
|
*
|
|
104
277
|
* Calling this IS the "I made changes, apply please" contract: it always
|
|
@@ -862,15 +1035,123 @@ function findIncomingKey(values, field)
|
|
|
862
1035
|
return null;
|
|
863
1036
|
}
|
|
864
1037
|
|
|
865
|
-
function incomingKeyCandidates(field)
|
|
866
|
-
{
|
|
1038
|
+
function incomingKeyCandidates(field)
|
|
1039
|
+
{
|
|
867
1040
|
const aliases = field.aliases === undefined
|
|
868
1041
|
? field.alias === undefined ? [] : [field.alias]
|
|
869
1042
|
: Array.isArray(field.aliases) ? field.aliases : [field.aliases];
|
|
870
|
-
return [field.name, ...aliases].filter(value => typeof value === "string" && value.length);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
|
|
1043
|
+
return [field.name, ...aliases].filter(value => typeof value === "string" && value.length);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function getChildCollection(target, property)
|
|
1047
|
+
{
|
|
1048
|
+
if (!(target instanceof CjsModel))
|
|
1049
|
+
{
|
|
1050
|
+
throw new TypeError("CjsModel child collection target must be a CjsModel instance.");
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (typeof property !== "string" || !property)
|
|
1054
|
+
{
|
|
1055
|
+
throw new TypeError("CjsModel child collection property must be a non-empty string.");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const field = CjsSchema.getField(target.constructor, property);
|
|
1059
|
+
if (!field)
|
|
1060
|
+
{
|
|
1061
|
+
throw new TypeError(`${CjsSchema.getClassName(target.constructor)} has no schema field named ${JSON.stringify(property)}.`);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const fieldType = field.type || field.jsType;
|
|
1065
|
+
if (!CHILD_COLLECTION_KINDS.has(fieldType?.kind))
|
|
1066
|
+
{
|
|
1067
|
+
throw new TypeError(`${field.name} must be a schema array or list child collection.`);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const collection = target[field.name];
|
|
1071
|
+
if (!Array.isArray(collection))
|
|
1072
|
+
{
|
|
1073
|
+
throw new TypeError(`${field.name} must contain an ordinary JavaScript Array.`);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return { field, collection };
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function assertChildObject(child, property)
|
|
1080
|
+
{
|
|
1081
|
+
if (!child || typeof child !== "object" || Array.isArray(child) || ArrayBuffer.isView(child))
|
|
1082
|
+
{
|
|
1083
|
+
throw new TypeError(`${property} requires a non-null child object.`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function assertChildCallback(callback, optionName)
|
|
1088
|
+
{
|
|
1089
|
+
if (callback !== undefined && callback !== null && typeof callback !== "function")
|
|
1090
|
+
{
|
|
1091
|
+
throw new TypeError(`CjsModel child ${optionName} option must be a function.`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function recordChildMutation(target, field, options)
|
|
1096
|
+
{
|
|
1097
|
+
if (options.markDirty === false) return;
|
|
1098
|
+
target.__state.dirty = true;
|
|
1099
|
+
if (options.notify !== false) addDeclaredFieldTokens(target, field);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function notifyListModified(target, event, index, secondIndex, child, collection)
|
|
1103
|
+
{
|
|
1104
|
+
if (typeof target.OnListModified === "function")
|
|
1105
|
+
{
|
|
1106
|
+
target.OnListModified(event, index, secondIndex, child, collection);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function createChildEventPayload(target, property, child, index, options)
|
|
1111
|
+
{
|
|
1112
|
+
return {
|
|
1113
|
+
property,
|
|
1114
|
+
child,
|
|
1115
|
+
index,
|
|
1116
|
+
source: options.source ?? target
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function invokeChildCallback(callback, target, payload, optionName)
|
|
1121
|
+
{
|
|
1122
|
+
if (callback === undefined || callback === null) return;
|
|
1123
|
+
assertChildCallback(callback, optionName);
|
|
1124
|
+
callback.call(target, payload);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function emitChildEvent(target, eventName, payload, options)
|
|
1128
|
+
{
|
|
1129
|
+
if (options.skipEvents !== true && target.__state.suppressEvents === 0)
|
|
1130
|
+
{
|
|
1131
|
+
target.EmitEvent(eventName, target, payload);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function settleChildMutation(target, field, options)
|
|
1136
|
+
{
|
|
1137
|
+
if (options.skipUpdate === true) return;
|
|
1138
|
+
|
|
1139
|
+
if (options.markDirty === false)
|
|
1140
|
+
{
|
|
1141
|
+
if (options.skipEvents !== true && target.__state.suppressEvents === 0)
|
|
1142
|
+
{
|
|
1143
|
+
target.EmitEvent("modified", target, createModifiedPayload(
|
|
1144
|
+
new Set([ field.name ]),
|
|
1145
|
+
options.source ?? target
|
|
1146
|
+
));
|
|
1147
|
+
}
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (!target.__state.updating) target.UpdateValues(options);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Adds one field's declared @io.flag / @io.rebuild tokens to their stores.
|
|
874
1155
|
// Duplicate adds are no-ops (Sets). Nothing in the model layer ever clears
|
|
875
1156
|
// these stores - getters clear flags, work methods clear rebuild tokens.
|
|
876
1157
|
function addDeclaredFieldTokens(target, field)
|
package/src/schema/CjsSchema.js
CHANGED
|
@@ -232,7 +232,19 @@ export class CjsSchema
|
|
|
232
232
|
unknown: fieldDecorator("type", { kind: "unknown" }),
|
|
233
233
|
vec2: fieldDecorator("type", { kind: "vec2" }),
|
|
234
234
|
vec3: fieldDecorator("type", { kind: "vec3" }),
|
|
235
|
-
vec4: fieldDecorator("type", { kind: "vec4" })
|
|
235
|
+
vec4: fieldDecorator("type", { kind: "vec4" }),
|
|
236
|
+
|
|
237
|
+
// An enum names the vocabulary a field's values are drawn from, which
|
|
238
|
+
// is part of its type - it belongs here beside int32 and string rather
|
|
239
|
+
// than under a separate namespace. define and hideInherited show this
|
|
240
|
+
// namespace already carries class decorators as well as field ones.
|
|
241
|
+
enum: values => fieldDecorator("enum", normalizeEnumDefinition(values)),
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Hides named inherited fields from a subclass's schema, for a subclass
|
|
245
|
+
* that genuinely does not carry part of its parent's shape.
|
|
246
|
+
*/
|
|
247
|
+
hideInherited: fieldNames => hiddenInheritedFieldsDecorator(normalizeHiddenInheritedFields(fieldNames))
|
|
236
248
|
});
|
|
237
249
|
|
|
238
250
|
static io = Object.freeze({
|