@axi-engine/fields 0.2.0 → 0.2.1

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.d.mts CHANGED
@@ -330,6 +330,7 @@ declare class Fields {
330
330
  * Removes all fields from the collection, ensuring each is properly destroyed.
331
331
  */
332
332
  clear(): void;
333
+ destroy(): void;
333
334
  }
334
335
 
335
336
  declare const DefaultFields_base: {
@@ -355,6 +356,7 @@ declare const DefaultFields_base: {
355
356
  get<T extends Field<any>>(name: string): T;
356
357
  remove(names: string | string[]): void;
357
358
  clear(): void;
359
+ destroy(): void;
358
360
  };
359
361
  } & {
360
362
  new (...args: any[]): {
@@ -379,6 +381,7 @@ declare const DefaultFields_base: {
379
381
  get<T extends Field<any>>(name: string): T;
380
382
  remove(names: string | string[]): void;
381
383
  clear(): void;
384
+ destroy(): void;
382
385
  };
383
386
  } & {
384
387
  new (...args: any[]): {
@@ -403,6 +406,7 @@ declare const DefaultFields_base: {
403
406
  get<T extends Field<any>>(name: string): T;
404
407
  remove(names: string | string[]): void;
405
408
  clear(): void;
409
+ destroy(): void;
406
410
  };
407
411
  } & {
408
412
  new (...args: any[]): {
@@ -426,31 +430,38 @@ declare const DefaultFields_base: {
426
430
  get<T extends Field<any>>(name: string): T;
427
431
  remove(names: string | string[]): void;
428
432
  clear(): void;
433
+ destroy(): void;
429
434
  };
430
435
  } & typeof Fields;
431
436
  declare class DefaultFields extends DefaultFields_base {
432
437
  }
433
438
 
439
+ interface FieldsFactory<TFields extends Fields> {
440
+ fields(): TFields;
441
+ }
442
+ declare class DefaultFieldsFactory implements FieldsFactory<DefaultFields> {
443
+ private readonly fieldRegistry;
444
+ constructor(fieldRegistry: FieldRegistry);
445
+ fields(): DefaultFields;
446
+ }
434
447
  /**
435
448
  * Defines the contract for a factory that creates nodes for a FieldTree.
436
449
  * This allows for custom implementations of Fields and FieldTree to be used.
437
450
  */
438
- interface TreeNodeFactory {
439
- fields<T extends Fields>(): T;
440
- tree<T extends FieldTree>(): T;
451
+ interface TreeNodeFactory<TFields extends Fields> extends FieldsFactory<TFields> {
452
+ fields(): TFields;
453
+ tree(): FieldTree<TFields>;
441
454
  }
442
455
  /**
443
456
  * The default factory implementation that creates standard DefaultFields and FieldTree instances.
444
457
  */
445
- declare class DefaultTreeNodeFactory implements TreeNodeFactory {
446
- private readonly fieldRegistry;
458
+ declare class DefaultTreeNodeFactory extends DefaultFieldsFactory implements TreeNodeFactory<DefaultFields> {
447
459
  constructor(fieldRegistry: FieldRegistry);
448
- fields: () => any;
449
- tree: () => any;
460
+ tree(): FieldTree<DefaultFields>;
450
461
  }
451
462
 
452
463
  /** A type alias for any container that can be a child node in a FieldTree */
453
- type TreeOrFieldsNode = FieldTree | Fields;
464
+ type TreeNode<F extends Fields> = FieldTree<F> | F;
454
465
  /**
455
466
  * Represents a hierarchical data structure for managing the global state of the system.
456
467
  *
@@ -459,26 +470,51 @@ type TreeOrFieldsNode = FieldTree | Fields;
459
470
  * and overall game progress. It uses a path-based system for accessing and
460
471
  * manipulating nested data, similar to a file system.
461
472
  *
462
- * @todo
463
- * - Add node removal functionality.
464
- * - Implement an event system for node creation/removal.
465
473
  */
466
- declare class FieldTree {
474
+ declare class FieldTree<TFields extends Fields> {
467
475
  static readonly typeName = "fieldTree";
468
476
  readonly typeName = "fieldTree";
469
477
  /** @private The internal map storing child nodes (branches or leaves). */
470
478
  private readonly _nodes;
471
479
  /** @private The factory used to create new child nodes. */
472
480
  private readonly _factory;
481
+ /**
482
+ * An event emitter that fires immediately after a new node is added to this tree branch.
483
+ * @event
484
+ * @param {object} event - The event payload.
485
+ * @param {string} event.name - The name (key) of the added node.
486
+ * @param event.node - The node instance that was added.
487
+ * @example
488
+ * myTree.onAdd.subscribe(({ name, node }) => {
489
+ * console.log(`Node '${name}' was added.`, node);
490
+ * });
491
+ */
492
+ onAdd: Emitter<[event: {
493
+ name: string;
494
+ node: TreeNode<TFields>;
495
+ }]>;
496
+ /**
497
+ * An event emitter that fires once after one or more nodes have been successfully removed.
498
+ * @event
499
+ * @param {object} event - The event payload.
500
+ * @param {string[]} event.names - An array of names of the nodes that were removed.
501
+ * @example
502
+ * myTree.onRemove.subscribe(({ names }) => {
503
+ * console.log(`Nodes removed: ${names.join(', ')}`);
504
+ * });
505
+ */
506
+ onRemove: Emitter<[event: {
507
+ names: string[];
508
+ }]>;
473
509
  /**
474
510
  * Gets the collection of direct child nodes of this tree branch.
475
511
  */
476
- get nodes(): Map<string, TreeOrFieldsNode>;
512
+ get nodes(): Map<string, TreeNode<TFields>>;
477
513
  /**
478
514
  * Creates an instance of FieldTree.
479
515
  * @param {TreeNodeFactory} factory - A factory responsible for creating new nodes within the tree.
480
516
  */
481
- constructor(factory: TreeNodeFactory);
517
+ constructor(factory: TreeNodeFactory<TFields>);
482
518
  /**
483
519
  * Checks if a direct child node with the given name exists.
484
520
  * @param {string} name - The name of the direct child node.
@@ -494,18 +530,29 @@ declare class FieldTree {
494
530
  /**
495
531
  * Adds a pre-existing node as a direct child of this tree branch.
496
532
  * @param {string} name - The name to assign to the new child node.
497
- * @param {TreeOrFieldsNode} node - The node instance to add.
498
- * @returns {TreeOrFieldsNode} The added node.
533
+ * @param {TreeNode} node - The node instance to add.
534
+ * @returns {TreeNode} The added node.
499
535
  * @throws If a node with the same name already exists.
500
536
  */
501
- addNode(name: string, node: TreeOrFieldsNode): TreeOrFieldsNode;
537
+ addNode(name: string, node: TreeNode<TFields>): TreeNode<TFields>;
502
538
  /**
503
539
  * Retrieves a direct child node by its name.
504
540
  * @param {string} name - The name of the child node.
505
- * @returns {TreeOrFieldsNode} The retrieved node.
541
+ * @returns {TreeNode} The retrieved node.
506
542
  * @throws If a node with the given name cannot be found.
507
543
  */
508
- getNode(name: string): TreeOrFieldsNode;
544
+ getNode(name: string): TreeNode<TFields>;
545
+ /**
546
+ * Removes one or more nodes from this tree branch.
547
+ *
548
+ * This method first validates that all specified nodes exist. If validation passes,
549
+ * it recursively calls `destroy()` on each node to ensure proper cleanup of the entire subtree.
550
+ * Finally, it emits a single `onRemove` event with the names of all successfully removed nodes.
551
+ *
552
+ * @param {string | string[]} names - A single name or an array of names of the nodes to remove.
553
+ * @throws If any of the specified names do not correspond to an existing node.
554
+ */
555
+ removeNode(names: string | string[]): void;
509
556
  /**
510
557
  * Creates a new `FieldTree` (branch) node at the specified path.
511
558
  * @param {PathType} path - The path where the new `FieldTree` should be created.
@@ -513,7 +560,7 @@ declare class FieldTree {
513
560
  * @returns {FieldTree} The newly created `FieldTree` instance.
514
561
  * @throws If the path is invalid or a node already exists at the target location.
515
562
  */
516
- createFieldTree<T extends FieldTree>(path: PathType, createPath?: boolean): T;
563
+ createFieldTree<T extends FieldTree<TFields>>(path: PathType, createPath?: boolean): T;
517
564
  /**
518
565
  * Creates a new `Fields` (leaf) container at the specified path.
519
566
  * @param {PathType} path - The path where the new `Fields` container should be created.
@@ -521,33 +568,47 @@ declare class FieldTree {
521
568
  * @returns {Fields} The newly created `Fields` instance.
522
569
  * @throws If the path is invalid or a node already exists at the target location.
523
570
  */
524
- createFields<T extends Fields>(path: PathType, createPath?: boolean): T;
571
+ createFields(path: PathType, createPath?: boolean): TFields;
525
572
  /**
526
573
  * Retrieves a `FieldTree` (branch) node from a specified path.
527
574
  * @param {PathType} path - The path to the `FieldTree` node.
528
575
  * @returns {FieldTree} The `FieldTree` instance at the specified path.
529
576
  * @throws If the path is invalid or the node at the path is not a `FieldTree`.
530
577
  */
531
- getFieldTree(path: PathType): FieldTree;
578
+ getFieldTree(path: PathType): FieldTree<TFields>;
532
579
  /**
533
580
  * Retrieves a `Fields` (leaf) container from a specified path.
534
581
  * @param {PathType} path - The path to the `Fields` container.
535
582
  * @returns {Fields} The `Fields` instance at the specified path.
536
583
  * @throws If the path is invalid or the node at the path is not a `Fields` container.
537
584
  */
538
- getFields(path: PathType): Fields;
585
+ getFields(path: PathType): TFields;
539
586
  /**
540
587
  * Retrieves a `FieldTree` at the specified path. If it or any part of the path doesn't exist, it will be created.
541
588
  * @param {PathType} path - The path to the `FieldTree` node.
542
589
  * @returns {FieldTree} The existing or newly created `FieldTree` instance.
543
590
  */
544
- getOrCreateFieldTree(path: PathType): FieldTree;
591
+ getOrCreateFieldTree(path: PathType): FieldTree<TFields>;
545
592
  /**
546
593
  * Retrieves a `Fields` container at the specified path. If it or any part of the path doesn't exist, it will be created.
547
594
  * @param {PathType} path - The path to the `Fields` container.
548
595
  * @returns {Fields} The existing or newly created `Fields` instance.
549
596
  */
550
597
  getOrCreateFields(path: PathType): Fields;
598
+ /**
599
+ * Removes all child nodes from this tree branch.
600
+ * This method ensures that `destroy()` is called on each child node, allowing for
601
+ * a full, recursive cleanup of the entire subtree.
602
+ */
603
+ clear(): void;
604
+ /**
605
+ * Performs a complete cleanup of this node and its entire subtree.
606
+ *
607
+ * It recursively destroys all child nodes by calling `clear()` and then
608
+ * unsubscribes all listeners from its own event emitters.
609
+ * This method should be called when a node is no longer needed.
610
+ */
611
+ destroy(): void;
551
612
  /**
552
613
  * @private
553
614
  * Navigates the tree to the parent of a target node.
@@ -699,30 +760,19 @@ interface FieldsSnapshot {
699
760
  * into a storable snapshot and back.
700
761
  * It delegates the actual serialization of each `Field` and `Policy` to their respective serializers.
701
762
  *
702
- * @todo This implementation is coupled to creating `DefaultFields` instances during hydration.
703
- * To make the system fully extensible, this class should be refactored to use a
704
- * `FieldsRegistry` (a `ConstructorRegistry<Fields>`). This would allow it to
705
- * hydrate any custom `Fields` class (e.g., `ReactiveFields`) based on the `__type`
706
- * property in the snapshot, mirroring the pattern used by `FieldSerializer`.
707
- *
708
763
  * @todo Implement a `patch(fields, snapshot)` method. It should perform a non-destructive
709
764
  * update, creating new fields, removing missing ones, and patching existing ones
710
765
  * in place, preserving the container instance itself.
711
766
  */
712
- declare class FieldsSerializer {
713
- private readonly fieldRegistry;
714
- private readonly policySerializer;
715
- /**
716
- * An internal instance of FieldSerializer to handle individual fields.
717
- * @private
718
- */
767
+ declare class FieldsSerializer<TFields extends Fields> {
768
+ private readonly fieldsFactory;
719
769
  private readonly fieldSerializer;
720
770
  /**
721
771
  * Creates an instance of FieldsSerializer.
722
- * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
723
- * @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
772
+ * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
773
+ * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
724
774
  */
725
- constructor(fieldRegistry: FieldRegistry, policySerializer: PolicySerializer);
775
+ constructor(fieldsFactory: FieldsFactory<TFields>, fieldSerializer: FieldSerializer);
726
776
  /**
727
777
  * Creates a serializable snapshot of a `Fields` container.
728
778
  *
@@ -735,12 +785,11 @@ declare class FieldsSerializer {
735
785
  /**
736
786
  * Restores a `Fields` container instance from its snapshot representation.
737
787
  *
738
- * **Limitation:** This method is currently hardcoded to always create an instance of `DefaultFields`.
739
788
  * It iterates through the field snapshots and hydrates them individually, adding them to the new container.
740
789
  * @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
741
- * @returns {DefaultFields} A new `DefaultFields` instance populated with the restored fields.
790
+ * @returns {Fields} A new `DefaultFields` instance populated with the restored fields.
742
791
  */
743
- hydrate(snapshot: FieldsSnapshot): DefaultFields;
792
+ hydrate(snapshot: FieldsSnapshot): TFields;
744
793
  }
745
794
 
746
795
  /**
@@ -772,21 +821,21 @@ interface FieldTreeSnapshot {
772
821
  * updates. This method should traverse the existing tree and the snapshot,
773
822
  * patching nodes in place to maintain object references.
774
823
  */
775
- declare class FieldTreeSerializer {
824
+ declare class FieldTreeSerializer<TFields extends Fields> {
776
825
  private readonly fieldTreeNodeFactory;
777
826
  private readonly fieldsSerializer;
778
- constructor(fieldTreeNodeFactory: TreeNodeFactory, fieldsSerializer: FieldsSerializer);
827
+ constructor(fieldTreeNodeFactory: TreeNodeFactory<TFields>, fieldsSerializer: FieldsSerializer<TFields>);
779
828
  /**
780
829
  * Creates a serializable snapshot of the entire tree and its contained fields.
781
830
  * @returns A plain JavaScript object representing the complete state managed by this tree.
782
831
  */
783
- snapshot(tree: FieldTree): FieldTreeSnapshot;
832
+ snapshot(tree: FieldTree<TFields>): FieldTreeSnapshot;
784
833
  /**
785
834
  * Restores the state of the tree from a snapshot.
786
835
  * It intelligently creates missing nodes based on `__type` metadata and delegates hydration to child nodes.
787
836
  * @param snapshot The snapshot object to load.
788
837
  */
789
- hydrate(snapshot: FieldTreeSnapshot): FieldTree;
838
+ hydrate(snapshot: FieldTreeSnapshot): FieldTree<TFields>;
790
839
  }
791
840
 
792
- export { type BooleanField, ClampMaxPolicy, ClampMaxPolicySerializerHandler, ClampMinPolicy, ClampMinPolicySerializerHandler, ClampPolicy, ClampPolicySerializerHandler, DefaultBooleanField, type DefaultBooleanFieldOptions, DefaultField, DefaultFields, DefaultNumericField, type DefaultNumericFieldOptions, DefaultStringField, type DefaultStringFieldOptions, DefaultTreeNodeFactory, type Field, type FieldOptions, FieldRegistry, FieldSerializer, type FieldSnapshot, FieldTree, FieldTreeSerializer, type FieldTreeSnapshot, Fields, FieldsSerializer, type FieldsSnapshot, type NumericField, Policies, type Policy, PolicySerializer, type PolicySerializerHandler, type StringField, type TreeNodeFactory, type TreeOrFieldsNode, clampMaxPolicy, clampMinPolicy, clampPolicy };
841
+ export { type BooleanField, ClampMaxPolicy, ClampMaxPolicySerializerHandler, ClampMinPolicy, ClampMinPolicySerializerHandler, ClampPolicy, ClampPolicySerializerHandler, DefaultBooleanField, type DefaultBooleanFieldOptions, DefaultField, DefaultFields, DefaultFieldsFactory, DefaultNumericField, type DefaultNumericFieldOptions, DefaultStringField, type DefaultStringFieldOptions, DefaultTreeNodeFactory, type Field, type FieldOptions, FieldRegistry, FieldSerializer, type FieldSnapshot, FieldTree, FieldTreeSerializer, type FieldTreeSnapshot, Fields, type FieldsFactory, FieldsSerializer, type FieldsSnapshot, type NumericField, Policies, type Policy, PolicySerializer, type PolicySerializerHandler, type StringField, type TreeNode, type TreeNodeFactory, clampMaxPolicy, clampMinPolicy, clampPolicy };
package/dist/index.d.ts CHANGED
@@ -330,6 +330,7 @@ declare class Fields {
330
330
  * Removes all fields from the collection, ensuring each is properly destroyed.
331
331
  */
332
332
  clear(): void;
333
+ destroy(): void;
333
334
  }
334
335
 
335
336
  declare const DefaultFields_base: {
@@ -355,6 +356,7 @@ declare const DefaultFields_base: {
355
356
  get<T extends Field<any>>(name: string): T;
356
357
  remove(names: string | string[]): void;
357
358
  clear(): void;
359
+ destroy(): void;
358
360
  };
359
361
  } & {
360
362
  new (...args: any[]): {
@@ -379,6 +381,7 @@ declare const DefaultFields_base: {
379
381
  get<T extends Field<any>>(name: string): T;
380
382
  remove(names: string | string[]): void;
381
383
  clear(): void;
384
+ destroy(): void;
382
385
  };
383
386
  } & {
384
387
  new (...args: any[]): {
@@ -403,6 +406,7 @@ declare const DefaultFields_base: {
403
406
  get<T extends Field<any>>(name: string): T;
404
407
  remove(names: string | string[]): void;
405
408
  clear(): void;
409
+ destroy(): void;
406
410
  };
407
411
  } & {
408
412
  new (...args: any[]): {
@@ -426,31 +430,38 @@ declare const DefaultFields_base: {
426
430
  get<T extends Field<any>>(name: string): T;
427
431
  remove(names: string | string[]): void;
428
432
  clear(): void;
433
+ destroy(): void;
429
434
  };
430
435
  } & typeof Fields;
431
436
  declare class DefaultFields extends DefaultFields_base {
432
437
  }
433
438
 
439
+ interface FieldsFactory<TFields extends Fields> {
440
+ fields(): TFields;
441
+ }
442
+ declare class DefaultFieldsFactory implements FieldsFactory<DefaultFields> {
443
+ private readonly fieldRegistry;
444
+ constructor(fieldRegistry: FieldRegistry);
445
+ fields(): DefaultFields;
446
+ }
434
447
  /**
435
448
  * Defines the contract for a factory that creates nodes for a FieldTree.
436
449
  * This allows for custom implementations of Fields and FieldTree to be used.
437
450
  */
438
- interface TreeNodeFactory {
439
- fields<T extends Fields>(): T;
440
- tree<T extends FieldTree>(): T;
451
+ interface TreeNodeFactory<TFields extends Fields> extends FieldsFactory<TFields> {
452
+ fields(): TFields;
453
+ tree(): FieldTree<TFields>;
441
454
  }
442
455
  /**
443
456
  * The default factory implementation that creates standard DefaultFields and FieldTree instances.
444
457
  */
445
- declare class DefaultTreeNodeFactory implements TreeNodeFactory {
446
- private readonly fieldRegistry;
458
+ declare class DefaultTreeNodeFactory extends DefaultFieldsFactory implements TreeNodeFactory<DefaultFields> {
447
459
  constructor(fieldRegistry: FieldRegistry);
448
- fields: () => any;
449
- tree: () => any;
460
+ tree(): FieldTree<DefaultFields>;
450
461
  }
451
462
 
452
463
  /** A type alias for any container that can be a child node in a FieldTree */
453
- type TreeOrFieldsNode = FieldTree | Fields;
464
+ type TreeNode<F extends Fields> = FieldTree<F> | F;
454
465
  /**
455
466
  * Represents a hierarchical data structure for managing the global state of the system.
456
467
  *
@@ -459,26 +470,51 @@ type TreeOrFieldsNode = FieldTree | Fields;
459
470
  * and overall game progress. It uses a path-based system for accessing and
460
471
  * manipulating nested data, similar to a file system.
461
472
  *
462
- * @todo
463
- * - Add node removal functionality.
464
- * - Implement an event system for node creation/removal.
465
473
  */
466
- declare class FieldTree {
474
+ declare class FieldTree<TFields extends Fields> {
467
475
  static readonly typeName = "fieldTree";
468
476
  readonly typeName = "fieldTree";
469
477
  /** @private The internal map storing child nodes (branches or leaves). */
470
478
  private readonly _nodes;
471
479
  /** @private The factory used to create new child nodes. */
472
480
  private readonly _factory;
481
+ /**
482
+ * An event emitter that fires immediately after a new node is added to this tree branch.
483
+ * @event
484
+ * @param {object} event - The event payload.
485
+ * @param {string} event.name - The name (key) of the added node.
486
+ * @param event.node - The node instance that was added.
487
+ * @example
488
+ * myTree.onAdd.subscribe(({ name, node }) => {
489
+ * console.log(`Node '${name}' was added.`, node);
490
+ * });
491
+ */
492
+ onAdd: Emitter<[event: {
493
+ name: string;
494
+ node: TreeNode<TFields>;
495
+ }]>;
496
+ /**
497
+ * An event emitter that fires once after one or more nodes have been successfully removed.
498
+ * @event
499
+ * @param {object} event - The event payload.
500
+ * @param {string[]} event.names - An array of names of the nodes that were removed.
501
+ * @example
502
+ * myTree.onRemove.subscribe(({ names }) => {
503
+ * console.log(`Nodes removed: ${names.join(', ')}`);
504
+ * });
505
+ */
506
+ onRemove: Emitter<[event: {
507
+ names: string[];
508
+ }]>;
473
509
  /**
474
510
  * Gets the collection of direct child nodes of this tree branch.
475
511
  */
476
- get nodes(): Map<string, TreeOrFieldsNode>;
512
+ get nodes(): Map<string, TreeNode<TFields>>;
477
513
  /**
478
514
  * Creates an instance of FieldTree.
479
515
  * @param {TreeNodeFactory} factory - A factory responsible for creating new nodes within the tree.
480
516
  */
481
- constructor(factory: TreeNodeFactory);
517
+ constructor(factory: TreeNodeFactory<TFields>);
482
518
  /**
483
519
  * Checks if a direct child node with the given name exists.
484
520
  * @param {string} name - The name of the direct child node.
@@ -494,18 +530,29 @@ declare class FieldTree {
494
530
  /**
495
531
  * Adds a pre-existing node as a direct child of this tree branch.
496
532
  * @param {string} name - The name to assign to the new child node.
497
- * @param {TreeOrFieldsNode} node - The node instance to add.
498
- * @returns {TreeOrFieldsNode} The added node.
533
+ * @param {TreeNode} node - The node instance to add.
534
+ * @returns {TreeNode} The added node.
499
535
  * @throws If a node with the same name already exists.
500
536
  */
501
- addNode(name: string, node: TreeOrFieldsNode): TreeOrFieldsNode;
537
+ addNode(name: string, node: TreeNode<TFields>): TreeNode<TFields>;
502
538
  /**
503
539
  * Retrieves a direct child node by its name.
504
540
  * @param {string} name - The name of the child node.
505
- * @returns {TreeOrFieldsNode} The retrieved node.
541
+ * @returns {TreeNode} The retrieved node.
506
542
  * @throws If a node with the given name cannot be found.
507
543
  */
508
- getNode(name: string): TreeOrFieldsNode;
544
+ getNode(name: string): TreeNode<TFields>;
545
+ /**
546
+ * Removes one or more nodes from this tree branch.
547
+ *
548
+ * This method first validates that all specified nodes exist. If validation passes,
549
+ * it recursively calls `destroy()` on each node to ensure proper cleanup of the entire subtree.
550
+ * Finally, it emits a single `onRemove` event with the names of all successfully removed nodes.
551
+ *
552
+ * @param {string | string[]} names - A single name or an array of names of the nodes to remove.
553
+ * @throws If any of the specified names do not correspond to an existing node.
554
+ */
555
+ removeNode(names: string | string[]): void;
509
556
  /**
510
557
  * Creates a new `FieldTree` (branch) node at the specified path.
511
558
  * @param {PathType} path - The path where the new `FieldTree` should be created.
@@ -513,7 +560,7 @@ declare class FieldTree {
513
560
  * @returns {FieldTree} The newly created `FieldTree` instance.
514
561
  * @throws If the path is invalid or a node already exists at the target location.
515
562
  */
516
- createFieldTree<T extends FieldTree>(path: PathType, createPath?: boolean): T;
563
+ createFieldTree<T extends FieldTree<TFields>>(path: PathType, createPath?: boolean): T;
517
564
  /**
518
565
  * Creates a new `Fields` (leaf) container at the specified path.
519
566
  * @param {PathType} path - The path where the new `Fields` container should be created.
@@ -521,33 +568,47 @@ declare class FieldTree {
521
568
  * @returns {Fields} The newly created `Fields` instance.
522
569
  * @throws If the path is invalid or a node already exists at the target location.
523
570
  */
524
- createFields<T extends Fields>(path: PathType, createPath?: boolean): T;
571
+ createFields(path: PathType, createPath?: boolean): TFields;
525
572
  /**
526
573
  * Retrieves a `FieldTree` (branch) node from a specified path.
527
574
  * @param {PathType} path - The path to the `FieldTree` node.
528
575
  * @returns {FieldTree} The `FieldTree` instance at the specified path.
529
576
  * @throws If the path is invalid or the node at the path is not a `FieldTree`.
530
577
  */
531
- getFieldTree(path: PathType): FieldTree;
578
+ getFieldTree(path: PathType): FieldTree<TFields>;
532
579
  /**
533
580
  * Retrieves a `Fields` (leaf) container from a specified path.
534
581
  * @param {PathType} path - The path to the `Fields` container.
535
582
  * @returns {Fields} The `Fields` instance at the specified path.
536
583
  * @throws If the path is invalid or the node at the path is not a `Fields` container.
537
584
  */
538
- getFields(path: PathType): Fields;
585
+ getFields(path: PathType): TFields;
539
586
  /**
540
587
  * Retrieves a `FieldTree` at the specified path. If it or any part of the path doesn't exist, it will be created.
541
588
  * @param {PathType} path - The path to the `FieldTree` node.
542
589
  * @returns {FieldTree} The existing or newly created `FieldTree` instance.
543
590
  */
544
- getOrCreateFieldTree(path: PathType): FieldTree;
591
+ getOrCreateFieldTree(path: PathType): FieldTree<TFields>;
545
592
  /**
546
593
  * Retrieves a `Fields` container at the specified path. If it or any part of the path doesn't exist, it will be created.
547
594
  * @param {PathType} path - The path to the `Fields` container.
548
595
  * @returns {Fields} The existing or newly created `Fields` instance.
549
596
  */
550
597
  getOrCreateFields(path: PathType): Fields;
598
+ /**
599
+ * Removes all child nodes from this tree branch.
600
+ * This method ensures that `destroy()` is called on each child node, allowing for
601
+ * a full, recursive cleanup of the entire subtree.
602
+ */
603
+ clear(): void;
604
+ /**
605
+ * Performs a complete cleanup of this node and its entire subtree.
606
+ *
607
+ * It recursively destroys all child nodes by calling `clear()` and then
608
+ * unsubscribes all listeners from its own event emitters.
609
+ * This method should be called when a node is no longer needed.
610
+ */
611
+ destroy(): void;
551
612
  /**
552
613
  * @private
553
614
  * Navigates the tree to the parent of a target node.
@@ -699,30 +760,19 @@ interface FieldsSnapshot {
699
760
  * into a storable snapshot and back.
700
761
  * It delegates the actual serialization of each `Field` and `Policy` to their respective serializers.
701
762
  *
702
- * @todo This implementation is coupled to creating `DefaultFields` instances during hydration.
703
- * To make the system fully extensible, this class should be refactored to use a
704
- * `FieldsRegistry` (a `ConstructorRegistry<Fields>`). This would allow it to
705
- * hydrate any custom `Fields` class (e.g., `ReactiveFields`) based on the `__type`
706
- * property in the snapshot, mirroring the pattern used by `FieldSerializer`.
707
- *
708
763
  * @todo Implement a `patch(fields, snapshot)` method. It should perform a non-destructive
709
764
  * update, creating new fields, removing missing ones, and patching existing ones
710
765
  * in place, preserving the container instance itself.
711
766
  */
712
- declare class FieldsSerializer {
713
- private readonly fieldRegistry;
714
- private readonly policySerializer;
715
- /**
716
- * An internal instance of FieldSerializer to handle individual fields.
717
- * @private
718
- */
767
+ declare class FieldsSerializer<TFields extends Fields> {
768
+ private readonly fieldsFactory;
719
769
  private readonly fieldSerializer;
720
770
  /**
721
771
  * Creates an instance of FieldsSerializer.
722
- * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
723
- * @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
772
+ * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
773
+ * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
724
774
  */
725
- constructor(fieldRegistry: FieldRegistry, policySerializer: PolicySerializer);
775
+ constructor(fieldsFactory: FieldsFactory<TFields>, fieldSerializer: FieldSerializer);
726
776
  /**
727
777
  * Creates a serializable snapshot of a `Fields` container.
728
778
  *
@@ -735,12 +785,11 @@ declare class FieldsSerializer {
735
785
  /**
736
786
  * Restores a `Fields` container instance from its snapshot representation.
737
787
  *
738
- * **Limitation:** This method is currently hardcoded to always create an instance of `DefaultFields`.
739
788
  * It iterates through the field snapshots and hydrates them individually, adding them to the new container.
740
789
  * @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
741
- * @returns {DefaultFields} A new `DefaultFields` instance populated with the restored fields.
790
+ * @returns {Fields} A new `DefaultFields` instance populated with the restored fields.
742
791
  */
743
- hydrate(snapshot: FieldsSnapshot): DefaultFields;
792
+ hydrate(snapshot: FieldsSnapshot): TFields;
744
793
  }
745
794
 
746
795
  /**
@@ -772,21 +821,21 @@ interface FieldTreeSnapshot {
772
821
  * updates. This method should traverse the existing tree and the snapshot,
773
822
  * patching nodes in place to maintain object references.
774
823
  */
775
- declare class FieldTreeSerializer {
824
+ declare class FieldTreeSerializer<TFields extends Fields> {
776
825
  private readonly fieldTreeNodeFactory;
777
826
  private readonly fieldsSerializer;
778
- constructor(fieldTreeNodeFactory: TreeNodeFactory, fieldsSerializer: FieldsSerializer);
827
+ constructor(fieldTreeNodeFactory: TreeNodeFactory<TFields>, fieldsSerializer: FieldsSerializer<TFields>);
779
828
  /**
780
829
  * Creates a serializable snapshot of the entire tree and its contained fields.
781
830
  * @returns A plain JavaScript object representing the complete state managed by this tree.
782
831
  */
783
- snapshot(tree: FieldTree): FieldTreeSnapshot;
832
+ snapshot(tree: FieldTree<TFields>): FieldTreeSnapshot;
784
833
  /**
785
834
  * Restores the state of the tree from a snapshot.
786
835
  * It intelligently creates missing nodes based on `__type` metadata and delegates hydration to child nodes.
787
836
  * @param snapshot The snapshot object to load.
788
837
  */
789
- hydrate(snapshot: FieldTreeSnapshot): FieldTree;
838
+ hydrate(snapshot: FieldTreeSnapshot): FieldTree<TFields>;
790
839
  }
791
840
 
792
- export { type BooleanField, ClampMaxPolicy, ClampMaxPolicySerializerHandler, ClampMinPolicy, ClampMinPolicySerializerHandler, ClampPolicy, ClampPolicySerializerHandler, DefaultBooleanField, type DefaultBooleanFieldOptions, DefaultField, DefaultFields, DefaultNumericField, type DefaultNumericFieldOptions, DefaultStringField, type DefaultStringFieldOptions, DefaultTreeNodeFactory, type Field, type FieldOptions, FieldRegistry, FieldSerializer, type FieldSnapshot, FieldTree, FieldTreeSerializer, type FieldTreeSnapshot, Fields, FieldsSerializer, type FieldsSnapshot, type NumericField, Policies, type Policy, PolicySerializer, type PolicySerializerHandler, type StringField, type TreeNodeFactory, type TreeOrFieldsNode, clampMaxPolicy, clampMinPolicy, clampPolicy };
841
+ export { type BooleanField, ClampMaxPolicy, ClampMaxPolicySerializerHandler, ClampMinPolicy, ClampMinPolicySerializerHandler, ClampPolicy, ClampPolicySerializerHandler, DefaultBooleanField, type DefaultBooleanFieldOptions, DefaultField, DefaultFields, DefaultFieldsFactory, DefaultNumericField, type DefaultNumericFieldOptions, DefaultStringField, type DefaultStringFieldOptions, DefaultTreeNodeFactory, type Field, type FieldOptions, FieldRegistry, FieldSerializer, type FieldSnapshot, FieldTree, FieldTreeSerializer, type FieldTreeSnapshot, Fields, type FieldsFactory, FieldsSerializer, type FieldsSnapshot, type NumericField, Policies, type Policy, PolicySerializer, type PolicySerializerHandler, type StringField, type TreeNode, type TreeNodeFactory, clampMaxPolicy, clampMinPolicy, clampPolicy };
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  DefaultBooleanField: () => DefaultBooleanField,
30
30
  DefaultField: () => DefaultField,
31
31
  DefaultFields: () => DefaultFields,
32
+ DefaultFieldsFactory: () => DefaultFieldsFactory,
32
33
  DefaultNumericField: () => DefaultNumericField,
33
34
  DefaultStringField: () => DefaultStringField,
34
35
  DefaultTreeNodeFactory: () => DefaultTreeNodeFactory,
@@ -491,6 +492,11 @@ var Fields = class _Fields {
491
492
  clear() {
492
493
  this.remove(Array.from(this._fields.keys()));
493
494
  }
495
+ destroy() {
496
+ this.clear();
497
+ this.onAdd.clear();
498
+ this.onRemove.clear();
499
+ }
494
500
  };
495
501
 
496
502
  // src/mixins/with-boolean-fields.mixin.ts
@@ -563,6 +569,29 @@ var FieldTree = class _FieldTree {
563
569
  _nodes = /* @__PURE__ */ new Map();
564
570
  /** @private The factory used to create new child nodes. */
565
571
  _factory;
572
+ /**
573
+ * An event emitter that fires immediately after a new node is added to this tree branch.
574
+ * @event
575
+ * @param {object} event - The event payload.
576
+ * @param {string} event.name - The name (key) of the added node.
577
+ * @param event.node - The node instance that was added.
578
+ * @example
579
+ * myTree.onAdd.subscribe(({ name, node }) => {
580
+ * console.log(`Node '${name}' was added.`, node);
581
+ * });
582
+ */
583
+ onAdd = new import_utils5.Emitter();
584
+ /**
585
+ * An event emitter that fires once after one or more nodes have been successfully removed.
586
+ * @event
587
+ * @param {object} event - The event payload.
588
+ * @param {string[]} event.names - An array of names of the nodes that were removed.
589
+ * @example
590
+ * myTree.onRemove.subscribe(({ names }) => {
591
+ * console.log(`Nodes removed: ${names.join(', ')}`);
592
+ * });
593
+ */
594
+ onRemove = new import_utils5.Emitter();
566
595
  /**
567
596
  * Gets the collection of direct child nodes of this tree branch.
568
597
  */
@@ -596,19 +625,20 @@ var FieldTree = class _FieldTree {
596
625
  /**
597
626
  * Adds a pre-existing node as a direct child of this tree branch.
598
627
  * @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.
628
+ * @param {TreeNode} node - The node instance to add.
629
+ * @returns {TreeNode} The added node.
601
630
  * @throws If a node with the same name already exists.
602
631
  */
603
632
  addNode(name, node) {
604
633
  (0, import_utils5.throwIf)(this.has(name), `Can't add node with name: '${name}', node already exists`);
605
634
  this._nodes.set(name, node);
635
+ this.onAdd.emit({ name, node });
606
636
  return node;
607
637
  }
608
638
  /**
609
639
  * Retrieves a direct child node by its name.
610
640
  * @param {string} name - The name of the child node.
611
- * @returns {TreeOrFieldsNode} The retrieved node.
641
+ * @returns {TreeNode} The retrieved node.
612
642
  * @throws If a node with the given name cannot be found.
613
643
  */
614
644
  getNode(name) {
@@ -616,6 +646,29 @@ var FieldTree = class _FieldTree {
616
646
  (0, import_utils5.throwIfEmpty)(node, `Can't find node with name '${name}'`);
617
647
  return node;
618
648
  }
649
+ /**
650
+ * Removes one or more nodes from this tree branch.
651
+ *
652
+ * This method first validates that all specified nodes exist. If validation passes,
653
+ * it recursively calls `destroy()` on each node to ensure proper cleanup of the entire subtree.
654
+ * Finally, it emits a single `onRemove` event with the names of all successfully removed nodes.
655
+ *
656
+ * @param {string | string[]} names - A single name or an array of names of the nodes to remove.
657
+ * @throws If any of the specified names do not correspond to an existing node.
658
+ */
659
+ removeNode(names) {
660
+ const toRemoveNames = Array.isArray(names) ? names : [names];
661
+ toRemoveNames.forEach((name) => {
662
+ (0, import_utils5.throwIf)(!this.has(name), `Can't remove node with name: '${name}', node doesn't exists`);
663
+ });
664
+ toRemoveNames.forEach((name) => {
665
+ this._nodes.get(name).destroy();
666
+ this._nodes.delete(name);
667
+ });
668
+ if (toRemoveNames.length) {
669
+ this.onRemove.emit({ names: toRemoveNames });
670
+ }
671
+ }
619
672
  /**
620
673
  * Creates a new `FieldTree` (branch) node at the specified path.
621
674
  * @param {PathType} path - The path where the new `FieldTree` should be created.
@@ -686,6 +739,26 @@ var FieldTree = class _FieldTree {
686
739
  const traversedPath = this.traversePath(path, true);
687
740
  return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFields(traversedPath.leafName) : traversedPath.branch.createFields(traversedPath.leafName);
688
741
  }
742
+ /**
743
+ * Removes all child nodes from this tree branch.
744
+ * This method ensures that `destroy()` is called on each child node, allowing for
745
+ * a full, recursive cleanup of the entire subtree.
746
+ */
747
+ clear() {
748
+ this.removeNode(Array.from(this._nodes.keys()));
749
+ }
750
+ /**
751
+ * Performs a complete cleanup of this node and its entire subtree.
752
+ *
753
+ * It recursively destroys all child nodes by calling `clear()` and then
754
+ * unsubscribes all listeners from its own event emitters.
755
+ * This method should be called when a node is no longer needed.
756
+ */
757
+ destroy() {
758
+ this.clear();
759
+ this.onAdd.clear();
760
+ this.onRemove.clear();
761
+ }
689
762
  /**
690
763
  * @private
691
764
  * Navigates the tree to the parent of a target node.
@@ -718,12 +791,21 @@ var FieldTree = class _FieldTree {
718
791
  };
719
792
 
720
793
  // src/field-tree-node-factory.ts
721
- var DefaultTreeNodeFactory = class {
794
+ var DefaultFieldsFactory = class {
722
795
  constructor(fieldRegistry) {
723
796
  this.fieldRegistry = fieldRegistry;
724
797
  }
725
- fields = () => new DefaultFields(this.fieldRegistry);
726
- tree = () => new FieldTree(this);
798
+ fields() {
799
+ return new DefaultFields(this.fieldRegistry);
800
+ }
801
+ };
802
+ var DefaultTreeNodeFactory = class extends DefaultFieldsFactory {
803
+ constructor(fieldRegistry) {
804
+ super(fieldRegistry);
805
+ }
806
+ tree() {
807
+ return new FieldTree(this);
808
+ }
727
809
  };
728
810
 
729
811
  // src/serializer/policies/clamp-policy-serializer-handler.ts
@@ -856,19 +938,13 @@ var FieldSerializer = class {
856
938
  var FieldsSerializer = class {
857
939
  /**
858
940
  * 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.
941
+ * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
942
+ * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
861
943
  */
862
- constructor(fieldRegistry, policySerializer) {
863
- this.fieldRegistry = fieldRegistry;
864
- this.policySerializer = policySerializer;
865
- this.fieldSerializer = new FieldSerializer(this.fieldRegistry, this.policySerializer);
944
+ constructor(fieldsFactory, fieldSerializer) {
945
+ this.fieldsFactory = fieldsFactory;
946
+ this.fieldSerializer = fieldSerializer;
866
947
  }
867
- /**
868
- * An internal instance of FieldSerializer to handle individual fields.
869
- * @private
870
- */
871
- fieldSerializer;
872
948
  /**
873
949
  * Creates a serializable snapshot of a `Fields` container.
874
950
  *
@@ -887,14 +963,13 @@ var FieldsSerializer = class {
887
963
  /**
888
964
  * Restores a `Fields` container instance from its snapshot representation.
889
965
  *
890
- * **Limitation:** This method is currently hardcoded to always create an instance of `DefaultFields`.
891
966
  * It iterates through the field snapshots and hydrates them individually, adding them to the new container.
892
967
  * @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
893
- * @returns {DefaultFields} A new `DefaultFields` instance populated with the restored fields.
968
+ * @returns {Fields} A new `DefaultFields` instance populated with the restored fields.
894
969
  */
895
970
  hydrate(snapshot) {
896
971
  const { __type, ...fieldsData } = snapshot;
897
- const fields = new DefaultFields(this.fieldRegistry);
972
+ const fields = this.fieldsFactory.fields();
898
973
  for (const fieldName in fieldsData) {
899
974
  const fieldSnapshot = fieldsData[fieldName];
900
975
  const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
@@ -943,7 +1018,7 @@ var FieldTreeSerializer = class {
943
1018
  }
944
1019
  if (nodeData.__type === FieldTree.typeName) {
945
1020
  tree.addNode(key, this.hydrate(nodeData));
946
- } else {
1021
+ } else if (nodeData.__type === Fields.typeName) {
947
1022
  tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
948
1023
  }
949
1024
  }
@@ -961,6 +1036,7 @@ var FieldTreeSerializer = class {
961
1036
  DefaultBooleanField,
962
1037
  DefaultField,
963
1038
  DefaultFields,
1039
+ DefaultFieldsFactory,
964
1040
  DefaultNumericField,
965
1041
  DefaultStringField,
966
1042
  DefaultTreeNodeFactory,
package/dist/index.mjs CHANGED
@@ -443,6 +443,11 @@ var Fields = class _Fields {
443
443
  clear() {
444
444
  this.remove(Array.from(this._fields.keys()));
445
445
  }
446
+ destroy() {
447
+ this.clear();
448
+ this.onAdd.clear();
449
+ this.onRemove.clear();
450
+ }
446
451
  };
447
452
 
448
453
  // src/mixins/with-boolean-fields.mixin.ts
@@ -507,7 +512,7 @@ var DefaultFields = class extends WithBooleanFields(WithStringFields(WithNumeric
507
512
  };
508
513
 
509
514
  // src/field-tree.ts
510
- import { ensurePathArray, ensurePathString, throwIf as throwIf3, throwIfEmpty as throwIfEmpty2 } from "@axi-engine/utils";
515
+ import { Emitter as Emitter3, ensurePathArray, ensurePathString, throwIf as throwIf3, throwIfEmpty as throwIfEmpty2 } from "@axi-engine/utils";
511
516
  var FieldTree = class _FieldTree {
512
517
  static typeName = "fieldTree";
513
518
  typeName = _FieldTree.typeName;
@@ -515,6 +520,29 @@ var FieldTree = class _FieldTree {
515
520
  _nodes = /* @__PURE__ */ new Map();
516
521
  /** @private The factory used to create new child nodes. */
517
522
  _factory;
523
+ /**
524
+ * An event emitter that fires immediately after a new node is added to this tree branch.
525
+ * @event
526
+ * @param {object} event - The event payload.
527
+ * @param {string} event.name - The name (key) of the added node.
528
+ * @param event.node - The node instance that was added.
529
+ * @example
530
+ * myTree.onAdd.subscribe(({ name, node }) => {
531
+ * console.log(`Node '${name}' was added.`, node);
532
+ * });
533
+ */
534
+ onAdd = new Emitter3();
535
+ /**
536
+ * An event emitter that fires once after one or more nodes have been successfully removed.
537
+ * @event
538
+ * @param {object} event - The event payload.
539
+ * @param {string[]} event.names - An array of names of the nodes that were removed.
540
+ * @example
541
+ * myTree.onRemove.subscribe(({ names }) => {
542
+ * console.log(`Nodes removed: ${names.join(', ')}`);
543
+ * });
544
+ */
545
+ onRemove = new Emitter3();
518
546
  /**
519
547
  * Gets the collection of direct child nodes of this tree branch.
520
548
  */
@@ -548,19 +576,20 @@ var FieldTree = class _FieldTree {
548
576
  /**
549
577
  * Adds a pre-existing node as a direct child of this tree branch.
550
578
  * @param {string} name - The name to assign to the new child node.
551
- * @param {TreeOrFieldsNode} node - The node instance to add.
552
- * @returns {TreeOrFieldsNode} The added node.
579
+ * @param {TreeNode} node - The node instance to add.
580
+ * @returns {TreeNode} The added node.
553
581
  * @throws If a node with the same name already exists.
554
582
  */
555
583
  addNode(name, node) {
556
584
  throwIf3(this.has(name), `Can't add node with name: '${name}', node already exists`);
557
585
  this._nodes.set(name, node);
586
+ this.onAdd.emit({ name, node });
558
587
  return node;
559
588
  }
560
589
  /**
561
590
  * Retrieves a direct child node by its name.
562
591
  * @param {string} name - The name of the child node.
563
- * @returns {TreeOrFieldsNode} The retrieved node.
592
+ * @returns {TreeNode} The retrieved node.
564
593
  * @throws If a node with the given name cannot be found.
565
594
  */
566
595
  getNode(name) {
@@ -568,6 +597,29 @@ var FieldTree = class _FieldTree {
568
597
  throwIfEmpty2(node, `Can't find node with name '${name}'`);
569
598
  return node;
570
599
  }
600
+ /**
601
+ * Removes one or more nodes from this tree branch.
602
+ *
603
+ * This method first validates that all specified nodes exist. If validation passes,
604
+ * it recursively calls `destroy()` on each node to ensure proper cleanup of the entire subtree.
605
+ * Finally, it emits a single `onRemove` event with the names of all successfully removed nodes.
606
+ *
607
+ * @param {string | string[]} names - A single name or an array of names of the nodes to remove.
608
+ * @throws If any of the specified names do not correspond to an existing node.
609
+ */
610
+ removeNode(names) {
611
+ const toRemoveNames = Array.isArray(names) ? names : [names];
612
+ toRemoveNames.forEach((name) => {
613
+ throwIf3(!this.has(name), `Can't remove node with name: '${name}', node doesn't exists`);
614
+ });
615
+ toRemoveNames.forEach((name) => {
616
+ this._nodes.get(name).destroy();
617
+ this._nodes.delete(name);
618
+ });
619
+ if (toRemoveNames.length) {
620
+ this.onRemove.emit({ names: toRemoveNames });
621
+ }
622
+ }
571
623
  /**
572
624
  * Creates a new `FieldTree` (branch) node at the specified path.
573
625
  * @param {PathType} path - The path where the new `FieldTree` should be created.
@@ -638,6 +690,26 @@ var FieldTree = class _FieldTree {
638
690
  const traversedPath = this.traversePath(path, true);
639
691
  return traversedPath.branch.has(traversedPath.leafName) ? traversedPath.branch.getFields(traversedPath.leafName) : traversedPath.branch.createFields(traversedPath.leafName);
640
692
  }
693
+ /**
694
+ * Removes all child nodes from this tree branch.
695
+ * This method ensures that `destroy()` is called on each child node, allowing for
696
+ * a full, recursive cleanup of the entire subtree.
697
+ */
698
+ clear() {
699
+ this.removeNode(Array.from(this._nodes.keys()));
700
+ }
701
+ /**
702
+ * Performs a complete cleanup of this node and its entire subtree.
703
+ *
704
+ * It recursively destroys all child nodes by calling `clear()` and then
705
+ * unsubscribes all listeners from its own event emitters.
706
+ * This method should be called when a node is no longer needed.
707
+ */
708
+ destroy() {
709
+ this.clear();
710
+ this.onAdd.clear();
711
+ this.onRemove.clear();
712
+ }
641
713
  /**
642
714
  * @private
643
715
  * Navigates the tree to the parent of a target node.
@@ -670,12 +742,21 @@ var FieldTree = class _FieldTree {
670
742
  };
671
743
 
672
744
  // src/field-tree-node-factory.ts
673
- var DefaultTreeNodeFactory = class {
745
+ var DefaultFieldsFactory = class {
674
746
  constructor(fieldRegistry) {
675
747
  this.fieldRegistry = fieldRegistry;
676
748
  }
677
- fields = () => new DefaultFields(this.fieldRegistry);
678
- tree = () => new FieldTree(this);
749
+ fields() {
750
+ return new DefaultFields(this.fieldRegistry);
751
+ }
752
+ };
753
+ var DefaultTreeNodeFactory = class extends DefaultFieldsFactory {
754
+ constructor(fieldRegistry) {
755
+ super(fieldRegistry);
756
+ }
757
+ tree() {
758
+ return new FieldTree(this);
759
+ }
679
760
  };
680
761
 
681
762
  // src/serializer/policies/clamp-policy-serializer-handler.ts
@@ -808,19 +889,13 @@ var FieldSerializer = class {
808
889
  var FieldsSerializer = class {
809
890
  /**
810
891
  * Creates an instance of FieldsSerializer.
811
- * @param {FieldRegistry} fieldRegistry - A registry that maps string type names to Field constructors.
812
- * @param {PolicySerializer} policySerializer - A serializer dedicated to handling Policy instances.
892
+ * @param {FieldsFactory} fieldsFactory - A registry that maps string type names to Field constructors.
893
+ * @param {FieldSerializer} fieldSerializer - A serializer of field instances.
813
894
  */
814
- constructor(fieldRegistry, policySerializer) {
815
- this.fieldRegistry = fieldRegistry;
816
- this.policySerializer = policySerializer;
817
- this.fieldSerializer = new FieldSerializer(this.fieldRegistry, this.policySerializer);
895
+ constructor(fieldsFactory, fieldSerializer) {
896
+ this.fieldsFactory = fieldsFactory;
897
+ this.fieldSerializer = fieldSerializer;
818
898
  }
819
- /**
820
- * An internal instance of FieldSerializer to handle individual fields.
821
- * @private
822
- */
823
- fieldSerializer;
824
899
  /**
825
900
  * Creates a serializable snapshot of a `Fields` container.
826
901
  *
@@ -839,14 +914,13 @@ var FieldsSerializer = class {
839
914
  /**
840
915
  * Restores a `Fields` container instance from its snapshot representation.
841
916
  *
842
- * **Limitation:** This method is currently hardcoded to always create an instance of `DefaultFields`.
843
917
  * It iterates through the field snapshots and hydrates them individually, adding them to the new container.
844
918
  * @param {FieldsSnapshot} snapshot - The plain object snapshot to deserialize.
845
- * @returns {DefaultFields} A new `DefaultFields` instance populated with the restored fields.
919
+ * @returns {Fields} A new `DefaultFields` instance populated with the restored fields.
846
920
  */
847
921
  hydrate(snapshot) {
848
922
  const { __type, ...fieldsData } = snapshot;
849
- const fields = new DefaultFields(this.fieldRegistry);
923
+ const fields = this.fieldsFactory.fields();
850
924
  for (const fieldName in fieldsData) {
851
925
  const fieldSnapshot = fieldsData[fieldName];
852
926
  const restoredField = this.fieldSerializer.hydrate(fieldSnapshot);
@@ -895,7 +969,7 @@ var FieldTreeSerializer = class {
895
969
  }
896
970
  if (nodeData.__type === FieldTree.typeName) {
897
971
  tree.addNode(key, this.hydrate(nodeData));
898
- } else {
972
+ } else if (nodeData.__type === Fields.typeName) {
899
973
  tree.addNode(key, this.fieldsSerializer.hydrate(nodeData));
900
974
  }
901
975
  }
@@ -912,6 +986,7 @@ export {
912
986
  DefaultBooleanField,
913
987
  DefaultField,
914
988
  DefaultFields,
989
+ DefaultFieldsFactory,
915
990
  DefaultNumericField,
916
991
  DefaultStringField,
917
992
  DefaultTreeNodeFactory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axi-engine/fields",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "keywords": [