gnss-state
Version:
Shared singleton state service for Angular MFE architecture
1,069 lines • 76.5 kB
TypeScript
import { BehaviorSubject, Observable } from 'rxjs';
import { StateObjType } from '../types/state-obj.type';
import * as i0 from "@angular/core";
/**
* A comprehensive reactive state management service for Angular applications that provides
* centralized state storage, hierarchical organization, and robust backup/rollback functionality.
*
* This service implements a sophisticated state management pattern using RxJS BehaviorSubjects
* to enable reactive data flow throughout Angular applications. It supports both flat and
* hierarchical state organization through groups, automatic backup creation for rollback
* operations, and deep merging capabilities for complex state updates.
*
* ## Key Features
*
* - **Reactive State Management**: Built on RxJS BehaviorSubjects for reactive data flow
* - **Hierarchical Organization**: Support for grouped state subjects with nested structures
* - **Backup & Rollback**: Automatic backup creation with reset functionality for undo operations
* - **Deep Merging**: Intelligent object merging for partial state updates
* - **Type Safety**: Full TypeScript support with type guards and strict typing
* - **Memory Management**: Proper cleanup and resource management to prevent memory leaks
* - **Change Detection**: Optimized updates with deep equality checks to prevent unnecessary emissions
*
* ## Usage Patterns
*
* ### Basic State Operations
* ```typescript
* // Inject the service
* constructor(private stateModel: StateModel) {}
*
* // Set initial state
* this.stateModel.set('userProfile', { id: 1, name: 'John', email: 'john@example.com' });
*
* // Subscribe to state changes
* this.stateModel.observe('userProfile').subscribe(profile => {
* console.log('Profile updated:', profile);
* });
*
* // Update existing state
* this.stateModel.update('userProfile', { name: 'Jane Doe' });
*
* // Reset to backup value
* this.stateModel.reset('userProfile');
* ```
*
* ### Grouped State Management
* ```typescript
* // Create and populate a group
* const users = [
* { id: 'user1', name: 'John', role: 'admin' },
* { id: 'user2', name: 'Jane', role: 'user' }
* ];
* this.stateModel.loadGroup('users', 'id', users);
*
* // Access grouped subjects
* this.stateModel.observe('user1', 'users').subscribe(user => {
* console.log('User updated:', user);
* });
*
* // Update grouped state
* this.stateModel.set('currentUser', { id: 3, name: 'Bob' }, 'users');
* ```
*
* ### Form Integration with Backup/Reset
* ```typescript
* // Initialize form state
* this.stateModel.set('formData', this.form.value);
*
* // Track form changes
* this.form.valueChanges.subscribe(value => {
* this.stateModel.update('formData', value);
* });
*
* // Reset form to initial state
* onResetClick() {
* this.stateModel.reset('formData');
* this.form.patchValue(this.stateModel.value('formData'));
* }
* ```
*
* ## Architecture
*
* The service maintains several core registries:
* - **Main Directory**: Flat registry of named BehaviorSubjects
* - **Groups**: Hierarchical registry for organized state subjects
* - **Backups**: Rollback data for main directory subjects
* - **Group Backups**: Rollback data for grouped subjects
* - **Change Tracking**: Reactive notifications for state modifications
*
* ## Performance Optimizations
*
* - Deep equality checks prevent unnecessary BehaviorSubject emissions
* - Automatic cleanup prevents memory leaks
* - Smart merging preserves existing state properties
* - Lazy initialization of subjects and groups
*
* @example
* ## Complete Example: User Management with Groups
* ```typescript
* @Component({...})
* export class UserManagementComponent implements OnInit {
* constructor(private stateModel: StateModel) {}
*
* ngOnInit() {
* // Initialize user management state
* this.loadUsers();
* this.setupSubscriptions();
* }
*
* private loadUsers() {
* const users = [
* { id: 'user1', name: 'John Doe', email: 'john@example.com', active: true },
* { id: 'user2', name: 'Jane Smith', email: 'jane@example.com', active: false }
* ];
*
* // Create grouped state for users
* this.stateModel.loadGroup('users', 'id', users);
*
* // Set selected user
* this.stateModel.set('selectedUser', users[0], 'users');
* }
*
* private setupSubscriptions() {
* // React to selected user changes
* this.stateModel.observe('selectedUser', 'users').subscribe(user => {
* if (user) {
* this.loadUserDetails(user);
* }
* });
*
* // Monitor all group changes
* this.stateModel.groupList.subscribe(groups => {
* console.log('Available groups:', groups);
* });
* }
*
* updateUser(userId: string, updates: Partial<User>) {
* // Merge partial updates with existing user data
* this.stateModel.dirMerge(updates, userId, 'users');
* }
*
* resetUserChanges(userId: string) {
* // Reset user to backup state
* this.stateModel.reset(userId, 'users');
* }
* }
* ```
*
* @see {@link StateObjType} - Type definition for state values
* @see {@link BehaviorSubject} - RxJS reactive primitive used for state storage
*
* @public
* @injectable
* @since 1.0.0
*/
export declare class StateModel {
/**
* Registry of backup values for state subjects to enable rollback functionality.
*
* Stores the original values of state subjects when they are created or modified through the
* {@link StateModel.set} method. These backup values remain immutable during subsequent
* {@link StateModel.update} operations and can be restored using {@link StateModel.reset}.
* This mechanism supports undo/reset patterns commonly used in form controls and dialog
* components.
*
* @remarks
* The backup system follows these rules:
* - Backups are created/updated only by the `set()` method
* - The `update()` method does not modify backup values
* - The `reset()` method restores values from this backup registry
* - Backups are stored as deep clones to prevent reference mutations
*
* @example
* Basic backup and reset workflow:
* ```typescript
* // Setting creates backup automatically
* stateModel.set('userForm', { name: 'John', email: 'john@example.com' });
*
* // Backup contains original value
* console.log(stateModel.backups['userForm']);
* // Output: { name: 'John', email: 'john@example.com' }
*
* // Updates don't affect backup
* stateModel.update('userForm', { name: 'Jane', email: 'jane@example.com' });
*
* // Backup remains unchanged
* console.log(stateModel.backups['userForm']);
* // Output: { name: 'John', email: 'john@example.com' }
*
* // Reset restores from backup
* stateModel.reset('userForm');
* // Current value: { name: 'John', email: 'john@example.com' }
* ```
*
* @see {@link StateModel.set} - Creates and updates backup values
* @see {@link StateModel.update} - Modifies state without affecting backups
* @see {@link StateModel.reset} - Restores state from backup values
* @see {@link StateModel.groupBackups} - Backup registry for grouped subjects
*
* @since 1.0.0
*/
backups: Record<string, StateObjType>;
/**
* A registry of reactive state subjects that can be observed by any component
* throughout the application. This registry uses a key-value mapping pattern
* where each key represents a unique state identifier and each value is a
* BehaviorSubject containing the state data.
*
* This design pattern provides centralized state management with reactive
* capabilities, allowing components to subscribe to specific state changes
* without tight coupling to state producers.
*
* @example
* ```typescript
* // Subscribe to a specific state
* this.stateModel.dir['userProfile'].subscribe(profile => {
* console.log('User profile updated:', profile);
* });
*
* // Access current value
* const currentUser = this.stateModel.dir['userProfile'].value;
* ```
*
* @see {@link StateModel.observe} For safe access to subjects with automatic creation
* @see {@link StateModel.set} For creating and updating state subjects
* @see {@link StateModel.update} For modifying existing state subjects
*
* @public
* @since 1.0.0
*/
dir: Record<string, BehaviorSubject<StateObjType>>;
/**
* A reactive registry of all group names currently managed by the state system.
*
* This BehaviorSubject maintains an array of string identifiers representing
* all group names that exist in the {@link StateModel.groups} registry. It provides
* a reactive way to track when new groups are created or existing groups are removed,
* enabling components to respond to structural changes in the grouped state architecture.
*
* The group list is automatically maintained by the state management system:
* - Group names are added when {@link StateModel.loadGroup} creates new groups
* - Group names are removed when {@link StateModel.removeGroup} deletes groups
* - The list is kept in sync with the actual {@link StateModel.groups} registry
*
* @example
* Subscribe to group registry changes:
* ```typescript
* this.stateModel.groupList.subscribe(groupNames => {
* console.log('Available groups:', groupNames);
* // Output: ['users', 'settings', 'navigation']
* });
* ```
*
* @example
* Check if a specific group exists:
* ```typescript
* const hasUsersGroup = this.stateModel.groupList.value.includes('users');
* ```
*
* @see {@link StateModel.groups} - The actual group registry this list tracks
* @see {@link StateModel.loadGroup} - Method that adds group names to this list
* @see {@link StateModel.removeGroup} - Method that removes group names from this list
*
* @public
* @since 1.0.0
*/
groupList: BehaviorSubject<string[]>;
/**
* Hierarchical registry of backup values for grouped state subjects to enable rollback functionality.
*
* This two-tier backup system maintains original values for state subjects organized within groups,
* complementing the main {@link StateModel.backups} registry for ungrouped subjects. Each group
* maintains its own backup registry, where backup values remain immutable during subsequent
* {@link StateModel.update} operations and can be restored using {@link StateModel.reset}.
*
* The structure follows a group-name → subject-name → backup-value hierarchy, allowing for
* fine-grained backup management within the grouped state architecture. This supports complex
* UI patterns where different feature areas require independent rollback capabilities.
*
* @remarks
* The hierarchical backup system follows these rules:
* - Group backups are created/updated only by the `set()` method when a group name is specified
* - The `update()` method does not modify backup values in any group
* - The `reset()` method can restore individual subjects within specific groups
* - Group removal via `removeGroup()` clears all backup data for that group
* - Backups are stored as deep clones to prevent reference mutations
*
* @example
* Grouped backup and reset workflow:
* ```typescript
* // Setting with group creates backup automatically
* stateModel.set('currentUser', { id: 1, name: 'John' }, 'users');
* stateModel.set('permissions', ['read', 'write'], 'users');
*
* // Group backup structure:
* // {
* // 'users': {
* // 'currentUser': { id: 1, name: 'John' },
* // 'permissions': ['read', 'write']
* // }
* // }
*
* // Updates don't affect group backups
* stateModel.update('currentUser', { id: 1, name: 'Jane' }, 'users');
*
* // Group backup remains unchanged
* console.log(stateModel.groupBackups['users']['currentUser']);
* // Output: { id: 1, name: 'John' }
*
* // Reset specific subject within group
* stateModel.reset('currentUser', 'users');
* // Current value restored: { id: 1, name: 'John' }
* ```
*
* @see {@link StateModel.backups} - Main backup registry for ungrouped subjects
* @see {@link StateModel.groups} - The grouped subjects this backup system supports
* @see {@link StateModel.set} - Creates and updates group backup values
* @see {@link StateModel.reset} - Restores state from group backup values
* @see {@link StateModel.removeGroup} - Clears all backups for a specific group
*
* @public
* @since 1.0.0
*/
groupBackups: Record<string, Record<string, StateObjType>>;
/**
* A Directory of grouped BehaviorSubjects where each group is itself a BehaviorSubject
* containing a Record of named BehaviorSubjects. This nested structure allows for
* reactive group management where the entire group can be observed for changes,
* and individual subjects within each group can also be observed independently.
*
* The outer Record maps group names to BehaviorSubjects, where each BehaviorSubject
* contains a Record mapping subject names to their respective BehaviorSubjects.
* This enables hierarchical state management with both group-level and item-level
* reactivity.
*
* @example
* ```typescript
* // Access a specific subject within a group
* const userSubject = this.groups['users'].value['john'];
*
* // Subscribe to changes in the entire group
* this.groups['users'].subscribe(groupRecord => {
* console.log('Group changed:', Object.keys(groupRecord));
* });
*
* // Subscribe to changes in a specific subject within the group
* this.groups['users'].value['john'].subscribe(userData => {
* console.log('User data changed:', userData);
* });
* ```
*/
groups: Record<string, BehaviorSubject<Record<string, BehaviorSubject<StateObjType>>>>;
/**
* A reactive tracker that emits the name of the most recently updated state subject.
*
* This BehaviorSubject provides a centralized way to monitor state change activity
* across the entire state management system. It emits the subject name whenever
* any state modification occurs through the {@link StateModel.set}, {@link StateModel.update},
* or {@link StateModel.initiate} methods, enabling reactive side effects and UI updates
* based on specific state changes.
*
* The tracker operates across both individual subjects in {@link StateModel.dir}
* and grouped subjects in {@link StateModel.groups}, providing unified change
* detection regardless of state organization.
*
* @example
* Monitor all state changes:
* ```typescript
* this.stateModel.updated.subscribe(subjectName => {
* if (subjectName) {
* console.log(`State subject '${subjectName}' was updated`);
* // Trigger side effects, analytics, or UI updates
* }
* });
* ```
*
* @example
* React to specific subject changes:
* ```typescript
* this.stateModel.updated.pipe(
* filter(name => name === 'userProfile'),
* switchMap(() => this.stateModel.observe('userProfile'))
* ).subscribe(profile => {
* // Handle user profile changes
* });
* ```
*
* @remarks
* - Emits `undefined` on initialization
* - Always emits the subject name, not the group name for grouped subjects
* - Useful for implementing global state change listeners and audit trails
* - Can be used to invalidate caches or trigger data synchronization
*
* @see {@link StateModel.set} - Method that triggers updates when setting state
* @see {@link StateModel.update} - Method that triggers updates when modifying state
* @see {@link StateModel.initiate} - Method that triggers updates when initializing state
*
* @public
* @since 1.0.0
*/
updated: BehaviorSubject<string | undefined>;
/**
* Type guard that determines whether a value is a plain object suitable for deep merging operations.
*
* This utility method implements a type-safe check to identify plain objects created by object
* literals or the Object constructor, distinguishing them from arrays, dates, class instances,
* and other specialized object types. It uses the reliable `Object.prototype.toString.call()`
* approach to ensure accurate type detection across different JavaScript contexts and
* inheritance chains.
*
* Plain objects are defined as objects with `[object Object]` as their internal [[Class]] slot,
* which includes:
* - Object literals: `{ key: 'value' }`
* - Objects created with `new Object()`
* - Objects created with `Object.create(Object.prototype)`
*
* Non-plain objects that return `false` include:
* - Arrays: `[1, 2, 3]`
* - Dates: `new Date()`
* - RegExp: `/pattern/`
* - Functions: `() => {}`
* - Class instances: `new MyClass()`
* - Built-in objects: `Math`, `JSON`, etc.
* - Primitives: `null`, `undefined`, strings, numbers, booleans
*
* @param val - The value to test for plain object status. Accepts any type including
* primitives, objects, arrays, null, or undefined.
*
* @returns Type predicate indicating whether the value is a plain object. When `true`,
* TypeScript narrows the type to `Record<string, unknown>`, enabling safe property access
* and iteration. When `false`, the value is not suitable for deep merging operations.
*
* @example
* Basic plain object detection:
* ```typescript
* this.isPlainObject({}); // true
* this.isPlainObject({ name: 'John' }); // true
* this.isPlainObject(new Object()); // true
* this.isPlainObject([1, 2, 3]); // false
* this.isPlainObject(new Date()); // false
* this.isPlainObject(null); // false
* this.isPlainObject('string'); // false
* ```
*
* @example
* Usage in merge operations:
* ```typescript
* if (this.isPlainObject(sourceObj) && this.isPlainObject(targetObj)) {
* // Safe to perform recursive merge
* return this.merge(sourceObj, targetObj);
* } else {
* // Replace non-plain objects completely
* return sourceObj;
* }
* ```
*
* @example
* Type narrowing with type guard:
* ```typescript
* function processValue(input: unknown) {
* if (this.isPlainObject(input)) {
* // TypeScript knows 'input' is Record<string, unknown>
* console.log(Object.keys(input)); // No type error
* return input.someProperty; // Safe property access
* }
* // Handle non-plain objects differently
* return input;
* }
* ```
*
* @remarks
* - Uses `Object.prototype.toString.call()` for reliable cross-frame type detection
* - Serves as a TypeScript type guard for enhanced type safety in merge operations
* - Essential for preventing incorrect recursive merging of arrays, dates, and class instances
* - Performance is O(1) with minimal overhead for type checking
* - Does not check for object enumerability or property descriptors
*
* @see {@link StateModel.merge} - Primary consumer of this type guard for safe object merging
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
*
* @internal
* @since 1.0.0
*/
private isPlainObject;
/**
* Retrieves the current values from all BehaviorSubjects in the state registry.
*
* Extracts and returns the current values from all registered state subjects,
* either from the main {@link StateModel.dir} registry or from a specific group
* within {@link StateModel.groups}. The returned object uses subject names as keys
* and their current values as the corresponding values, providing a snapshot
* of the current state at the time of invocation.
*
* Values are deep cloned through the {@link StateModel.value} method to prevent
* external mutations from affecting the state management system.
*
* @param groupName - Optional group name to limit extraction to subjects within
* a specific group. When `null` (default), retrieves values from all subjects
* in the main directory. When specified, only retrieves values from subjects
* within the named group.
*
* @returns A record object where keys are subject names and values are the
* current state values of the corresponding BehaviorSubjects. Returns an
* empty object if no subjects exist or if the specified group doesn't exist.
*
* @example
* Get all values from the main directory:
* ```typescript
* const allValues = this.stateModel.getDirValues();
* // Returns: { userProfile: {...}, settings: {...}, navigation: {...} }
* ```
*
* @example
* Get values from a specific group:
* ```typescript
* const userValues = this.stateModel.getDirValues('users');
* // Returns: { currentUser: {...}, permissions: [...] }
* ```
*
* @see {@link StateModel.dir} - Main state subject registry
* @see {@link StateModel.groups} - Grouped state subject registry
* @see {@link StateModel.value} - Method used internally to extract values
*
* @public
* @since 1.0.0
*/
getDirValues(groupName?: string | null): Record<string, StateObjType>;
/**
* Determines whether a BehaviorSubject with the specified name exists in the state registry.
*
* This method performs existence checks in either the main {@link StateModel.dir} registry
* or within a specific group in {@link StateModel.groups}. When a group name is provided,
* the method first verifies the group exists before checking for the subject within that group.
* This dual-level checking supports the hierarchical state management architecture.
*
* @param subjectName - The name of the BehaviorSubject to check for existence.
* This parameter is required and represents the unique identifier for the state subject.
* @param groupName - Optional group name to limit the search to subjects within
* a specific group. When `null` (default), searches in the main directory registry.
* When specified, searches only within the named group.
*
* @returns `true` if the subject exists in the specified location (main directory
* or within the specified group), `false` otherwise. Returns `false` if the
* specified group doesn't exist.
*
* @example
* Check for subject in main directory:
* ```typescript
* const hasUserProfile = this.stateModel.has('userProfile');
* // Returns true if 'userProfile' exists in main directory
* ```
*
* @example
* Check for subject within a specific group:
* ```typescript
* const hasCurrentUser = this.stateModel.has('currentUser', 'users');
* // Returns true if 'currentUser' exists within 'users' group
* ```
*
* @see {@link StateModel.dir} - Main state subject registry
* @see {@link StateModel.groups} - Grouped state subject registry
* @see {@link StateModel.observe} - Method for safely accessing subjects with automatic creation
*
* @public
* @since 1.0.0
*/
has(subjectName: string, groupName?: string | null): boolean;
/**
* Initializes or resets a BehaviorSubject to an undefined state within the state management system.
*
* This method creates new BehaviorSubjects with an initial value of `undefined`, or resets existing
* subjects to `undefined` if they currently contain other values. It supports both standalone subjects
* in the main {@link StateModel.dir} registry and grouped subjects within {@link StateModel.groups}.
* When working with grouped subjects, the method will automatically create the parent group if it
* doesn't exist.
*
* The initialization process includes:
* - Creating new BehaviorSubjects with `undefined` as the initial value
* - Resetting existing subjects to `undefined` (no-op if already `undefined`)
* - Setting up corresponding backup entries for rollback functionality
* - Auto-creating group structures when specified
* - Triggering the {@link StateModel.updated} notification system
*
* @param subjectName - The unique identifier for the BehaviorSubject to initialize or reset.
* Must be a non-empty string that will serve as the key in the state registry.
* @param groupName - Optional group name to organize the subject within a hierarchical structure.
* When `null` (default), the subject is created in the main directory. When specified, the subject
* is created within the named group, and the group itself is created if it doesn't exist.
*
* @returns void - This method performs side effects but returns no value.
*
* @example
* Initialize a standalone subject:
* ```typescript
* // Creates a new BehaviorSubject with undefined value
* this.stateModel.initiate('userProfile');
*
* // Access the initialized subject
* this.stateModel.observe('userProfile').subscribe(value => {
* console.log(value); // undefined initially
* });
* ```
*
* @example
* Initialize a subject within a group:
* ```typescript
* // Creates both 'users' group and 'currentUser' subject if they don't exist
* this.stateModel.initiate('currentUser', 'users');
*
* // Reset an existing grouped subject to undefined
* this.stateModel.set('currentUser', { id: 1, name: 'John' }, 'users');
* this.stateModel.initiate('currentUser', 'users'); // Resets to undefined
* ```
*
* @example
* Resetting existing subjects:
* ```typescript
* // Subject with existing data
* this.stateModel.set('settings', { theme: 'dark', lang: 'en' });
*
* // Reset to undefined (triggers backup and notification)
* this.stateModel.initiate('settings');
*
* // No-op if already undefined
* this.stateModel.initiate('settings'); // No change, no notification
* ```
*
* @remarks
* - Early return optimization: If the target subject already has an `undefined` value, the method returns early without triggering notifications
* - Group auto-creation: When a group name is specified but the group doesn't exist, both the group and backup registry are automatically created
* - Backup management: All initiated subjects receive corresponding backup entries set to `undefined`
* - Notification system: The {@link StateModel.updated} BehaviorSubject is notified with the subject name after successful operations
*
* @see {@link StateModel.set} - For setting subjects with specific initial values
* @see {@link StateModel.observe} - For safely accessing and subscribing to subjects
* @see {@link StateModel.reset} - For restoring subjects from backup values
* @see {@link StateModel.loadGroup} - For bulk group creation with multiple subjects
*
* @public
* @since 1.0.0
*/
initiate(subjectName: string, groupName?: string | null): void;
/**
* Performs deep equality comparison between two values to determine if they are structurally identical.
*
* This utility method implements recursive deep comparison for complex data structures including
* objects, arrays, and primitives. It's used throughout the state management system to prevent
* unnecessary BehaviorSubject emissions when values haven't actually changed, optimizing
* performance and preventing infinite subscription loops.
*
* The comparison follows these rules:
* - Uses strict equality (`===`) for primitives and identical references
* - Considers `null` and `undefined` as distinct values (not equal to each other)
* - Recursively compares array elements in order
* - Recursively compares object properties by key-value pairs
* - Returns `false` for mismatched types or structures
*
* @param value - The first value to compare. Can be any type including primitives,
* objects, arrays, null, or undefined.
* @param other - The second value to compare against. Can be any type including
* primitives, objects, arrays, null, or undefined.
*
* @returns `true` if the values are deeply equal, `false` if they differ in structure
* or content, or `undefined` if the comparison cannot be determined (currently this
* method always returns a boolean).
*
* @example
* Primitive comparisons:
* ```typescript
* this.stateModel.isEqual(5, 5); // true
* this.stateModel.isEqual('hello', 'hi'); // false
* this.stateModel.isEqual(null, undefined); // false
* ```
*
* @example
* Array comparisons:
* ```typescript
* this.stateModel.isEqual([1, 2, 3], [1, 2, 3]); // true
* this.stateModel.isEqual([1, 2], [1, 2, 3]); // false
* this.stateModel.isEqual([[1, 2]], [[1, 2]]); // true (nested arrays)
* ```
*
* @example
* Object comparisons:
* ```typescript
* const obj1 = { name: 'John', age: 30, hobbies: ['read', 'code'] };
* const obj2 = { name: 'John', age: 30, hobbies: ['read', 'code'] };
* const obj3 = { age: 30, name: 'John', hobbies: ['read', 'code'] };
*
* this.stateModel.isEqual(obj1, obj2); // true
* this.stateModel.isEqual(obj1, obj3); // true (key order doesn't matter)
* ```
*
* @example
* Usage in state management optimization:
* ```typescript
* // Prevents unnecessary emissions
* const currentValue = this.dir['userProfile'].value;
* if (!this.isEqual(currentValue, newValue)) {
* this.dir['userProfile'].next(newValue);
* }
* ```
*
* @remarks
* - This method is used internally by {@link StateModel.set}, {@link StateModel.update},
* and {@link StateModel.reset} to optimize performance
* - Handles circular references gracefully by comparing object structure
* - Performance scales with object depth and complexity
* - Does not compare object prototypes or non-enumerable properties
*
* @see {@link StateModel.set} - Uses this method to prevent duplicate state updates
* @see {@link StateModel.update} - Uses this method for change detection
* @see {@link StateModel.dirEquals} - Higher-level equality check for directory objects
*
* @public
* @since 1.0.0
*/
isEqual(value: unknown, other: unknown): boolean | undefined;
/**
* Creates and initializes a hierarchical group within the state management system.
*
* This method establishes a new group in the {@link StateModel.groups} registry as a
* BehaviorSubject containing a Record of named BehaviorSubjects. The group provides
* hierarchical state organization where both the group container and individual subjects
* within it are reactive. Optionally populates the group with BehaviorSubjects created
* from an array of objects, using a specified property as the subject identifier.
*
* The method performs the following operations:
* - Creates the group BehaviorSubject if it doesn't exist
* - Optionally creates individual BehaviorSubjects from object array using keyName as identifier
* - Updates the reactive {@link StateModel.groupList} registry
* - Maintains referential integrity within the hierarchical state structure
*
* @param groupName - The unique identifier for the group to create or initialize.
* This name will be used as the key in the {@link StateModel.groups} registry.
* @param keyName - Optional property name within objects to use as BehaviorSubject
* identifiers. When provided with `objArray`, this property's value becomes the
* subject name within the group. Must exist as a string or number property in each object.
* @param objArray - Optional array of objects to transform into BehaviorSubjects within
* the group. Each object becomes the initial value of a BehaviorSubject, with the
* subject name derived from the object's `keyName` property.
*
* @example
* Create an empty group:
* ```typescript
* this.stateModel.loadGroup('users');
* // Creates: groups['users'] = BehaviorSubject<Record<string, BehaviorSubject<StateObjType>>>({})
* ```
*
* @example
* Populate group from object array:
* ```typescript
* const userData = [
* { id: 'user1', name: 'John', role: 'admin' },
* { id: 'user2', name: 'Jane', role: 'user' }
* ];
* this.stateModel.loadGroup('users', 'id', userData);
* // Creates subjects: groups['users'].value['user1'], groups['users'].value['user2']
* ```
*
* @see {@link StateModel.groups} - The hierarchical group registry this method modifies
* @see {@link StateModel.groupList} - Reactive list of all group names, updated by this method
* @see {@link StateModel.removeGroup} - Method for removing entire groups
* @see {@link StateModel.observe} - Method for accessing subjects within groups
*
* @public
* @since 1.0.0
*/
loadGroup(groupName: string, keyName?: string | null, objArray?: unknown[] | null): void;
/**
* Performs a deep merge of two objects, combining their properties into a new object.
*
* This method recursively merges nested objects while preserving the structure of both input objects.
* Properties from `newObj` take precedence over properties from `oldObj`. For nested plain objects,
* the merge operation continues recursively. For primitives, arrays, dates, and other non-plain
* object types, values from `newObj` completely replace corresponding values from `oldObj`.
*
* The merge operation follows these rules:
* - If `newObj` is not a plain object (null, undefined, array, etc.), returns `oldObj` unchanged
* - If `oldObj` is null or undefined, treats it as an empty object for merging purposes
* - Nested plain objects are merged recursively using the same rules
* - Non-object values (primitives, arrays, dates, etc.) from `newObj` replace values in `oldObj`
* - Returns a new object without mutating the original input objects
*
* @param newObj - The source object whose properties will be merged into the base object.
* If not a plain object, the merge operation returns `oldObj` unchanged.
* @param oldObj - The base object to merge into. Null or undefined values are treated as empty objects.
*
* @returns A new merged object containing properties from both inputs, with `newObj` properties
* taking precedence. Returns `oldObj` unchanged if `newObj` is not a plain object.
*
* @example
* Basic object merging:
* ```typescript
* const base = { name: 'John', age: 30, city: 'New York' };
* const updates = { age: 31, country: 'USA' };
* const result = this.stateModel.merge(updates, base);
* // Result: { name: 'John', age: 31, city: 'New York', country: 'USA' }
* ```
*
* @example
* Nested object merging:
* ```typescript
* const base = { user: { name: 'John', settings: { theme: 'light', lang: 'en' } } };
* const updates = { user: { settings: { theme: 'dark' } } };
* const result = this.stateModel.merge(updates, base);
* // Result: { user: { name: 'John', settings: { theme: 'dark', lang: 'en' } } }
* ```
*
* @example
* Handling non-plain objects:
* ```typescript
* const base = { items: [1, 2, 3], date: new Date('2023-01-01') };
* const updates = { items: [4, 5], date: new Date('2024-01-01') };
* const result = this.stateModel.merge(updates, base);
* // Arrays and dates are replaced, not merged
* // Result: { items: [4, 5], date: new Date('2024-01-01') }
* ```
*
* @example
* State update pattern:
* ```typescript
* // Merge partial user profile updates
* const currentProfile = this.stateModel.value('userProfile');
* const profileUpdates = { preferences: { notifications: false } };
* const mergedProfile = this.stateModel.merge(profileUpdates, currentProfile);
* this.stateModel.set('userProfile', mergedProfile);
* ```
*
* @remarks
* - Uses {@link StateModel.isPlainObject} to determine if objects can be merged recursively
* - Preserves prototype chains and does not modify input objects
* - Performance scales with object depth and number of properties
* - Ideal for implementing partial state updates and configuration merging
*
* @see {@link StateModel.isPlainObject} - Helper method used to identify mergeable objects
* @see {@link StateModel.update} - Method that could benefit from merge operations
* @see {@link StateModel.set} - Method for applying merged results to state
*
* @public
* @since 1.0.0
*/
merge(newObj: Record<string, unknown> | null | undefined, oldObj: Record<string, unknown> | null | undefined): Record<string, unknown> | null | undefined;
/**
* Compares an external object with the current value of a state subject for
* deep equality.
*
* This method performs deep equality comparison between a provided object and the current
* value of a BehaviorSubject stored in either the main {@link StateModel.dir} registry
* or within a specific group in {@link StateModel.groups}. It leverages the internal
* {@link StateModel.isEqual} method to provide structural comparison rather than reference
* equality, making it ideal for change detection and validation scenarios.
*
* The comparison process follows this hierarchy:
* 1. First checks for the subject in the main directory registry
* 2. If not found and a group name is provided, searches within the specified group
* 3. Returns `undefined` if the subject doesn't exist in either location
* 4. Performs deep equality comparison if the subject is found
*
* @param obj - The external object to compare against the state subject's current value.
* Can be any type including primitives, objects, arrays, null, or undefined.
* @param dirObjName - The name of the state subject to compare against. This identifier
* is used to locate the BehaviorSubject in either the main directory or specified group.
* @param groupName - Optional group name to limit the search to subjects within a specific
* group. When `null` (default), searches only in the main directory. When specified,
* searches only within the named group if not found in the main directory.
*
* @returns `true` if the objects are deeply equal, `false` if they differ in structure
* or content, or `undefined` if the specified subject doesn't exist in the target location.
*
* @example
* Compare with main directory subject:
* ```typescript
* const userProfile = { id: 1, name: 'John', email: 'john@example.com' };
* const isEqual = this.stateModel.dirEquals(userProfile, 'currentUser');
*
* if (isEqual === true) {
* console.log('Objects are identical');
* } else if (isEqual === false) {
* console.log('Objects differ');
* } else {
* console.log('Subject does not exist');
* }
* ```
*
* @example
* Compare with grouped subject:
* ```typescript
* const settingsData = { theme: 'dark', language: 'en', notifications: true };
* const isEqual = this.stateModel.dirEquals(settingsData, 'preferences', 'user');
*
* // Use result for change detection
* if (isEqual === false) {
* // Settings have changed, update UI or trigger side effects
* this.stateModel.set('preferences', settingsData, 'user');
* }
* ```
*
* @example
* Form validation use case:
* ```typescript
* // Check if form data matches current state before submission
* const formData = this.userForm.value;
* const hasChanges = this.stateModel.dirEquals(formData, 'userProfile') === false;
*
* if (hasChanges) {
* // Enable save button or show unsaved changes indicator
* this.showUnsavedChanges = true;
* }
* ```
*
* @remarks
* - This method is particularly useful for change detection in reactive forms and components
* - The comparison is structural, not referential, allowing for meaningful equality checks
* - Returning `undefined` allows callers to distinguish between "different values" and "subject not found"
* - Performance scales with object complexity due to deep comparison algorithm
*
* @see {@link StateModel.isEqual} - The underlying deep equality comparison method
* @see {@link StateModel.dir} - Main state subject registry searched by this method
* @see {@link StateModel.groups} - Grouped state registry searched when group name is specified
* @see {@link StateModel.has} - Method to check subject existence without value comparison
*
* @public
* @since 1.0.0
*/
dirEquals(obj: unknown, dirObjName: string, groupName?: string | null): boolean | undefined;
/**
* Merges a new object with the current value of a state subject, updating the subject if changes are detected.
*
* This method provides intelligent merging capabilities by combining a new object with the existing
* value of a BehaviorSubject stored in either the main {@link StateModel.dir} registry or within
* a specific group in {@link StateModel.groups}. It leverages the internal {@link StateModel.merge}
* method for deep merging and automatically updates the state subject only when the merged result
* differs from the current value, providing optimization for unnecessary operations.
*
* The merge operation follows this workflow:
* 1. Locates the target state subject in the main directory or specified group
* 2. If the subject doesn't exist, creates it using {@link StateModel.set} with the new object
* 3. If the subject exists, performs a deep merge of the new object with the current value
* 4. Updates the subject only if the merged result differs from the current value
* 5. Returns the final merged result for further use
*
* This method is ideal for implementing partial state updates, configuration merging, and
* incremental data modifications without losing existing state properties.
*
* @param newObj - The source object to merge with the current state subject value.
* Properties from this object take precedence during the merge operation. If not a plain
* object (null, undefined, array, etc.), the merge may not behave as expected.
* @param dirObjName - The name of the state subject to merge with. This identifier is used
* to locate the BehaviorSubject in either the main directory or specified group.
* @param groupName - Optional group name to locate the subject within a hierarchical structure.
* When `null` (default), searches in the main directory. When specified, searches within
* the named group and creates the subject there if it doesn't exist.
*
* @returns The merged object result, or the original `newObj` if the target subject didn't
* exist and was newly created. Returns `undefined` if the merge operation cannot be completed.
*
* @example
* Merge with existing main directory subject:
* ```typescript
* // Existing state
* this.stateModel.set('userProfile', { name: 'John', email: 'john@example.com', age: 30 });
*
* // Merge partial updates
* const updates = { email: 'john.doe@example.com', city: 'New York' };
* const result = this.stateModel.dirMerge(updates, 'userProfile');
* // Result: { name: 'John', email: 'john.doe@example.com', age: 30, city: 'New York' }
* ```
*
* @example
* Merge with grouped subject:
* ```typescript
* // Existing grouped state
* this.stateModel.set('preferences', { theme: 'light', language: 'en' }, 'settings');
*
* // Merge new preferences
* const newPrefs = { theme: 'dark', notifications: true };
* const result = this.stateModel.dirMerge(newPrefs, 'preferences', 'settings');
* // Result: { theme: 'dark', language: 'en', notifications: true }
* ```
*
* @example
* Auto-creation when subject doesn't exist:
* ```typescript
* // Subject doesn't exist yet
* const initialData = { status: 'active', priority: 'high' };
* const result = this.stateModel.dirMerge(initialData, 'taskState');
* // Creates new subject with initialData and returns it
* ```
*
* @example
* Nested object merging:
* ```typescript
* // Existing nested state
* this.stateModel.set('appConfig', {
* ui: { theme: 'light', sidebar: 'expanded' },
* api: { timeout: 5000, retries: 3 }
* });
*
* // Merge partial nested updates
* const updates = { ui: { theme: 'dark' }, api: { timeout: 8000 } };
* const result = this.stateModel.dirMerge(updates, 'appConfig');
* // Result: {
* // ui: { theme: 'dark', sidebar: 'expanded' },
* // api: { timeout: 8000, retries: 3 }
* // }
* ```
*
* @remarks
* - Uses {@link StateModel.merge} internally for deep merging operations
* - Automatically creates missing subjects using {@link StateModel.set} when needed
* - Updates subjects only when merged results differ from current values (performance optimization)
* - Supports both standalone subjects and grouped subjects within hierarchical organization
* - The merge operation preserves existing properties not present in the new object
* - Does not affect backup values - only updates current state values
*
* @see {@link StateModel.merge} - The underlying deep merge method used by this operation
* @see {@link StateModel.set} - Method used to create subjects when they don't exist
* @see {@link StateModel.isEqual} - Method used for change detection optimization
* @see {@link StateModel.dir} - Main state subject registry
* @see {@link StateModel.groups} - Grouped state registry for hierarchical organization
* @see {@link StateModel.dirEquals} - Method for comparing objects with state subjects
*
* @public
* @since 1.0.0
*/
dirMerge(newObj: Record<string, unknown> | null | undefined, dirObjName: string, groupName?: string | null): Record<string, unknown> | null | undefined;
/**
* Provides safe access to a BehaviorSubject with automatic initialization if it doesn't exist.
*
* This method serves as the primary access point for retrieving BehaviorSubjects from either
* the main {@link StateModel.dir} registry or from grouped subjects within {@link StateModel.groups}.
* It implements a fail-safe pattern by automatically creating missing subjects or groups with
* `undefined` initial values, enabling reactive subscription patterns even when the target
* state hasn't been explicitly initialized yet.
*
* The method follows a hierarchical search pattern:
* - For ungrouped subjects: searches in the main directory registry
* - For grouped subjects: searches within the specified group, creating the group if necessary
* - Auto-initializes missing subjects using {@link StateModel.initiate} with `undefined` values
* - Guarantees that a valid BehaviorSubject is always returned for subscription
*
* This design pattern supports reactive programming where components can subscribe to state
* changes before the state producer has set initial values, preventing timing-related issues
* in component initialization and data flow.
*
* @param subjectName - The unique identifier for the BehaviorSubject to retrieve or create.
* Must be a non-empty string that serves as the key in the state registry.
* @param groupName - Optional group name to locate the subject within a hierarchical structure.
* When `null` (default), searches in the main directory. When specified, searches within
* the named group and creates the group if it doesn't exist.
*
* @returns A BehaviorSubject that can be immediately subscribed to. If the subject didn't
* exist, it will be initialized with an `undefined` value and can be updated later through
* {@link StateModel.set} or {@link StateModel.update} methods.
*
* @example
* Subscribe to an ungrouped subject:
* ```typescript
* // Safe subscription even if 'userProfile' doesn't exist yet
* this.stateModel.observe('userProfile').subscribe(profile => {
* if (profile) {
* console.log('User profile loaded:', profile);
* } else {
* console.log('User profile not yet initialized');
* }
* });
* ```
*
* @exa