@axi-engine/fields 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/index.d.mts +644 -217
- package/dist/index.d.ts +644 -217
- package/dist/index.js +705 -397
- package/dist/index.mjs +686 -378
- package/package.json +3 -9
package/dist/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,49 +15,45 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var index_exports = {};
|
|
32
22
|
__export(index_exports, {
|
|
33
|
-
BaseFields: () => BaseFields,
|
|
34
23
|
ClampMaxPolicy: () => ClampMaxPolicy,
|
|
24
|
+
ClampMaxPolicySerializerHandler: () => ClampMaxPolicySerializerHandler,
|
|
35
25
|
ClampMinPolicy: () => ClampMinPolicy,
|
|
26
|
+
ClampMinPolicySerializerHandler: () => ClampMinPolicySerializerHandler,
|
|
36
27
|
ClampPolicy: () => ClampPolicy,
|
|
37
|
-
|
|
28
|
+
ClampPolicySerializerHandler: () => ClampPolicySerializerHandler,
|
|
29
|
+
DefaultBooleanField: () => DefaultBooleanField,
|
|
30
|
+
DefaultField: () => DefaultField,
|
|
31
|
+
DefaultFields: () => DefaultFields,
|
|
32
|
+
DefaultNumericField: () => DefaultNumericField,
|
|
33
|
+
DefaultStringField: () => DefaultStringField,
|
|
34
|
+
DefaultTreeNodeFactory: () => DefaultTreeNodeFactory,
|
|
35
|
+
FieldRegistry: () => FieldRegistry,
|
|
36
|
+
FieldSerializer: () => FieldSerializer,
|
|
38
37
|
FieldTree: () => FieldTree,
|
|
38
|
+
FieldTreeSerializer: () => FieldTreeSerializer,
|
|
39
39
|
Fields: () => Fields,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
FieldsSerializer: () => FieldsSerializer,
|
|
41
|
+
Policies: () => Policies,
|
|
42
|
+
PolicySerializer: () => PolicySerializer,
|
|
43
43
|
clampMaxPolicy: () => clampMaxPolicy,
|
|
44
44
|
clampMinPolicy: () => clampMinPolicy,
|
|
45
45
|
clampPolicy: () => clampPolicy
|
|
46
46
|
});
|
|
47
47
|
module.exports = __toCommonJS(index_exports);
|
|
48
48
|
|
|
49
|
-
// src/
|
|
50
|
-
var
|
|
51
|
-
FieldsNodeType2["fieldTree"] = "FieldTree";
|
|
52
|
-
FieldsNodeType2["fields"] = "Fields";
|
|
53
|
-
return FieldsNodeType2;
|
|
54
|
-
})(FieldsNodeType || {});
|
|
55
|
-
|
|
56
|
-
// src/field-policies.ts
|
|
57
|
-
var _ClampPolicy = class _ClampPolicy {
|
|
49
|
+
// src/policies/clamp-policy.ts
|
|
50
|
+
var ClampPolicy = class _ClampPolicy {
|
|
58
51
|
constructor(min, max) {
|
|
59
52
|
this.min = min;
|
|
60
53
|
this.max = max;
|
|
61
|
-
this.id = _ClampPolicy.id;
|
|
62
54
|
}
|
|
55
|
+
static id = "clamp";
|
|
56
|
+
id = _ClampPolicy.id;
|
|
63
57
|
apply(val) {
|
|
64
58
|
return Math.max(this.min, Math.min(this.max, val));
|
|
65
59
|
}
|
|
@@ -68,27 +62,17 @@ var _ClampPolicy = class _ClampPolicy {
|
|
|
68
62
|
this.max = max;
|
|
69
63
|
}
|
|
70
64
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
apply(val) {
|
|
79
|
-
return Math.max(this.min, val);
|
|
80
|
-
}
|
|
81
|
-
updateBounds(min) {
|
|
82
|
-
this.min = min;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
_ClampMinPolicy.id = "clampMin";
|
|
86
|
-
var ClampMinPolicy = _ClampMinPolicy;
|
|
87
|
-
var _ClampMaxPolicy = class _ClampMaxPolicy {
|
|
65
|
+
function clampPolicy(min, max) {
|
|
66
|
+
return new ClampPolicy(min, max);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/policies/clamp-max-policy.ts
|
|
70
|
+
var ClampMaxPolicy = class _ClampMaxPolicy {
|
|
88
71
|
constructor(max) {
|
|
89
72
|
this.max = max;
|
|
90
|
-
this.id = _ClampMaxPolicy.id;
|
|
91
73
|
}
|
|
74
|
+
static id = "clampMax";
|
|
75
|
+
id = _ClampMaxPolicy.id;
|
|
92
76
|
apply(val) {
|
|
93
77
|
return Math.min(this.max, val);
|
|
94
78
|
}
|
|
@@ -96,58 +80,33 @@ var _ClampMaxPolicy = class _ClampMaxPolicy {
|
|
|
96
80
|
this.max = max;
|
|
97
81
|
}
|
|
98
82
|
};
|
|
99
|
-
_ClampMaxPolicy.id = "clampMax";
|
|
100
|
-
var ClampMaxPolicy = _ClampMaxPolicy;
|
|
101
|
-
function clampPolicy(min, max) {
|
|
102
|
-
return new ClampPolicy(min, max);
|
|
103
|
-
}
|
|
104
|
-
function clampMinPolicy(min) {
|
|
105
|
-
return new ClampMinPolicy(min);
|
|
106
|
-
}
|
|
107
83
|
function clampMaxPolicy(max) {
|
|
108
84
|
return new ClampMaxPolicy(max);
|
|
109
85
|
}
|
|
110
86
|
|
|
111
|
-
// src/
|
|
112
|
-
var
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
* Creates an instance of a Field.
|
|
116
|
-
* @param name A unique identifier for the field.
|
|
117
|
-
* @param initialVal The initial value of the field.
|
|
118
|
-
* @param options Optional configuration for the field.
|
|
119
|
-
* @param options.policies An array of policies to apply to the field's value on every `set` operation.
|
|
120
|
-
*/
|
|
121
|
-
constructor(name, initialVal, options) {
|
|
122
|
-
this.policies = /* @__PURE__ */ new Map();
|
|
123
|
-
this._val = (0, import_signals_core.signal)(initialVal);
|
|
124
|
-
this.name = name;
|
|
125
|
-
options?.policies?.forEach((policy) => this.policies.set(policy.id, policy));
|
|
126
|
-
this.set(initialVal);
|
|
87
|
+
// src/policies/clamp-min-policy.ts
|
|
88
|
+
var ClampMinPolicy = class _ClampMinPolicy {
|
|
89
|
+
constructor(min) {
|
|
90
|
+
this.min = min;
|
|
127
91
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
get val() {
|
|
133
|
-
return this._val.value;
|
|
92
|
+
static id = "clampMin";
|
|
93
|
+
id = _ClampMinPolicy.id;
|
|
94
|
+
apply(val) {
|
|
95
|
+
return Math.max(this.min, val);
|
|
134
96
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* Subscribe to this signal to react to value changes.
|
|
138
|
-
*/
|
|
139
|
-
get signal() {
|
|
140
|
-
return this._val;
|
|
97
|
+
updateBounds(min) {
|
|
98
|
+
this.min = min;
|
|
141
99
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
100
|
+
};
|
|
101
|
+
function clampMinPolicy(min) {
|
|
102
|
+
return new ClampMinPolicy(min);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/policies/policies.ts
|
|
106
|
+
var Policies = class {
|
|
107
|
+
policies = /* @__PURE__ */ new Map();
|
|
108
|
+
get items() {
|
|
109
|
+
return this.policies;
|
|
151
110
|
}
|
|
152
111
|
/**
|
|
153
112
|
* Retrieves a specific policy instance by its ID.
|
|
@@ -156,7 +115,7 @@ var Field = class {
|
|
|
156
115
|
* @param id The unique ID of the policy to retrieve.
|
|
157
116
|
* @returns The policy instance, or `undefined` if not found.
|
|
158
117
|
*/
|
|
159
|
-
|
|
118
|
+
get(id) {
|
|
160
119
|
return this.policies.get(id);
|
|
161
120
|
}
|
|
162
121
|
/**
|
|
@@ -165,17 +124,18 @@ var Field = class {
|
|
|
165
124
|
* If a policy with the same ID already exists, its `destroy` method will be called before it is replaced.
|
|
166
125
|
* @param policy The policy instance to add.
|
|
167
126
|
*/
|
|
168
|
-
|
|
127
|
+
add(policy) {
|
|
169
128
|
const existed = this.policies.get(policy.id);
|
|
170
129
|
existed?.destroy?.();
|
|
171
130
|
this.policies.set(policy.id, policy);
|
|
131
|
+
return this;
|
|
172
132
|
}
|
|
173
133
|
/**
|
|
174
134
|
* Removes a policy from the field by its ID and call `destroy` method.
|
|
175
135
|
* @param policyId The unique ID of the policy to remove.
|
|
176
136
|
* @returns `true` if the policy was found and removed, otherwise `false`.
|
|
177
137
|
*/
|
|
178
|
-
|
|
138
|
+
remove(policyId) {
|
|
179
139
|
const policyToRemove = this.policies.get(policyId);
|
|
180
140
|
if (!policyToRemove) {
|
|
181
141
|
return false;
|
|
@@ -183,11 +143,14 @@ var Field = class {
|
|
|
183
143
|
policyToRemove.destroy?.();
|
|
184
144
|
return this.policies.delete(policyId);
|
|
185
145
|
}
|
|
146
|
+
isEmpty() {
|
|
147
|
+
return this.policies.size === 0;
|
|
148
|
+
}
|
|
186
149
|
/**
|
|
187
150
|
* Removes all policies from the field.
|
|
188
151
|
* After this, `set()` will no longer apply any transformations to the value until new policies are added.
|
|
189
152
|
*/
|
|
190
|
-
|
|
153
|
+
clear() {
|
|
191
154
|
this.policies.forEach((policy) => policy.destroy?.());
|
|
192
155
|
this.policies.clear();
|
|
193
156
|
}
|
|
@@ -195,413 +158,775 @@ var Field = class {
|
|
|
195
158
|
* Forces the current value to be re-processed by all policies.
|
|
196
159
|
* Useful if a policy's logic has changed and you need to re-evaluate the current state.
|
|
197
160
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
161
|
+
apply(val) {
|
|
162
|
+
let finalVal = val;
|
|
163
|
+
this.policies.forEach((policy) => finalVal = policy.apply(finalVal));
|
|
164
|
+
return finalVal;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/field-definitions/default-field.ts
|
|
169
|
+
var import_utils = require("@axi-engine/utils");
|
|
170
|
+
var import_dequal = require("dequal");
|
|
171
|
+
var DefaultField = class _DefaultField {
|
|
172
|
+
/** A type keyword of the field */
|
|
173
|
+
static typeName = "default";
|
|
174
|
+
typeName = _DefaultField.typeName;
|
|
175
|
+
/** A unique identifier for the field. */
|
|
176
|
+
_name;
|
|
177
|
+
_value;
|
|
178
|
+
_onChange = new import_utils.Emitter();
|
|
179
|
+
onChange;
|
|
180
|
+
policies = new Policies();
|
|
181
|
+
get name() {
|
|
182
|
+
return this._name;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Gets the current raw value of the field.
|
|
186
|
+
* For reactive updates, it's recommended to use the `.signal` property instead.
|
|
187
|
+
*/
|
|
188
|
+
get value() {
|
|
189
|
+
return this._value;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Sets a new value for the field.
|
|
193
|
+
* The provided value will be processed by all registered policies before the underlying signal is updated.
|
|
194
|
+
* @param val The new value to set.
|
|
195
|
+
*/
|
|
196
|
+
set value(val) {
|
|
197
|
+
const oldVal = this._value;
|
|
198
|
+
const finalVal = this.policies.apply(val);
|
|
199
|
+
if (!(0, import_dequal.dequal)(this._value, finalVal)) {
|
|
200
|
+
this._value = finalVal;
|
|
201
|
+
this._onChange.emit(this._value, oldVal);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Creates an instance of a Field.
|
|
206
|
+
* @param name A unique identifier for the field.
|
|
207
|
+
* @param initialVal The initial value of the field.
|
|
208
|
+
* @param options Optional configuration for the field.
|
|
209
|
+
* @param options.policies An array of policies to apply to the field's value on every `set` operation.
|
|
210
|
+
* @param options.isEqual An function for compare old and new value, by default uses the strictEquals from `utils`
|
|
211
|
+
*
|
|
212
|
+
*/
|
|
213
|
+
constructor(name, initialVal, options) {
|
|
214
|
+
this.onChange = this._onChange;
|
|
215
|
+
this._name = name;
|
|
216
|
+
options?.policies?.forEach((policy) => this.policies.add(policy));
|
|
217
|
+
this.value = initialVal;
|
|
218
|
+
}
|
|
219
|
+
setValueSilently(val) {
|
|
220
|
+
this._value = this.policies.apply(val);
|
|
221
|
+
}
|
|
222
|
+
batchUpdate(updateFn) {
|
|
223
|
+
this.value = updateFn(this.value);
|
|
200
224
|
}
|
|
201
225
|
/**
|
|
202
226
|
* Cleans up resources used by the field and its policies.
|
|
203
227
|
* This should be called when the field is no longer needed to prevent memory leaks from reactive policies.
|
|
204
228
|
*/
|
|
205
229
|
destroy() {
|
|
206
|
-
this.
|
|
230
|
+
this.policies.clear();
|
|
231
|
+
this._onChange.clear();
|
|
207
232
|
}
|
|
208
233
|
};
|
|
209
234
|
|
|
210
|
-
// src/
|
|
211
|
-
var
|
|
212
|
-
|
|
235
|
+
// src/field-definitions/default-boolean-field.ts
|
|
236
|
+
var DefaultBooleanField = class _DefaultBooleanField extends DefaultField {
|
|
237
|
+
static typeName = "boolean";
|
|
238
|
+
typeName = _DefaultBooleanField.typeName;
|
|
239
|
+
constructor(name, initialVal, options) {
|
|
240
|
+
super(name, initialVal, options);
|
|
241
|
+
}
|
|
242
|
+
toggle() {
|
|
243
|
+
this.value = !this.value;
|
|
244
|
+
return this.value;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/field-definitions/default-string-field.ts
|
|
249
|
+
var DefaultStringField = class _DefaultStringField extends DefaultField {
|
|
250
|
+
static typeName = "string";
|
|
251
|
+
typeName = _DefaultStringField.typeName;
|
|
252
|
+
constructor(name, initialVal, options) {
|
|
253
|
+
super(name, initialVal, options);
|
|
254
|
+
}
|
|
255
|
+
append(str) {
|
|
256
|
+
this.value = this.value + str;
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
prepend(str) {
|
|
260
|
+
this.value = str + this.value;
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
trim() {
|
|
264
|
+
this.value = this.value.trim();
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
isEmpty() {
|
|
268
|
+
return this.value.length === 0;
|
|
269
|
+
}
|
|
270
|
+
clear() {
|
|
271
|
+
this.value = "";
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/field-definitions/default-numeric-field.ts
|
|
276
|
+
var import_utils2 = require("@axi-engine/utils");
|
|
277
|
+
var DefaultNumericField = class _DefaultNumericField extends DefaultField {
|
|
278
|
+
static typeName = "numeric";
|
|
279
|
+
typeName = _DefaultNumericField.typeName;
|
|
213
280
|
get min() {
|
|
214
|
-
const policy = this.
|
|
281
|
+
const policy = this.policies.get(ClampPolicy.id) ?? this.policies.get(ClampMinPolicy.id);
|
|
215
282
|
return policy?.min;
|
|
216
283
|
}
|
|
217
284
|
get max() {
|
|
218
|
-
const policy = this.
|
|
285
|
+
const policy = this.policies.get(ClampPolicy.id) ?? this.policies.get(ClampMaxPolicy.id);
|
|
219
286
|
return policy?.max;
|
|
220
287
|
}
|
|
221
|
-
get isMin() {
|
|
222
|
-
const min = this.min;
|
|
223
|
-
return (0, import_utils.isNullOrUndefined)(min) ? false : this.val <= min;
|
|
224
|
-
}
|
|
225
|
-
get isMax() {
|
|
226
|
-
const max = this.max;
|
|
227
|
-
return (0, import_utils.isNullOrUndefined)(max) ? false : this.val >= max;
|
|
228
|
-
}
|
|
229
288
|
constructor(name, initialVal, options) {
|
|
230
289
|
const policies = options?.policies ?? [];
|
|
231
|
-
if (!(0,
|
|
290
|
+
if (!(0, import_utils2.isNullOrUndefined)(options?.min) && !(0, import_utils2.isNullOrUndefined)(options?.max)) {
|
|
232
291
|
policies.unshift(clampPolicy(options.min, options.max));
|
|
233
|
-
} else if (!(0,
|
|
292
|
+
} else if (!(0, import_utils2.isNullOrUndefined)(options?.min)) {
|
|
234
293
|
policies.unshift(clampMinPolicy(options.min));
|
|
235
|
-
} else if (!(0,
|
|
294
|
+
} else if (!(0, import_utils2.isNullOrUndefined)(options?.max)) {
|
|
236
295
|
policies.unshift(clampMaxPolicy(options.max));
|
|
237
296
|
}
|
|
238
297
|
super(name, initialVal, { policies });
|
|
239
298
|
}
|
|
299
|
+
isMin() {
|
|
300
|
+
const min = this.min;
|
|
301
|
+
return (0, import_utils2.isNullOrUndefined)(min) ? false : this.value <= min;
|
|
302
|
+
}
|
|
303
|
+
isMax() {
|
|
304
|
+
const max = this.max;
|
|
305
|
+
return (0, import_utils2.isNullOrUndefined)(max) ? false : this.value >= max;
|
|
306
|
+
}
|
|
240
307
|
inc(amount = 1) {
|
|
241
|
-
this.
|
|
308
|
+
this.value = this.value + amount;
|
|
242
309
|
}
|
|
243
310
|
dec(amount = 1) {
|
|
244
|
-
this.
|
|
311
|
+
this.value = this.value - amount;
|
|
245
312
|
}
|
|
246
313
|
};
|
|
247
314
|
|
|
248
|
-
// src/
|
|
249
|
-
var
|
|
250
|
-
var
|
|
315
|
+
// src/utils/constructor-registry.ts
|
|
316
|
+
var import_utils3 = require("@axi-engine/utils");
|
|
317
|
+
var ConstructorRegistry = class {
|
|
318
|
+
items = /* @__PURE__ */ new Map();
|
|
319
|
+
/**
|
|
320
|
+
* Registers a constructor with a unique string identifier.
|
|
321
|
+
*
|
|
322
|
+
* @param typeId - The unique identifier for the constructor (e.g., a static `typeName` property from a class).
|
|
323
|
+
* @param ctor - The class constructor to register.
|
|
324
|
+
* @returns The registry instance for chainable calls.
|
|
325
|
+
* @throws If a constructor with the same `typeId` is already registered.
|
|
326
|
+
*/
|
|
327
|
+
register(typeId, ctor) {
|
|
328
|
+
(0, import_utils3.throwIf)(this.items.has(typeId), `A constructor with typeId '${typeId}' is already registered.`);
|
|
329
|
+
this.items.set(typeId, ctor);
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Retrieves a constructor by its identifier.
|
|
334
|
+
*
|
|
335
|
+
* @param typeId - The identifier of the constructor to retrieve.
|
|
336
|
+
* @returns The found class constructor.
|
|
337
|
+
* @throws If no constructor is found for the given `typeId`.
|
|
338
|
+
*/
|
|
339
|
+
get(typeId) {
|
|
340
|
+
const Ctor = this.items.get(typeId);
|
|
341
|
+
(0, import_utils3.throwIfEmpty)(Ctor, `No constructor found for typeId '${typeId}'`);
|
|
342
|
+
return Ctor;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Checks if a constructor for a given identifier is registered.
|
|
346
|
+
* @param typeId - The identifier to check.
|
|
347
|
+
* @returns `true` if a constructor is registered, otherwise `false`.
|
|
348
|
+
*/
|
|
349
|
+
has(typeId) {
|
|
350
|
+
return this.items.has(typeId);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Clears all registered constructors from the registry.
|
|
354
|
+
*/
|
|
355
|
+
clear() {
|
|
356
|
+
this.items.clear();
|
|
357
|
+
}
|
|
358
|
+
};
|
|
251
359
|
|
|
252
|
-
//
|
|
253
|
-
var
|
|
254
|
-
var AxiEventEmitter = class extends import_eventemitter3.default {
|
|
255
|
-
// Currently, we don't need to add any custom logic.
|
|
256
|
-
// The main purpose of this class is to create an abstraction layer.
|
|
360
|
+
// src/field-registry.ts
|
|
361
|
+
var FieldRegistry = class extends ConstructorRegistry {
|
|
257
362
|
};
|
|
258
363
|
|
|
259
|
-
// src/
|
|
260
|
-
var
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
364
|
+
// src/fields.ts
|
|
365
|
+
var import_utils4 = require("@axi-engine/utils");
|
|
366
|
+
var Fields = class _Fields {
|
|
367
|
+
static typeName = "fields";
|
|
368
|
+
typeName = _Fields.typeName;
|
|
369
|
+
_fields = /* @__PURE__ */ new Map();
|
|
370
|
+
_fieldRegistry;
|
|
371
|
+
/**
|
|
372
|
+
* An event emitter that fires when a new field is added to the collection.
|
|
373
|
+
* @event
|
|
374
|
+
* @param {object} event - The event payload.
|
|
375
|
+
* @param {string} event.name - The name of the added field.
|
|
376
|
+
* @param {Field<any>} event.field - The `Field` instance that was added.
|
|
377
|
+
*/
|
|
378
|
+
onAdd = new import_utils4.Emitter();
|
|
379
|
+
/**
|
|
380
|
+
* An event emitter that fires after one or more fields have been removed.
|
|
381
|
+
* @event
|
|
382
|
+
* @param {object} event - The event payload.
|
|
383
|
+
* @param {string[]} event.names - An array of names of the fields that were successfully removed.
|
|
384
|
+
*/
|
|
385
|
+
onRemove = new import_utils4.Emitter();
|
|
265
386
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* Avoid to change any data in the map manually.
|
|
387
|
+
* Gets the read-only map of all `Field` instances in this container.
|
|
388
|
+
* @returns {Map<string, Field<any>>} The collection of fields.
|
|
269
389
|
*/
|
|
270
390
|
get fields() {
|
|
271
391
|
return this._fields;
|
|
272
392
|
}
|
|
273
393
|
/**
|
|
274
|
-
*
|
|
275
|
-
* @param
|
|
276
|
-
* @returns `true` if the field exists, otherwise `false`.
|
|
394
|
+
* Creates an instance of Fields.
|
|
395
|
+
* @param {FieldRegistry} fieldRegistry - The registry used to create new `Field` instances.
|
|
277
396
|
*/
|
|
278
|
-
|
|
279
|
-
|
|
397
|
+
constructor(fieldRegistry) {
|
|
398
|
+
this._fieldRegistry = fieldRegistry;
|
|
280
399
|
}
|
|
281
400
|
/**
|
|
282
|
-
*
|
|
283
|
-
* @param name The
|
|
284
|
-
* @
|
|
285
|
-
* @returns The newly created `Field` instance.
|
|
401
|
+
* Checks if a field with the given name exists in the collection.
|
|
402
|
+
* @param {string} name The name of the field to check.
|
|
403
|
+
* @returns {boolean} `true` if the field exists, otherwise `false`.
|
|
286
404
|
*/
|
|
287
|
-
|
|
288
|
-
return this.
|
|
405
|
+
has(name) {
|
|
406
|
+
return this._fields.has(name);
|
|
289
407
|
}
|
|
290
408
|
/**
|
|
291
|
-
* Adds a pre-existing `Field` instance to the collection.
|
|
292
|
-
*
|
|
293
|
-
* @param field The `Field` instance to add.
|
|
294
|
-
* @returns The added `Field` instance
|
|
409
|
+
* Adds a pre-existing `Field` instance to the collection and fires the `onAdd` event.
|
|
410
|
+
* @template T - The specific `Field` type being added.
|
|
411
|
+
* @param {Field<any>} field - The `Field` instance to add.
|
|
412
|
+
* @returns {T} The added `Field` instance, cast to type `T`.
|
|
413
|
+
* @throws If a field with the same name already exists.
|
|
295
414
|
*/
|
|
296
415
|
add(field) {
|
|
297
|
-
(0,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.events.emit("created", {
|
|
302
|
-
fieldName: field.name,
|
|
416
|
+
(0, import_utils4.throwIf)(this.has(field.name), `Field with name '${field.name}' already exists`);
|
|
417
|
+
this._fields.set(field.name, field);
|
|
418
|
+
this.onAdd.emit({
|
|
419
|
+
name: field.name,
|
|
303
420
|
field
|
|
304
421
|
});
|
|
305
422
|
return field;
|
|
306
423
|
}
|
|
307
424
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* @
|
|
311
|
-
* @
|
|
425
|
+
* Creates a new `Field` instance of a specified type, adds it to the collection, and returns it.
|
|
426
|
+
* This is the primary factory method for creating fields within this container.
|
|
427
|
+
* @template T - The expected `Field` type to be returned.
|
|
428
|
+
* @param {string} typeName - The registered type name of the field to create (e.g., 'numeric', 'boolean').
|
|
429
|
+
* @param {string} name - The unique name for the new field.
|
|
430
|
+
* @param {*} initialValue - The initial value for the new field.
|
|
431
|
+
* @param {*} [options] - Optional configuration passed to the field's constructor.
|
|
432
|
+
* @returns {T} The newly created `Field` instance.
|
|
312
433
|
*/
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
434
|
+
create(typeName, name, initialValue, options) {
|
|
435
|
+
const Ctor = this._fieldRegistry.get(typeName);
|
|
436
|
+
const field = new Ctor(name, initialValue, options);
|
|
437
|
+
this.add(field);
|
|
438
|
+
return field;
|
|
316
439
|
}
|
|
317
440
|
/**
|
|
318
|
-
*
|
|
319
|
-
* @
|
|
320
|
-
* @param
|
|
321
|
-
* @
|
|
441
|
+
* Updates an existing field's value or creates a new one if it doesn't exist.
|
|
442
|
+
* @template T - The expected `Field` type.
|
|
443
|
+
* @param {string} typeName - The type name to use if a new field needs to be created.
|
|
444
|
+
* @param {string} name - The name of the field to update or create.
|
|
445
|
+
* @param {*} value - The new value to set.
|
|
446
|
+
* @param {*} [options] - Optional configuration, used only if a new field is created.
|
|
447
|
+
* @returns {T} The existing or newly created `Field` instance.
|
|
322
448
|
*/
|
|
323
|
-
upset(name, value) {
|
|
449
|
+
upset(typeName, name, value, options) {
|
|
324
450
|
if (this.has(name)) {
|
|
325
451
|
const field = this.get(name);
|
|
326
|
-
field.
|
|
452
|
+
field.value = value;
|
|
327
453
|
return field;
|
|
328
454
|
}
|
|
329
|
-
return this.create(name, value);
|
|
455
|
+
return this.create(typeName, name, value, options);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Retrieves a field by its name.
|
|
459
|
+
* @template T - The expected `Field` type to be returned.
|
|
460
|
+
* @param {string} name - The name of the field to retrieve.
|
|
461
|
+
* @returns {T} The `Field` instance.
|
|
462
|
+
* @throws If the field does not exist.
|
|
463
|
+
*/
|
|
464
|
+
get(name) {
|
|
465
|
+
(0, import_utils4.throwIf)(!this._fields.has(name), `Field with name '${name}' not exists`);
|
|
466
|
+
return this._fields.get(name);
|
|
330
467
|
}
|
|
331
468
|
/**
|
|
332
469
|
* Removes one or more fields from the collection.
|
|
333
470
|
* This method ensures that the `destroy` method of each removed field is called to clean up its resources.
|
|
334
|
-
* @param names A single name or an array of names to remove.
|
|
471
|
+
* @param {string| string[]} names A single name or an array of names to remove.
|
|
335
472
|
*/
|
|
336
473
|
remove(names) {
|
|
337
474
|
const namesToRemove = Array.isArray(names) ? names : [names];
|
|
338
|
-
const fieldsMap = new Map(this._fields.value);
|
|
339
475
|
const reallyRemoved = namesToRemove.filter((name) => {
|
|
340
|
-
const field =
|
|
476
|
+
const field = this._fields.get(name);
|
|
341
477
|
if (!field) {
|
|
342
478
|
return false;
|
|
343
479
|
}
|
|
344
480
|
field.destroy();
|
|
345
|
-
|
|
346
|
-
return true;
|
|
481
|
+
return this._fields.delete(name);
|
|
347
482
|
});
|
|
348
483
|
if (!reallyRemoved.length) {
|
|
349
484
|
return;
|
|
350
485
|
}
|
|
351
|
-
this.
|
|
352
|
-
this.events.emit("removed", { fieldNames: reallyRemoved });
|
|
486
|
+
this.onRemove.emit({ names: reallyRemoved });
|
|
353
487
|
}
|
|
354
488
|
/**
|
|
355
489
|
* Removes all fields from the collection, ensuring each is properly destroyed.
|
|
356
490
|
*/
|
|
357
491
|
clear() {
|
|
358
|
-
this.remove(Array.from(this._fields.
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Creates a serializable snapshot of the current state of all fields.
|
|
362
|
-
* @returns A plain JavaScript object representing the values of all fields.
|
|
363
|
-
*/
|
|
364
|
-
snapshot() {
|
|
365
|
-
const dump = {
|
|
366
|
-
__type: "Fields" /* fields */
|
|
367
|
-
};
|
|
368
|
-
this._fields.value.forEach((field, key) => dump[key] = field.val);
|
|
369
|
-
return dump;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Restores the state of the fields from a snapshot.
|
|
373
|
-
* It uses the `upset` logic to create or update fields based on the snapshot data.
|
|
374
|
-
* @param snapshot The snapshot object to load.
|
|
375
|
-
*/
|
|
376
|
-
hydrate(snapshot) {
|
|
377
|
-
for (let key in snapshot) {
|
|
378
|
-
if (key === "__type") {
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
this.upset(key, snapshot[key]);
|
|
382
|
-
}
|
|
492
|
+
this.remove(Array.from(this._fields.keys()));
|
|
383
493
|
}
|
|
384
494
|
};
|
|
385
495
|
|
|
386
|
-
// src/fields.ts
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
upsetNumber(name, value, options) {
|
|
393
|
-
if (this.has(name)) {
|
|
394
|
-
const field = this.getNumber(name);
|
|
395
|
-
field.set(value);
|
|
396
|
-
return field;
|
|
496
|
+
// src/mixins/with-boolean-fields.mixin.ts
|
|
497
|
+
function WithBooleanFields(Base) {
|
|
498
|
+
return class FieldsWithBoolean extends Base {
|
|
499
|
+
createBoolean(name, initialValue, options) {
|
|
500
|
+
return this.create(DefaultBooleanField.typeName, name, initialValue, options);
|
|
397
501
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
getNumber(name) {
|
|
401
|
-
const field = this.get(name);
|
|
402
|
-
(0, import_utils3.throwIf)(!(field instanceof NumberField), `wrong field type, field ${name} not a instance of NUmberField`);
|
|
403
|
-
return field;
|
|
404
|
-
}
|
|
405
|
-
create(name, initialValue) {
|
|
406
|
-
return this.add(new Field(name, initialValue));
|
|
407
|
-
}
|
|
408
|
-
upset(name, value) {
|
|
409
|
-
if (this.has(name)) {
|
|
410
|
-
const field = this.get(name);
|
|
411
|
-
field.set(value);
|
|
412
|
-
return field;
|
|
502
|
+
upsetBoolean(name, value, options) {
|
|
503
|
+
return this.upset(DefaultBooleanField.typeName, name, value, options);
|
|
413
504
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
505
|
+
getBoolean(name) {
|
|
506
|
+
return this.get(name);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/mixins/with-string-fields.mixin.ts
|
|
512
|
+
function WithStringFields(Base) {
|
|
513
|
+
return class FieldsWithString extends Base {
|
|
514
|
+
createString(name, initialValue, options) {
|
|
515
|
+
return this.create(DefaultStringField.typeName, name, initialValue, options);
|
|
516
|
+
}
|
|
517
|
+
upsetString(name, value, options) {
|
|
518
|
+
return this.upset(DefaultStringField.typeName, name, value, options);
|
|
519
|
+
}
|
|
520
|
+
getString(name) {
|
|
521
|
+
return this.get(name);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/mixins/with-numeric-fields.mixin.ts
|
|
527
|
+
function WithNumericFields(Base) {
|
|
528
|
+
return class FieldsWithNumeric extends Base {
|
|
529
|
+
createNumeric(name, initialValue, options) {
|
|
530
|
+
return this.create(DefaultNumericField.typeName, name, initialValue, options);
|
|
531
|
+
}
|
|
532
|
+
upsetNumeric(name, value, options) {
|
|
533
|
+
return this.upset(DefaultNumericField.typeName, name, value, options);
|
|
534
|
+
}
|
|
535
|
+
getNumeric(name) {
|
|
536
|
+
return this.get(name);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
}
|
|
421
540
|
|
|
422
|
-
// src/
|
|
423
|
-
|
|
541
|
+
// src/mixins/with-default-fields.mixin.ts
|
|
542
|
+
function WithDefaultFields(Base) {
|
|
543
|
+
return class FieldsWithDefault extends Base {
|
|
544
|
+
createDefault(name, initialValue, options) {
|
|
545
|
+
return this.create(DefaultField.typeName, name, initialValue, options);
|
|
546
|
+
}
|
|
547
|
+
upsetDefault(name, value, options) {
|
|
548
|
+
return this.upset(DefaultField.typeName, name, value, options);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/default-fields.ts
|
|
554
|
+
var DefaultFields = class extends WithBooleanFields(WithStringFields(WithNumericFields(WithDefaultFields(Fields)))) {
|
|
424
555
|
};
|
|
425
556
|
|
|
426
557
|
// src/field-tree.ts
|
|
427
|
-
var
|
|
428
|
-
var import_utils4 = require("@axi-engine/utils");
|
|
558
|
+
var import_utils5 = require("@axi-engine/utils");
|
|
429
559
|
var FieldTree = class _FieldTree {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
560
|
+
static typeName = "fieldTree";
|
|
561
|
+
typeName = _FieldTree.typeName;
|
|
562
|
+
/** @private The internal map storing child nodes (branches or leaves). */
|
|
563
|
+
_nodes = /* @__PURE__ */ new Map();
|
|
564
|
+
/** @private The factory used to create new child nodes. */
|
|
565
|
+
_factory;
|
|
566
|
+
/**
|
|
567
|
+
* Gets the collection of direct child nodes of this tree branch.
|
|
568
|
+
*/
|
|
569
|
+
get nodes() {
|
|
570
|
+
return this._nodes;
|
|
433
571
|
}
|
|
434
572
|
/**
|
|
435
|
-
*
|
|
436
|
-
*
|
|
573
|
+
* Creates an instance of FieldTree.
|
|
574
|
+
* @param {TreeNodeFactory} factory - A factory responsible for creating new nodes within the tree.
|
|
437
575
|
*/
|
|
438
|
-
|
|
439
|
-
|
|
576
|
+
constructor(factory) {
|
|
577
|
+
this._factory = factory;
|
|
440
578
|
}
|
|
441
579
|
/**
|
|
442
|
-
* Checks if a
|
|
443
|
-
* @
|
|
580
|
+
* Checks if a direct child node with the given name exists.
|
|
581
|
+
* @param {string} name - The name of the direct child node.
|
|
582
|
+
* @returns {boolean} `true` if the node exists, otherwise `false`.
|
|
583
|
+
*/
|
|
584
|
+
has(name) {
|
|
585
|
+
return this._nodes.has(name);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Checks if a node exists at a given path, traversing the tree.
|
|
589
|
+
* @param {PathType} path - The path to check (e.g., 'player/stats' or ['player', 'stats']).
|
|
590
|
+
* @returns {boolean} `true` if the entire path resolves to a node, otherwise `false`.
|
|
444
591
|
*/
|
|
445
592
|
hasPath(path) {
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
for (let i = 0; i < pathParts.length; i++) {
|
|
449
|
-
const part = pathParts[i];
|
|
450
|
-
const nextNode = currentNode._items.value.get(part);
|
|
451
|
-
if (!nextNode) {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
if (nextNode instanceof BaseFields) {
|
|
455
|
-
if (i === pathParts.length - 1) {
|
|
456
|
-
return true;
|
|
457
|
-
}
|
|
458
|
-
(0, import_utils4.throwIf)(
|
|
459
|
-
pathParts.length - i > 2,
|
|
460
|
-
`Path validation failed, full path: ${(0, import_utils4.ensurePathString)(path)}, has extra nodes after Fields placed at: ${(0, import_utils4.ensurePathString)(pathParts.slice(0, i + 1))}`
|
|
461
|
-
);
|
|
462
|
-
return nextNode.has(pathParts[i + 1]);
|
|
463
|
-
}
|
|
464
|
-
currentNode = nextNode;
|
|
465
|
-
}
|
|
466
|
-
return true;
|
|
593
|
+
const traversedPath = this.traversePath(path);
|
|
594
|
+
return traversedPath.branch.has(traversedPath.leafName);
|
|
467
595
|
}
|
|
468
596
|
/**
|
|
469
|
-
*
|
|
470
|
-
* @param name The name
|
|
471
|
-
* @
|
|
472
|
-
* @
|
|
597
|
+
* Adds a pre-existing node as a direct child of this tree branch.
|
|
598
|
+
* @param {string} name - The name to assign to the new child node.
|
|
599
|
+
* @param {TreeOrFieldsNode} node - The node instance to add.
|
|
600
|
+
* @returns {TreeOrFieldsNode} The added node.
|
|
601
|
+
* @throws If a node with the same name already exists.
|
|
473
602
|
*/
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
(
|
|
603
|
+
addNode(name, node) {
|
|
604
|
+
(0, import_utils5.throwIf)(this.has(name), `Can't add node with name: '${name}', node already exists`);
|
|
605
|
+
this._nodes.set(name, node);
|
|
477
606
|
return node;
|
|
478
607
|
}
|
|
479
608
|
/**
|
|
480
|
-
* Retrieves a child node
|
|
481
|
-
* @param name The name of the child node.
|
|
482
|
-
* @returns The
|
|
483
|
-
* @throws If
|
|
609
|
+
* Retrieves a direct child node by its name.
|
|
610
|
+
* @param {string} name - The name of the child node.
|
|
611
|
+
* @returns {TreeOrFieldsNode} The retrieved node.
|
|
612
|
+
* @throws If a node with the given name cannot be found.
|
|
484
613
|
*/
|
|
485
|
-
|
|
486
|
-
const node = this.
|
|
487
|
-
(0,
|
|
614
|
+
getNode(name) {
|
|
615
|
+
const node = this._nodes.get(name);
|
|
616
|
+
(0, import_utils5.throwIfEmpty)(node, `Can't find node with name '${name}'`);
|
|
488
617
|
return node;
|
|
489
618
|
}
|
|
490
619
|
/**
|
|
491
|
-
*
|
|
492
|
-
* @param
|
|
493
|
-
* @
|
|
494
|
-
* @
|
|
620
|
+
* Creates a new `FieldTree` (branch) node at the specified path.
|
|
621
|
+
* @param {PathType} path - The path where the new `FieldTree` should be created.
|
|
622
|
+
* @param {boolean} [createPath=false] - If `true`, any missing parent branches in the path will be created automatically.
|
|
623
|
+
* @returns {FieldTree} The newly created `FieldTree` instance.
|
|
624
|
+
* @throws If the path is invalid or a node already exists at the target location.
|
|
625
|
+
*/
|
|
626
|
+
createFieldTree(path, createPath) {
|
|
627
|
+
const traversedPath = this.traversePath(path, createPath);
|
|
628
|
+
return traversedPath.branch.addNode(traversedPath.leafName, this._factory.tree());
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Creates a new `Fields` (leaf) container at the specified path.
|
|
632
|
+
* @param {PathType} path - The path where the new `Fields` container should be created.
|
|
633
|
+
* @param {boolean} [createPath=false] - If `true`, any missing parent branches in the path will be created automatically.
|
|
634
|
+
* @returns {Fields} The newly created `Fields` instance.
|
|
635
|
+
* @throws If the path is invalid or a node already exists at the target location.
|
|
636
|
+
*/
|
|
637
|
+
createFields(path, createPath) {
|
|
638
|
+
const traversedPath = this.traversePath(path, createPath);
|
|
639
|
+
return traversedPath.branch.addNode(traversedPath.leafName, this._factory.fields());
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Retrieves a `FieldTree` (branch) node from a specified path.
|
|
643
|
+
* @param {PathType} path - The path to the `FieldTree` node.
|
|
644
|
+
* @returns {FieldTree} The `FieldTree` instance at the specified path.
|
|
645
|
+
* @throws If the path is invalid or the node at the path is not a `FieldTree`.
|
|
495
646
|
*/
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
647
|
+
getFieldTree(path) {
|
|
648
|
+
const traversedPath = this.traversePath(path);
|
|
649
|
+
const node = traversedPath.branch.getNode(traversedPath.leafName);
|
|
650
|
+
(0, import_utils5.throwIf)(
|
|
651
|
+
!(node instanceof _FieldTree),
|
|
652
|
+
`Node with name: ${traversedPath.leafName} by path: '${(0, import_utils5.ensurePathString)(path)}' should be instance of FieldTree`
|
|
653
|
+
);
|
|
499
654
|
return node;
|
|
500
655
|
}
|
|
501
656
|
/**
|
|
502
|
-
* Retrieves a
|
|
503
|
-
* @param
|
|
504
|
-
* @returns The
|
|
505
|
-
* @throws If
|
|
657
|
+
* Retrieves a `Fields` (leaf) container from a specified path.
|
|
658
|
+
* @param {PathType} path - The path to the `Fields` container.
|
|
659
|
+
* @returns {Fields} The `Fields` instance at the specified path.
|
|
660
|
+
* @throws If the path is invalid or the node at the path is not a `Fields` container.
|
|
506
661
|
*/
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
662
|
+
getFields(path) {
|
|
663
|
+
const traversedPath = this.traversePath(path);
|
|
664
|
+
const node = traversedPath.branch.getNode(traversedPath.leafName);
|
|
665
|
+
(0, import_utils5.throwIf)(
|
|
666
|
+
!(node instanceof Fields),
|
|
667
|
+
`Node with name: ${traversedPath.leafName} by path: '${(0, import_utils5.ensurePathString)(path)}' should be instance of Fields`
|
|
668
|
+
);
|
|
510
669
|
return node;
|
|
511
670
|
}
|
|
512
671
|
/**
|
|
513
|
-
*
|
|
514
|
-
* @param
|
|
515
|
-
* @returns The newly created `FieldTree` instance.
|
|
672
|
+
* Retrieves a `FieldTree` at the specified path. If it or any part of the path doesn't exist, it will be created.
|
|
673
|
+
* @param {PathType} path - The path to the `FieldTree` node.
|
|
674
|
+
* @returns {FieldTree} The existing or newly created `FieldTree` instance.
|
|
516
675
|
*/
|
|
517
|
-
|
|
518
|
-
|
|
676
|
+
getOrCreateFieldTree(path) {
|
|
677
|
+
const traversedPath = this.traversePath(path, true);
|
|
678
|
+
return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFieldTree(traversedPath.leafName) : traversedPath.branch.createFieldTree(traversedPath.leafName);
|
|
519
679
|
}
|
|
520
680
|
/**
|
|
521
|
-
*
|
|
522
|
-
* @param
|
|
523
|
-
* @returns The newly created `Fields` instance.
|
|
681
|
+
* Retrieves a `Fields` container at the specified path. If it or any part of the path doesn't exist, it will be created.
|
|
682
|
+
* @param {PathType} path - The path to the `Fields` container.
|
|
683
|
+
* @returns {Fields} The existing or newly created `Fields` instance.
|
|
524
684
|
*/
|
|
525
|
-
|
|
526
|
-
|
|
685
|
+
getOrCreateFields(path) {
|
|
686
|
+
const traversedPath = this.traversePath(path, true);
|
|
687
|
+
return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFields(traversedPath.leafName) : traversedPath.branch.createFields(traversedPath.leafName);
|
|
527
688
|
}
|
|
528
689
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
690
|
+
* @private
|
|
691
|
+
* Navigates the tree to the parent of a target node.
|
|
692
|
+
* This is the core traversal logic for all path-based operations.
|
|
693
|
+
* @param {PathType} path - The full path to the target node.
|
|
694
|
+
* @param {boolean} [createPath=false] - If `true`, creates missing `FieldTree` branches along the path.
|
|
695
|
+
* @returns {{branch: FieldTree, leafName: string}} An object containing the final branch (parent node) and the name of the leaf (target node).
|
|
696
|
+
* @throws If the path is empty, invalid, or contains a `Fields` container as an intermediate segment.
|
|
697
|
+
*/
|
|
698
|
+
traversePath(path, createPath) {
|
|
699
|
+
const pathArr = (0, import_utils5.ensurePathArray)(path);
|
|
700
|
+
(0, import_utils5.throwIfEmpty)(pathArr, "The path is empty");
|
|
701
|
+
const leafName = pathArr.pop();
|
|
702
|
+
let currentNode = this;
|
|
703
|
+
for (const pathPart of pathArr) {
|
|
704
|
+
let node;
|
|
705
|
+
if (currentNode.has(pathPart)) {
|
|
706
|
+
node = currentNode.getNode(pathPart);
|
|
707
|
+
} else {
|
|
708
|
+
if (createPath) {
|
|
709
|
+
node = currentNode.createFieldTree(pathPart);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
(0, import_utils5.throwIfEmpty)(node, `Can't find node with name ${pathPart} by path parsing: ${(0, import_utils5.ensurePathString)(path)}`);
|
|
713
|
+
(0, import_utils5.throwIf)(node instanceof Fields, `Node with name ${pathPart} should be instance of FieldTree`);
|
|
714
|
+
currentNode = node;
|
|
715
|
+
}
|
|
716
|
+
return { branch: currentNode, leafName };
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// src/field-tree-node-factory.ts
|
|
721
|
+
var DefaultTreeNodeFactory = class {
|
|
722
|
+
constructor(fieldRegistry) {
|
|
723
|
+
this.fieldRegistry = fieldRegistry;
|
|
724
|
+
}
|
|
725
|
+
fields = () => new DefaultFields(this.fieldRegistry);
|
|
726
|
+
tree = () => new FieldTree(this);
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/serializer/policies/clamp-policy-serializer-handler.ts
|
|
730
|
+
var ClampPolicySerializerHandler = class {
|
|
731
|
+
snapshot(policy) {
|
|
732
|
+
return { min: policy.min, max: policy.max };
|
|
733
|
+
}
|
|
734
|
+
hydrate(data) {
|
|
735
|
+
return new ClampPolicy(data.min, data.max);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/serializer/policies/clamp-max-policy-serializer-handler.ts
|
|
740
|
+
var ClampMaxPolicySerializerHandler = class {
|
|
741
|
+
snapshot(policy) {
|
|
742
|
+
return { max: policy.max };
|
|
743
|
+
}
|
|
744
|
+
hydrate(data) {
|
|
745
|
+
return new ClampMaxPolicy(data.max);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// src/serializer/policies/clamp-min-policy-serializer-handler.ts
|
|
750
|
+
var ClampMinPolicySerializerHandler = class {
|
|
751
|
+
snapshot(policy) {
|
|
752
|
+
return { min: policy.min };
|
|
753
|
+
}
|
|
754
|
+
hydrate(data) {
|
|
755
|
+
return new ClampMinPolicy(data.min);
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
// src/serializer/policy-serializer.ts
|
|
760
|
+
var import_utils6 = require("@axi-engine/utils");
|
|
761
|
+
var PolicySerializer = class {
|
|
762
|
+
handlers = /* @__PURE__ */ new Map();
|
|
763
|
+
register(policyId, handler) {
|
|
764
|
+
(0, import_utils6.throwIf)(this.handlers.has(policyId), `A handler for policy ID '${policyId}' is already registered.`);
|
|
765
|
+
this.handlers.set(policyId, handler);
|
|
766
|
+
return this;
|
|
767
|
+
}
|
|
768
|
+
clearHandlers() {
|
|
769
|
+
this.handlers.clear();
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Creates a serializable snapshot of a policy instance.
|
|
773
|
+
* The snapshot includes the policy's state and a `__type` identifier.
|
|
774
|
+
* @param policy The policy instance to snapshot.
|
|
775
|
+
* @returns A plain object ready for JSON serialization.
|
|
776
|
+
* @throws If no handler is registered for the policy's ID.
|
|
777
|
+
*/
|
|
778
|
+
snapshot(policy) {
|
|
779
|
+
const handler = this.handlers.get(policy.id);
|
|
780
|
+
(0, import_utils6.throwIfEmpty)(handler, `No serializer handler registered for policy ID: '${policy.id}'`);
|
|
781
|
+
const data = handler.snapshot(policy);
|
|
782
|
+
return {
|
|
783
|
+
__type: policy.id,
|
|
784
|
+
...data
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Restores a policy instance from its snapshot representation.
|
|
789
|
+
* @param snapshot The plain object snapshot, which must contain a `__type` property.
|
|
790
|
+
* @returns A new, fully functional policy instance.
|
|
791
|
+
* @throws If the snapshot is invalid or no handler is registered for its `__type`.
|
|
792
|
+
*/
|
|
793
|
+
hydrate(snapshot) {
|
|
794
|
+
const typeId = snapshot?.__type;
|
|
795
|
+
(0, import_utils6.throwIfEmpty)(typeId, 'Invalid policy snapshot: missing "__type" identifier.');
|
|
796
|
+
const handler = this.handlers.get(typeId);
|
|
797
|
+
(0, import_utils6.throwIfEmpty)(handler, `No serializer handler registered for policy ID: '${typeId}'`);
|
|
798
|
+
const { __type, ...data } = snapshot;
|
|
799
|
+
return handler.hydrate(data);
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// src/serializer/field-serializer.ts
|
|
804
|
+
var import_utils7 = require("@axi-engine/utils");
|
|
805
|
+
var FieldSerializer = class {
|
|
806
|
+
/**
|
|
807
|
+
* Creates an instance of FieldSerializer.
|
|
808
|
+
* @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
|
|
809
|
+
* @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
|
|
532
810
|
*/
|
|
533
|
-
|
|
534
|
-
|
|
811
|
+
constructor(fieldRegistry, policySerializer) {
|
|
812
|
+
this.fieldRegistry = fieldRegistry;
|
|
813
|
+
this.policySerializer = policySerializer;
|
|
535
814
|
}
|
|
536
815
|
/**
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
* @
|
|
540
|
-
* @
|
|
816
|
+
* Creates a serializable snapshot of a Field instance.
|
|
817
|
+
* The snapshot includes the field's type, name, current value, and the state of all its policies.
|
|
818
|
+
* @param {Field<any>} field - The Field instance to serialize.
|
|
819
|
+
* @returns {FieldSnapshot} A plain object ready for JSON serialization.
|
|
541
820
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
821
|
+
snapshot(field) {
|
|
822
|
+
let snapshot = {
|
|
823
|
+
__type: field.typeName,
|
|
824
|
+
name: field.name,
|
|
825
|
+
value: field.value
|
|
826
|
+
};
|
|
827
|
+
if (!field.policies.isEmpty()) {
|
|
828
|
+
const serializedPolicies = [];
|
|
829
|
+
field.policies.items.forEach((policy) => serializedPolicies.push(this.policySerializer.snapshot(policy)));
|
|
830
|
+
snapshot.policies = serializedPolicies;
|
|
548
831
|
}
|
|
549
|
-
return
|
|
832
|
+
return snapshot;
|
|
550
833
|
}
|
|
551
834
|
/**
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
* @param
|
|
556
|
-
* @returns
|
|
835
|
+
* Restores a Field instance from its snapshot representation.
|
|
836
|
+
* It uses the `__type` property to find the correct constructor and hydrates
|
|
837
|
+
* the field with its value and all its policies.
|
|
838
|
+
* @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
|
|
839
|
+
* @returns {Field<any>} A new, fully functional Field instance.
|
|
840
|
+
* @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
|
|
557
841
|
*/
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
842
|
+
hydrate(snapshot) {
|
|
843
|
+
const fieldType = snapshot.__type;
|
|
844
|
+
(0, import_utils7.throwIfEmpty)(fieldType, 'Invalid field snapshot: missing "__type" identifier.');
|
|
845
|
+
const Ctor = this.fieldRegistry.get(fieldType);
|
|
846
|
+
let policies;
|
|
847
|
+
if (!(0, import_utils7.isNullOrUndefined)(snapshot.policies)) {
|
|
848
|
+
policies = [];
|
|
849
|
+
snapshot.policies.forEach((p) => policies.push(this.policySerializer.hydrate(p)));
|
|
850
|
+
}
|
|
851
|
+
return new Ctor(snapshot.name, snapshot.value, { policies });
|
|
563
852
|
}
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/serializer/fields-serializer.ts
|
|
856
|
+
var FieldsSerializer = class {
|
|
564
857
|
/**
|
|
565
|
-
* Creates
|
|
566
|
-
* @param
|
|
567
|
-
* @param
|
|
568
|
-
* @returns The newly created `NumberField` instance.
|
|
858
|
+
* Creates an instance of FieldsSerializer.
|
|
859
|
+
* @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
|
|
860
|
+
* @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
|
|
569
861
|
*/
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
862
|
+
constructor(fieldRegistry, policySerializer) {
|
|
863
|
+
this.fieldRegistry = fieldRegistry;
|
|
864
|
+
this.policySerializer = policySerializer;
|
|
865
|
+
this.fieldSerializer = new FieldSerializer(this.fieldRegistry, this.policySerializer);
|
|
574
866
|
}
|
|
575
867
|
/**
|
|
576
|
-
*
|
|
577
|
-
* @
|
|
578
|
-
|
|
868
|
+
* An internal instance of FieldSerializer to handle individual fields.
|
|
869
|
+
* @private
|
|
870
|
+
*/
|
|
871
|
+
fieldSerializer;
|
|
872
|
+
/**
|
|
873
|
+
* Creates a serializable snapshot of a `Fields` container.
|
|
874
|
+
*
|
|
875
|
+
* The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
|
|
876
|
+
* for each `Field` within the container.
|
|
877
|
+
* @param {Fields} fields - The `Fields` instance to serialize.
|
|
878
|
+
* @returns {FieldsSnapshot} A plain object ready for JSON serialization.
|
|
579
879
|
*/
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
880
|
+
snapshot(fields) {
|
|
881
|
+
const res = {
|
|
882
|
+
__type: fields.typeName
|
|
883
|
+
};
|
|
884
|
+
fields.fields.forEach((field) => res[field.name] = this.fieldSerializer.snapshot(field));
|
|
885
|
+
return res;
|
|
584
886
|
}
|
|
585
887
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
888
|
+
* Restores a `Fields` container instance from its snapshot representation.
|
|
889
|
+
*
|
|
890
|
+
* **Limitation:** This method is currently hardcoded to always create an instance of `DefaultFields`.
|
|
891
|
+
* It iterates through the field snapshots and hydrates them individually, adding them to the new container.
|
|
892
|
+
* @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
|
|
893
|
+
* @returns {DefaultFields} A new `DefaultFields` instance populated with the restored fields.
|
|
589
894
|
*/
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
895
|
+
hydrate(snapshot) {
|
|
896
|
+
const { __type, ...fieldsData } = snapshot;
|
|
897
|
+
const fields = new DefaultFields(this.fieldRegistry);
|
|
898
|
+
for (const fieldName in fieldsData) {
|
|
899
|
+
const fieldSnapshot = fieldsData[fieldName];
|
|
900
|
+
const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
|
|
901
|
+
fields.add(restoredField);
|
|
902
|
+
}
|
|
903
|
+
return fields;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
// src/serializer/field-tree-serializer.ts
|
|
908
|
+
var import_utils8 = require("@axi-engine/utils");
|
|
909
|
+
var FieldTreeSerializer = class {
|
|
910
|
+
constructor(fieldTreeNodeFactory, fieldsSerializer) {
|
|
911
|
+
this.fieldTreeNodeFactory = fieldTreeNodeFactory;
|
|
912
|
+
this.fieldsSerializer = fieldsSerializer;
|
|
594
913
|
}
|
|
595
914
|
/**
|
|
596
915
|
* Creates a serializable snapshot of the entire tree and its contained fields.
|
|
597
916
|
* @returns A plain JavaScript object representing the complete state managed by this tree.
|
|
598
917
|
*/
|
|
599
|
-
snapshot() {
|
|
600
|
-
const
|
|
601
|
-
__type:
|
|
918
|
+
snapshot(tree) {
|
|
919
|
+
const res = {
|
|
920
|
+
__type: tree.typeName
|
|
602
921
|
};
|
|
603
|
-
|
|
604
|
-
|
|
922
|
+
tree.nodes.forEach((node, key) => {
|
|
923
|
+
if (node.typeName === tree.typeName) {
|
|
924
|
+
res[key] = this.snapshot(node);
|
|
925
|
+
} else if (node.typeName === Fields.typeName) {
|
|
926
|
+
res[key] = this.fieldsSerializer.snapshot(node);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
return res;
|
|
605
930
|
}
|
|
606
931
|
/**
|
|
607
932
|
* Restores the state of the tree from a snapshot.
|
|
@@ -609,61 +934,44 @@ var FieldTree = class _FieldTree {
|
|
|
609
934
|
* @param snapshot The snapshot object to load.
|
|
610
935
|
*/
|
|
611
936
|
hydrate(snapshot) {
|
|
612
|
-
|
|
613
|
-
|
|
937
|
+
const { __type, ...nodes } = snapshot;
|
|
938
|
+
const tree = this.fieldTreeNodeFactory.tree();
|
|
939
|
+
for (const key in nodes) {
|
|
940
|
+
const nodeData = nodes[key];
|
|
941
|
+
if ((0, import_utils8.isString)(nodeData)) {
|
|
614
942
|
continue;
|
|
615
943
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (type === "Fields" /* fields */) {
|
|
621
|
-
node = this.createFields(key);
|
|
622
|
-
} else if (type === "FieldTree" /* fieldTree */) {
|
|
623
|
-
node = this.createFieldTree(key);
|
|
624
|
-
} else {
|
|
625
|
-
console.warn(`Node '${key}' in snapshot has no __type metadata. Skipping.`);
|
|
626
|
-
}
|
|
944
|
+
if (nodeData.__type === FieldTree.typeName) {
|
|
945
|
+
tree.addNode(key, this.hydrate(nodeData));
|
|
946
|
+
} else {
|
|
947
|
+
tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
|
|
627
948
|
}
|
|
628
|
-
node?.hydrate(field);
|
|
629
949
|
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* @private
|
|
633
|
-
* Generic internal method for creating and adding a new node to the tree.
|
|
634
|
-
* @param name The name of the node to create.
|
|
635
|
-
* @param ctor The constructor for the node type (e.g., `FieldTree` or `Fields`).
|
|
636
|
-
* @returns The newly created node instance.
|
|
637
|
-
*/
|
|
638
|
-
createNode(name, ctor) {
|
|
639
|
-
const currentItems = this._items.value;
|
|
640
|
-
(0, import_utils4.throwIf)(currentItems.has(name), `Can't create node with name: '${name}', node already exists`);
|
|
641
|
-
const res = new ctor();
|
|
642
|
-
const newItems = new Map(currentItems);
|
|
643
|
-
newItems.set(name, res);
|
|
644
|
-
this._items.value = newItems;
|
|
645
|
-
this.events.emit("created", {
|
|
646
|
-
type: "created",
|
|
647
|
-
name,
|
|
648
|
-
path: [],
|
|
649
|
-
// todo: need to decide how to pass full path
|
|
650
|
-
node: res
|
|
651
|
-
});
|
|
652
|
-
return res;
|
|
950
|
+
return tree;
|
|
653
951
|
}
|
|
654
952
|
};
|
|
655
953
|
// Annotate the CommonJS export names for ESM import in node:
|
|
656
954
|
0 && (module.exports = {
|
|
657
|
-
BaseFields,
|
|
658
955
|
ClampMaxPolicy,
|
|
956
|
+
ClampMaxPolicySerializerHandler,
|
|
659
957
|
ClampMinPolicy,
|
|
958
|
+
ClampMinPolicySerializerHandler,
|
|
660
959
|
ClampPolicy,
|
|
661
|
-
|
|
960
|
+
ClampPolicySerializerHandler,
|
|
961
|
+
DefaultBooleanField,
|
|
962
|
+
DefaultField,
|
|
963
|
+
DefaultFields,
|
|
964
|
+
DefaultNumericField,
|
|
965
|
+
DefaultStringField,
|
|
966
|
+
DefaultTreeNodeFactory,
|
|
967
|
+
FieldRegistry,
|
|
968
|
+
FieldSerializer,
|
|
662
969
|
FieldTree,
|
|
970
|
+
FieldTreeSerializer,
|
|
663
971
|
Fields,
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
972
|
+
FieldsSerializer,
|
|
973
|
+
Policies,
|
|
974
|
+
PolicySerializer,
|
|
667
975
|
clampMaxPolicy,
|
|
668
976
|
clampMinPolicy,
|
|
669
977
|
clampPolicy
|