@axi-engine/fields 0.3.3 → 0.3.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.
Files changed (164) hide show
  1. package/dist/core-field-tree-factory.d.ts +13 -0
  2. package/dist/core-field-tree-factory.d.ts.map +1 -0
  3. package/dist/core-field-tree-factory.js +14 -0
  4. package/dist/core-field-tree-factory.js.map +1 -0
  5. package/dist/core-field-tree.d.ts +5 -0
  6. package/dist/core-field-tree.d.ts.map +1 -0
  7. package/dist/core-field-tree.js +4 -0
  8. package/dist/core-field-tree.js.map +1 -0
  9. package/dist/core-fields-factory.d.ts +10 -0
  10. package/dist/core-fields-factory.d.ts.map +1 -0
  11. package/dist/core-fields-factory.js +14 -0
  12. package/dist/core-fields-factory.js.map +1 -0
  13. package/dist/core-fields.d.ts +47 -0
  14. package/dist/core-fields.d.ts.map +1 -0
  15. package/dist/core-fields.js +8 -0
  16. package/dist/core-fields.js.map +1 -0
  17. package/dist/data-store-field-resolver.d.ts +26 -0
  18. package/dist/data-store-field-resolver.d.ts.map +1 -0
  19. package/dist/data-store-field-resolver.js +21 -0
  20. package/dist/data-store-field-resolver.js.map +1 -0
  21. package/dist/data-store.d.ts +38 -0
  22. package/dist/data-store.d.ts.map +1 -0
  23. package/dist/data-store.js +141 -0
  24. package/dist/data-store.js.map +1 -0
  25. package/dist/field-definitions/core-boolean-field.d.ts +11 -0
  26. package/dist/field-definitions/core-boolean-field.d.ts.map +1 -0
  27. package/dist/field-definitions/core-boolean-field.js +13 -0
  28. package/dist/field-definitions/core-boolean-field.js.map +1 -0
  29. package/dist/field-definitions/core-field.d.ts +51 -0
  30. package/dist/field-definitions/core-field.d.ts.map +1 -0
  31. package/dist/field-definitions/core-field.js +74 -0
  32. package/dist/field-definitions/core-field.js.map +1 -0
  33. package/dist/field-definitions/core-numeric-field.d.ts +18 -0
  34. package/dist/field-definitions/core-numeric-field.d.ts.map +1 -0
  35. package/dist/field-definitions/core-numeric-field.js +45 -0
  36. package/dist/field-definitions/core-numeric-field.js.map +1 -0
  37. package/dist/field-definitions/core-string-field.d.ts +15 -0
  38. package/dist/field-definitions/core-string-field.d.ts.map +1 -0
  39. package/dist/field-definitions/core-string-field.js +27 -0
  40. package/dist/field-definitions/core-string-field.js.map +1 -0
  41. package/dist/field-definitions/index.d.ts +5 -0
  42. package/dist/field-definitions/index.d.ts.map +1 -0
  43. package/dist/field-definitions/index.js +5 -0
  44. package/dist/field-definitions/index.js.map +1 -0
  45. package/dist/field-registry.d.ts +5 -0
  46. package/dist/field-registry.d.ts.map +1 -0
  47. package/dist/field-registry.js +4 -0
  48. package/dist/field-registry.js.map +1 -0
  49. package/dist/field-tree-factory.d.ts +12 -0
  50. package/dist/field-tree-factory.d.ts.map +1 -0
  51. package/dist/field-tree-factory.js +2 -0
  52. package/dist/field-tree-factory.js.map +1 -0
  53. package/dist/field-tree.d.ts +171 -0
  54. package/dist/field-tree.d.ts.map +1 -0
  55. package/dist/field-tree.js +248 -0
  56. package/dist/field-tree.js.map +1 -0
  57. package/dist/field.d.ts +34 -0
  58. package/dist/field.d.ts.map +1 -0
  59. package/dist/field.js +2 -0
  60. package/dist/field.js.map +1 -0
  61. package/dist/fields-factory.d.ts +5 -0
  62. package/dist/fields-factory.d.ts.map +1 -0
  63. package/dist/fields-factory.js +2 -0
  64. package/dist/fields-factory.js.map +1 -0
  65. package/dist/fields.d.ts +101 -0
  66. package/dist/fields.d.ts.map +1 -0
  67. package/dist/fields.js +143 -0
  68. package/dist/fields.js.map +1 -0
  69. package/dist/index.d.mts +794 -815
  70. package/dist/index.d.ts +18 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +18 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/index.mjs +1084 -1116
  75. package/dist/mixins/mixin-factory.d.ts +29 -0
  76. package/dist/mixins/mixin-factory.d.ts.map +1 -0
  77. package/dist/mixins/mixin-factory.js +32 -0
  78. package/dist/mixins/mixin-factory.js.map +1 -0
  79. package/dist/mixins/with-boolean-fields.mixin.d.ts +9 -0
  80. package/dist/mixins/with-boolean-fields.mixin.d.ts.map +1 -0
  81. package/dist/mixins/with-boolean-fields.mixin.js +4 -0
  82. package/dist/mixins/with-boolean-fields.mixin.js.map +1 -0
  83. package/dist/mixins/with-default-generic-fields.mixin.d.ts +31 -0
  84. package/dist/mixins/with-default-generic-fields.mixin.d.ts.map +1 -0
  85. package/dist/mixins/with-default-generic-fields.mixin.js +15 -0
  86. package/dist/mixins/with-default-generic-fields.mixin.js.map +1 -0
  87. package/dist/mixins/with-numeric-fields.mixin.d.ts +9 -0
  88. package/dist/mixins/with-numeric-fields.mixin.d.ts.map +1 -0
  89. package/dist/mixins/with-numeric-fields.mixin.js +4 -0
  90. package/dist/mixins/with-numeric-fields.mixin.js.map +1 -0
  91. package/dist/mixins/with-string-fields.mixin.d.ts +9 -0
  92. package/dist/mixins/with-string-fields.mixin.d.ts.map +1 -0
  93. package/dist/mixins/with-string-fields.mixin.js +4 -0
  94. package/dist/mixins/with-string-fields.mixin.js.map +1 -0
  95. package/dist/policies/clamp-max-policy.d.ts +11 -0
  96. package/dist/policies/clamp-max-policy.d.ts.map +1 -0
  97. package/dist/policies/clamp-max-policy.js +18 -0
  98. package/dist/policies/clamp-max-policy.js.map +1 -0
  99. package/dist/policies/clamp-min-policy.d.ts +11 -0
  100. package/dist/policies/clamp-min-policy.d.ts.map +1 -0
  101. package/dist/policies/clamp-min-policy.js +18 -0
  102. package/dist/policies/clamp-min-policy.js.map +1 -0
  103. package/dist/policies/clamp-policy.d.ts +12 -0
  104. package/dist/policies/clamp-policy.d.ts.map +1 -0
  105. package/dist/policies/clamp-policy.js +21 -0
  106. package/dist/policies/clamp-policy.js.map +1 -0
  107. package/dist/policies/index.d.ts +6 -0
  108. package/dist/policies/index.d.ts.map +1 -0
  109. package/dist/policies/index.js +6 -0
  110. package/dist/policies/index.js.map +1 -0
  111. package/dist/policies/policies.d.ts +38 -0
  112. package/dist/policies/policies.d.ts.map +1 -0
  113. package/dist/policies/policies.js +62 -0
  114. package/dist/policies/policies.js.map +1 -0
  115. package/dist/policies/policy.d.ts +6 -0
  116. package/dist/policies/policy.d.ts.map +1 -0
  117. package/dist/policies/policy.js +2 -0
  118. package/dist/policies/policy.js.map +1 -0
  119. package/dist/serializer/field-serializer.d.ts +52 -0
  120. package/dist/serializer/field-serializer.d.ts.map +1 -0
  121. package/dist/serializer/field-serializer.js +66 -0
  122. package/dist/serializer/field-serializer.js.map +1 -0
  123. package/dist/serializer/field-tree-serializer.d.ts +50 -0
  124. package/dist/serializer/field-tree-serializer.d.ts.map +1 -0
  125. package/dist/serializer/field-tree-serializer.js +68 -0
  126. package/dist/serializer/field-tree-serializer.js.map +1 -0
  127. package/dist/serializer/fields-serializer.d.ts +49 -0
  128. package/dist/serializer/fields-serializer.d.ts.map +1 -0
  129. package/dist/serializer/fields-serializer.js +57 -0
  130. package/dist/serializer/fields-serializer.js.map +1 -0
  131. package/dist/serializer/index.d.ts +8 -0
  132. package/dist/serializer/index.d.ts.map +1 -0
  133. package/dist/serializer/index.js +8 -0
  134. package/dist/serializer/index.js.map +1 -0
  135. package/dist/serializer/policies/clamp-max-policy-serializer-handler.d.ts +13 -0
  136. package/dist/serializer/policies/clamp-max-policy-serializer-handler.d.ts.map +1 -0
  137. package/dist/serializer/policies/clamp-max-policy-serializer-handler.js +10 -0
  138. package/dist/serializer/policies/clamp-max-policy-serializer-handler.js.map +1 -0
  139. package/dist/serializer/policies/clamp-min-policy-serializer-handler.d.ts +13 -0
  140. package/dist/serializer/policies/clamp-min-policy-serializer-handler.d.ts.map +1 -0
  141. package/dist/serializer/policies/clamp-min-policy-serializer-handler.js +10 -0
  142. package/dist/serializer/policies/clamp-min-policy-serializer-handler.js.map +1 -0
  143. package/dist/serializer/policies/clamp-policy-serializer-handler.d.ts +16 -0
  144. package/dist/serializer/policies/clamp-policy-serializer-handler.d.ts.map +1 -0
  145. package/dist/serializer/policies/clamp-policy-serializer-handler.js +10 -0
  146. package/dist/serializer/policies/clamp-policy-serializer-handler.js.map +1 -0
  147. package/dist/serializer/policy-serializer.d.ts +41 -0
  148. package/dist/serializer/policy-serializer.d.ts.map +1 -0
  149. package/dist/serializer/policy-serializer.js +43 -0
  150. package/dist/serializer/policy-serializer.js.map +1 -0
  151. package/dist/setup.d.ts +41 -0
  152. package/dist/setup.d.ts.map +1 -0
  153. package/dist/setup.js +57 -0
  154. package/dist/setup.js.map +1 -0
  155. package/dist/store.d.ts +137 -0
  156. package/dist/store.d.ts.map +1 -0
  157. package/dist/store.js +2 -0
  158. package/dist/store.js.map +1 -0
  159. package/package.json +44 -44
  160. package/dist/index.cjs +0 -1232
  161. package/dist/index.d.cts +0 -1018
  162. package/dist/index.d.cts.map +0 -1
  163. package/dist/index.d.mts.map +0 -1
  164. package/dist/index.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,1202 +1,1170 @@
1
- import { ConstructorRegistry, Emitter, ensurePathArray, ensurePathString, isBoolean, isNullOrUndefined, isNumber, isString, throwIf, throwIfEmpty } from "@axi-engine/utils";
2
- import { dequal } from "dequal";
3
-
4
- //#region src/policies/clamp-policy.ts
5
- var ClampPolicy = class ClampPolicy {
6
- static id = "clamp";
7
- id = ClampPolicy.id;
8
- constructor(min, max) {
9
- this.min = min;
10
- this.max = max;
11
- }
12
- apply(val) {
13
- return Math.max(this.min, Math.min(this.max, val));
14
- }
15
- updateBounds(min, max) {
16
- this.min = min;
17
- this.max = max;
18
- }
1
+ // src/policies/clamp-policy.ts
2
+ var ClampPolicy = class _ClampPolicy {
3
+ constructor(min, max) {
4
+ this.min = min;
5
+ this.max = max;
6
+ }
7
+ static id = "clamp";
8
+ id = _ClampPolicy.id;
9
+ apply(val) {
10
+ return Math.max(this.min, Math.min(this.max, val));
11
+ }
12
+ updateBounds(min, max) {
13
+ this.min = min;
14
+ this.max = max;
15
+ }
19
16
  };
20
17
  function clampPolicy(min, max) {
21
- return new ClampPolicy(min, max);
18
+ return new ClampPolicy(min, max);
22
19
  }
23
20
 
24
- //#endregion
25
- //#region src/policies/clamp-max-policy.ts
26
- var ClampMaxPolicy = class ClampMaxPolicy {
27
- static id = "clampMax";
28
- id = ClampMaxPolicy.id;
29
- constructor(max) {
30
- this.max = max;
31
- }
32
- apply(val) {
33
- return Math.min(this.max, val);
34
- }
35
- updateBounds(max) {
36
- this.max = max;
37
- }
21
+ // src/policies/clamp-max-policy.ts
22
+ var ClampMaxPolicy = class _ClampMaxPolicy {
23
+ constructor(max) {
24
+ this.max = max;
25
+ }
26
+ static id = "clampMax";
27
+ id = _ClampMaxPolicy.id;
28
+ apply(val) {
29
+ return Math.min(this.max, val);
30
+ }
31
+ updateBounds(max) {
32
+ this.max = max;
33
+ }
38
34
  };
39
35
  function clampMaxPolicy(max) {
40
- return new ClampMaxPolicy(max);
36
+ return new ClampMaxPolicy(max);
41
37
  }
42
38
 
43
- //#endregion
44
- //#region src/policies/clamp-min-policy.ts
45
- var ClampMinPolicy = class ClampMinPolicy {
46
- static id = "clampMin";
47
- id = ClampMinPolicy.id;
48
- constructor(min) {
49
- this.min = min;
50
- }
51
- apply(val) {
52
- return Math.max(this.min, val);
53
- }
54
- updateBounds(min) {
55
- this.min = min;
56
- }
39
+ // src/policies/clamp-min-policy.ts
40
+ var ClampMinPolicy = class _ClampMinPolicy {
41
+ constructor(min) {
42
+ this.min = min;
43
+ }
44
+ static id = "clampMin";
45
+ id = _ClampMinPolicy.id;
46
+ apply(val) {
47
+ return Math.max(this.min, val);
48
+ }
49
+ updateBounds(min) {
50
+ this.min = min;
51
+ }
57
52
  };
58
53
  function clampMinPolicy(min) {
59
- return new ClampMinPolicy(min);
54
+ return new ClampMinPolicy(min);
60
55
  }
61
56
 
62
- //#endregion
63
- //#region src/policies/policies.ts
57
+ // src/policies/policies.ts
64
58
  var Policies = class {
65
- policies = /* @__PURE__ */ new Map();
66
- get items() {
67
- return this.policies;
68
- }
69
- /**
70
- * Retrieves a specific policy instance by its ID.
71
- * Useful for accessing a policy's internal state or methods.
72
- * @template P The expected type of the policy.
73
- * @param id The unique ID of the policy to retrieve.
74
- * @returns The policy instance, or `undefined` if not found.
75
- */
76
- get(id) {
77
- return this.policies.get(id);
78
- }
79
- /**
80
- * Adds a new policy to the field or replaces an existing one with the same ID.
81
- * The new policy will be applied on the next `set()` operation.
82
- * If a policy with the same ID already exists, its `destroy` method will be called before it is replaced.
83
- * @param policy The policy instance to add.
84
- */
85
- add(policy) {
86
- this.policies.get(policy.id)?.destroy?.();
87
- this.policies.set(policy.id, policy);
88
- return this;
89
- }
90
- /**
91
- * Removes a policy from the field by its ID and call `destroy` method.
92
- * @param policyId The unique ID of the policy to remove.
93
- * @returns `true` if the policy was found and removed, otherwise `false`.
94
- */
95
- remove(policyId) {
96
- const policyToRemove = this.policies.get(policyId);
97
- if (!policyToRemove) return false;
98
- policyToRemove.destroy?.();
99
- return this.policies.delete(policyId);
100
- }
101
- isEmpty() {
102
- return this.policies.size === 0;
103
- }
104
- /**
105
- * Removes all policies from the field.
106
- * After this, `set()` will no longer apply any transformations to the value until new policies are added.
107
- */
108
- clear() {
109
- this.policies.forEach((policy) => policy.destroy?.());
110
- this.policies.clear();
111
- }
112
- /**
113
- * Forces the current value to be re-processed by all policies.
114
- * Useful if a policy's logic has changed and you need to re-evaluate the current state.
115
- */
116
- apply(val) {
117
- let finalVal = val;
118
- this.policies.forEach((policy) => finalVal = policy.apply(finalVal));
119
- return finalVal;
120
- }
59
+ policies = /* @__PURE__ */ new Map();
60
+ get items() {
61
+ return this.policies;
62
+ }
63
+ /**
64
+ * Retrieves a specific policy instance by its ID.
65
+ * Useful for accessing a policy's internal state or methods.
66
+ * @template P The expected type of the policy.
67
+ * @param id The unique ID of the policy to retrieve.
68
+ * @returns The policy instance, or `undefined` if not found.
69
+ */
70
+ get(id) {
71
+ return this.policies.get(id);
72
+ }
73
+ /**
74
+ * Adds a new policy to the field or replaces an existing one with the same ID.
75
+ * The new policy will be applied on the next `set()` operation.
76
+ * If a policy with the same ID already exists, its `destroy` method will be called before it is replaced.
77
+ * @param policy The policy instance to add.
78
+ */
79
+ add(policy) {
80
+ const existed = this.policies.get(policy.id);
81
+ existed?.destroy?.();
82
+ this.policies.set(policy.id, policy);
83
+ return this;
84
+ }
85
+ /**
86
+ * Removes a policy from the field by its ID and call `destroy` method.
87
+ * @param policyId The unique ID of the policy to remove.
88
+ * @returns `true` if the policy was found and removed, otherwise `false`.
89
+ */
90
+ remove(policyId) {
91
+ const policyToRemove = this.policies.get(policyId);
92
+ if (!policyToRemove) {
93
+ return false;
94
+ }
95
+ policyToRemove.destroy?.();
96
+ return this.policies.delete(policyId);
97
+ }
98
+ isEmpty() {
99
+ return this.policies.size === 0;
100
+ }
101
+ /**
102
+ * Removes all policies from the field.
103
+ * After this, `set()` will no longer apply any transformations to the value until new policies are added.
104
+ */
105
+ clear() {
106
+ this.policies.forEach((policy) => policy.destroy?.());
107
+ this.policies.clear();
108
+ }
109
+ /**
110
+ * Forces the current value to be re-processed by all policies.
111
+ * Useful if a policy's logic has changed and you need to re-evaluate the current state.
112
+ */
113
+ apply(val) {
114
+ let finalVal = val;
115
+ this.policies.forEach((policy) => finalVal = policy.apply(finalVal));
116
+ return finalVal;
117
+ }
121
118
  };
122
119
 
123
- //#endregion
124
- //#region src/mixins/mixin-factory.ts
125
- /**
126
- * A higher-order function that generates a mixin for a specific Field type.
127
- * This factory removes the need to write boilerplate mixin code for every new field type.
128
- *
129
- * @param typeName The `typeName` of the Field to create (e.g., 'boolean', 'my-signal-field').
130
- * @param baseMethodName The base name for the generated methods (e.g., 'Boolean', 'MySignal').
131
- * @returns A fully functional, typed mixin.
132
- */
120
+ // src/mixins/mixin-factory.ts
133
121
  function createTypedMethodsMixin(typeName, baseMethodName) {
134
- const methodNames = {
135
- create: `create${baseMethodName}`,
136
- upset: `upset${baseMethodName}`,
137
- get: `get${baseMethodName}`
138
- };
139
- return function(Base) {
140
- return class FieldsWith extends Base {
141
- [methodNames.create](name, initialValue, options) {
142
- return this.create(typeName, name, initialValue, options);
143
- }
144
- [methodNames.upset](name, value, options) {
145
- return this.upset(typeName, name, value, options);
146
- }
147
- [methodNames.get](name) {
148
- return this.get(name);
149
- }
150
- };
151
- };
122
+ const methodNames = {
123
+ create: `create${baseMethodName}`,
124
+ upset: `upset${baseMethodName}`,
125
+ get: `get${baseMethodName}`
126
+ };
127
+ return function(Base) {
128
+ return class FieldsWith extends Base {
129
+ // createBoolean, createMySignal, etc.
130
+ [methodNames.create](name, initialValue, options) {
131
+ return this.create(typeName, name, initialValue, options);
132
+ }
133
+ // upsetBoolean, upsetMySignal, etc.
134
+ [methodNames.upset](name, value, options) {
135
+ return this.upset(typeName, name, value, options);
136
+ }
137
+ // getBoolean, getMySignal, etc.
138
+ [methodNames.get](name) {
139
+ return this.get(name);
140
+ }
141
+ };
142
+ };
152
143
  }
153
144
 
154
- //#endregion
155
- //#region src/field-definitions/core-field.ts
156
- /**
157
- * A state container that wraps a value.
158
- * It allows applying a pipeline of transformation or validation "policies" before any new value is set.
159
- *
160
- * @template T The type of the value this field holds.
161
- *
162
- */
163
- var CoreField = class CoreField {
164
- /** A type keyword of the field */
165
- static typeName = "default";
166
- typeName = CoreField.typeName;
167
- /** A unique identifier for the field. */
168
- _name;
169
- _value;
170
- _onChange = new Emitter();
171
- onChange;
172
- policies = new Policies();
173
- get name() {
174
- return this._name;
175
- }
176
- /**
177
- * Gets the current raw value of the field.
178
- * For reactive updates, it's recommended to use the `.signal` property instead.
179
- */
180
- get value() {
181
- return this._value;
182
- }
183
- /**
184
- * Sets a new value for the field.
185
- * The provided value will be processed by all registered policies before the underlying signal is updated.
186
- * @param val The new value to set.
187
- */
188
- set value(val) {
189
- const oldVal = this._value;
190
- const finalVal = this.policies.apply(val);
191
- if (!dequal(this._value, finalVal)) {
192
- this._value = finalVal;
193
- this._onChange.emit(this._value, oldVal);
194
- }
195
- }
196
- /**
197
- * Creates an instance of a Field.
198
- * @param name A unique identifier for the field.
199
- * @param initialVal The initial value of the field.
200
- * @param options Optional configuration for the field.
201
- * @param options.policies An array of policies to apply to the field's value on every `set` operation.
202
- * @param options.isEqual An function for compare old and new value, by default uses the strictEquals from `utils`
203
- *
204
- */
205
- constructor(name, initialVal, options) {
206
- this.onChange = this._onChange;
207
- this._name = name;
208
- options?.policies?.forEach((policy) => this.policies.add(policy));
209
- this.value = initialVal;
210
- }
211
- setValueSilently(val) {
212
- this._value = this.policies.apply(val);
213
- }
214
- batchUpdate(updateFn) {
215
- this.value = updateFn(this.value);
216
- }
217
- /**
218
- * Cleans up resources used by the field and its policies.
219
- * This should be called when the field is no longer needed to prevent memory leaks from reactive policies.
220
- */
221
- destroy() {
222
- this.policies.clear();
223
- this._onChange.clear();
224
- }
145
+ // src/field-definitions/core-field.ts
146
+ import { Emitter } from "@axi-engine/utils";
147
+ import { dequal } from "dequal";
148
+ var CoreField = class _CoreField {
149
+ /** A type keyword of the field */
150
+ static typeName = "default";
151
+ typeName = _CoreField.typeName;
152
+ /** A unique identifier for the field. */
153
+ _name;
154
+ _value;
155
+ _onChange = new Emitter();
156
+ onChange;
157
+ policies = new Policies();
158
+ get name() {
159
+ return this._name;
160
+ }
161
+ /**
162
+ * Gets the current raw value of the field.
163
+ * For reactive updates, it's recommended to use the `.signal` property instead.
164
+ */
165
+ get value() {
166
+ return this._value;
167
+ }
168
+ /**
169
+ * Sets a new value for the field.
170
+ * The provided value will be processed by all registered policies before the underlying signal is updated.
171
+ * @param val The new value to set.
172
+ */
173
+ set value(val) {
174
+ const oldVal = this._value;
175
+ const finalVal = this.policies.apply(val);
176
+ if (!dequal(this._value, finalVal)) {
177
+ this._value = finalVal;
178
+ this._onChange.emit(this._value, oldVal);
179
+ }
180
+ }
181
+ /**
182
+ * Creates an instance of a Field.
183
+ * @param name A unique identifier for the field.
184
+ * @param initialVal The initial value of the field.
185
+ * @param options Optional configuration for the field.
186
+ * @param options.policies An array of policies to apply to the field's value on every `set` operation.
187
+ * @param options.isEqual An function for compare old and new value, by default uses the strictEquals from `utils`
188
+ *
189
+ */
190
+ constructor(name, initialVal, options) {
191
+ this.onChange = this._onChange;
192
+ this._name = name;
193
+ options?.policies?.forEach((policy) => this.policies.add(policy));
194
+ this.value = initialVal;
195
+ }
196
+ setValueSilently(val) {
197
+ this._value = this.policies.apply(val);
198
+ }
199
+ batchUpdate(updateFn) {
200
+ this.value = updateFn(this.value);
201
+ }
202
+ /**
203
+ * Cleans up resources used by the field and its policies.
204
+ * This should be called when the field is no longer needed to prevent memory leaks from reactive policies.
205
+ */
206
+ destroy() {
207
+ this.policies.clear();
208
+ this._onChange.clear();
209
+ }
225
210
  };
226
211
 
227
- //#endregion
228
- //#region src/field-definitions/core-boolean-field.ts
229
- var CoreBooleanField = class CoreBooleanField extends CoreField {
230
- static typeName = "boolean";
231
- typeName = CoreBooleanField.typeName;
232
- constructor(name, initialVal, options) {
233
- super(name, initialVal, options);
234
- }
235
- toggle() {
236
- this.value = !this.value;
237
- return this.value;
238
- }
212
+ // src/field-definitions/core-boolean-field.ts
213
+ var CoreBooleanField = class _CoreBooleanField extends CoreField {
214
+ static typeName = "boolean";
215
+ typeName = _CoreBooleanField.typeName;
216
+ constructor(name, initialVal, options) {
217
+ super(name, initialVal, options);
218
+ }
219
+ toggle() {
220
+ this.value = !this.value;
221
+ return this.value;
222
+ }
239
223
  };
240
224
 
241
- //#endregion
242
- //#region src/field-definitions/core-string-field.ts
243
- var CoreStringField = class CoreStringField extends CoreField {
244
- static typeName = "string";
245
- typeName = CoreStringField.typeName;
246
- constructor(name, initialVal, options) {
247
- super(name, initialVal, options);
248
- }
249
- append(str) {
250
- this.value = this.value + str;
251
- return this;
252
- }
253
- prepend(str) {
254
- this.value = str + this.value;
255
- return this;
256
- }
257
- trim() {
258
- this.value = this.value.trim();
259
- return this;
260
- }
261
- isEmpty() {
262
- return this.value.length === 0;
263
- }
264
- clear() {
265
- this.value = "";
266
- }
225
+ // src/field-definitions/core-string-field.ts
226
+ var CoreStringField = class _CoreStringField extends CoreField {
227
+ static typeName = "string";
228
+ typeName = _CoreStringField.typeName;
229
+ constructor(name, initialVal, options) {
230
+ super(name, initialVal, options);
231
+ }
232
+ append(str) {
233
+ this.value = this.value + str;
234
+ return this;
235
+ }
236
+ prepend(str) {
237
+ this.value = str + this.value;
238
+ return this;
239
+ }
240
+ trim() {
241
+ this.value = this.value.trim();
242
+ return this;
243
+ }
244
+ isEmpty() {
245
+ return this.value.length === 0;
246
+ }
247
+ clear() {
248
+ this.value = "";
249
+ }
267
250
  };
268
251
 
269
- //#endregion
270
- //#region src/field-definitions/core-numeric-field.ts
271
- var CoreNumericField = class CoreNumericField extends CoreField {
272
- static typeName = "numeric";
273
- typeName = CoreNumericField.typeName;
274
- get min() {
275
- return (this.policies.get(ClampPolicy.id) ?? this.policies.get(ClampMinPolicy.id))?.min;
276
- }
277
- get max() {
278
- return (this.policies.get(ClampPolicy.id) ?? this.policies.get(ClampMaxPolicy.id))?.max;
279
- }
280
- constructor(name, initialVal, options) {
281
- const policies = options?.policies ?? [];
282
- if (!isNullOrUndefined(options?.min) && !isNullOrUndefined(options?.max)) policies.unshift(clampPolicy(options.min, options.max));
283
- else if (!isNullOrUndefined(options?.min)) policies.unshift(clampMinPolicy(options.min));
284
- else if (!isNullOrUndefined(options?.max)) policies.unshift(clampMaxPolicy(options.max));
285
- super(name, initialVal, { policies });
286
- }
287
- isMin() {
288
- const min = this.min;
289
- return isNullOrUndefined(min) ? false : this.value <= min;
290
- }
291
- isMax() {
292
- const max = this.max;
293
- return isNullOrUndefined(max) ? false : this.value >= max;
294
- }
295
- inc(amount = 1) {
296
- this.value = this.value + amount;
297
- }
298
- dec(amount = 1) {
299
- this.value = this.value - amount;
300
- }
252
+ // src/field-definitions/core-numeric-field.ts
253
+ import { isNullOrUndefined } from "@axi-engine/utils";
254
+ var CoreNumericField = class _CoreNumericField extends CoreField {
255
+ static typeName = "numeric";
256
+ typeName = _CoreNumericField.typeName;
257
+ get min() {
258
+ const policy = this.policies.get(ClampPolicy.id) ?? this.policies.get(ClampMinPolicy.id);
259
+ return policy?.min;
260
+ }
261
+ get max() {
262
+ const policy = this.policies.get(ClampPolicy.id) ?? this.policies.get(ClampMaxPolicy.id);
263
+ return policy?.max;
264
+ }
265
+ constructor(name, initialVal, options) {
266
+ const policies = options?.policies ?? [];
267
+ if (!isNullOrUndefined(options?.min) && !isNullOrUndefined(options?.max)) {
268
+ policies.unshift(clampPolicy(options.min, options.max));
269
+ } else if (!isNullOrUndefined(options?.min)) {
270
+ policies.unshift(clampMinPolicy(options.min));
271
+ } else if (!isNullOrUndefined(options?.max)) {
272
+ policies.unshift(clampMaxPolicy(options.max));
273
+ }
274
+ super(name, initialVal, { policies });
275
+ }
276
+ isMin() {
277
+ const min = this.min;
278
+ return isNullOrUndefined(min) ? false : this.value <= min;
279
+ }
280
+ isMax() {
281
+ const max = this.max;
282
+ return isNullOrUndefined(max) ? false : this.value >= max;
283
+ }
284
+ inc(amount = 1) {
285
+ this.value = this.value + amount;
286
+ }
287
+ dec(amount = 1) {
288
+ this.value = this.value - amount;
289
+ }
301
290
  };
302
291
 
303
- //#endregion
304
- //#region src/field-registry.ts
305
- var FieldRegistry = class extends ConstructorRegistry {};
292
+ // src/field-registry.ts
293
+ import { ConstructorRegistry } from "@axi-engine/utils";
294
+ var FieldRegistry = class extends ConstructorRegistry {
295
+ };
306
296
 
307
- //#endregion
308
- //#region src/fields.ts
309
- /**
310
- * A container for a collection of named `Field` instances.
311
- *
312
- * This class acts as a "leaf" node in the `FieldTree` hierarchy, managing a flat
313
- * key-value store of reactive data points. It uses a `FieldRegistry` to dynamically
314
- * create `Field` instances of different types.
315
- */
316
- var Fields = class Fields {
317
- static typeName = "fields";
318
- typeName = Fields.typeName;
319
- _fields = /* @__PURE__ */ new Map();
320
- _fieldRegistry;
321
- /**
322
- * An event emitter that fires when a new field is added to the collection.
323
- * @event
324
- * @param {object} event - The event payload.
325
- * @param {string} event.name - The name of the added field.
326
- * @param {Field<any>} event.field - The `Field` instance that was added.
327
- */
328
- onAdd = new Emitter();
329
- /**
330
- * An event emitter that fires after one or more fields have been removed.
331
- * @event
332
- * @param {object} event - The event payload.
333
- * @param {string[]} event.names - An array of names of the fields that were successfully removed.
334
- */
335
- onRemove = new Emitter();
336
- /**
337
- * Gets the read-only map of all `Field` instances in this container.
338
- * @returns {Map<string, Field<any>>} The collection of fields.
339
- */
340
- get fields() {
341
- return this._fields;
342
- }
343
- /**
344
- * Creates an instance of Fields.
345
- * @param {FieldRegistry} fieldRegistry - The registry used to create new `Field` instances.
346
- */
347
- constructor(fieldRegistry) {
348
- this._fieldRegistry = fieldRegistry;
349
- }
350
- /**
351
- * Checks if a field with the given name exists in the collection.
352
- * @param {string} name The name of the field to check.
353
- * @returns {boolean} `true` if the field exists, otherwise `false`.
354
- */
355
- has(name) {
356
- return this._fields.has(name);
357
- }
358
- /**
359
- * Adds a pre-existing `Field` instance to the collection and fires the `onAdd` event.
360
- * @template T - The specific `Field` type being added.
361
- * @param {Field<any>} field - The `Field` instance to add.
362
- * @returns {T} The added `Field` instance, cast to type `T`.
363
- * @throws If a field with the same name already exists.
364
- */
365
- add(field) {
366
- throwIf(this.has(field.name), `Field with name '${field.name}' already exists`);
367
- this._fields.set(field.name, field);
368
- this.onAdd.emit({
369
- name: field.name,
370
- field
371
- });
372
- return field;
373
- }
374
- /**
375
- * Creates a new `Field` instance of a specified type, adds it to the collection, and returns it.
376
- * This is the primary factory method for creating fields within this container.
377
- * @template T - The expected `Field` type to be returned.
378
- * @param {string} typeName - The registered type name of the field to create (e.g., 'numeric', 'boolean').
379
- * @param {string} name - The unique name for the new field.
380
- * @param {*} initialValue - The initial value for the new field.
381
- * @param {*} [options] - Optional configuration passed to the field's constructor.
382
- * @returns {T} The newly created `Field` instance.
383
- */
384
- create(typeName, name, initialValue, options) {
385
- const field = new (this._fieldRegistry.get(typeName))(name, initialValue, options);
386
- this.add(field);
387
- return field;
388
- }
389
- /**
390
- * Updates an existing field's value or creates a new one if it doesn't exist.
391
- * @template T - The expected `Field` type.
392
- * @param {string} typeName - The type name to use if a new field needs to be created.
393
- * @param {string} name - The name of the field to update or create.
394
- * @param {*} value - The new value to set.
395
- * @param {*} [options] - Optional configuration, used only if a new field is created.
396
- * @returns {T} The existing or newly created `Field` instance.
397
- */
398
- upset(typeName, name, value, options) {
399
- if (this.has(name)) {
400
- const field = this.get(name);
401
- field.value = value;
402
- return field;
403
- }
404
- return this.create(typeName, name, value, options);
405
- }
406
- /**
407
- * Retrieves a field by its name.
408
- * @template TField - The expected `Field` type to be returned.
409
- * @param {string} name - The name of the field to retrieve.
410
- * @returns {TField} The `Field` instance.
411
- * @throws If the field does not exist.
412
- */
413
- get(name) {
414
- throwIf(!this._fields.has(name), `Field with name '${name}' not exists`);
415
- return this._fields.get(name);
416
- }
417
- /**
418
- * Removes one or more fields from the collection.
419
- * This method ensures that the `destroy` method of each removed field is called to clean up its resources.
420
- * @param {string| string[]} names A single name or an array of names to remove.
421
- */
422
- remove(names) {
423
- const reallyRemoved = (Array.isArray(names) ? names : [names]).filter((name) => {
424
- const field = this._fields.get(name);
425
- if (!field) return false;
426
- field.destroy();
427
- return this._fields.delete(name);
428
- });
429
- if (!reallyRemoved.length) return;
430
- this.onRemove.emit({ names: reallyRemoved });
431
- }
432
- /**
433
- * Removes all fields from the collection, ensuring each is properly destroyed.
434
- */
435
- clear() {
436
- this.remove(Array.from(this._fields.keys()));
437
- }
438
- destroy() {
439
- this.clear();
440
- this.onAdd.clear();
441
- this.onRemove.clear();
442
- }
297
+ // src/fields.ts
298
+ import { Emitter as Emitter2, throwIf } from "@axi-engine/utils";
299
+ var Fields = class _Fields {
300
+ static typeName = "fields";
301
+ typeName = _Fields.typeName;
302
+ _fields = /* @__PURE__ */ new Map();
303
+ _fieldRegistry;
304
+ /**
305
+ * An event emitter that fires when a new field is added to the collection.
306
+ * @event
307
+ * @param {object} event - The event payload.
308
+ * @param {string} event.name - The name of the added field.
309
+ * @param {Field<any>} event.field - The `Field` instance that was added.
310
+ */
311
+ onAdd = new Emitter2();
312
+ /**
313
+ * An event emitter that fires after one or more fields have been removed.
314
+ * @event
315
+ * @param {object} event - The event payload.
316
+ * @param {string[]} event.names - An array of names of the fields that were successfully removed.
317
+ */
318
+ onRemove = new Emitter2();
319
+ /**
320
+ * Gets the read-only map of all `Field` instances in this container.
321
+ * @returns {Map<string, Field<any>>} The collection of fields.
322
+ */
323
+ get fields() {
324
+ return this._fields;
325
+ }
326
+ /**
327
+ * Creates an instance of Fields.
328
+ * @param {FieldRegistry} fieldRegistry - The registry used to create new `Field` instances.
329
+ */
330
+ constructor(fieldRegistry) {
331
+ this._fieldRegistry = fieldRegistry;
332
+ }
333
+ /**
334
+ * Checks if a field with the given name exists in the collection.
335
+ * @param {string} name The name of the field to check.
336
+ * @returns {boolean} `true` if the field exists, otherwise `false`.
337
+ */
338
+ has(name) {
339
+ return this._fields.has(name);
340
+ }
341
+ /**
342
+ * Adds a pre-existing `Field` instance to the collection and fires the `onAdd` event.
343
+ * @template T - The specific `Field` type being added.
344
+ * @param {Field<any>} field - The `Field` instance to add.
345
+ * @returns {T} The added `Field` instance, cast to type `T`.
346
+ * @throws If a field with the same name already exists.
347
+ */
348
+ add(field) {
349
+ throwIf(this.has(field.name), `Field with name '${field.name}' already exists`);
350
+ this._fields.set(field.name, field);
351
+ this.onAdd.emit({
352
+ name: field.name,
353
+ field
354
+ });
355
+ return field;
356
+ }
357
+ /**
358
+ * Creates a new `Field` instance of a specified type, adds it to the collection, and returns it.
359
+ * This is the primary factory method for creating fields within this container.
360
+ * @template T - The expected `Field` type to be returned.
361
+ * @param {string} typeName - The registered type name of the field to create (e.g., 'numeric', 'boolean').
362
+ * @param {string} name - The unique name for the new field.
363
+ * @param {*} initialValue - The initial value for the new field.
364
+ * @param {*} [options] - Optional configuration passed to the field's constructor.
365
+ * @returns {T} The newly created `Field` instance.
366
+ */
367
+ create(typeName, name, initialValue, options) {
368
+ const Ctor = this._fieldRegistry.get(typeName);
369
+ const field = new Ctor(name, initialValue, options);
370
+ this.add(field);
371
+ return field;
372
+ }
373
+ /**
374
+ * Updates an existing field's value or creates a new one if it doesn't exist.
375
+ * @template T - The expected `Field` type.
376
+ * @param {string} typeName - The type name to use if a new field needs to be created.
377
+ * @param {string} name - The name of the field to update or create.
378
+ * @param {*} value - The new value to set.
379
+ * @param {*} [options] - Optional configuration, used only if a new field is created.
380
+ * @returns {T} The existing or newly created `Field` instance.
381
+ */
382
+ upset(typeName, name, value, options) {
383
+ if (this.has(name)) {
384
+ const field = this.get(name);
385
+ field.value = value;
386
+ return field;
387
+ }
388
+ return this.create(typeName, name, value, options);
389
+ }
390
+ /**
391
+ * Retrieves a field by its name.
392
+ * @template TField - The expected `Field` type to be returned.
393
+ * @param {string} name - The name of the field to retrieve.
394
+ * @returns {TField} The `Field` instance.
395
+ * @throws If the field does not exist.
396
+ */
397
+ get(name) {
398
+ throwIf(!this._fields.has(name), `Field with name '${name}' not exists`);
399
+ return this._fields.get(name);
400
+ }
401
+ /**
402
+ * Removes one or more fields from the collection.
403
+ * This method ensures that the `destroy` method of each removed field is called to clean up its resources.
404
+ * @param {string| string[]} names A single name or an array of names to remove.
405
+ */
406
+ remove(names) {
407
+ const namesToRemove = Array.isArray(names) ? names : [names];
408
+ const reallyRemoved = namesToRemove.filter((name) => {
409
+ const field = this._fields.get(name);
410
+ if (!field) {
411
+ return false;
412
+ }
413
+ field.destroy();
414
+ return this._fields.delete(name);
415
+ });
416
+ if (!reallyRemoved.length) {
417
+ return;
418
+ }
419
+ this.onRemove.emit({ names: reallyRemoved });
420
+ }
421
+ /**
422
+ * Removes all fields from the collection, ensuring each is properly destroyed.
423
+ */
424
+ clear() {
425
+ this.remove(Array.from(this._fields.keys()));
426
+ }
427
+ destroy() {
428
+ this.clear();
429
+ this.onAdd.clear();
430
+ this.onRemove.clear();
431
+ }
443
432
  };
444
433
 
445
- //#endregion
446
- //#region src/field-tree.ts
447
- /**
448
- * Represents a hierarchical data structure for managing the global state of the system.
449
- *
450
- * This class acts as the single source of truth for long-term data that exists
451
- * across different scenes and scripts, such as player stats, inventory,
452
- * and overall game progress. It uses a path-based system for accessing and
453
- * manipulating nested data, similar to a file system.
454
- *
455
- */
456
- var FieldTree = class FieldTree {
457
- static typeName = "fieldTree";
458
- typeName = FieldTree.typeName;
459
- /** @private The internal map storing child nodes (branches or leaves). */
460
- _nodes = /* @__PURE__ */ new Map();
461
- /** @private The factory used to create new child nodes. */
462
- _factory;
463
- /**
464
- * An event emitter that fires immediately after a new node is added to this tree branch.
465
- * @event
466
- * @param {object} event - The event payload.
467
- * @param {string} event.name - The name (key) of the added node.
468
- * @param event.node - The node instance that was added.
469
- * @example
470
- * myTree.onAdd.subscribe(({ name, node }) => {
471
- * console.log(`Node '${name}' was added.`, node);
472
- * });
473
- */
474
- onAdd = new Emitter();
475
- /**
476
- * An event emitter that fires once after one or more nodes have been successfully removed.
477
- * @event
478
- * @param {object} event - The event payload.
479
- * @param {string[]} event.names - An array of names of the nodes that were removed.
480
- * @example
481
- * myTree.onRemove.subscribe(({ names }) => {
482
- * console.log(`Nodes removed: ${names.join(', ')}`);
483
- * });
484
- */
485
- onRemove = new Emitter();
486
- /**
487
- * Gets the collection of direct child nodes of this tree branch.
488
- */
489
- get nodes() {
490
- return this._nodes;
491
- }
492
- /**
493
- * Creates an instance of FieldTree.
494
- * @param {FieldTreeFactory} factory - A factory responsible for creating new nodes within the tree.
495
- */
496
- constructor(factory) {
497
- this._factory = factory;
498
- }
499
- /**
500
- * Checks if a direct child node with the given name exists.
501
- * @param {string} name - The name of the direct child node.
502
- * @returns {boolean} `true` if the node exists, otherwise `false`.
503
- */
504
- has(name) {
505
- return this._nodes.has(name);
506
- }
507
- /**
508
- * Checks if a node exists at a given path, traversing the tree.
509
- * @param {PathType} path - The path to check (e.g., 'player/stats' or ['player', 'stats']).
510
- * @returns {boolean} `true` if the entire path resolves to a node, otherwise `false`.
511
- */
512
- hasPath(path) {
513
- const traversedPath = this.traversePath(path);
514
- return traversedPath.branch.has(traversedPath.leafName);
515
- }
516
- /**
517
- * Adds a pre-existing node as a direct child of this tree branch.
518
- * @param {string} name - The name to assign to the new child node.
519
- * @param {TreeNode} node - The node instance to add.
520
- * @returns {TreeNode} The added node.
521
- * @throws If a node with the same name already exists.
522
- */
523
- addNode(name, node) {
524
- throwIf(this.has(name), `Can't add node with name: '${name}', node already exists`);
525
- this._nodes.set(name, node);
526
- this.onAdd.emit({
527
- name,
528
- node
529
- });
530
- return node;
531
- }
532
- /**
533
- * Retrieves a direct child node by its name.
534
- * @param {string} name - The name of the child node.
535
- * @returns {TreeNode} The retrieved node.
536
- * @throws If a node with the given name cannot be found.
537
- */
538
- getNode(name) {
539
- const node = this._nodes.get(name);
540
- throwIfEmpty(node, `Can't find node with name '${name}'`);
541
- return node;
542
- }
543
- /**
544
- * Removes one or more nodes from this tree branch.
545
- *
546
- * This method first validates that all specified nodes exist. If validation passes,
547
- * it recursively calls `destroy()` on each node to ensure proper cleanup of the entire subtree.
548
- * Finally, it emits a single `onRemove` event with the names of all successfully removed nodes.
549
- *
550
- * @param {string | string[]} names - A single name or an array of names of the nodes to remove.
551
- * @throws If any of the specified names do not correspond to an existing node.
552
- */
553
- removeNode(names) {
554
- const toRemoveNames = Array.isArray(names) ? names : [names];
555
- toRemoveNames.forEach((name) => {
556
- throwIf(!this.has(name), `Can't remove node with name: '${name}', node doesn't exists`);
557
- });
558
- toRemoveNames.forEach((name) => {
559
- this._nodes.get(name).destroy();
560
- this._nodes.delete(name);
561
- });
562
- if (toRemoveNames.length) this.onRemove.emit({ names: toRemoveNames });
563
- }
564
- /**
565
- * Creates a new `FieldTree` (branch) node at the specified path.
566
- * @param {PathType} path - The path where the new `FieldTree` should be created.
567
- * @param {boolean} [createPath=false] - If `true`, any missing parent branches in the path will be created automatically.
568
- * @returns {FieldTree} The newly created `FieldTree` instance.
569
- * @throws If the path is invalid or a node already exists at the target location.
570
- */
571
- createFieldTree(path, createPath) {
572
- const traversedPath = this.traversePath(path, createPath);
573
- return traversedPath.branch.addNode(traversedPath.leafName, this._factory.tree());
574
- }
575
- /**
576
- * Creates a new `Fields` (leaf) container at the specified path.
577
- * @param {PathType} path - The path where the new `Fields` container should be created.
578
- * @param {boolean} [createPath=false] - If `true`, any missing parent branches in the path will be created automatically.
579
- * @returns {Fields} The newly created `Fields` instance.
580
- * @throws If the path is invalid or a node already exists at the target location.
581
- */
582
- createFields(path, createPath) {
583
- const traversedPath = this.traversePath(path, createPath);
584
- return traversedPath.branch.addNode(traversedPath.leafName, this._factory.fields());
585
- }
586
- /**
587
- * Retrieves a `FieldTree` (branch) node from a specified path.
588
- * @param {PathType} path - The path to the `FieldTree` node.
589
- * @returns {FieldTree} The `FieldTree` instance at the specified path.
590
- * @throws If the path is invalid or the node at the path is not a `FieldTree`.
591
- */
592
- getFieldTree(path) {
593
- const traversedPath = this.traversePath(path);
594
- const node = traversedPath.branch.getNode(traversedPath.leafName);
595
- throwIf(!(node instanceof FieldTree), `Node with name: ${traversedPath.leafName} by path: '${ensurePathString(path)}' should be instance of FieldTree`);
596
- return node;
597
- }
598
- /**
599
- * Retrieves a `Fields` (leaf) container from a specified path.
600
- * @param {PathType} path - The path to the `Fields` container.
601
- * @returns {Fields} The `Fields` instance at the specified path.
602
- * @throws If the path is invalid or the node at the path is not a `Fields` container.
603
- */
604
- getFields(path) {
605
- const traversedPath = this.traversePath(path);
606
- const node = traversedPath.branch.getNode(traversedPath.leafName);
607
- throwIf(!(node instanceof Fields), `Node with name: ${traversedPath.leafName} by path: '${ensurePathString(path)}' should be instance of Fields`);
608
- return node;
609
- }
610
- /**
611
- * Retrieves a `FieldTree` at the specified path. If it or any part of the path doesn't exist, it will be created.
612
- * @param {PathType} path - The path to the `FieldTree` node.
613
- * @returns {FieldTree} The existing or newly created `FieldTree` instance.
614
- */
615
- getOrCreateFieldTree(path) {
616
- const traversedPath = this.traversePath(path, true);
617
- return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFieldTree(traversedPath.leafName) : traversedPath.branch.createFieldTree(traversedPath.leafName);
618
- }
619
- /**
620
- * Retrieves a `Fields` container at the specified path. If it or any part of the path doesn't exist, it will be created.
621
- * @param {PathType} path - The path to the `Fields` container.
622
- * @returns {Fields} The existing or newly created `Fields` instance.
623
- */
624
- getOrCreateFields(path) {
625
- const traversedPath = this.traversePath(path, true);
626
- return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFields(traversedPath.leafName) : traversedPath.branch.createFields(traversedPath.leafName);
627
- }
628
- /**
629
- * Finds the parent node for a given path.
630
- * @param path The path to the target node.
631
- * @returns The parent node (either a FieldTree or Fields).
632
- * @throws An error if the path is invalid or any intermediate node is not a FieldTree.
633
- */
634
- findParentNode(path) {
635
- return this.traversePath(path).branch;
636
- }
637
- /**
638
- * Removes all child nodes from this tree branch.
639
- * This method ensures that `destroy()` is called on each child node, allowing for
640
- * a full, recursive cleanup of the entire subtree.
641
- */
642
- clear() {
643
- this.removeNode(Array.from(this._nodes.keys()));
644
- }
645
- /**
646
- * Performs a complete cleanup of this node and its entire subtree.
647
- *
648
- * It recursively destroys all child nodes by calling `clear()` and then
649
- * unsubscribes all listeners from its own event emitters.
650
- * This method should be called when a node is no longer needed.
651
- */
652
- destroy() {
653
- this.clear();
654
- this.onAdd.clear();
655
- this.onRemove.clear();
656
- }
657
- /**
658
- * @private
659
- * Navigates the tree to the parent of a target node.
660
- * This is the core traversal logic for all path-based operations.
661
- * @param {PathType} path - The full path to the target node.
662
- * @param {boolean} [createPath=false] - If `true`, creates missing `FieldTree` branches along the path.
663
- * @returns {{branch: FieldTree, leafName: string}} An object containing the final branch (parent node) and the name of the leaf (target node).
664
- * @throws If the path is empty, invalid, or contains a `Fields` container as an intermediate segment.
665
- */
666
- traversePath(path, createPath) {
667
- const pathArr = ensurePathArray(path);
668
- throwIfEmpty(pathArr, "The path is empty");
669
- const leafName = pathArr.pop();
670
- let currentNode = this;
671
- for (const pathPart of pathArr) {
672
- let node;
673
- if (currentNode.has(pathPart)) node = currentNode.getNode(pathPart);
674
- else if (createPath) node = currentNode.createFieldTree(pathPart);
675
- throwIfEmpty(node, `Can't find node with name ${pathPart} by path parsing: ${ensurePathString(path)}`);
676
- throwIf(node instanceof Fields, `Node with name ${pathPart} should be instance of FieldTree`);
677
- currentNode = node;
678
- }
679
- return {
680
- branch: currentNode,
681
- leafName
682
- };
683
- }
434
+ // src/field-tree.ts
435
+ import { Emitter as Emitter3, ensurePathArray, ensurePathString, throwIf as throwIf2, throwIfEmpty } from "@axi-engine/utils";
436
+ var FieldTree = class _FieldTree {
437
+ static typeName = "fieldTree";
438
+ typeName = _FieldTree.typeName;
439
+ /** @private The internal map storing child nodes (branches or leaves). */
440
+ _nodes = /* @__PURE__ */ new Map();
441
+ /** @private The factory used to create new child nodes. */
442
+ _factory;
443
+ /**
444
+ * An event emitter that fires immediately after a new node is added to this tree branch.
445
+ * @event
446
+ * @param {object} event - The event payload.
447
+ * @param {string} event.name - The name (key) of the added node.
448
+ * @param event.node - The node instance that was added.
449
+ * @example
450
+ * myTree.onAdd.subscribe(({ name, node }) => {
451
+ * console.log(`Node '${name}' was added.`, node);
452
+ * });
453
+ */
454
+ onAdd = new Emitter3();
455
+ /**
456
+ * An event emitter that fires once after one or more nodes have been successfully removed.
457
+ * @event
458
+ * @param {object} event - The event payload.
459
+ * @param {string[]} event.names - An array of names of the nodes that were removed.
460
+ * @example
461
+ * myTree.onRemove.subscribe(({ names }) => {
462
+ * console.log(`Nodes removed: ${names.join(', ')}`);
463
+ * });
464
+ */
465
+ onRemove = new Emitter3();
466
+ /**
467
+ * Gets the collection of direct child nodes of this tree branch.
468
+ */
469
+ get nodes() {
470
+ return this._nodes;
471
+ }
472
+ /**
473
+ * Creates an instance of FieldTree.
474
+ * @param {FieldTreeFactory} factory - A factory responsible for creating new nodes within the tree.
475
+ */
476
+ constructor(factory) {
477
+ this._factory = factory;
478
+ }
479
+ /**
480
+ * Checks if a direct child node with the given name exists.
481
+ * @param {string} name - The name of the direct child node.
482
+ * @returns {boolean} `true` if the node exists, otherwise `false`.
483
+ */
484
+ has(name) {
485
+ return this._nodes.has(name);
486
+ }
487
+ /**
488
+ * Checks if a node exists at a given path, traversing the tree.
489
+ * @param {PathType} path - The path to check (e.g., 'player/stats' or ['player', 'stats']).
490
+ * @returns {boolean} `true` if the entire path resolves to a node, otherwise `false`.
491
+ */
492
+ hasPath(path) {
493
+ const traversedPath = this.traversePath(path);
494
+ return traversedPath.branch.has(traversedPath.leafName);
495
+ }
496
+ /**
497
+ * Adds a pre-existing node as a direct child of this tree branch.
498
+ * @param {string} name - The name to assign to the new child node.
499
+ * @param {TreeNode} node - The node instance to add.
500
+ * @returns {TreeNode} The added node.
501
+ * @throws If a node with the same name already exists.
502
+ */
503
+ addNode(name, node) {
504
+ throwIf2(this.has(name), `Can't add node with name: '${name}', node already exists`);
505
+ this._nodes.set(name, node);
506
+ this.onAdd.emit({ name, node });
507
+ return node;
508
+ }
509
+ /**
510
+ * Retrieves a direct child node by its name.
511
+ * @param {string} name - The name of the child node.
512
+ * @returns {TreeNode} The retrieved node.
513
+ * @throws If a node with the given name cannot be found.
514
+ */
515
+ getNode(name) {
516
+ const node = this._nodes.get(name);
517
+ throwIfEmpty(node, `Can't find node with name '${name}'`);
518
+ return node;
519
+ }
520
+ /**
521
+ * Removes one or more nodes from this tree branch.
522
+ *
523
+ * This method first validates that all specified nodes exist. If validation passes,
524
+ * it recursively calls `destroy()` on each node to ensure proper cleanup of the entire subtree.
525
+ * Finally, it emits a single `onRemove` event with the names of all successfully removed nodes.
526
+ *
527
+ * @param {string | string[]} names - A single name or an array of names of the nodes to remove.
528
+ * @throws If any of the specified names do not correspond to an existing node.
529
+ */
530
+ removeNode(names) {
531
+ const toRemoveNames = Array.isArray(names) ? names : [names];
532
+ toRemoveNames.forEach((name) => {
533
+ throwIf2(!this.has(name), `Can't remove node with name: '${name}', node doesn't exists`);
534
+ });
535
+ toRemoveNames.forEach((name) => {
536
+ this._nodes.get(name).destroy();
537
+ this._nodes.delete(name);
538
+ });
539
+ if (toRemoveNames.length) {
540
+ this.onRemove.emit({ names: toRemoveNames });
541
+ }
542
+ }
543
+ /**
544
+ * Creates a new `FieldTree` (branch) node at the specified path.
545
+ * @param {PathType} path - The path where the new `FieldTree` should be created.
546
+ * @param {boolean} [createPath=false] - If `true`, any missing parent branches in the path will be created automatically.
547
+ * @returns {FieldTree} The newly created `FieldTree` instance.
548
+ * @throws If the path is invalid or a node already exists at the target location.
549
+ */
550
+ createFieldTree(path, createPath) {
551
+ const traversedPath = this.traversePath(path, createPath);
552
+ return traversedPath.branch.addNode(traversedPath.leafName, this._factory.tree());
553
+ }
554
+ /**
555
+ * Creates a new `Fields` (leaf) container at the specified path.
556
+ * @param {PathType} path - The path where the new `Fields` container should be created.
557
+ * @param {boolean} [createPath=false] - If `true`, any missing parent branches in the path will be created automatically.
558
+ * @returns {Fields} The newly created `Fields` instance.
559
+ * @throws If the path is invalid or a node already exists at the target location.
560
+ */
561
+ createFields(path, createPath) {
562
+ const traversedPath = this.traversePath(path, createPath);
563
+ return traversedPath.branch.addNode(traversedPath.leafName, this._factory.fields());
564
+ }
565
+ /**
566
+ * Retrieves a `FieldTree` (branch) node from a specified path.
567
+ * @param {PathType} path - The path to the `FieldTree` node.
568
+ * @returns {FieldTree} The `FieldTree` instance at the specified path.
569
+ * @throws If the path is invalid or the node at the path is not a `FieldTree`.
570
+ */
571
+ getFieldTree(path) {
572
+ const traversedPath = this.traversePath(path);
573
+ const node = traversedPath.branch.getNode(traversedPath.leafName);
574
+ throwIf2(
575
+ !(node instanceof _FieldTree),
576
+ `Node with name: ${traversedPath.leafName} by path: '${ensurePathString(path)}' should be instance of FieldTree`
577
+ );
578
+ return node;
579
+ }
580
+ /**
581
+ * Retrieves a `Fields` (leaf) container from a specified path.
582
+ * @param {PathType} path - The path to the `Fields` container.
583
+ * @returns {Fields} The `Fields` instance at the specified path.
584
+ * @throws If the path is invalid or the node at the path is not a `Fields` container.
585
+ */
586
+ getFields(path) {
587
+ const traversedPath = this.traversePath(path);
588
+ const node = traversedPath.branch.getNode(traversedPath.leafName);
589
+ throwIf2(
590
+ !(node instanceof Fields),
591
+ `Node with name: ${traversedPath.leafName} by path: '${ensurePathString(path)}' should be instance of Fields`
592
+ );
593
+ return node;
594
+ }
595
+ /**
596
+ * Retrieves a `FieldTree` at the specified path. If it or any part of the path doesn't exist, it will be created.
597
+ * @param {PathType} path - The path to the `FieldTree` node.
598
+ * @returns {FieldTree} The existing or newly created `FieldTree` instance.
599
+ */
600
+ getOrCreateFieldTree(path) {
601
+ const traversedPath = this.traversePath(path, true);
602
+ return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFieldTree(traversedPath.leafName) : traversedPath.branch.createFieldTree(traversedPath.leafName);
603
+ }
604
+ /**
605
+ * Retrieves a `Fields` container at the specified path. If it or any part of the path doesn't exist, it will be created.
606
+ * @param {PathType} path - The path to the `Fields` container.
607
+ * @returns {Fields} The existing or newly created `Fields` instance.
608
+ */
609
+ getOrCreateFields(path) {
610
+ const traversedPath = this.traversePath(path, true);
611
+ return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFields(traversedPath.leafName) : traversedPath.branch.createFields(traversedPath.leafName);
612
+ }
613
+ /**
614
+ * Finds the parent node for a given path.
615
+ * @param path The path to the target node.
616
+ * @returns The parent node (either a FieldTree or Fields).
617
+ * @throws An error if the path is invalid or any intermediate node is not a FieldTree.
618
+ */
619
+ findParentNode(path) {
620
+ const info = this.traversePath(path);
621
+ return info.branch;
622
+ }
623
+ /**
624
+ * Removes all child nodes from this tree branch.
625
+ * This method ensures that `destroy()` is called on each child node, allowing for
626
+ * a full, recursive cleanup of the entire subtree.
627
+ */
628
+ clear() {
629
+ this.removeNode(Array.from(this._nodes.keys()));
630
+ }
631
+ /**
632
+ * Performs a complete cleanup of this node and its entire subtree.
633
+ *
634
+ * It recursively destroys all child nodes by calling `clear()` and then
635
+ * unsubscribes all listeners from its own event emitters.
636
+ * This method should be called when a node is no longer needed.
637
+ */
638
+ destroy() {
639
+ this.clear();
640
+ this.onAdd.clear();
641
+ this.onRemove.clear();
642
+ }
643
+ /**
644
+ * @private
645
+ * Navigates the tree to the parent of a target node.
646
+ * This is the core traversal logic for all path-based operations.
647
+ * @param {PathType} path - The full path to the target node.
648
+ * @param {boolean} [createPath=false] - If `true`, creates missing `FieldTree` branches along the path.
649
+ * @returns {{branch: FieldTree, leafName: string}} An object containing the final branch (parent node) and the name of the leaf (target node).
650
+ * @throws If the path is empty, invalid, or contains a `Fields` container as an intermediate segment.
651
+ */
652
+ traversePath(path, createPath) {
653
+ const pathArr = ensurePathArray(path);
654
+ throwIfEmpty(pathArr, "The path is empty");
655
+ const leafName = pathArr.pop();
656
+ let currentNode = this;
657
+ for (const pathPart of pathArr) {
658
+ let node;
659
+ if (currentNode.has(pathPart)) {
660
+ node = currentNode.getNode(pathPart);
661
+ } else {
662
+ if (createPath) {
663
+ node = currentNode.createFieldTree(pathPart);
664
+ }
665
+ }
666
+ throwIfEmpty(node, `Can't find node with name ${pathPart} by path parsing: ${ensurePathString(path)}`);
667
+ throwIf2(node instanceof Fields, `Node with name ${pathPart} should be instance of FieldTree`);
668
+ currentNode = node;
669
+ }
670
+ return { branch: currentNode, leafName };
671
+ }
684
672
  };
685
673
 
686
- //#endregion
687
- //#region src/mixins/with-boolean-fields.mixin.ts
688
- const WithBooleanFields = createTypedMethodsMixin(CoreBooleanField.typeName, "Boolean");
674
+ // src/mixins/with-boolean-fields.mixin.ts
675
+ var WithBooleanFields = createTypedMethodsMixin(CoreBooleanField.typeName, "Boolean");
689
676
 
690
- //#endregion
691
- //#region src/mixins/with-string-fields.mixin.ts
692
- const WithStringFields = createTypedMethodsMixin(CoreBooleanField.typeName, "String");
677
+ // src/mixins/with-string-fields.mixin.ts
678
+ var WithStringFields = createTypedMethodsMixin(CoreStringField.typeName, "String");
693
679
 
694
- //#endregion
695
- //#region src/mixins/with-numeric-fields.mixin.ts
696
- const WithNumericFields = createTypedMethodsMixin(CoreBooleanField.typeName, "Numeric");
680
+ // src/mixins/with-numeric-fields.mixin.ts
681
+ var WithNumericFields = createTypedMethodsMixin(CoreNumericField.typeName, "Numeric");
697
682
 
698
- //#endregion
699
- //#region src/mixins/with-default-generic-fields.mixin.ts
683
+ // src/mixins/with-default-generic-fields.mixin.ts
700
684
  function WithDefaultGenericFields(Base) {
701
- return class FieldsWithDefaultGeneric extends Base {
702
- createGeneric(name, initialValue, options) {
703
- return this.create(CoreField.typeName, name, initialValue, options);
704
- }
705
- upsetGeneric(name, value, options) {
706
- return this.upset(CoreField.typeName, name, value, options);
707
- }
708
- getGeneric(name) {
709
- return this.get(name);
710
- }
711
- };
685
+ return class FieldsWithDefaultGeneric extends Base {
686
+ createGeneric(name, initialValue, options) {
687
+ return this.create(CoreField.typeName, name, initialValue, options);
688
+ }
689
+ upsetGeneric(name, value, options) {
690
+ return this.upset(CoreField.typeName, name, value, options);
691
+ }
692
+ getGeneric(name) {
693
+ return this.get(name);
694
+ }
695
+ };
712
696
  }
713
697
 
714
- //#endregion
715
- //#region src/core-fields.ts
716
- var CoreFields = class extends WithBooleanFields(WithStringFields(WithNumericFields(WithDefaultGenericFields(Fields)))) {};
698
+ // src/core-fields.ts
699
+ var CoreFields = class extends WithBooleanFields(WithStringFields(WithNumericFields(WithDefaultGenericFields(Fields)))) {
700
+ };
717
701
 
718
- //#endregion
719
- //#region src/core-fields-factory.ts
702
+ // src/core-fields-factory.ts
720
703
  var CoreFieldsFactory = class {
721
- _fieldRegistry;
722
- get fieldRegistry() {
723
- return this._fieldRegistry;
724
- }
725
- constructor(fieldRegistry) {
726
- this._fieldRegistry = fieldRegistry;
727
- }
728
- fields() {
729
- return new CoreFields(this._fieldRegistry);
730
- }
704
+ _fieldRegistry;
705
+ get fieldRegistry() {
706
+ return this._fieldRegistry;
707
+ }
708
+ constructor(fieldRegistry) {
709
+ this._fieldRegistry = fieldRegistry;
710
+ }
711
+ fields() {
712
+ return new CoreFields(this._fieldRegistry);
713
+ }
731
714
  };
732
715
 
733
- //#endregion
734
- //#region src/core-field-tree.ts
735
- var CoreFieldTree = class extends FieldTree {};
716
+ // src/core-field-tree.ts
717
+ var CoreFieldTree = class extends FieldTree {
718
+ };
736
719
 
737
- //#endregion
738
- //#region src/core-field-tree-factory.ts
739
- /**
740
- * The default factory implementation that creates standard DefaultFields and FieldTree instances.
741
- */
720
+ // src/core-field-tree-factory.ts
742
721
  var CoreTreeNodeFactory = class extends CoreFieldsFactory {
743
- constructor(fieldRegistry) {
744
- super(fieldRegistry);
745
- }
746
- tree() {
747
- return new CoreFieldTree(this);
748
- }
722
+ constructor(fieldRegistry) {
723
+ super(fieldRegistry);
724
+ }
725
+ tree() {
726
+ return new CoreFieldTree(this);
727
+ }
749
728
  };
750
729
 
751
- //#endregion
752
- //#region src/serializer/policies/clamp-policy-serializer-handler.ts
730
+ // src/serializer/policies/clamp-policy-serializer-handler.ts
753
731
  var ClampPolicySerializerHandler = class {
754
- snapshot(policy) {
755
- return {
756
- min: policy.min,
757
- max: policy.max
758
- };
759
- }
760
- hydrate(data) {
761
- return new ClampPolicy(data.min, data.max);
762
- }
732
+ snapshot(policy) {
733
+ return { min: policy.min, max: policy.max };
734
+ }
735
+ hydrate(data) {
736
+ return new ClampPolicy(data.min, data.max);
737
+ }
763
738
  };
764
739
 
765
- //#endregion
766
- //#region src/serializer/policies/clamp-max-policy-serializer-handler.ts
740
+ // src/serializer/policies/clamp-max-policy-serializer-handler.ts
767
741
  var ClampMaxPolicySerializerHandler = class {
768
- snapshot(policy) {
769
- return { max: policy.max };
770
- }
771
- hydrate(data) {
772
- return new ClampMaxPolicy(data.max);
773
- }
742
+ snapshot(policy) {
743
+ return { max: policy.max };
744
+ }
745
+ hydrate(data) {
746
+ return new ClampMaxPolicy(data.max);
747
+ }
774
748
  };
775
749
 
776
- //#endregion
777
- //#region src/serializer/policies/clamp-min-policy-serializer-handler.ts
750
+ // src/serializer/policies/clamp-min-policy-serializer-handler.ts
778
751
  var ClampMinPolicySerializerHandler = class {
779
- snapshot(policy) {
780
- return { min: policy.min };
781
- }
782
- hydrate(data) {
783
- return new ClampMinPolicy(data.min);
784
- }
752
+ snapshot(policy) {
753
+ return { min: policy.min };
754
+ }
755
+ hydrate(data) {
756
+ return new ClampMinPolicy(data.min);
757
+ }
785
758
  };
786
759
 
787
- //#endregion
788
- //#region src/serializer/policy-serializer.ts
760
+ // src/serializer/policy-serializer.ts
761
+ import { throwIf as throwIf3, throwIfEmpty as throwIfEmpty2 } from "@axi-engine/utils";
789
762
  var PolicySerializer = class {
790
- handlers = /* @__PURE__ */ new Map();
791
- register(policyId, handler) {
792
- throwIf(this.handlers.has(policyId), `A handler for policy ID '${policyId}' is already registered.`);
793
- this.handlers.set(policyId, handler);
794
- return this;
795
- }
796
- clearHandlers() {
797
- this.handlers.clear();
798
- }
799
- /**
800
- * Creates a serializable snapshot of a policy instance.
801
- * The snapshot includes the policy's state and a `__type` identifier.
802
- * @param policy The policy instance to snapshot.
803
- * @returns A plain object ready for JSON serialization.
804
- * @throws If no handler is registered for the policy's ID.
805
- */
806
- snapshot(policy) {
807
- const handler = this.handlers.get(policy.id);
808
- throwIfEmpty(handler, `No serializer handler registered for policy ID: '${policy.id}'`);
809
- const data = handler.snapshot(policy);
810
- return {
811
- __type: policy.id,
812
- ...data
813
- };
814
- }
815
- /**
816
- * Restores a policy instance from its snapshot representation.
817
- * @param snapshot The plain object snapshot, which must contain a `__type` property.
818
- * @returns A new, fully functional policy instance.
819
- * @throws If the snapshot is invalid or no handler is registered for its `__type`.
820
- */
821
- hydrate(snapshot) {
822
- const typeId = snapshot?.__type;
823
- throwIfEmpty(typeId, "Invalid policy snapshot: missing \"__type\" identifier.");
824
- const handler = this.handlers.get(typeId);
825
- throwIfEmpty(handler, `No serializer handler registered for policy ID: '${typeId}'`);
826
- const { __type, ...data } = snapshot;
827
- return handler.hydrate(data);
828
- }
763
+ handlers = /* @__PURE__ */ new Map();
764
+ register(policyId, handler) {
765
+ throwIf3(this.handlers.has(policyId), `A handler for policy ID '${policyId}' is already registered.`);
766
+ this.handlers.set(policyId, handler);
767
+ return this;
768
+ }
769
+ clearHandlers() {
770
+ this.handlers.clear();
771
+ }
772
+ /**
773
+ * Creates a serializable snapshot of a policy instance.
774
+ * The snapshot includes the policy's state and a `__type` identifier.
775
+ * @param policy The policy instance to snapshot.
776
+ * @returns A plain object ready for JSON serialization.
777
+ * @throws If no handler is registered for the policy's ID.
778
+ */
779
+ snapshot(policy) {
780
+ const handler = this.handlers.get(policy.id);
781
+ throwIfEmpty2(handler, `No serializer handler registered for policy ID: '${policy.id}'`);
782
+ const data = handler.snapshot(policy);
783
+ return {
784
+ __type: policy.id,
785
+ ...data
786
+ };
787
+ }
788
+ /**
789
+ * Restores a policy instance from its snapshot representation.
790
+ * @param snapshot The plain object snapshot, which must contain a `__type` property.
791
+ * @returns A new, fully functional policy instance.
792
+ * @throws If the snapshot is invalid or no handler is registered for its `__type`.
793
+ */
794
+ hydrate(snapshot) {
795
+ const typeId = snapshot?.__type;
796
+ throwIfEmpty2(typeId, 'Invalid policy snapshot: missing "__type" identifier.');
797
+ const handler = this.handlers.get(typeId);
798
+ throwIfEmpty2(handler, `No serializer handler registered for policy ID: '${typeId}'`);
799
+ const { __type, ...data } = snapshot;
800
+ return handler.hydrate(data);
801
+ }
829
802
  };
830
803
 
831
- //#endregion
832
- //#region src/serializer/field-serializer.ts
833
- /**
834
- * Orchestrates the serialization and deserialization of Field instances.
835
- *
836
- * This class acts as a central point for converting complex Field objects into
837
- * plain, storable data (snapshots) and vice-versa. It uses a `FieldRegistry`
838
- * to resolve class constructors and a `PolicySerializer` to handle the state
839
- * of any attached policies.
840
- *
841
- * @todo Implement a `patch(field, snapshot)` method.
842
- * Unlike `hydrate`, which creates a new
843
- * instance, `patch` should update the state of an *existing* field instance
844
- * without breaking external references to it.
845
- */
804
+ // src/serializer/field-serializer.ts
805
+ import { isNullOrUndefined as isNullOrUndefined2, throwIfEmpty as throwIfEmpty3 } from "@axi-engine/utils";
846
806
  var FieldSerializer = class {
847
- /**
848
- * Creates an instance of FieldSerializer.
849
- * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
850
- * @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
851
- */
852
- constructor(fieldRegistry, policySerializer) {
853
- this.fieldRegistry = fieldRegistry;
854
- this.policySerializer = policySerializer;
855
- }
856
- /**
857
- * Creates a serializable snapshot of a Field instance.
858
- * The snapshot includes the field's type, name, current value, and the state of all its policies.
859
- * @param {Field<any>} field - The Field instance to serialize.
860
- * @returns {FieldSnapshot} A plain object ready for JSON serialization.
861
- */
862
- snapshot(field) {
863
- let snapshot = {
864
- __type: field.typeName,
865
- name: field.name,
866
- value: field.value
867
- };
868
- if (!field.policies.isEmpty()) {
869
- const serializedPolicies = [];
870
- field.policies.items.forEach((policy) => serializedPolicies.push(this.policySerializer.snapshot(policy)));
871
- snapshot.policies = serializedPolicies;
872
- }
873
- return snapshot;
874
- }
875
- /**
876
- * Restores a Field instance from its snapshot representation.
877
- * It uses the `__type` property to find the correct constructor and hydrates
878
- * the field with its value and all its policies.
879
- * @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
880
- * @returns {Field<any>} A new, fully functional Field instance.
881
- * @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
882
- */
883
- hydrate(snapshot) {
884
- const fieldType = snapshot.__type;
885
- throwIfEmpty(fieldType, "Invalid field snapshot: missing \"__type\" identifier.");
886
- const Ctor = this.fieldRegistry.get(fieldType);
887
- let policies;
888
- if (!isNullOrUndefined(snapshot.policies)) {
889
- policies = [];
890
- snapshot.policies.forEach((p) => policies.push(this.policySerializer.hydrate(p)));
891
- }
892
- return new Ctor(snapshot.name, snapshot.value, { policies });
893
- }
807
+ /**
808
+ * Creates an instance of FieldSerializer.
809
+ * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
810
+ * @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
811
+ */
812
+ constructor(fieldRegistry, policySerializer) {
813
+ this.fieldRegistry = fieldRegistry;
814
+ this.policySerializer = policySerializer;
815
+ }
816
+ /**
817
+ * Creates a serializable snapshot of a Field instance.
818
+ * The snapshot includes the field's type, name, current value, and the state of all its policies.
819
+ * @param {Field<any>} field - The Field instance to serialize.
820
+ * @returns {FieldSnapshot} A plain object ready for JSON serialization.
821
+ */
822
+ snapshot(field) {
823
+ let snapshot = {
824
+ __type: field.typeName,
825
+ name: field.name,
826
+ value: field.value
827
+ };
828
+ if (!field.policies.isEmpty()) {
829
+ const serializedPolicies = [];
830
+ field.policies.items.forEach((policy) => serializedPolicies.push(this.policySerializer.snapshot(policy)));
831
+ snapshot.policies = serializedPolicies;
832
+ }
833
+ return snapshot;
834
+ }
835
+ /**
836
+ * Restores a Field instance from its snapshot representation.
837
+ * It uses the `__type` property to find the correct constructor and hydrates
838
+ * the field with its value and all its policies.
839
+ * @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
840
+ * @returns {Field<any>} A new, fully functional Field instance.
841
+ * @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
842
+ */
843
+ hydrate(snapshot) {
844
+ const fieldType = snapshot.__type;
845
+ throwIfEmpty3(fieldType, 'Invalid field snapshot: missing "__type" identifier.');
846
+ const Ctor = this.fieldRegistry.get(fieldType);
847
+ let policies;
848
+ if (!isNullOrUndefined2(snapshot.policies)) {
849
+ policies = [];
850
+ snapshot.policies.forEach((p) => policies.push(this.policySerializer.hydrate(p)));
851
+ }
852
+ return new Ctor(snapshot.name, snapshot.value, { policies });
853
+ }
894
854
  };
895
855
 
896
- //#endregion
897
- //#region src/serializer/fields-serializer.ts
898
- /**
899
- * Orchestrates the serialization and deserialization of `Fields` container instances.
900
- *
901
- * This class acts as a high-level composer, responsible for converting an entire `Fields` object
902
- * into a storable snapshot and back.
903
- * It delegates the actual serialization of each `Field` and `Policy` to their respective serializers.
904
- *
905
- * @todo Implement a `patch(fields, snapshot)` method. It should perform a non-destructive
906
- * update, creating new fields, removing missing ones, and patching existing ones
907
- * in place, preserving the container instance itself.
908
- */
856
+ // src/serializer/fields-serializer.ts
909
857
  var FieldsSerializer = class {
910
- /**
911
- * Creates an instance of FieldsSerializer.
912
- * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
913
- * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
914
- */
915
- constructor(fieldsFactory, fieldSerializer) {
916
- this.fieldsFactory = fieldsFactory;
917
- this.fieldSerializer = fieldSerializer;
918
- }
919
- /**
920
- * Creates a serializable snapshot of a `Fields` container.
921
- *
922
- * The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
923
- * for each `Field` within the container.
924
- * @param {Fields} fields - The `Fields` instance to serialize.
925
- * @returns {FieldsSnapshot} A plain object ready for JSON serialization.
926
- */
927
- snapshot(fields) {
928
- const res = { __type: fields.typeName };
929
- fields.fields.forEach((field) => res[field.name] = this.fieldSerializer.snapshot(field));
930
- return res;
931
- }
932
- /**
933
- * Restores a `Fields` container instance from its snapshot representation.
934
- *
935
- * It iterates through the field snapshots and hydrates them individually, adding them to the new container.
936
- * @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
937
- * @returns {Fields} A new `DefaultFields` instance populated with the restored fields.
938
- */
939
- hydrate(snapshot) {
940
- const { __type, ...fieldsData } = snapshot;
941
- const fields = this.fieldsFactory.fields();
942
- for (const fieldName in fieldsData) {
943
- const fieldSnapshot = fieldsData[fieldName];
944
- const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
945
- fields.add(restoredField);
946
- }
947
- return fields;
948
- }
858
+ /**
859
+ * Creates an instance of FieldsSerializer.
860
+ * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
861
+ * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
862
+ */
863
+ constructor(fieldsFactory, fieldSerializer) {
864
+ this.fieldsFactory = fieldsFactory;
865
+ this.fieldSerializer = fieldSerializer;
866
+ }
867
+ /**
868
+ * Creates a serializable snapshot of a `Fields` container.
869
+ *
870
+ * The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
871
+ * for each `Field` within the container.
872
+ * @param {Fields} fields - The `Fields` instance to serialize.
873
+ * @returns {FieldsSnapshot} A plain object ready for JSON serialization.
874
+ */
875
+ snapshot(fields) {
876
+ const res = {
877
+ __type: fields.typeName
878
+ };
879
+ fields.fields.forEach((field) => res[field.name] = this.fieldSerializer.snapshot(field));
880
+ return res;
881
+ }
882
+ /**
883
+ * Restores a `Fields` container instance from its snapshot representation.
884
+ *
885
+ * It iterates through the field snapshots and hydrates them individually, adding them to the new container.
886
+ * @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
887
+ * @returns {Fields} A new `DefaultFields` instance populated with the restored fields.
888
+ */
889
+ hydrate(snapshot) {
890
+ const { __type, ...fieldsData } = snapshot;
891
+ const fields = this.fieldsFactory.fields();
892
+ for (const fieldName in fieldsData) {
893
+ const fieldSnapshot = fieldsData[fieldName];
894
+ const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
895
+ fields.add(restoredField);
896
+ }
897
+ return fields;
898
+ }
949
899
  };
950
900
 
951
- //#endregion
952
- //#region src/serializer/field-tree-serializer.ts
953
- /**
954
- * Orchestrates the recursive serialization and deserialization of `FieldTree` instances.
955
- *
956
- * This class handles the conversion of an entire `FieldTree` object graph into a
957
- * plain, storable snapshot and vice-versa. It delegates the processing of `Fields`
958
- * leaf nodes to a dedicated `FieldsSerializer`.
959
- * @todo Refactoring: The current implementation uses `if/else` logic in `snapshot` and `hydrate`
960
- * to process different node types. A more extensible approach would be to use a
961
- * registry of dedicated handlers for each node type.
962
- * This would allow new node types to be supported without
963
- * modifying this class, adhering to the Open/Closed Principle.
964
- *
965
- * @todo Implement a `patch(tree, snapshot)` method for recursive, non-destructive
966
- * updates. This method should traverse the existing tree and the snapshot,
967
- * patching nodes in place to maintain object references.
968
- */
901
+ // src/serializer/field-tree-serializer.ts
902
+ import { isString } from "@axi-engine/utils";
969
903
  var FieldTreeSerializer = class {
970
- constructor(fieldTreeNodeFactory, fieldsSerializer) {
971
- this.fieldTreeNodeFactory = fieldTreeNodeFactory;
972
- this.fieldsSerializer = fieldsSerializer;
973
- }
974
- /**
975
- * Creates a serializable snapshot of the entire tree and its contained fields.
976
- * @returns A plain JavaScript object representing the complete state managed by this tree.
977
- */
978
- snapshot(tree) {
979
- const res = { __type: tree.typeName };
980
- tree.nodes.forEach((node, key) => {
981
- if (node.typeName === tree.typeName) res[key] = this.snapshot(node);
982
- else if (node.typeName === Fields.typeName) res[key] = this.fieldsSerializer.snapshot(node);
983
- });
984
- return res;
985
- }
986
- /**
987
- * Restores the state of the tree from a snapshot.
988
- * It intelligently creates missing nodes based on `__type` metadata and delegates hydration to child nodes.
989
- * @param snapshot The snapshot object to load.
990
- */
991
- hydrate(snapshot) {
992
- const { __type, ...nodes } = snapshot;
993
- const tree = this.fieldTreeNodeFactory.tree();
994
- for (const key in nodes) {
995
- const nodeData = nodes[key];
996
- if (isString(nodeData)) continue;
997
- if (nodeData.__type === FieldTree.typeName) tree.addNode(key, this.hydrate(nodeData));
998
- else if (nodeData.__type === Fields.typeName) tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
999
- }
1000
- return tree;
1001
- }
904
+ constructor(fieldTreeNodeFactory, fieldsSerializer) {
905
+ this.fieldTreeNodeFactory = fieldTreeNodeFactory;
906
+ this.fieldsSerializer = fieldsSerializer;
907
+ }
908
+ /**
909
+ * Creates a serializable snapshot of the entire tree and its contained fields.
910
+ * @returns A plain JavaScript object representing the complete state managed by this tree.
911
+ */
912
+ snapshot(tree) {
913
+ const res = {
914
+ __type: tree.typeName
915
+ };
916
+ tree.nodes.forEach((node, key) => {
917
+ if (node.typeName === tree.typeName) {
918
+ res[key] = this.snapshot(node);
919
+ } else if (node.typeName === Fields.typeName) {
920
+ res[key] = this.fieldsSerializer.snapshot(node);
921
+ }
922
+ });
923
+ return res;
924
+ }
925
+ /**
926
+ * Restores the state of the tree from a snapshot.
927
+ * It intelligently creates missing nodes based on `__type` metadata and delegates hydration to child nodes.
928
+ * @param snapshot The snapshot object to load.
929
+ */
930
+ hydrate(snapshot) {
931
+ const { __type, ...nodes } = snapshot;
932
+ const tree = this.fieldTreeNodeFactory.tree();
933
+ for (const key in nodes) {
934
+ const nodeData = nodes[key];
935
+ if (isString(nodeData)) {
936
+ continue;
937
+ }
938
+ if (nodeData.__type === FieldTree.typeName) {
939
+ tree.addNode(key, this.hydrate(nodeData));
940
+ } else if (nodeData.__type === Fields.typeName) {
941
+ tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
942
+ }
943
+ }
944
+ return tree;
945
+ }
1002
946
  };
1003
947
 
1004
- //#endregion
1005
- //#region src/data-store-field-resolver.ts
948
+ // src/data-store-field-resolver.ts
949
+ import { isBoolean, isNumber, isString as isString2 } from "@axi-engine/utils";
1006
950
  var NumericFieldResolver = class {
1007
- typeName = CoreNumericField.typeName;
1008
- supports(value) {
1009
- return isNumber(value);
1010
- }
951
+ typeName = CoreNumericField.typeName;
952
+ supports(value) {
953
+ return isNumber(value);
954
+ }
1011
955
  };
1012
956
  var BooleanFieldResolver = class {
1013
- typeName = CoreBooleanField.typeName;
1014
- supports(value) {
1015
- return isBoolean(value);
1016
- }
957
+ typeName = CoreBooleanField.typeName;
958
+ supports(value) {
959
+ return isBoolean(value);
960
+ }
1017
961
  };
1018
962
  var StringFieldResolver = class {
1019
- typeName = CoreStringField.typeName;
1020
- supports(value) {
1021
- return isString(value);
1022
- }
963
+ typeName = CoreStringField.typeName;
964
+ supports(value) {
965
+ return isString2(value);
966
+ }
1023
967
  };
1024
968
 
1025
- //#endregion
1026
- //#region src/data-store.ts
969
+ // src/data-store.ts
970
+ import { ensurePathArray as ensurePathArray2, ensurePathString as ensurePathString2, throwIfEmpty as throwIfEmpty4 } from "@axi-engine/utils";
1027
971
  var DataStore = class {
1028
- resolvers = [];
1029
- rootFieldsName = "__root_fields";
1030
- _rootFields;
1031
- get rootFields() {
1032
- if (!this._rootFields) this._rootFields = this.tree.getOrCreateFields(this.rootFieldsName);
1033
- return this._rootFields;
1034
- }
1035
- constructor(tree) {
1036
- this.tree = tree;
1037
- this.registerResolver(new NumericFieldResolver());
1038
- this.registerResolver(new BooleanFieldResolver());
1039
- this.registerResolver(new StringFieldResolver());
1040
- }
1041
- registerResolver(resolver) {
1042
- this.resolvers.unshift(resolver);
1043
- }
1044
- clearResolvers() {
1045
- this.resolvers.length = 0;
1046
- }
1047
- getValue(path) {
1048
- return this.getField(path).value;
1049
- }
1050
- setValue(path, val) {
1051
- /** for case when field has policies */
1052
- const field = this.getField(path);
1053
- field.value = val;
1054
- return field.value;
1055
- }
1056
- createValue(path, val, options) {
1057
- const dest = this.getDestinationFields(path);
1058
- if (options?.fieldType) return dest.fields.create(options.fieldType, dest.leafName, val, options).value;
1059
- for (let resolver of this.resolvers) if (resolver.supports(val)) return dest.fields.create(resolver.typeName, dest.leafName, val, options).value;
1060
- return dest.fields.createGeneric(dest.leafName, val, options).value;
1061
- }
1062
- upsetValue(path, val, options) {
1063
- const dest = this.getDestinationFields(path);
1064
- if (options?.fieldType) return dest.fields.upset(options.fieldType, dest.leafName, val, options).value;
1065
- for (let resolver of this.resolvers) if (resolver.supports(val)) return dest.fields.upset(resolver.typeName, dest.leafName, val, options).value;
1066
- return dest.fields.upsetGeneric(dest.leafName, val, options).value;
1067
- }
1068
- createBoolean(path, initialValue, options) {
1069
- const dest = this.getDestinationFields(path);
1070
- return dest.fields.createBoolean(dest.leafName, initialValue, options);
1071
- }
1072
- createNumeric(path, initialValue, options) {
1073
- const dest = this.getDestinationFields(path);
1074
- return dest.fields.createNumeric(dest.leafName, initialValue, options);
1075
- }
1076
- createString(path, initialValue, options) {
1077
- const dest = this.getDestinationFields(path);
1078
- return dest.fields.createString(dest.leafName, initialValue, options);
1079
- }
1080
- createGeneric(path, initialValue, options) {
1081
- const dest = this.getDestinationFields(path);
1082
- return dest.fields.createGeneric(dest.leafName, initialValue, options);
1083
- }
1084
- getBoolean(path) {
1085
- return this.getField(path);
1086
- }
1087
- getNumeric(path) {
1088
- return this.getField(path);
1089
- }
1090
- getString(path) {
1091
- return this.getField(path);
1092
- }
1093
- getGeneric(path) {
1094
- return this.getField(path);
1095
- }
1096
- getField(path) {
1097
- const pathArr = ensurePathArray(path);
1098
- throwIfEmpty(pathArr, `Wrong path or path is empty: ${ensurePathString(path)}, should contain at least one path segment`);
1099
- if (this.isPathToRootFields(pathArr)) return this.rootFields.get(pathArr[0]);
1100
- const fieldName = pathArr.pop();
1101
- return this.tree.getFields(pathArr).get(fieldName);
1102
- }
1103
- createFields(path) {
1104
- return this.tree.createFields(path, true);
1105
- }
1106
- createTree(path) {
1107
- return this.tree.createFieldTree(path, true);
1108
- }
1109
- getFields(path) {
1110
- return this.tree.getFields(path);
1111
- }
1112
- getTree(path) {
1113
- return this.tree.getFieldTree(path);
1114
- }
1115
- remove(path) {
1116
- const pathArr = ensurePathArray(path);
1117
- throwIfEmpty(pathArr, `Wrong path or path is empty: ${ensurePathString(path)}, should contain at least one path segment`);
1118
- /** remove field from root fields */
1119
- if (this.isPathToRootFields(pathArr)) {
1120
- this.rootFields.remove(pathArr);
1121
- return;
1122
- }
1123
- const node = this.tree.findParentNode(pathArr);
1124
- const leafName = pathArr[pathArr.length - 1];
1125
- if (node instanceof CoreFields) node.remove(leafName);
1126
- else if (node instanceof CoreFieldTree) node.removeNode(leafName);
1127
- }
1128
- isPathToRootFields(path) {
1129
- return ensurePathArray(path).length === 1;
1130
- }
1131
- getDestinationFields(path) {
1132
- const pathArr = ensurePathArray(path);
1133
- if (this.isPathToRootFields(pathArr)) return {
1134
- fields: this.rootFields,
1135
- leafName: pathArr[0]
1136
- };
1137
- const leafName = pathArr.pop();
1138
- return {
1139
- fields: this.tree.getOrCreateFields(path),
1140
- leafName
1141
- };
1142
- }
972
+ constructor(tree) {
973
+ this.tree = tree;
974
+ this.registerResolver(new NumericFieldResolver());
975
+ this.registerResolver(new BooleanFieldResolver());
976
+ this.registerResolver(new StringFieldResolver());
977
+ }
978
+ resolvers = [];
979
+ rootFieldsName = "__root_fields";
980
+ _rootFields;
981
+ get rootFields() {
982
+ if (!this._rootFields) {
983
+ this._rootFields = this.tree.getOrCreateFields(this.rootFieldsName);
984
+ }
985
+ return this._rootFields;
986
+ }
987
+ registerResolver(resolver) {
988
+ this.resolvers.unshift(resolver);
989
+ }
990
+ clearResolvers() {
991
+ this.resolvers.length = 0;
992
+ }
993
+ getValue(path) {
994
+ return this.getField(path).value;
995
+ }
996
+ setValue(path, val) {
997
+ const field = this.getField(path);
998
+ field.value = val;
999
+ return field.value;
1000
+ }
1001
+ createValue(path, val, options) {
1002
+ const dest = this.getDestinationFields(path);
1003
+ if (options?.fieldType) {
1004
+ return dest.fields.create(options.fieldType, dest.leafName, val, options).value;
1005
+ }
1006
+ for (let resolver of this.resolvers) {
1007
+ if (resolver.supports(val)) {
1008
+ return dest.fields.create(resolver.typeName, dest.leafName, val, options).value;
1009
+ }
1010
+ }
1011
+ return dest.fields.createGeneric(dest.leafName, val, options).value;
1012
+ }
1013
+ upsetValue(path, val, options) {
1014
+ const dest = this.getDestinationFields(path);
1015
+ if (options?.fieldType) {
1016
+ return dest.fields.upset(options.fieldType, dest.leafName, val, options).value;
1017
+ }
1018
+ for (let resolver of this.resolvers) {
1019
+ if (resolver.supports(val)) {
1020
+ return dest.fields.upset(resolver.typeName, dest.leafName, val, options).value;
1021
+ }
1022
+ }
1023
+ return dest.fields.upsetGeneric(dest.leafName, val, options).value;
1024
+ }
1025
+ createBoolean(path, initialValue, options) {
1026
+ const dest = this.getDestinationFields(path);
1027
+ return dest.fields.createBoolean(dest.leafName, initialValue, options);
1028
+ }
1029
+ createNumeric(path, initialValue, options) {
1030
+ const dest = this.getDestinationFields(path);
1031
+ return dest.fields.createNumeric(dest.leafName, initialValue, options);
1032
+ }
1033
+ createString(path, initialValue, options) {
1034
+ const dest = this.getDestinationFields(path);
1035
+ return dest.fields.createString(dest.leafName, initialValue, options);
1036
+ }
1037
+ createGeneric(path, initialValue, options) {
1038
+ const dest = this.getDestinationFields(path);
1039
+ return dest.fields.createGeneric(dest.leafName, initialValue, options);
1040
+ }
1041
+ getBoolean(path) {
1042
+ return this.getField(path);
1043
+ }
1044
+ getNumeric(path) {
1045
+ return this.getField(path);
1046
+ }
1047
+ getString(path) {
1048
+ return this.getField(path);
1049
+ }
1050
+ getGeneric(path) {
1051
+ return this.getField(path);
1052
+ }
1053
+ getField(path) {
1054
+ const pathArr = ensurePathArray2(path);
1055
+ throwIfEmpty4(pathArr, `Wrong path or path is empty: ${ensurePathString2(path)}, should contain at least one path segment`);
1056
+ if (this.isPathToRootFields(pathArr)) {
1057
+ return this.rootFields.get(pathArr[0]);
1058
+ }
1059
+ const fieldName = pathArr.pop();
1060
+ const fields = this.tree.getFields(pathArr);
1061
+ return fields.get(fieldName);
1062
+ }
1063
+ createFields(path) {
1064
+ return this.tree.createFields(path, true);
1065
+ }
1066
+ createTree(path) {
1067
+ return this.tree.createFieldTree(path, true);
1068
+ }
1069
+ getFields(path) {
1070
+ return this.tree.getFields(path);
1071
+ }
1072
+ getTree(path) {
1073
+ return this.tree.getFieldTree(path);
1074
+ }
1075
+ remove(path) {
1076
+ const pathArr = ensurePathArray2(path);
1077
+ throwIfEmpty4(pathArr, `Wrong path or path is empty: ${ensurePathString2(path)}, should contain at least one path segment`);
1078
+ if (this.isPathToRootFields(pathArr)) {
1079
+ this.rootFields.remove(pathArr);
1080
+ return;
1081
+ }
1082
+ const node = this.tree.findParentNode(pathArr);
1083
+ const leafName = pathArr[pathArr.length - 1];
1084
+ if (node instanceof CoreFields) {
1085
+ node.remove(leafName);
1086
+ } else if (node instanceof CoreFieldTree) {
1087
+ node.removeNode(leafName);
1088
+ }
1089
+ }
1090
+ isPathToRootFields(path) {
1091
+ return ensurePathArray2(path).length === 1;
1092
+ }
1093
+ getDestinationFields(path) {
1094
+ const pathArr = ensurePathArray2(path);
1095
+ if (this.isPathToRootFields(pathArr)) {
1096
+ return { fields: this.rootFields, leafName: pathArr[0] };
1097
+ }
1098
+ const leafName = pathArr.pop();
1099
+ return { fields: this.tree.getOrCreateFields(path), leafName };
1100
+ }
1143
1101
  };
1144
1102
 
1145
- //#endregion
1146
- //#region src/setup.ts
1147
- /**
1148
- * Creates and configures a FieldRegistry with all the core field types.
1149
- * @returns {FieldRegistry} A pre-configured FieldRegistry instance.
1150
- */
1103
+ // src/setup.ts
1151
1104
  function createCoreFieldRegistry() {
1152
- const fieldRegistry = new FieldRegistry();
1153
- fieldRegistry.register(CoreField.typeName, CoreField);
1154
- fieldRegistry.register(CoreNumericField.typeName, CoreNumericField);
1155
- fieldRegistry.register(CoreStringField.typeName, CoreStringField);
1156
- fieldRegistry.register(CoreBooleanField.typeName, CoreBooleanField);
1157
- return fieldRegistry;
1105
+ const fieldRegistry = new FieldRegistry();
1106
+ fieldRegistry.register(CoreField.typeName, CoreField);
1107
+ fieldRegistry.register(CoreNumericField.typeName, CoreNumericField);
1108
+ fieldRegistry.register(CoreStringField.typeName, CoreStringField);
1109
+ fieldRegistry.register(CoreBooleanField.typeName, CoreBooleanField);
1110
+ return fieldRegistry;
1158
1111
  }
1159
- /**
1160
- * Creates and configures a PolicySerializer with handlers for core policies.
1161
- * @returns {PolicySerializer} A pre-configured PolicySerializer instance.
1162
- */
1163
1112
  function createCorePolicySerializer() {
1164
- const policySerializer = new PolicySerializer();
1165
- policySerializer.register(ClampPolicy.id, new ClampPolicySerializerHandler());
1166
- policySerializer.register(ClampMinPolicy.id, new ClampMinPolicySerializerHandler());
1167
- policySerializer.register(ClampMaxPolicy.id, new ClampMaxPolicySerializerHandler());
1168
- return policySerializer;
1113
+ const policySerializer = new PolicySerializer();
1114
+ policySerializer.register(ClampPolicy.id, new ClampPolicySerializerHandler());
1115
+ policySerializer.register(ClampMinPolicy.id, new ClampMinPolicySerializerHandler());
1116
+ policySerializer.register(ClampMaxPolicy.id, new ClampMaxPolicySerializerHandler());
1117
+ return policySerializer;
1169
1118
  }
1170
- /**
1171
- * Creates a factory for CoreFieldTree and CoreFields nodes.
1172
- * @param {FieldRegistry} fieldRegistry - The registry to be used by the factory.
1173
- * @returns {CoreTreeNodeFactory} A new CoreTreeNodeFactory instance.
1174
- */
1175
1119
  function createCoreTreeNodeFactory(fieldRegistry) {
1176
- return new CoreTreeNodeFactory(fieldRegistry);
1120
+ return new CoreTreeNodeFactory(fieldRegistry);
1177
1121
  }
1178
- /**
1179
- * Creates a fully configured serializer for a FieldTree.
1180
- * This function composes all necessary serializers (FieldTree, Fields, Field) for a complete setup.
1181
- * @param {CoreTreeNodeFactory} fieldTreeNodeFactory - The factory used to create new tree nodes during deserialization.
1182
- * @param policySerializer
1183
- * @returns {FieldTreeSerializer<CoreFields>} A top-level serializer for the entire field tree.
1184
- */
1185
1122
  function createCoreTreeSerializer(fieldTreeNodeFactory, policySerializer) {
1186
- return new FieldTreeSerializer(fieldTreeNodeFactory, new FieldsSerializer(fieldTreeNodeFactory, new FieldSerializer(fieldTreeNodeFactory.fieldRegistry, policySerializer ?? createCorePolicySerializer())));
1123
+ return new FieldTreeSerializer(
1124
+ fieldTreeNodeFactory,
1125
+ new FieldsSerializer(
1126
+ fieldTreeNodeFactory,
1127
+ new FieldSerializer(fieldTreeNodeFactory.fieldRegistry, policySerializer ?? createCorePolicySerializer())
1128
+ )
1129
+ );
1187
1130
  }
1188
- /**
1189
- * Creates a complete core setup for the field system.
1190
- * @returns {{factory: CoreTreeNodeFactory, serializer: FieldTreeSerializer<CoreFields>}}
1191
- */
1192
1131
  function createCoreFieldSystem(config) {
1193
- const factory = createCoreTreeNodeFactory(config?.registry ?? createCoreFieldRegistry());
1194
- return {
1195
- factory,
1196
- serializer: createCoreTreeSerializer(factory, config?.policySerializer)
1197
- };
1132
+ const registry = config?.registry ?? createCoreFieldRegistry();
1133
+ const factory = createCoreTreeNodeFactory(registry);
1134
+ const serializer = createCoreTreeSerializer(factory, config?.policySerializer);
1135
+ return { factory, serializer };
1198
1136
  }
1199
-
1200
- //#endregion
1201
- export { ClampMaxPolicy, ClampMaxPolicySerializerHandler, ClampMinPolicy, ClampMinPolicySerializerHandler, ClampPolicy, ClampPolicySerializerHandler, CoreBooleanField, CoreField, CoreFieldTree, CoreFields, CoreFieldsFactory, CoreNumericField, CoreStringField, CoreTreeNodeFactory, DataStore, FieldRegistry, FieldSerializer, FieldTree, FieldTreeSerializer, Fields, FieldsSerializer, Policies, PolicySerializer, clampMaxPolicy, clampMinPolicy, clampPolicy, createCoreFieldRegistry, createCoreFieldSystem, createCorePolicySerializer, createCoreTreeNodeFactory, createCoreTreeSerializer, createTypedMethodsMixin };
1202
- //# sourceMappingURL=index.mjs.map
1137
+ export {
1138
+ ClampMaxPolicy,
1139
+ ClampMaxPolicySerializerHandler,
1140
+ ClampMinPolicy,
1141
+ ClampMinPolicySerializerHandler,
1142
+ ClampPolicy,
1143
+ ClampPolicySerializerHandler,
1144
+ CoreBooleanField,
1145
+ CoreField,
1146
+ CoreFieldTree,
1147
+ CoreFields,
1148
+ CoreFieldsFactory,
1149
+ CoreNumericField,
1150
+ CoreStringField,
1151
+ CoreTreeNodeFactory,
1152
+ DataStore,
1153
+ FieldRegistry,
1154
+ FieldSerializer,
1155
+ FieldTree,
1156
+ FieldTreeSerializer,
1157
+ Fields,
1158
+ FieldsSerializer,
1159
+ Policies,
1160
+ PolicySerializer,
1161
+ clampMaxPolicy,
1162
+ clampMinPolicy,
1163
+ clampPolicy,
1164
+ createCoreFieldRegistry,
1165
+ createCoreFieldSystem,
1166
+ createCorePolicySerializer,
1167
+ createCoreTreeNodeFactory,
1168
+ createCoreTreeSerializer,
1169
+ createTypedMethodsMixin
1170
+ };