npm package: @jmbaquerosanchez/store
Description: A powerful, event-driven hierarchical store system for JavaScript/TypeScript applications. This library provides a flexible way to manage application state with support for nested structures, event bubbling, persistence, and proxy-based property access.
Author: Juan Manuel Baquero
Latest version: 0.0.1

# @jmbaquerosanchez/store

A powerful, event-driven hierarchical store system for JavaScript/TypeScript applications. This library provides a flexible way to manage application state with support for nested structures, event bubbling, persistence, and proxy-based property access.

## Features

- 🏗️ **Hierarchical Structure**: Create nested stores and boxes with parent-child relationships
- 📡 **Event System**: Built-in event emission and bubbling with EventEmitter3
- 💾 **Persistence**: Optional data persistence with pluggable storage backends
- 🔗 **Proxy Access**: Seamless property-based access to stores and boxes
- 🎯 **Type Safe**: Full TypeScript support with comprehensive type definitions
- 🧪 **Well Tested**: Comprehensive test suite with 158+ tests
- ⚡ **Performant**: Optimized for large numbers of stores and boxes

## Installation

```bash
npm install @jmbaquerosanchez/store eventemitter3
```

## Quick Start

```typescript
import { Store, APP_STORE, LocalStorage } from "@jmbaquerosanchez/store";

// Initialize with storage (optional)
APP_STORE.init(new LocalStorage());

// Create a root store
const rootStore = new Store({ id: "app" });

// Create nested structure
const userStore = rootStore.createStore({ id: "user" });
const profileBox = userStore.createBox({
  id: "profile",
  value: { name: "John", age: 30 },
});

// Access via proxy
console.log((rootStore as any).user.profile.value.name); // "John"

// Listen to changes
profileBox.on("store-update", (info) => {
  console.log("Profile updated:", info.value);
});

// Update value
profileBox.value = { name: "Jane", age: 25 }; // Triggers event
```

## Core Concepts

### Store

A `Store` is a container that can hold other stores (children) and boxes. It represents a namespace or category in your application state.

### Box

A `Box` is a data container that holds a single value. It's the leaf node in your store hierarchy where actual data is stored.

### APP_STORE

A global singleton that manages storage configuration and provides persistence capabilities.

## API Reference

### Store Class

#### Constructor

```typescript
const store = new Store({
  id: string, // Unique identifier
  container: Store, // Parent store
  emitOnBoxCreation: boolean, // Emit events when boxes are created (default: true)
  bubbling: boolean, // Enable event bubbling (default: true)
  persist: boolean, // Enable persistence for child boxes (default: false)
  overrideStorage: boolean, // Override existing stored values (default: false)
});
```

#### Methods

##### `createStore(params)`

Create a child store.

```typescript
const childStore = parentStore.createStore({
  id: "child",
  emitOnCreation: true, // Emit event when created
  bubbling: true, // Enable event bubbling
  persist: false, // Don't persist by default
});
```

##### `createBox(params)`

Create a box to store data.

```typescript
const box = store.createBox({
  id: "userData",
  value: { name: "John", age: 30 },
  emitOnCreation: true, // Emit event when created (default: true)
  bubbling: true, // Enable event bubbling (default: true)
  persist: false, // Don't persist this box (default: false)
  overrideStorage: false, // Don't override stored value (default: false)
});
```

##### `createBoxFromStringPath(params)`

Create a box using a dot-separated path, creating intermediate stores as needed.

```typescript
const box = rootStore.createBoxFromStringPath({
  stringPath: "user.profile.settings",
  value: { theme: "dark", language: "en" },
  emitOnCreation: true,
  bubbling: true,
  persist: true,
});

// This creates: rootStore.user.profile.settings (box)
// Where user and profile are stores, settings is a box
```

##### `getElementFromStringPath(path)`

Get an element by its path.

```typescript
const result = rootStore.getElementFromStringPath(
  "user.profile.settings.theme"
);
console.log(result?.element); // 'dark'
console.log(result?.box); // The settings box
console.log(result?.pathFromBoxValue); // 'theme'
```

##### `getElementFromStringPathAsync(path)`

Asynchronously wait for an element to be created.

```typescript
// Wait for element to be created (useful for dynamic loading)
const result = await rootStore.getElementFromStringPathAsync(
  "future.data.value"
);
console.log(result.element); // Value when it becomes available
```

##### `saveBoxValueFromStringPath(path, value)`

Update a value within a box using a path.

```typescript
// Update nested value in a box
const updatedValue = rootStore.saveBoxValueFromStringPath(
  "user.profile.name",
  "Jane"
);
console.log(updatedValue); // The entire updated box value
```

#### Properties

##### `storeTree`

Get a plain object representation of the store hierarchy.

```typescript
const tree = rootStore.storeTree;
console.log(tree.user.profile.name); // Access values directly
```

##### `bubbling`

Get or set the bubbling behavior.

```typescript
store.bubbling = false; // Disable event bubbling
console.log(store.bubbling); // false
```

#### Proxy Access

Stores support property-based access for convenient usage:

```typescript
// Create boxes by assignment
(rootStore as any).user = { name: "John", age: 30 };
(rootStore as any).settings = { theme: "dark" };

// Access boxes
const userBox = (rootStore as any).user; // Returns Box instance
const userData = (rootStore as any).user.value; // Returns { name: 'John', age: 30 }

// Update values
(rootStore as any).user = { name: "Jane", age: 25 };
```

### Box Class

#### Constructor

```typescript
const box = new Box({
  id: string, // Unique identifier
  value: any, // Initial value
  container: Store, // Parent store
  bubbling: boolean, // Enable event bubbling (default: true)
  persist: boolean, // Enable persistence (default: false)
  overrideStorage: boolean, // Override stored value (default: false)
});
```

#### Properties

##### `value`

Get or set the box value. Setting triggers events and persistence (if enabled).

```typescript
box.value = { name: "John", age: 30 };
console.log(box.value); // { name: 'John', age: 30 }
```

##### `key`

Get the full hierarchical key of the box.

```typescript
console.log(box.key); // 'rootStore.user.profile'
```

##### `bubbling`

Control event bubbling for this box.

```typescript
box.bubbling = false; // Disable bubbling for this box
```

#### Methods

##### `persist(options)`

Manually persist the box value.

```typescript
box.persist({
  override: true, // Override existing stored value
  emitOnRetrieve: false, // Don't emit event when retrieving
  setPreviousOnRetrieve: true, // Set previous value when retrieving
});
```

##### `getValueFromStringPath(path)`

Get a nested value from the box value.

```typescript
const box = store.createBox({
  id: "config",
  value: { theme: { color: "blue", mode: "dark" } },
});

const color = box.getValueFromStringPath("theme.color"); // 'blue'
const theme = box.getValueFromStringPath("theme"); // { color: 'blue', mode: 'dark' }
const all = box.getValueFromStringPath(""); // Entire value
```

### Events

Both stores and boxes emit `store-update` events when values change.

#### Event Structure

```typescript
interface IStoreUpdatedInfo {
  value: any; // New value
  previousValue: any; // Previous value
  target: Store | Box; // Original emitter
  currentTarget: Store | Box; // Current handler in bubbling chain
}
```

#### Listening to Events

```typescript
// Listen on specific box
box.on("store-update", (info) => {
  console.log("Box changed:", info.value);
  console.log("Previous:", info.previousValue);
});

// Listen on store (receives all child events due to bubbling)
store.on("store-update", (info) => {
  console.log("Something changed in store hierarchy");
  console.log("Original source:", info.target);
  console.log("Current level:", info.currentTarget);
});

// Remove listener
const listener = (info) => console.log("Changed:", info.value);
box.on("store-update", listener);
box.off("store-update", listener);
```

#### Event Bubbling

Events bubble up from boxes to their containing stores, and from child stores to parent stores.

```typescript
const rootStore = new Store({ id: "root" });
const childStore = rootStore.createStore({ id: "child" });
const box = childStore.createBox({ id: "data", value: "test" });

// Listen at root level
rootStore.on("store-update", (info) => {
  console.log("Event bubbled to root");
  console.log("Original source:", info.target === box); // true
  console.log("Current handler:", info.currentTarget === rootStore); // true
});

// This will bubble up to rootStore
box.value = "new value";
```

#### Controlling Bubbling

```typescript
// Disable bubbling at store level
const store = new Store({ id: "isolated", bubbling: false });

// Disable bubbling at box level
const box = new Box({ id: "quiet", value: "test", bubbling: false });

// Child elements inherit bubbling settings
const childBox = store.createBox({ id: "child", value: "test" });
// childBox.bubbling will be false (inherited from store)
```

### Persistence

#### Setting up Storage

```typescript
import { APP_STORE, LocalStorage } from "@jmbaquerosanchez/store";

// Use built-in LocalStorage
APP_STORE.init(new LocalStorage());

// Or with custom namespace
APP_STORE.init(new LocalStorage("myapp_"));

// Or implement custom storage
class CustomStorage {
  load(key: string): any {
    // Load from your storage system
    return JSON.parse(localStorage.getItem(key) || "null");
  }

  save(key: string, data: any, override: boolean = true): any {
    // Save to your storage system
    if (override || !localStorage.getItem(key)) {
      localStorage.setItem(key, JSON.stringify(data));
    }
  }

  remove(key: string): void {
    // Remove from your storage system
    localStorage.removeItem(key);
  }
}

APP_STORE.init(new CustomStorage());
```

#### Persistent Boxes

```typescript
// Create persistent box
const userBox = store.createBox({
  id: "user",
  value: { name: "John", age: 30 },
  persist: true, // Enable persistence
  overrideStorage: false, // Don't override existing stored value
});

// Value will be automatically loaded from storage if it exists
// and saved whenever it changes

// Manual persistence
userBox.persist({
  override: true, // Override existing stored value
  emitOnRetrieve: true, // Emit event when loading from storage
});
```

#### Storage Keys

Storage keys are automatically generated based on the hierarchical path:

```typescript
const rootStore = new Store({ id: "app" });
const userStore = rootStore.createStore({ id: "user" });
const profileBox = userStore.createBox({
  id: "profile",
  value: data,
  persist: true,
});

// Storage key will be: 'app.user.profile'
```

## Advanced Usage

### Complex Nested Structures

```typescript
const appStore = new Store({ id: "app" });

// Create multi-level structure
const userStore = appStore.createStore({ id: "user" });
const profileStore = userStore.createStore({ id: "profile" });

// Add data at various levels
profileStore.createBox({
  id: "basic",
  value: { name: "John", email: "john@example.com" },
});
profileStore.createBox({
  id: "preferences",
  value: { theme: "dark", language: "en" },
});

// Access via different methods
console.log(appStore.storeTree.user.profile.basic.name); // 'John'
console.log((appStore as any).user.profile.basic.value.name); // 'John'

const result = appStore.getElementFromStringPath(
  "user.profile.preferences.theme"
);
console.log(result?.element); // 'dark'
```

### Dynamic Path Creation

```typescript
const rootStore = new Store({ id: "root" });

// Dynamically create nested structure
const deepBox = rootStore.createBoxFromStringPath({
  stringPath: "modules.auth.user.session",
  value: { token: "abc123", expires: Date.now() + 3600000 },
  persist: true,
});

// Automatically creates: modules (store) -> auth (store) -> user (store) -> session (box)
```

### Event-Driven Architecture

```typescript
const appStore = new Store({ id: "app" });

// Global state listener
appStore.on("store-update", (info) => {
  console.log(`State changed at: ${info.target.key}`);

  // React to specific changes
  if (info.target.key.includes("user.auth")) {
    console.log("Authentication state changed");
  }

  if (info.target.key.includes("ui.theme")) {
    console.log("Theme changed, updating UI");
  }
});

// Create reactive modules
const authBox = appStore.createBoxFromStringPath({
  stringPath: "user.auth.status",
  value: "logged-out",
});

const themeBox = appStore.createBoxFromStringPath({
  stringPath: "ui.theme.current",
  value: "light",
});

// Changes automatically trigger global listener
authBox.value = "logged-in"; // Triggers: "State changed at: app.user.auth.status"
themeBox.value = "dark"; // Triggers: "State changed at: app.ui.theme.current"
```

### Performance Optimization

```typescript
// Disable events for bulk operations
const store = new Store({ id: "bulk", emitOnBoxCreation: false });

// Create many boxes without events
for (let i = 0; i < 1000; i++) {
  store.createBox({
    id: `item${i}`,
    value: { index: i, data: `value${i}` },
    emitOnCreation: false, // Skip individual creation events
  });
}

// Re-enable events for normal operation
store.emitOnBoxCreation = true;
```

### Error Handling

```typescript
try {
  // This will throw if no storage is configured
  const box = store.createBox({
    id: "persistent",
    value: "test",
    persist: true,
  });
} catch (error) {
  console.error("Persistence not available:", error.message);
}

try {
  // This will throw if key already exists
  store.createBox({ id: "duplicate", value: "first" });
  store.createBox({ id: "duplicate", value: "second" });
} catch (error) {
  console.error("Duplicate key:", error.message);
  // error.name === 'STORE_DUPLICATE_KEYS'
}
```

## Best Practices

### 1. Organize with Meaningful Hierarchies

```typescript
const appStore = new Store({ id: "app" });

// Feature-based organization
const authStore = appStore.createStore({ id: "auth" });
const uiStore = appStore.createStore({ id: "ui" });
const dataStore = appStore.createStore({ id: "data" });

// User-related data
authStore.createBox({ id: "currentUser", value: null });
authStore.createBox({ id: "permissions", value: [] });

// UI state
uiStore.createBox({ id: "theme", value: "light" });
uiStore.createBox({ id: "sidebarOpen", value: false });

// Application data
dataStore.createBox({ id: "users", value: [] });
dataStore.createBox({ id: "settings", value: {} });
```

### 2. Use TypeScript for Better Development Experience

```typescript
interface User {
  id: string;
  name: string;
  email: string;
}

interface AppState {
  currentUser: User | null;
  theme: "light" | "dark";
  users: User[];
}

// Type-safe box creation
const userBox = store.createBox<User | null>({
  id: "currentUser",
  value: null,
});

// Type-safe access (with assertion)
const typedStore = store as any as {
  currentUser: Box & { value: User | null };
  theme: Box & { value: "light" | "dark" };
};

console.log(typedStore.currentUser.value?.name);
```

### 3. Manage Events Efficiently

```typescript
// Use specific listeners instead of global ones when possible
userBox.on("store-update", handleUserChange);

// Group related listeners
const uiListeners = {
  handleThemeChange: (info) => updateCSS(info.value),
  handleSidebarToggle: (info) => animateSidebar(info.value),
};

themeBox.on("store-update", uiListeners.handleThemeChange);
sidebarBox.on("store-update", uiListeners.handleSidebarToggle);

// Clean up when needed
Object.values(uiListeners).forEach((listener) => {
  themeBox.off("store-update", listener);
});
```

### 4. Strategic Persistence

```typescript
// Persist user preferences
const preferencesBox = store.createBox({
  id: "preferences",
  value: { theme: "light", language: "en" },
  persist: true,
  overrideStorage: false, // Respect existing user settings
});

// Don't persist temporary UI state
const loadingBox = store.createBox({
  id: "loading",
  value: false,
  persist: false, // Loading state shouldn't survive page refresh
});

// Persist important data with override
const userDataBox = store.createBox({
  id: "userData",
  value: userData,
  persist: true,
  overrideStorage: true, // Always save latest user data
});
```

## Constants and Types

### Event Constants

```typescript
import { STORE_EVENTS } from "@jmbaquerosanchez/store";

console.log(STORE_EVENTS.UPDATE); // 'store-update'
```

### Error Constants

```typescript
import { STORE_DUPLICATE_KEYS } from "@jmbaquerosanchez/store";

try {
  // ... duplicate key operation
} catch (error) {
  if (error.name === STORE_DUPLICATE_KEYS) {
    // Handle duplicate key error specifically
  }
}
```

### Type Interfaces

```typescript
import type {
  IStore,
  IBox,
  IStoreParams,
  IBoxParams,
  IStoreUpdatedInfo,
  IStorage,
  IAppStore,
} from "@jmbaquerosanchez/store";
```

## Scripts

### Run tests

```bash
npm test          # Run tests once
npm run test:dev  # Watch mode in development
```

### Build

```bash
npm run build     # Build the library
```

### Lint

```bash
npm run lint      # Check code quality
```

## Testing

The library includes a comprehensive test suite with 158+ tests covering all functionality. Tests include:

- Unit tests for all classes and methods
- Integration tests for complex scenarios
- Error handling and edge cases
- Performance and memory management
- Type safety verification

## Contributing

This library is well-tested and documented. When contributing:

1. Add tests for new functionality
2. Update documentation
3. Follow TypeScript best practices
4. Ensure all tests pass

## License

Copyright (c) 2025 [Juan Manuel Baqueros Sánchez]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to use, install, and integrate the Software for any purpose, **provided that such use is done exclusively through the official NPM package as published by the original author**.

You may not copy, modify, sublicense, or distribute copies of the source code itself outside of the official NPM distribution, nor may you publish or redistribute a modified version of this package under a different name.

Any violation of this clause will be considered a breach of license.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY.

Official NPM package: https://www.npmjs.com/package/[nombre-del-paquete]

## Changelog

### v0.0.0

- Initial release
- Core store and box functionality
- Event system with bubbling
- Persistence with LocalStorage
- Proxy-based property access
- Comprehensive TypeScript support