npm package: @razaman2/data-manager
Description: A highly robust, reactive, and path-based data management container for TypeScript and JavaScript. Built on top of `@razaman2/object-manager`, it provides a seamless way to manipulate deeply nested object states using string paths, while emitting fine-gra
Author: razaman2
Latest version: 3.3.12

# @razaman2/data-manager

A highly robust, reactive, and path-based data management container for TypeScript and JavaScript. Built on top of `@razaman2/object-manager`, it provides a seamless way to manipulate deeply nested object states using string paths, while emitting fine-grained events for granular reactivity.

## Features

- **Dot-Notation Paths**: Access or update deep nested properties without manual traversal (e.g., `user.profile.settings.theme`).
- **Reactive Event Emitters**: Granular pub/sub hooks into specific state paths whenever they mutate.
- **Smart Merging**: Intelligently handles `defaultData` versus current `data`, prioritizing structural stability.
- **Ignored Paths**: Protect specific keys or paths from being accessed or modified via regex, strings, or functions.
- **TypeScript Ready**: Full generic `<T>` support allows strict type enforcing on your state container.

---

## Installation

```bash
npm install @razaman2/data-manager @razaman2/object-manager @razaman2/event-emitter
```

---

## Core Concepts

The `DataManager` works by managing a source of truth `data` object, but it also respects a `defaultData` payload. When the manager initializes, it deep-merges `defaultData` and the provided `data`. If state is ever wiped via `replaceData()`, it falls back exactly to the `defaultData` structure natively.

```typescript
import DataManager from "@razaman2/data-manager";

const manager = new DataManager({
    defaultData: { count: 0, theme: "light" }, // Fallback state
    data: { theme: "dark" }                    // User's active state
});

// Merged result: { count: 0, theme: "dark" }
```

---

## TypeScript Usage

You can pass a custom type interface `T` into `DataManager<T>` to strictly type the underlying object graph.

```typescript
interface AppState {
    user: {
        id: number;
        role: "admin" | "user";
    };
    preferences: Record<string, boolean>;
}

// Initializing strictly
const store = new DataManager<AppState>({
    data: {
        user: { id: 1, role: "admin" },
        preferences: { notifications: true }
    }
});
```

---

## Detailed API Reference

### `getData(path?: string | number, alternative?: any): any`
Fetches data from the state tree.
- **`path`**: A dot-notation string indicating the deep path to fetch. Omitting it returns the entire state object.
- **`alternative`**: A fallback value if the target path is `undefined`.

```typescript
store.getData(); // Full object
store.getData("user.role"); // "admin"
store.getData("preferences.newsletter", false); // returns false (alternative fallback)
```

### `setData(path: string | number, value: any): this`
### `setData(value: any): this`
Sets or deeply updates the object state. If updating via an object, it correctly patches only the paths provided.
```typescript
// Path-based assignment
store.setData("preferences.newsletter", true);
store.setData("user.roles.0", "superadmin");

// Deep Object Patching
store.setData({
    user: { id: 2 } 
}); // Doesn't destroy `user.role`, just patches `id`
```

### `replaceData(additions?: any, removals?: Set<string | number> | Array<string | number>): this`
Completely replaces the internal data tree, reverting first to `defaultData` and then assigning the new `additions`.
- **`additions`**: The new data object to cleanly adopt.
- **`removals`**: An Array or Set of specific dot-notation paths to rip out before applying new data.

```typescript
// Resets to defaultData, completely destroying current state
store.replaceData(); 

// Replace state entirely with new data
store.replaceData({ user: { id: 99, role: "user" } });

// Selectively remove specific paths while keeping the rest intact
store.replaceData(null, ["user.preferences"]); 
```

### `setIgnoredPath(match: string | RegExp | ((path: string) => boolean)): this`
Blocks the underlying object-manager from traversing into or mutating paths that match the specified constraints.

```typescript
// Block by Exact Match
store.setIgnoredPath("user.id");

// Block by Regex (protects anything inside preferences)
store.setIgnoredPath(/^preferences\..*/);

// Block by evaluation function
store.setIgnoredPath((path) => path.includes("secret"));
```

---

## Advanced Reactivity: Event Hooks

`DataManager` automatically emits changes whenever parts of the object are modified, making it perfectly suited as a global state manager for UI frameworks. To enable this, pass an `@razaman2/event-emitter` into the configuration.

It triggers two distinct events:
1. `localWrite`: Triggers whenever **any** state is set.
2. `localWrite.<path>`: Triggers for **every granular path** that is updated.

```typescript
import EventEmitter from "@razaman2/event-emitter";

const notifications = new EventEmitter();

const manager = new DataManager({
    notifications,
    data: { title: "Hello", views: 0 }
});

// Hook into specific property
notifications.on("localWrite.views", (newViews, oldViews, instance) => {
    console.log(`Views updated from ${oldViews} to ${newViews}`);
});

manager.setData("views", 1); 
// Console: "Views updated from 0 to 1"
```

## Internal Logging

If you want to trace state mutations during development, simply pass the `logging: true` flag in the `DataClient` config.

```typescript
const debugManager = new DataManager({
    logging: true,
    data: { app: "running" }
});
```
Every `setData` execution will result in a visual console log tracing the previous and modified states.

---

## Cookbook / Real-World Examples

### Example 1: Redux-Style State Container
You can comfortably use `DataManager` similarly to Redux, where a singular payload triggers updates.

```typescript
const store = new DataManager({
    data: { items: [], total: 0 }
});

const dispatchAction = (action: { type: string, payload: any }) => {
    switch (action.type) {
        case "ADD_ITEM":
            // Fetch existing
            const currentItems = store.getData("items");
            
            // Set individually using paths
            store.setData("items", [...currentItems, action.payload]);
            store.setData("total", store.getData("total") + action.payload.price);
            break;
    }
};

dispatchAction({ type: "ADD_ITEM", payload: { name: "Apple", price: 5 } });
```

### Example 2: API Payload Settings Merge
A fantastic use-case of `DataManager` is resolving partial settings fetched from a remote server without destroying local fallbacks.

```typescript
const localDefaults = {
    volume: 100,
    graphics: {
        resolution: "1920x1080",
        vsync: true,
        advanced: { shadows: "high" }
    }
};

const userSettings = new DataManager({
    defaultData: localDefaults,
});

// We fetch completely new settings from an API
const apiSettingsPayload = {
    graphics: { resolution: "2560x1440" }
    // Notice how they omitted volume, vsync, and advanced properties.
};

// Reset the data completely (to defaults), and then deeply patch the API settings into it
userSettings.replaceData().setData(apiSettingsPayload);

console.log(userSettings.getData());
// Results in:
// {
//      volume: 100, 
//      graphics: { resolution: "2560x1440", vsync: true, advanced: { shadows: "high" } }
// }
```

### Example 3: Form Inputs and Bulk Patching

When dealing with deep forms in a React or Vue component, it solves state tree clashing gracefully.

```typescript
const formState = new DataManager({
    data: {
        user: { firstName: "", lastName: "" },
        notifications: false
    }
});

// Single input change handles paths implicitly
formState.setData("user.firstName", "Ainsley");

// Or dispatch an entire bulk patch seamlessly
formState.setData({
    user: { lastName: "Clarke" },
    notifications: true
});

// The result contains the fully bound tree merged together natively.
```