@axi-engine/fields 0.3.9 → 0.3.11

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/dist/index.mjs CHANGED
@@ -408,6 +408,9 @@ var Fields = class _Fields {
408
408
  */
409
409
  remove(names) {
410
410
  const namesToRemove = Array.isArray(names) ? names : [names];
411
+ if (!namesToRemove.length) {
412
+ return;
413
+ }
411
414
  const reallyRemoved = namesToRemove.filter((name) => {
412
415
  const field = this._fields.get(name);
413
416
  if (!field) {
@@ -764,7 +767,7 @@ var CoreTreeNodeFactory = class extends CoreFieldsFactory {
764
767
  }
765
768
  };
766
769
 
767
- // src/serializer/policies/clamp-policy-serializer-handler.ts
770
+ // src/serializers/policies/clamp-policy-serializer-handler.ts
768
771
  var ClampPolicySerializerHandler = class {
769
772
  snapshot(policy) {
770
773
  return { min: policy.min, max: policy.max };
@@ -774,7 +777,7 @@ var ClampPolicySerializerHandler = class {
774
777
  }
775
778
  };
776
779
 
777
- // src/serializer/policies/clamp-max-policy-serializer-handler.ts
780
+ // src/serializers/policies/clamp-max-policy-serializer-handler.ts
778
781
  var ClampMaxPolicySerializerHandler = class {
779
782
  snapshot(policy) {
780
783
  return { max: policy.max };
@@ -784,7 +787,7 @@ var ClampMaxPolicySerializerHandler = class {
784
787
  }
785
788
  };
786
789
 
787
- // src/serializer/policies/clamp-min-policy-serializer-handler.ts
790
+ // src/serializers/policies/clamp-min-policy-serializer-handler.ts
788
791
  var ClampMinPolicySerializerHandler = class {
789
792
  snapshot(policy) {
790
793
  return { min: policy.min };
@@ -794,7 +797,7 @@ var ClampMinPolicySerializerHandler = class {
794
797
  }
795
798
  };
796
799
 
797
- // src/serializer/policy-serializer.ts
800
+ // src/serializers/policy-serializer.ts
798
801
  import { Registry as Registry2, throwIfEmpty as throwIfEmpty2 } from "@axi-engine/utils";
799
802
  var PolicySerializer = class {
800
803
  handlers = new Registry2();
@@ -831,9 +834,9 @@ var PolicySerializer = class {
831
834
  }
832
835
  };
833
836
 
834
- // src/serializer/field-serializer.ts
837
+ // src/serializers/field-hydrator.ts
835
838
  import { isNullOrUndefined as isNullOrUndefined2, throwIfEmpty as throwIfEmpty3 } from "@axi-engine/utils";
836
- var FieldSerializer = class {
839
+ var FieldHydrator = class {
837
840
  /**
838
841
  * Creates an instance of FieldSerializer.
839
842
  * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
@@ -843,6 +846,47 @@ var FieldSerializer = class {
843
846
  this.fieldRegistry = fieldRegistry;
844
847
  this.policySerializer = policySerializer;
845
848
  }
849
+ /**
850
+ * Restores a Field instance from its snapshot representation.
851
+ * It uses the `__type` property to find the correct constructor and hydrates
852
+ * the field with its value and all its policies.
853
+ * @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
854
+ * @returns {Field<any>} A new, fully functional Field instance.
855
+ * @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
856
+ */
857
+ hydrate(snapshot) {
858
+ const fieldType = snapshot.__type;
859
+ throwIfEmpty3(fieldType, 'Invalid field snapshot: missing "__type" identifier.');
860
+ const Ctor = this.fieldRegistry.getOrThrow(fieldType);
861
+ let policies = this.hydratePolicies(snapshot);
862
+ return new Ctor(snapshot.name, snapshot.value, { policies });
863
+ }
864
+ /**
865
+ * Updates an existing Field instance with data from a snapshot.
866
+ *
867
+ * This method modifies the field in-place, preserving the object reference.
868
+ * It updates the field's value and completely replaces its current policies
869
+ * with the ones defined in the snapshot.
870
+ *
871
+ * @param {Field<any>} field - The existing Field instance to update.
872
+ * @param {FieldSnapshot} snapshot - The snapshot containing the new state.
873
+ */
874
+ patch(field, snapshot) {
875
+ field.policies.clear();
876
+ const policies = this.hydratePolicies(snapshot);
877
+ policies?.forEach((p) => field.policies.add(p));
878
+ field.value = snapshot.value;
879
+ }
880
+ hydratePolicies(snapshot) {
881
+ return isNullOrUndefined2(snapshot.policies) ? void 0 : snapshot.policies.map((p) => this.policySerializer.hydrate(p));
882
+ }
883
+ };
884
+
885
+ // src/serializers/field-snapshotter.ts
886
+ var FieldSnapshotter = class {
887
+ constructor(policySerializer) {
888
+ this.policySerializer = policySerializer;
889
+ }
846
890
  /**
847
891
  * Creates a serializable snapshot of a Field instance.
848
892
  * The snapshot includes the field's type, name, current value, and the state of all its policies.
@@ -856,58 +900,22 @@ var FieldSerializer = class {
856
900
  value: field.value
857
901
  };
858
902
  if (!field.policies.isEmpty()) {
859
- const serializedPolicies = [];
860
- field.policies.items.forEach((policy) => serializedPolicies.push(this.policySerializer.snapshot(policy)));
861
- snapshot.policies = serializedPolicies;
903
+ snapshot.policies = Array.from(field.policies.items.values()).map((policy) => this.policySerializer.snapshot(policy));
862
904
  }
863
905
  return snapshot;
864
906
  }
865
- /**
866
- * Restores a Field instance from its snapshot representation.
867
- * It uses the `__type` property to find the correct constructor and hydrates
868
- * the field with its value and all its policies.
869
- * @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
870
- * @returns {Field<any>} A new, fully functional Field instance.
871
- * @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
872
- */
873
- hydrate(snapshot) {
874
- const fieldType = snapshot.__type;
875
- throwIfEmpty3(fieldType, 'Invalid field snapshot: missing "__type" identifier.');
876
- const Ctor = this.fieldRegistry.getOrThrow(fieldType);
877
- let policies;
878
- if (!isNullOrUndefined2(snapshot.policies)) {
879
- policies = [];
880
- snapshot.policies.forEach((p) => policies.push(this.policySerializer.hydrate(p)));
881
- }
882
- return new Ctor(snapshot.name, snapshot.value, { policies });
883
- }
884
907
  };
885
908
 
886
- // src/serializer/fields-serializer.ts
887
- var FieldsSerializer = class {
909
+ // src/serializers/fields-hydrator.ts
910
+ var FieldsHydrator = class {
888
911
  /**
889
912
  * Creates an instance of FieldsSerializer.
890
913
  * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
891
- * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
914
+ * @param {FieldHydrator} fieldHydrator - A hydrator of field instances.
892
915
  */
893
- constructor(fieldsFactory, fieldSerializer) {
916
+ constructor(fieldsFactory, fieldHydrator) {
894
917
  this.fieldsFactory = fieldsFactory;
895
- this.fieldSerializer = fieldSerializer;
896
- }
897
- /**
898
- * Creates a serializable snapshot of a `Fields` container.
899
- *
900
- * The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
901
- * for each `Field` within the container.
902
- * @param {Fields} fields - The `Fields` instance to serialize.
903
- * @returns {FieldsSnapshot} A plain object ready for JSON serialization.
904
- */
905
- snapshot(fields) {
906
- const res = {
907
- __type: fields.typeName
908
- };
909
- fields.fields.forEach((field) => res[field.name] = this.fieldSerializer.snapshot(field));
910
- return res;
918
+ this.fieldHydrator = fieldHydrator;
911
919
  }
912
920
  /**
913
921
  * Restores a `Fields` container instance from its snapshot representation.
@@ -921,37 +929,81 @@ var FieldsSerializer = class {
921
929
  const fields = this.fieldsFactory.fields();
922
930
  for (const fieldName in fieldsData) {
923
931
  const fieldSnapshot = fieldsData[fieldName];
924
- const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
932
+ const restoredField = this.fieldHydrator.hydrate(fieldSnapshot);
925
933
  fields.add(restoredField);
926
934
  }
927
935
  return fields;
928
936
  }
937
+ /**
938
+ * Synchronizes an existing `Fields` container with a snapshot.
939
+ *
940
+ * This method performs a "smart update":
941
+ * 1. **Removes** fields from the container that are missing in the snapshot.
942
+ * 2. **Patches** existing fields in-place using {@link FieldHydrator.patch}, preserving object references.
943
+ * 3. **Creates** (hydrates) and adds new fields that exist in the snapshot but not in the container.
944
+ *
945
+ * @param {Fields} fields - The target `Fields` container to update.
946
+ * @param {FieldsSnapshot} snapshot - The source snapshot containing the desired state.
947
+ */
948
+ patch(fields, snapshot) {
949
+ const { __type, ...fieldsData } = snapshot;
950
+ const snapshotKeys = new Set(Object.keys(fieldsData));
951
+ const fieldsToRemove = Array.from(fields.fields.values()).filter((f) => !snapshotKeys.has(f.name)).map((f) => f.name);
952
+ fields.remove(fieldsToRemove);
953
+ for (const fieldName in fieldsData) {
954
+ const fieldSnapshot = fieldsData[fieldName];
955
+ if (fields.has(fieldName)) {
956
+ const existingField = fields.get(fieldName);
957
+ this.fieldHydrator.patch(existingField, fieldSnapshot);
958
+ } else {
959
+ const newField = this.fieldHydrator.hydrate(fieldSnapshot);
960
+ fields.add(newField);
961
+ }
962
+ }
963
+ }
929
964
  };
930
965
 
931
- // src/serializer/field-tree-serializer.ts
932
- import { isString } from "@axi-engine/utils";
933
- var FieldTreeSerializer = class {
934
- constructor(fieldTreeNodeFactory, fieldsSerializer) {
935
- this.fieldTreeNodeFactory = fieldTreeNodeFactory;
936
- this.fieldsSerializer = fieldsSerializer;
966
+ // src/serializers/fields-snapshotter.ts
967
+ var FieldsSnapshotter = class {
968
+ /**
969
+ * Creates an instance of FieldsSnapshotter.
970
+ * @param {FieldSnapshotter} fieldSnapshotter - A serializer of field instances.
971
+ */
972
+ constructor(fieldSnapshotter) {
973
+ this.fieldSnapshotter = fieldSnapshotter;
937
974
  }
938
975
  /**
939
- * Creates a serializable snapshot of the entire tree and its contained fields.
940
- * @returns A plain JavaScript object representing the complete state managed by this tree.
976
+ * Creates a serializable snapshot of a `Fields` container.
977
+ *
978
+ * The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
979
+ * for each `Field` within the container.
980
+ * @param {Fields} fields - The `Fields` instance to serialize.
981
+ * @returns {FieldsSnapshot} A plain object ready for JSON serialization.
941
982
  */
942
- snapshot(tree) {
983
+ snapshot(fields) {
943
984
  const res = {
944
- __type: tree.typeName
985
+ __type: fields.typeName
945
986
  };
946
- tree.nodes.forEach((node, key) => {
947
- if (node.typeName === tree.typeName) {
948
- res[key] = this.snapshot(node);
949
- } else if (node.typeName === Fields.typeName) {
950
- res[key] = this.fieldsSerializer.snapshot(node);
951
- }
952
- });
987
+ fields.fields.forEach((field) => res[field.name] = this.fieldSnapshotter.snapshot(field));
953
988
  return res;
954
989
  }
990
+ };
991
+
992
+ // src/serializers/field-tree-hydrator.ts
993
+ import { isString, throwError } from "@axi-engine/utils";
994
+ var FieldTreeHydrator = class {
995
+ _factory;
996
+ _fieldsHydrator;
997
+ get factory() {
998
+ return this._factory;
999
+ }
1000
+ get fieldsHydrator() {
1001
+ return this._fieldsHydrator;
1002
+ }
1003
+ constructor(fieldTreeNodeFactory, fieldsHydrator) {
1004
+ this._factory = fieldTreeNodeFactory;
1005
+ this._fieldsHydrator = fieldsHydrator;
1006
+ }
955
1007
  /**
956
1008
  * Restores the state of the tree from a snapshot.
957
1009
  * It intelligently creates missing nodes based on `__type` metadata and delegates hydration to child nodes.
@@ -959,20 +1011,99 @@ var FieldTreeSerializer = class {
959
1011
  */
960
1012
  hydrate(snapshot) {
961
1013
  const { __type, ...nodes } = snapshot;
962
- const tree = this.fieldTreeNodeFactory.tree();
1014
+ const tree = this._factory.tree();
963
1015
  for (const key in nodes) {
964
1016
  const nodeData = nodes[key];
965
1017
  if (isString(nodeData)) {
966
1018
  continue;
967
1019
  }
968
- if (nodeData.__type === FieldTree.typeName) {
969
- tree.addNode(key, this.hydrate(nodeData));
970
- } else if (nodeData.__type === Fields.typeName) {
971
- tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
972
- }
1020
+ this.addNodeFromSnapshot(tree, key, nodeData);
973
1021
  }
974
1022
  return tree;
975
1023
  }
1024
+ /**
1025
+ * Synchronizes an existing `FieldTree` branch with a snapshot.
1026
+ *
1027
+ * This method performs a recursive update to match the tree state with the provided snapshot:
1028
+ * 1. **Removes** child nodes that are present in the tree but missing in the snapshot.
1029
+ * 2. **Creates** new nodes that are present in the snapshot but missing in the tree.
1030
+ * 3. **Replaces** nodes if their type has changed (e.g., replacing a `Fields` leaf with a `FieldTree` branch).
1031
+ * 4. **Patches** existing matching nodes in-place (recursively).
1032
+ *
1033
+ * @param {FieldTree} tree - The target tree instance to update.
1034
+ * @param {FieldTreeSnapshot} snapshot - The source snapshot containing the desired state.
1035
+ */
1036
+ patch(tree, snapshot) {
1037
+ const { __type, ...nodes } = snapshot;
1038
+ const snapshotKeys = new Set(Object.keys(nodes));
1039
+ const nodesToRemove = Array.from(tree.nodes.keys()).filter((key) => !snapshotKeys.has(key));
1040
+ tree.removeNode(nodesToRemove);
1041
+ for (const key in nodes) {
1042
+ const nodeData = nodes[key];
1043
+ if (isString(nodeData)) {
1044
+ continue;
1045
+ }
1046
+ if (!tree.has(key)) {
1047
+ this.addNodeFromSnapshot(tree, key, nodeData);
1048
+ } else {
1049
+ const treeNode = tree.getNode(key);
1050
+ if (treeNode.typeName !== nodeData.__type) {
1051
+ tree.removeNode(key);
1052
+ this.addNodeFromSnapshot(tree, key, nodeData);
1053
+ } else {
1054
+ if (nodeData.__type === FieldTree.typeName) {
1055
+ this.patch(treeNode, nodeData);
1056
+ } else if (nodeData.__type === Fields.typeName) {
1057
+ this.fieldsHydrator.patch(treeNode, nodeData);
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Helper method to instantiate and add a new node to the tree based on the snapshot type.
1065
+ *
1066
+ * It determines whether to create a nested `FieldTree` or a `Fields` container
1067
+ * by inspecting the `__type` property of the snapshot, hydrates it, and attaches it to the parent tree.
1068
+ *
1069
+ * @param {FieldTree} tree - The parent tree instance where the new node will be added.
1070
+ * @param {string} key - The name (key) for the new node.
1071
+ * @param {FieldsSnapshot | FieldTreeSnapshot} snapshot - The source snapshot data.
1072
+ * @throws If the snapshot contains an unsupported or unknown `__type`.
1073
+ */
1074
+ addNodeFromSnapshot(tree, key, snapshot) {
1075
+ if (snapshot.__type === FieldTree.typeName) {
1076
+ tree.addNode(key, this.hydrate(snapshot));
1077
+ } else if (snapshot.__type === Fields.typeName) {
1078
+ tree.addNode(key, this.fieldsHydrator.hydrate(snapshot));
1079
+ } else {
1080
+ throwError(`Can't hydrate node with unsupported type: ${snapshot.__type}`);
1081
+ }
1082
+ }
1083
+ };
1084
+
1085
+ // src/serializers/field-tree-snapshotter.ts
1086
+ var FieldTreeSnapshotter = class {
1087
+ constructor(fieldsSnapshotter) {
1088
+ this.fieldsSnapshotter = fieldsSnapshotter;
1089
+ }
1090
+ /**
1091
+ * Creates a serializable snapshot of the entire tree and its contained fields.
1092
+ * @returns A plain JavaScript object representing the complete state managed by this tree.
1093
+ */
1094
+ snapshot(tree) {
1095
+ const res = {
1096
+ __type: tree.typeName
1097
+ };
1098
+ tree.nodes.forEach((node, key) => {
1099
+ if (node.typeName === tree.typeName) {
1100
+ res[key] = this.snapshot(node);
1101
+ } else if (node.typeName === Fields.typeName) {
1102
+ res[key] = this.fieldsSnapshotter.snapshot(node);
1103
+ }
1104
+ });
1105
+ return res;
1106
+ }
976
1107
  };
977
1108
 
978
1109
  // src/data-store.ts
@@ -999,24 +1130,51 @@ var StringFieldResolver = class {
999
1130
  }
1000
1131
  };
1001
1132
 
1133
+ // src/guards.ts
1134
+ import { isNullOrUndefined as isNullOrUndefined3 } from "@axi-engine/utils";
1135
+ function isFields(value) {
1136
+ return !isNullOrUndefined3(value) && value.typeName === Fields.typeName;
1137
+ }
1138
+ function isFieldTree(value) {
1139
+ return !isNullOrUndefined3(value) && value.typeName === FieldTree.typeName;
1140
+ }
1141
+ function isDataStore(value) {
1142
+ return !isNullOrUndefined3(value) && value.typeName === DataStore.typeName;
1143
+ }
1144
+
1002
1145
  // src/data-store.ts
1003
1146
  var DataStore = class _DataStore {
1004
- constructor(tree) {
1005
- this.tree = tree;
1006
- this.registerResolver(new NumericFieldResolver());
1007
- this.registerResolver(new BooleanFieldResolver());
1008
- this.registerResolver(new StringFieldResolver());
1009
- }
1010
1147
  static typeName = "dataStore";
1011
1148
  typeName = _DataStore.typeName;
1012
1149
  resolvers = [];
1013
- rootFieldsName = "__root_fields";
1014
- _rootFields;
1015
- get rootFields() {
1016
- if (!this._rootFields) {
1017
- this._rootFields = this.tree.getOrCreateFields(this.rootFieldsName);
1150
+ _variables;
1151
+ _tree;
1152
+ _factory;
1153
+ get variables() {
1154
+ if (!this._variables) {
1155
+ this._variables = this._factory.fields();
1018
1156
  }
1019
- return this._rootFields;
1157
+ return this._variables;
1158
+ }
1159
+ get tree() {
1160
+ if (!this._tree) {
1161
+ this._tree = this._factory.tree();
1162
+ }
1163
+ return this._tree;
1164
+ }
1165
+ constructor(treeOrFactory, variables) {
1166
+ if (!isFieldTree(treeOrFactory)) {
1167
+ this._factory = treeOrFactory;
1168
+ } else {
1169
+ this._tree = treeOrFactory;
1170
+ this._factory = this._tree.factory;
1171
+ }
1172
+ if (variables) {
1173
+ this._variables = variables;
1174
+ }
1175
+ this.registerResolver(new NumericFieldResolver());
1176
+ this.registerResolver(new BooleanFieldResolver());
1177
+ this.registerResolver(new StringFieldResolver());
1020
1178
  }
1021
1179
  registerResolver(resolver) {
1022
1180
  this.resolvers.unshift(resolver);
@@ -1087,8 +1245,8 @@ var DataStore = class _DataStore {
1087
1245
  getField(path) {
1088
1246
  const pathArr = ensurePathArray2(path);
1089
1247
  throwIfEmpty4(pathArr, `Wrong path or path is empty: ${ensurePathString2(path)}, should contain at least one path segment`);
1090
- if (this.isPathToRootFields(pathArr)) {
1091
- return this.rootFields.get(pathArr[0]);
1248
+ if (this.isPathToVariables(pathArr)) {
1249
+ return this.variables.get(pathArr[0]);
1092
1250
  }
1093
1251
  const fieldName = pathArr.pop();
1094
1252
  const fields = this.tree.getFields(pathArr);
@@ -1109,8 +1267,8 @@ var DataStore = class _DataStore {
1109
1267
  remove(path) {
1110
1268
  const pathArr = ensurePathArray2(path);
1111
1269
  throwIfEmpty4(pathArr, `Wrong path or path is empty: ${ensurePathString2(path)}, should contain at least one path segment`);
1112
- if (this.isPathToRootFields(pathArr)) {
1113
- this.rootFields.remove(pathArr);
1270
+ if (this.isPathToVariables(pathArr)) {
1271
+ this.variables.remove(pathArr);
1114
1272
  return;
1115
1273
  }
1116
1274
  const node = this.tree.findParentNode(pathArr);
@@ -1121,17 +1279,6 @@ var DataStore = class _DataStore {
1121
1279
  node.removeNode(leafName);
1122
1280
  }
1123
1281
  }
1124
- isPathToRootFields(path) {
1125
- return ensurePathArray2(path).length === 1;
1126
- }
1127
- getDestinationFields(path) {
1128
- const pathArr = ensurePathArray2(path);
1129
- if (this.isPathToRootFields(pathArr)) {
1130
- return { fields: this.rootFields, leafName: pathArr[0] };
1131
- }
1132
- const leafName = pathArr.pop();
1133
- return { fields: this.tree.getOrCreateFields(path), leafName };
1134
- }
1135
1282
  /**
1136
1283
  * Creates a new, independent instance of the Store with a fresh, empty data state (FieldsTree).
1137
1284
  *
@@ -1142,13 +1289,13 @@ var DataStore = class _DataStore {
1142
1289
  * @returns {DataStore} A new, isolated DataStore instance.
1143
1290
  */
1144
1291
  createIsolated() {
1145
- return new _DataStore(this.tree.createDetachedTree());
1292
+ return new _DataStore(this._factory);
1146
1293
  }
1147
1294
  /** code below -> implementation of the DataStore from utils */
1148
1295
  has(path) {
1149
1296
  const pathArr = ensurePathArray2(path);
1150
- if (this.isPathToRootFields(pathArr)) {
1151
- return this.rootFields.has(pathArr[0]);
1297
+ if (this.isPathToVariables(pathArr)) {
1298
+ return this.variables.has(pathArr[0]);
1152
1299
  }
1153
1300
  return this.tree.hasPath(pathArr);
1154
1301
  }
@@ -1167,19 +1314,134 @@ var DataStore = class _DataStore {
1167
1314
  delete(path) {
1168
1315
  this.remove(path);
1169
1316
  }
1317
+ /**
1318
+ * @internal Used for serialization
1319
+ */
1320
+ getInternalVariables() {
1321
+ return this._variables;
1322
+ }
1323
+ /**
1324
+ * @internal Used for serialization
1325
+ */
1326
+ getInternalTree() {
1327
+ return this._tree;
1328
+ }
1329
+ /**
1330
+ * @internal Used for serialization
1331
+ */
1332
+ getOrCreateInternalVariables() {
1333
+ return this.variables;
1334
+ }
1335
+ /**
1336
+ * @internal Used for serialization
1337
+ */
1338
+ getOrCreateInternalTree() {
1339
+ return this.tree;
1340
+ }
1341
+ /**
1342
+ * @private
1343
+ */
1344
+ isPathToVariables(path) {
1345
+ return ensurePathArray2(path).length === 1;
1346
+ }
1347
+ /**
1348
+ * @private
1349
+ */
1350
+ getDestinationFields(path) {
1351
+ const pathArr = ensurePathArray2(path);
1352
+ if (this.isPathToVariables(pathArr)) {
1353
+ return { fields: this.variables, leafName: pathArr[0] };
1354
+ }
1355
+ const leafName = pathArr.pop();
1356
+ return { fields: this.tree.getOrCreateFields(path), leafName };
1357
+ }
1170
1358
  };
1171
1359
 
1172
- // src/guards.ts
1173
- import { isNullOrUndefined as isNullOrUndefined3 } from "@axi-engine/utils";
1174
- function isFields(value) {
1175
- return !isNullOrUndefined3(value) && value.typeName === Fields.typeName;
1176
- }
1177
- function isFieldTree(value) {
1178
- return !isNullOrUndefined3(value) && value.typeName === FieldTree.typeName;
1179
- }
1180
- function isDataStore(value) {
1181
- return !isNullOrUndefined3(value) && value.typeName === DataStore.typeName;
1182
- }
1360
+ // src/serializers/data-store-hydrator.ts
1361
+ import { isNullOrUndefined as isNullOrUndefined4 } from "@axi-engine/utils";
1362
+ var DataStoreHydrator = class {
1363
+ /**
1364
+ * Creates an instance of DataStoreSerializer.
1365
+ * @param {FieldTreeHydrator} fieldsFieldTreeHydrator - The serializer used for the underlying tree and fields.
1366
+ */
1367
+ constructor(fieldsFieldTreeHydrator) {
1368
+ this.fieldsFieldTreeHydrator = fieldsFieldTreeHydrator;
1369
+ }
1370
+ /**
1371
+ * Reconstructs a DataStore instance from a snapshot.
1372
+ *
1373
+ * If the snapshot contains a tree, the store is initialized with it.
1374
+ * If not, the store is initialized with the factory (lazy mode), and the
1375
+ * detached variables are injected if present.
1376
+ *
1377
+ * @param {DataStoreSnapshot} snapshot - The snapshot to hydrate.
1378
+ * @returns {DataStore} A new, fully restored DataStore instance.
1379
+ */
1380
+ hydrate(snapshot) {
1381
+ const tree = isNullOrUndefined4(snapshot.tree) ? void 0 : this.fieldsFieldTreeHydrator.hydrate(snapshot.tree);
1382
+ const variables = isNullOrUndefined4(snapshot.variables) ? void 0 : this.fieldsFieldTreeHydrator.fieldsHydrator.hydrate(snapshot.variables);
1383
+ return new DataStore(tree ? tree : this.fieldsFieldTreeHydrator.factory, variables);
1384
+ }
1385
+ /**
1386
+ * Synchronizes a DataStore instance with a snapshot.
1387
+ *
1388
+ * This method ensures the DataStore's internal state matches the snapshot by:
1389
+ * 1. **Destroying** internal containers (variables/tree) if they are missing in the snapshot.
1390
+ * 2. **Patching** (updating/creating) contents if they exist in the snapshot.
1391
+ *
1392
+ * This allows for a granular update where only specific parts of the store (e.g., only variables)
1393
+ * are modified if the snapshot contains partial data, or a full reset if parts are missing.
1394
+ *
1395
+ * @param {DataStore} store - The target DataStore to update.
1396
+ * @param {DataStoreSnapshot} snapshot - The source snapshot.
1397
+ */
1398
+ patch(store, snapshot) {
1399
+ if (!snapshot.variables) {
1400
+ store.getInternalVariables()?.destroy();
1401
+ } else {
1402
+ this.fieldsFieldTreeHydrator.fieldsHydrator.patch(store.getOrCreateInternalVariables(), snapshot.variables);
1403
+ }
1404
+ if (!snapshot.tree) {
1405
+ store.getInternalTree()?.destroy();
1406
+ } else {
1407
+ this.fieldsFieldTreeHydrator.patch(store.getOrCreateInternalTree(), snapshot.tree);
1408
+ }
1409
+ }
1410
+ };
1411
+
1412
+ // src/serializers/data-store-snapshotter.ts
1413
+ var DataStoreSnapshotter = class {
1414
+ /**
1415
+ * Creates an instance of DataStoreSnapshotter.
1416
+ * @param {FieldTreeSnapshotter} treeSnapshotter - The serializer used for the underlying tree and fields.
1417
+ */
1418
+ constructor(treeSnapshotter) {
1419
+ this.treeSnapshotter = treeSnapshotter;
1420
+ }
1421
+ /**
1422
+ * Captures the current state of a DataStore into a serializable snapshot.
1423
+ *
1424
+ * It checks for the existence of internal variables and the internal tree,
1425
+ * serializing them only if they have been initialized (lazy serialization).
1426
+ *
1427
+ * @param {DataStore} store - The store instance to serialize.
1428
+ * @returns {DataStoreSnapshot} The snapshot object.
1429
+ */
1430
+ snapshot(store) {
1431
+ let snapshot = {
1432
+ __type: store.typeName
1433
+ };
1434
+ const variables = store.getInternalVariables();
1435
+ if (variables) {
1436
+ snapshot.variables = this.treeSnapshotter.fieldsSnapshotter.snapshot(variables);
1437
+ }
1438
+ const tree = store.getInternalTree();
1439
+ if (tree) {
1440
+ snapshot.tree = this.treeSnapshotter.snapshot(tree);
1441
+ }
1442
+ return snapshot;
1443
+ }
1444
+ };
1183
1445
 
1184
1446
  // src/setup.ts
1185
1447
  function createCoreFieldRegistry() {
@@ -1201,11 +1463,11 @@ function createCoreTreeNodeFactory(fieldRegistry) {
1201
1463
  return new CoreTreeNodeFactory(fieldRegistry);
1202
1464
  }
1203
1465
  function createCoreTreeSerializer(fieldTreeNodeFactory, policySerializer) {
1204
- return new FieldTreeSerializer(
1466
+ return new FieldTreeHydrator(
1205
1467
  fieldTreeNodeFactory,
1206
- new FieldsSerializer(
1468
+ new FieldsHydrator(
1207
1469
  fieldTreeNodeFactory,
1208
- new FieldSerializer(fieldTreeNodeFactory.fieldRegistry, policySerializer ?? createCorePolicySerializer())
1470
+ new FieldHydrator(fieldTreeNodeFactory.fieldRegistry, policySerializer ?? createCorePolicySerializer())
1209
1471
  )
1210
1472
  );
1211
1473
  }
@@ -1231,12 +1493,17 @@ export {
1231
1493
  CoreStringField,
1232
1494
  CoreTreeNodeFactory,
1233
1495
  DataStore,
1496
+ DataStoreHydrator,
1497
+ DataStoreSnapshotter,
1498
+ FieldHydrator,
1234
1499
  FieldRegistry,
1235
- FieldSerializer,
1500
+ FieldSnapshotter,
1236
1501
  FieldTree,
1237
- FieldTreeSerializer,
1502
+ FieldTreeHydrator,
1503
+ FieldTreeSnapshotter,
1238
1504
  Fields,
1239
- FieldsSerializer,
1505
+ FieldsHydrator,
1506
+ FieldsSnapshotter,
1240
1507
  Policies,
1241
1508
  PolicySerializer,
1242
1509
  clampMaxPolicy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axi-engine/fields",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "A compact, reactive state management library based on a tree of observable fields.",
5
5
  "license": "MIT",
6
6
  "repository": {