@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.js CHANGED
@@ -35,12 +35,17 @@ __export(index_exports, {
35
35
  CoreStringField: () => CoreStringField,
36
36
  CoreTreeNodeFactory: () => CoreTreeNodeFactory,
37
37
  DataStore: () => DataStore,
38
+ DataStoreHydrator: () => DataStoreHydrator,
39
+ DataStoreSnapshotter: () => DataStoreSnapshotter,
40
+ FieldHydrator: () => FieldHydrator,
38
41
  FieldRegistry: () => FieldRegistry,
39
- FieldSerializer: () => FieldSerializer,
42
+ FieldSnapshotter: () => FieldSnapshotter,
40
43
  FieldTree: () => FieldTree,
41
- FieldTreeSerializer: () => FieldTreeSerializer,
44
+ FieldTreeHydrator: () => FieldTreeHydrator,
45
+ FieldTreeSnapshotter: () => FieldTreeSnapshotter,
42
46
  Fields: () => Fields,
43
- FieldsSerializer: () => FieldsSerializer,
47
+ FieldsHydrator: () => FieldsHydrator,
48
+ FieldsSnapshotter: () => FieldsSnapshotter,
44
49
  Policies: () => Policies,
45
50
  PolicySerializer: () => PolicySerializer,
46
51
  clampMaxPolicy: () => clampMaxPolicy,
@@ -468,6 +473,9 @@ var Fields = class _Fields {
468
473
  */
469
474
  remove(names) {
470
475
  const namesToRemove = Array.isArray(names) ? names : [names];
476
+ if (!namesToRemove.length) {
477
+ return;
478
+ }
471
479
  const reallyRemoved = namesToRemove.filter((name) => {
472
480
  const field = this._fields.get(name);
473
481
  if (!field) {
@@ -824,7 +832,7 @@ var CoreTreeNodeFactory = class extends CoreFieldsFactory {
824
832
  }
825
833
  };
826
834
 
827
- // src/serializer/policies/clamp-policy-serializer-handler.ts
835
+ // src/serializers/policies/clamp-policy-serializer-handler.ts
828
836
  var ClampPolicySerializerHandler = class {
829
837
  snapshot(policy) {
830
838
  return { min: policy.min, max: policy.max };
@@ -834,7 +842,7 @@ var ClampPolicySerializerHandler = class {
834
842
  }
835
843
  };
836
844
 
837
- // src/serializer/policies/clamp-max-policy-serializer-handler.ts
845
+ // src/serializers/policies/clamp-max-policy-serializer-handler.ts
838
846
  var ClampMaxPolicySerializerHandler = class {
839
847
  snapshot(policy) {
840
848
  return { max: policy.max };
@@ -844,7 +852,7 @@ var ClampMaxPolicySerializerHandler = class {
844
852
  }
845
853
  };
846
854
 
847
- // src/serializer/policies/clamp-min-policy-serializer-handler.ts
855
+ // src/serializers/policies/clamp-min-policy-serializer-handler.ts
848
856
  var ClampMinPolicySerializerHandler = class {
849
857
  snapshot(policy) {
850
858
  return { min: policy.min };
@@ -854,7 +862,7 @@ var ClampMinPolicySerializerHandler = class {
854
862
  }
855
863
  };
856
864
 
857
- // src/serializer/policy-serializer.ts
865
+ // src/serializers/policy-serializer.ts
858
866
  var import_utils6 = require("@axi-engine/utils");
859
867
  var PolicySerializer = class {
860
868
  handlers = new import_utils6.Registry();
@@ -891,9 +899,9 @@ var PolicySerializer = class {
891
899
  }
892
900
  };
893
901
 
894
- // src/serializer/field-serializer.ts
902
+ // src/serializers/field-hydrator.ts
895
903
  var import_utils7 = require("@axi-engine/utils");
896
- var FieldSerializer = class {
904
+ var FieldHydrator = class {
897
905
  /**
898
906
  * Creates an instance of FieldSerializer.
899
907
  * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
@@ -903,6 +911,47 @@ var FieldSerializer = class {
903
911
  this.fieldRegistry = fieldRegistry;
904
912
  this.policySerializer = policySerializer;
905
913
  }
914
+ /**
915
+ * Restores a Field instance from its snapshot representation.
916
+ * It uses the `__type` property to find the correct constructor and hydrates
917
+ * the field with its value and all its policies.
918
+ * @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
919
+ * @returns {Field<any>} A new, fully functional Field instance.
920
+ * @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
921
+ */
922
+ hydrate(snapshot) {
923
+ const fieldType = snapshot.__type;
924
+ (0, import_utils7.throwIfEmpty)(fieldType, 'Invalid field snapshot: missing "__type" identifier.');
925
+ const Ctor = this.fieldRegistry.getOrThrow(fieldType);
926
+ let policies = this.hydratePolicies(snapshot);
927
+ return new Ctor(snapshot.name, snapshot.value, { policies });
928
+ }
929
+ /**
930
+ * Updates an existing Field instance with data from a snapshot.
931
+ *
932
+ * This method modifies the field in-place, preserving the object reference.
933
+ * It updates the field's value and completely replaces its current policies
934
+ * with the ones defined in the snapshot.
935
+ *
936
+ * @param {Field<any>} field - The existing Field instance to update.
937
+ * @param {FieldSnapshot} snapshot - The snapshot containing the new state.
938
+ */
939
+ patch(field, snapshot) {
940
+ field.policies.clear();
941
+ const policies = this.hydratePolicies(snapshot);
942
+ policies?.forEach((p) => field.policies.add(p));
943
+ field.value = snapshot.value;
944
+ }
945
+ hydratePolicies(snapshot) {
946
+ return (0, import_utils7.isNullOrUndefined)(snapshot.policies) ? void 0 : snapshot.policies.map((p) => this.policySerializer.hydrate(p));
947
+ }
948
+ };
949
+
950
+ // src/serializers/field-snapshotter.ts
951
+ var FieldSnapshotter = class {
952
+ constructor(policySerializer) {
953
+ this.policySerializer = policySerializer;
954
+ }
906
955
  /**
907
956
  * Creates a serializable snapshot of a Field instance.
908
957
  * The snapshot includes the field's type, name, current value, and the state of all its policies.
@@ -916,58 +965,22 @@ var FieldSerializer = class {
916
965
  value: field.value
917
966
  };
918
967
  if (!field.policies.isEmpty()) {
919
- const serializedPolicies = [];
920
- field.policies.items.forEach((policy) => serializedPolicies.push(this.policySerializer.snapshot(policy)));
921
- snapshot.policies = serializedPolicies;
968
+ snapshot.policies = Array.from(field.policies.items.values()).map((policy) => this.policySerializer.snapshot(policy));
922
969
  }
923
970
  return snapshot;
924
971
  }
925
- /**
926
- * Restores a Field instance from its snapshot representation.
927
- * It uses the `__type` property to find the correct constructor and hydrates
928
- * the field with its value and all its policies.
929
- * @param {FieldSnapshot} snapshot - The plain object snapshot to deserialize.
930
- * @returns {Field<any>} A new, fully functional Field instance.
931
- * @throws If the snapshot is invalid, missing a `__type`, or if the type is not registered.
932
- */
933
- hydrate(snapshot) {
934
- const fieldType = snapshot.__type;
935
- (0, import_utils7.throwIfEmpty)(fieldType, 'Invalid field snapshot: missing "__type" identifier.');
936
- const Ctor = this.fieldRegistry.getOrThrow(fieldType);
937
- let policies;
938
- if (!(0, import_utils7.isNullOrUndefined)(snapshot.policies)) {
939
- policies = [];
940
- snapshot.policies.forEach((p) => policies.push(this.policySerializer.hydrate(p)));
941
- }
942
- return new Ctor(snapshot.name, snapshot.value, { policies });
943
- }
944
972
  };
945
973
 
946
- // src/serializer/fields-serializer.ts
947
- var FieldsSerializer = class {
974
+ // src/serializers/fields-hydrator.ts
975
+ var FieldsHydrator = class {
948
976
  /**
949
977
  * Creates an instance of FieldsSerializer.
950
978
  * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
951
- * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
979
+ * @param {FieldHydrator} fieldHydrator - A hydrator of field instances.
952
980
  */
953
- constructor(fieldsFactory, fieldSerializer) {
981
+ constructor(fieldsFactory, fieldHydrator) {
954
982
  this.fieldsFactory = fieldsFactory;
955
- this.fieldSerializer = fieldSerializer;
956
- }
957
- /**
958
- * Creates a serializable snapshot of a `Fields` container.
959
- *
960
- * The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
961
- * for each `Field` within the container.
962
- * @param {Fields} fields - The `Fields` instance to serialize.
963
- * @returns {FieldsSnapshot} A plain object ready for JSON serialization.
964
- */
965
- snapshot(fields) {
966
- const res = {
967
- __type: fields.typeName
968
- };
969
- fields.fields.forEach((field) => res[field.name] = this.fieldSerializer.snapshot(field));
970
- return res;
983
+ this.fieldHydrator = fieldHydrator;
971
984
  }
972
985
  /**
973
986
  * Restores a `Fields` container instance from its snapshot representation.
@@ -981,37 +994,81 @@ var FieldsSerializer = class {
981
994
  const fields = this.fieldsFactory.fields();
982
995
  for (const fieldName in fieldsData) {
983
996
  const fieldSnapshot = fieldsData[fieldName];
984
- const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
997
+ const restoredField = this.fieldHydrator.hydrate(fieldSnapshot);
985
998
  fields.add(restoredField);
986
999
  }
987
1000
  return fields;
988
1001
  }
1002
+ /**
1003
+ * Synchronizes an existing `Fields` container with a snapshot.
1004
+ *
1005
+ * This method performs a "smart update":
1006
+ * 1. **Removes** fields from the container that are missing in the snapshot.
1007
+ * 2. **Patches** existing fields in-place using {@link FieldHydrator.patch}, preserving object references.
1008
+ * 3. **Creates** (hydrates) and adds new fields that exist in the snapshot but not in the container.
1009
+ *
1010
+ * @param {Fields} fields - The target `Fields` container to update.
1011
+ * @param {FieldsSnapshot} snapshot - The source snapshot containing the desired state.
1012
+ */
1013
+ patch(fields, snapshot) {
1014
+ const { __type, ...fieldsData } = snapshot;
1015
+ const snapshotKeys = new Set(Object.keys(fieldsData));
1016
+ const fieldsToRemove = Array.from(fields.fields.values()).filter((f) => !snapshotKeys.has(f.name)).map((f) => f.name);
1017
+ fields.remove(fieldsToRemove);
1018
+ for (const fieldName in fieldsData) {
1019
+ const fieldSnapshot = fieldsData[fieldName];
1020
+ if (fields.has(fieldName)) {
1021
+ const existingField = fields.get(fieldName);
1022
+ this.fieldHydrator.patch(existingField, fieldSnapshot);
1023
+ } else {
1024
+ const newField = this.fieldHydrator.hydrate(fieldSnapshot);
1025
+ fields.add(newField);
1026
+ }
1027
+ }
1028
+ }
989
1029
  };
990
1030
 
991
- // src/serializer/field-tree-serializer.ts
992
- var import_utils8 = require("@axi-engine/utils");
993
- var FieldTreeSerializer = class {
994
- constructor(fieldTreeNodeFactory, fieldsSerializer) {
995
- this.fieldTreeNodeFactory = fieldTreeNodeFactory;
996
- this.fieldsSerializer = fieldsSerializer;
1031
+ // src/serializers/fields-snapshotter.ts
1032
+ var FieldsSnapshotter = class {
1033
+ /**
1034
+ * Creates an instance of FieldsSnapshotter.
1035
+ * @param {FieldSnapshotter} fieldSnapshotter - A serializer of field instances.
1036
+ */
1037
+ constructor(fieldSnapshotter) {
1038
+ this.fieldSnapshotter = fieldSnapshotter;
997
1039
  }
998
1040
  /**
999
- * Creates a serializable snapshot of the entire tree and its contained fields.
1000
- * @returns A plain JavaScript object representing the complete state managed by this tree.
1041
+ * Creates a serializable snapshot of a `Fields` container.
1042
+ *
1043
+ * The snapshot includes a `__type` identifier (currently hardcoded) and an array of snapshots
1044
+ * for each `Field` within the container.
1045
+ * @param {Fields} fields - The `Fields` instance to serialize.
1046
+ * @returns {FieldsSnapshot} A plain object ready for JSON serialization.
1001
1047
  */
1002
- snapshot(tree) {
1048
+ snapshot(fields) {
1003
1049
  const res = {
1004
- __type: tree.typeName
1050
+ __type: fields.typeName
1005
1051
  };
1006
- tree.nodes.forEach((node, key) => {
1007
- if (node.typeName === tree.typeName) {
1008
- res[key] = this.snapshot(node);
1009
- } else if (node.typeName === Fields.typeName) {
1010
- res[key] = this.fieldsSerializer.snapshot(node);
1011
- }
1012
- });
1052
+ fields.fields.forEach((field) => res[field.name] = this.fieldSnapshotter.snapshot(field));
1013
1053
  return res;
1014
1054
  }
1055
+ };
1056
+
1057
+ // src/serializers/field-tree-hydrator.ts
1058
+ var import_utils8 = require("@axi-engine/utils");
1059
+ var FieldTreeHydrator = class {
1060
+ _factory;
1061
+ _fieldsHydrator;
1062
+ get factory() {
1063
+ return this._factory;
1064
+ }
1065
+ get fieldsHydrator() {
1066
+ return this._fieldsHydrator;
1067
+ }
1068
+ constructor(fieldTreeNodeFactory, fieldsHydrator) {
1069
+ this._factory = fieldTreeNodeFactory;
1070
+ this._fieldsHydrator = fieldsHydrator;
1071
+ }
1015
1072
  /**
1016
1073
  * Restores the state of the tree from a snapshot.
1017
1074
  * It intelligently creates missing nodes based on `__type` metadata and delegates hydration to child nodes.
@@ -1019,24 +1076,103 @@ var FieldTreeSerializer = class {
1019
1076
  */
1020
1077
  hydrate(snapshot) {
1021
1078
  const { __type, ...nodes } = snapshot;
1022
- const tree = this.fieldTreeNodeFactory.tree();
1079
+ const tree = this._factory.tree();
1023
1080
  for (const key in nodes) {
1024
1081
  const nodeData = nodes[key];
1025
1082
  if ((0, import_utils8.isString)(nodeData)) {
1026
1083
  continue;
1027
1084
  }
1028
- if (nodeData.__type === FieldTree.typeName) {
1029
- tree.addNode(key, this.hydrate(nodeData));
1030
- } else if (nodeData.__type === Fields.typeName) {
1031
- tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
1032
- }
1085
+ this.addNodeFromSnapshot(tree, key, nodeData);
1033
1086
  }
1034
1087
  return tree;
1035
1088
  }
1089
+ /**
1090
+ * Synchronizes an existing `FieldTree` branch with a snapshot.
1091
+ *
1092
+ * This method performs a recursive update to match the tree state with the provided snapshot:
1093
+ * 1. **Removes** child nodes that are present in the tree but missing in the snapshot.
1094
+ * 2. **Creates** new nodes that are present in the snapshot but missing in the tree.
1095
+ * 3. **Replaces** nodes if their type has changed (e.g., replacing a `Fields` leaf with a `FieldTree` branch).
1096
+ * 4. **Patches** existing matching nodes in-place (recursively).
1097
+ *
1098
+ * @param {FieldTree} tree - The target tree instance to update.
1099
+ * @param {FieldTreeSnapshot} snapshot - The source snapshot containing the desired state.
1100
+ */
1101
+ patch(tree, snapshot) {
1102
+ const { __type, ...nodes } = snapshot;
1103
+ const snapshotKeys = new Set(Object.keys(nodes));
1104
+ const nodesToRemove = Array.from(tree.nodes.keys()).filter((key) => !snapshotKeys.has(key));
1105
+ tree.removeNode(nodesToRemove);
1106
+ for (const key in nodes) {
1107
+ const nodeData = nodes[key];
1108
+ if ((0, import_utils8.isString)(nodeData)) {
1109
+ continue;
1110
+ }
1111
+ if (!tree.has(key)) {
1112
+ this.addNodeFromSnapshot(tree, key, nodeData);
1113
+ } else {
1114
+ const treeNode = tree.getNode(key);
1115
+ if (treeNode.typeName !== nodeData.__type) {
1116
+ tree.removeNode(key);
1117
+ this.addNodeFromSnapshot(tree, key, nodeData);
1118
+ } else {
1119
+ if (nodeData.__type === FieldTree.typeName) {
1120
+ this.patch(treeNode, nodeData);
1121
+ } else if (nodeData.__type === Fields.typeName) {
1122
+ this.fieldsHydrator.patch(treeNode, nodeData);
1123
+ }
1124
+ }
1125
+ }
1126
+ }
1127
+ }
1128
+ /**
1129
+ * Helper method to instantiate and add a new node to the tree based on the snapshot type.
1130
+ *
1131
+ * It determines whether to create a nested `FieldTree` or a `Fields` container
1132
+ * by inspecting the `__type` property of the snapshot, hydrates it, and attaches it to the parent tree.
1133
+ *
1134
+ * @param {FieldTree} tree - The parent tree instance where the new node will be added.
1135
+ * @param {string} key - The name (key) for the new node.
1136
+ * @param {FieldsSnapshot | FieldTreeSnapshot} snapshot - The source snapshot data.
1137
+ * @throws If the snapshot contains an unsupported or unknown `__type`.
1138
+ */
1139
+ addNodeFromSnapshot(tree, key, snapshot) {
1140
+ if (snapshot.__type === FieldTree.typeName) {
1141
+ tree.addNode(key, this.hydrate(snapshot));
1142
+ } else if (snapshot.__type === Fields.typeName) {
1143
+ tree.addNode(key, this.fieldsHydrator.hydrate(snapshot));
1144
+ } else {
1145
+ (0, import_utils8.throwError)(`Can't hydrate node with unsupported type: ${snapshot.__type}`);
1146
+ }
1147
+ }
1148
+ };
1149
+
1150
+ // src/serializers/field-tree-snapshotter.ts
1151
+ var FieldTreeSnapshotter = class {
1152
+ constructor(fieldsSnapshotter) {
1153
+ this.fieldsSnapshotter = fieldsSnapshotter;
1154
+ }
1155
+ /**
1156
+ * Creates a serializable snapshot of the entire tree and its contained fields.
1157
+ * @returns A plain JavaScript object representing the complete state managed by this tree.
1158
+ */
1159
+ snapshot(tree) {
1160
+ const res = {
1161
+ __type: tree.typeName
1162
+ };
1163
+ tree.nodes.forEach((node, key) => {
1164
+ if (node.typeName === tree.typeName) {
1165
+ res[key] = this.snapshot(node);
1166
+ } else if (node.typeName === Fields.typeName) {
1167
+ res[key] = this.fieldsSnapshotter.snapshot(node);
1168
+ }
1169
+ });
1170
+ return res;
1171
+ }
1036
1172
  };
1037
1173
 
1038
1174
  // src/data-store.ts
1039
- var import_utils10 = require("@axi-engine/utils");
1175
+ var import_utils11 = require("@axi-engine/utils");
1040
1176
 
1041
1177
  // src/data-store-field-resolver.ts
1042
1178
  var import_utils9 = require("@axi-engine/utils");
@@ -1059,24 +1195,51 @@ var StringFieldResolver = class {
1059
1195
  }
1060
1196
  };
1061
1197
 
1198
+ // src/guards.ts
1199
+ var import_utils10 = require("@axi-engine/utils");
1200
+ function isFields(value) {
1201
+ return !(0, import_utils10.isNullOrUndefined)(value) && value.typeName === Fields.typeName;
1202
+ }
1203
+ function isFieldTree(value) {
1204
+ return !(0, import_utils10.isNullOrUndefined)(value) && value.typeName === FieldTree.typeName;
1205
+ }
1206
+ function isDataStore(value) {
1207
+ return !(0, import_utils10.isNullOrUndefined)(value) && value.typeName === DataStore.typeName;
1208
+ }
1209
+
1062
1210
  // src/data-store.ts
1063
1211
  var DataStore = class _DataStore {
1064
- constructor(tree) {
1065
- this.tree = tree;
1066
- this.registerResolver(new NumericFieldResolver());
1067
- this.registerResolver(new BooleanFieldResolver());
1068
- this.registerResolver(new StringFieldResolver());
1069
- }
1070
1212
  static typeName = "dataStore";
1071
1213
  typeName = _DataStore.typeName;
1072
1214
  resolvers = [];
1073
- rootFieldsName = "__root_fields";
1074
- _rootFields;
1075
- get rootFields() {
1076
- if (!this._rootFields) {
1077
- this._rootFields = this.tree.getOrCreateFields(this.rootFieldsName);
1215
+ _variables;
1216
+ _tree;
1217
+ _factory;
1218
+ get variables() {
1219
+ if (!this._variables) {
1220
+ this._variables = this._factory.fields();
1078
1221
  }
1079
- return this._rootFields;
1222
+ return this._variables;
1223
+ }
1224
+ get tree() {
1225
+ if (!this._tree) {
1226
+ this._tree = this._factory.tree();
1227
+ }
1228
+ return this._tree;
1229
+ }
1230
+ constructor(treeOrFactory, variables) {
1231
+ if (!isFieldTree(treeOrFactory)) {
1232
+ this._factory = treeOrFactory;
1233
+ } else {
1234
+ this._tree = treeOrFactory;
1235
+ this._factory = this._tree.factory;
1236
+ }
1237
+ if (variables) {
1238
+ this._variables = variables;
1239
+ }
1240
+ this.registerResolver(new NumericFieldResolver());
1241
+ this.registerResolver(new BooleanFieldResolver());
1242
+ this.registerResolver(new StringFieldResolver());
1080
1243
  }
1081
1244
  registerResolver(resolver) {
1082
1245
  this.resolvers.unshift(resolver);
@@ -1145,10 +1308,10 @@ var DataStore = class _DataStore {
1145
1308
  return this.getField(path);
1146
1309
  }
1147
1310
  getField(path) {
1148
- const pathArr = (0, import_utils10.ensurePathArray)(path);
1149
- (0, import_utils10.throwIfEmpty)(pathArr, `Wrong path or path is empty: ${(0, import_utils10.ensurePathString)(path)}, should contain at least one path segment`);
1150
- if (this.isPathToRootFields(pathArr)) {
1151
- return this.rootFields.get(pathArr[0]);
1311
+ const pathArr = (0, import_utils11.ensurePathArray)(path);
1312
+ (0, import_utils11.throwIfEmpty)(pathArr, `Wrong path or path is empty: ${(0, import_utils11.ensurePathString)(path)}, should contain at least one path segment`);
1313
+ if (this.isPathToVariables(pathArr)) {
1314
+ return this.variables.get(pathArr[0]);
1152
1315
  }
1153
1316
  const fieldName = pathArr.pop();
1154
1317
  const fields = this.tree.getFields(pathArr);
@@ -1167,10 +1330,10 @@ var DataStore = class _DataStore {
1167
1330
  return this.tree.getFieldTree(path);
1168
1331
  }
1169
1332
  remove(path) {
1170
- const pathArr = (0, import_utils10.ensurePathArray)(path);
1171
- (0, import_utils10.throwIfEmpty)(pathArr, `Wrong path or path is empty: ${(0, import_utils10.ensurePathString)(path)}, should contain at least one path segment`);
1172
- if (this.isPathToRootFields(pathArr)) {
1173
- this.rootFields.remove(pathArr);
1333
+ const pathArr = (0, import_utils11.ensurePathArray)(path);
1334
+ (0, import_utils11.throwIfEmpty)(pathArr, `Wrong path or path is empty: ${(0, import_utils11.ensurePathString)(path)}, should contain at least one path segment`);
1335
+ if (this.isPathToVariables(pathArr)) {
1336
+ this.variables.remove(pathArr);
1174
1337
  return;
1175
1338
  }
1176
1339
  const node = this.tree.findParentNode(pathArr);
@@ -1181,17 +1344,6 @@ var DataStore = class _DataStore {
1181
1344
  node.removeNode(leafName);
1182
1345
  }
1183
1346
  }
1184
- isPathToRootFields(path) {
1185
- return (0, import_utils10.ensurePathArray)(path).length === 1;
1186
- }
1187
- getDestinationFields(path) {
1188
- const pathArr = (0, import_utils10.ensurePathArray)(path);
1189
- if (this.isPathToRootFields(pathArr)) {
1190
- return { fields: this.rootFields, leafName: pathArr[0] };
1191
- }
1192
- const leafName = pathArr.pop();
1193
- return { fields: this.tree.getOrCreateFields(path), leafName };
1194
- }
1195
1347
  /**
1196
1348
  * Creates a new, independent instance of the Store with a fresh, empty data state (FieldsTree).
1197
1349
  *
@@ -1202,13 +1354,13 @@ var DataStore = class _DataStore {
1202
1354
  * @returns {DataStore} A new, isolated DataStore instance.
1203
1355
  */
1204
1356
  createIsolated() {
1205
- return new _DataStore(this.tree.createDetachedTree());
1357
+ return new _DataStore(this._factory);
1206
1358
  }
1207
1359
  /** code below -> implementation of the DataStore from utils */
1208
1360
  has(path) {
1209
- const pathArr = (0, import_utils10.ensurePathArray)(path);
1210
- if (this.isPathToRootFields(pathArr)) {
1211
- return this.rootFields.has(pathArr[0]);
1361
+ const pathArr = (0, import_utils11.ensurePathArray)(path);
1362
+ if (this.isPathToVariables(pathArr)) {
1363
+ return this.variables.has(pathArr[0]);
1212
1364
  }
1213
1365
  return this.tree.hasPath(pathArr);
1214
1366
  }
@@ -1227,19 +1379,134 @@ var DataStore = class _DataStore {
1227
1379
  delete(path) {
1228
1380
  this.remove(path);
1229
1381
  }
1382
+ /**
1383
+ * @internal Used for serialization
1384
+ */
1385
+ getInternalVariables() {
1386
+ return this._variables;
1387
+ }
1388
+ /**
1389
+ * @internal Used for serialization
1390
+ */
1391
+ getInternalTree() {
1392
+ return this._tree;
1393
+ }
1394
+ /**
1395
+ * @internal Used for serialization
1396
+ */
1397
+ getOrCreateInternalVariables() {
1398
+ return this.variables;
1399
+ }
1400
+ /**
1401
+ * @internal Used for serialization
1402
+ */
1403
+ getOrCreateInternalTree() {
1404
+ return this.tree;
1405
+ }
1406
+ /**
1407
+ * @private
1408
+ */
1409
+ isPathToVariables(path) {
1410
+ return (0, import_utils11.ensurePathArray)(path).length === 1;
1411
+ }
1412
+ /**
1413
+ * @private
1414
+ */
1415
+ getDestinationFields(path) {
1416
+ const pathArr = (0, import_utils11.ensurePathArray)(path);
1417
+ if (this.isPathToVariables(pathArr)) {
1418
+ return { fields: this.variables, leafName: pathArr[0] };
1419
+ }
1420
+ const leafName = pathArr.pop();
1421
+ return { fields: this.tree.getOrCreateFields(path), leafName };
1422
+ }
1230
1423
  };
1231
1424
 
1232
- // src/guards.ts
1233
- var import_utils11 = require("@axi-engine/utils");
1234
- function isFields(value) {
1235
- return !(0, import_utils11.isNullOrUndefined)(value) && value.typeName === Fields.typeName;
1236
- }
1237
- function isFieldTree(value) {
1238
- return !(0, import_utils11.isNullOrUndefined)(value) && value.typeName === FieldTree.typeName;
1239
- }
1240
- function isDataStore(value) {
1241
- return !(0, import_utils11.isNullOrUndefined)(value) && value.typeName === DataStore.typeName;
1242
- }
1425
+ // src/serializers/data-store-hydrator.ts
1426
+ var import_utils12 = require("@axi-engine/utils");
1427
+ var DataStoreHydrator = class {
1428
+ /**
1429
+ * Creates an instance of DataStoreSerializer.
1430
+ * @param {FieldTreeHydrator} fieldsFieldTreeHydrator - The serializer used for the underlying tree and fields.
1431
+ */
1432
+ constructor(fieldsFieldTreeHydrator) {
1433
+ this.fieldsFieldTreeHydrator = fieldsFieldTreeHydrator;
1434
+ }
1435
+ /**
1436
+ * Reconstructs a DataStore instance from a snapshot.
1437
+ *
1438
+ * If the snapshot contains a tree, the store is initialized with it.
1439
+ * If not, the store is initialized with the factory (lazy mode), and the
1440
+ * detached variables are injected if present.
1441
+ *
1442
+ * @param {DataStoreSnapshot} snapshot - The snapshot to hydrate.
1443
+ * @returns {DataStore} A new, fully restored DataStore instance.
1444
+ */
1445
+ hydrate(snapshot) {
1446
+ const tree = (0, import_utils12.isNullOrUndefined)(snapshot.tree) ? void 0 : this.fieldsFieldTreeHydrator.hydrate(snapshot.tree);
1447
+ const variables = (0, import_utils12.isNullOrUndefined)(snapshot.variables) ? void 0 : this.fieldsFieldTreeHydrator.fieldsHydrator.hydrate(snapshot.variables);
1448
+ return new DataStore(tree ? tree : this.fieldsFieldTreeHydrator.factory, variables);
1449
+ }
1450
+ /**
1451
+ * Synchronizes a DataStore instance with a snapshot.
1452
+ *
1453
+ * This method ensures the DataStore's internal state matches the snapshot by:
1454
+ * 1. **Destroying** internal containers (variables/tree) if they are missing in the snapshot.
1455
+ * 2. **Patching** (updating/creating) contents if they exist in the snapshot.
1456
+ *
1457
+ * This allows for a granular update where only specific parts of the store (e.g., only variables)
1458
+ * are modified if the snapshot contains partial data, or a full reset if parts are missing.
1459
+ *
1460
+ * @param {DataStore} store - The target DataStore to update.
1461
+ * @param {DataStoreSnapshot} snapshot - The source snapshot.
1462
+ */
1463
+ patch(store, snapshot) {
1464
+ if (!snapshot.variables) {
1465
+ store.getInternalVariables()?.destroy();
1466
+ } else {
1467
+ this.fieldsFieldTreeHydrator.fieldsHydrator.patch(store.getOrCreateInternalVariables(), snapshot.variables);
1468
+ }
1469
+ if (!snapshot.tree) {
1470
+ store.getInternalTree()?.destroy();
1471
+ } else {
1472
+ this.fieldsFieldTreeHydrator.patch(store.getOrCreateInternalTree(), snapshot.tree);
1473
+ }
1474
+ }
1475
+ };
1476
+
1477
+ // src/serializers/data-store-snapshotter.ts
1478
+ var DataStoreSnapshotter = class {
1479
+ /**
1480
+ * Creates an instance of DataStoreSnapshotter.
1481
+ * @param {FieldTreeSnapshotter} treeSnapshotter - The serializer used for the underlying tree and fields.
1482
+ */
1483
+ constructor(treeSnapshotter) {
1484
+ this.treeSnapshotter = treeSnapshotter;
1485
+ }
1486
+ /**
1487
+ * Captures the current state of a DataStore into a serializable snapshot.
1488
+ *
1489
+ * It checks for the existence of internal variables and the internal tree,
1490
+ * serializing them only if they have been initialized (lazy serialization).
1491
+ *
1492
+ * @param {DataStore} store - The store instance to serialize.
1493
+ * @returns {DataStoreSnapshot} The snapshot object.
1494
+ */
1495
+ snapshot(store) {
1496
+ let snapshot = {
1497
+ __type: store.typeName
1498
+ };
1499
+ const variables = store.getInternalVariables();
1500
+ if (variables) {
1501
+ snapshot.variables = this.treeSnapshotter.fieldsSnapshotter.snapshot(variables);
1502
+ }
1503
+ const tree = store.getInternalTree();
1504
+ if (tree) {
1505
+ snapshot.tree = this.treeSnapshotter.snapshot(tree);
1506
+ }
1507
+ return snapshot;
1508
+ }
1509
+ };
1243
1510
 
1244
1511
  // src/setup.ts
1245
1512
  function createCoreFieldRegistry() {
@@ -1261,11 +1528,11 @@ function createCoreTreeNodeFactory(fieldRegistry) {
1261
1528
  return new CoreTreeNodeFactory(fieldRegistry);
1262
1529
  }
1263
1530
  function createCoreTreeSerializer(fieldTreeNodeFactory, policySerializer) {
1264
- return new FieldTreeSerializer(
1531
+ return new FieldTreeHydrator(
1265
1532
  fieldTreeNodeFactory,
1266
- new FieldsSerializer(
1533
+ new FieldsHydrator(
1267
1534
  fieldTreeNodeFactory,
1268
- new FieldSerializer(fieldTreeNodeFactory.fieldRegistry, policySerializer ?? createCorePolicySerializer())
1535
+ new FieldHydrator(fieldTreeNodeFactory.fieldRegistry, policySerializer ?? createCorePolicySerializer())
1269
1536
  )
1270
1537
  );
1271
1538
  }
@@ -1292,12 +1559,17 @@ function createCoreFieldSystem(config) {
1292
1559
  CoreStringField,
1293
1560
  CoreTreeNodeFactory,
1294
1561
  DataStore,
1562
+ DataStoreHydrator,
1563
+ DataStoreSnapshotter,
1564
+ FieldHydrator,
1295
1565
  FieldRegistry,
1296
- FieldSerializer,
1566
+ FieldSnapshotter,
1297
1567
  FieldTree,
1298
- FieldTreeSerializer,
1568
+ FieldTreeHydrator,
1569
+ FieldTreeSnapshotter,
1299
1570
  Fields,
1300
- FieldsSerializer,
1571
+ FieldsHydrator,
1572
+ FieldsSnapshotter,
1301
1573
  Policies,
1302
1574
  PolicySerializer,
1303
1575
  clampMaxPolicy,