store

A deeply reactive store for managing application state, with full type safety and flexible readonly controls.

API

function store<T>(initial: T, options?: StoreOptions<T>): Store<T>

interface Store<T> {
  // Properties from T are converted to signals or nested stores
  [K in keyof T]: T[K] extends (...args: any[]) => any
    ? T[K]
    : T[K] extends any[]
    ? Signal<T[K]>
    : T[K] extends Record<string, any>
    ? Store<T[K]>
    : Signal<T[K]>;
  snapshot: () => T; // Reactive snapshot of entire store state
  set(value: T): void; // Replace entire store state
  update(partial: PartialDeep<T>): void; // Partial update
  cleanup(): void; // Cleanup reactivity
}

interface StoreOptions<T> {
  readonly?: boolean | readonly (keyof T)[];
}

snapshot

A reactive snapshot of the entire store state as a plain JavaScript object.

import { store } from '@hellajs/store';

const appStore = store({ count: 0, user: { name: 'Alice' } });

// Get a reactive snapshot of the entire store
const snapshot = appStore.snapshot();
console.log(snapshot); // { count: 0, user: { name: 'Alice' } }

set

Replaces the entire store state with a new value.

import { store } from '@hellajs/store';

const appStore = store({ count: 0, user: { name: 'Alice' } });

appStore.set({ count: 5, user: { name: 'Bob' } }); // Replace entire state

console.log(appStore.count()); // 5
console.log(appStore.user.name()); // Bob

update

Partial deep updates to the store state. Only updates properties that exist in the original store, performing deep merging for nested objects.

import { store } from '@hellajs/store';

const appStore = store({ count: 0, user: { name: 'Alice' } });

appStore.update({
  count: 5,  // Updates count only
  age: 30 // Ignored, not in initial state
});

console.log(appStore.count()); // 5

cleanup

Cleans up all reactive subscriptions to prevent memory leaks.

import { store } from '@hellajs/store';

const appStore = store({ count: 0, user: { name: 'Alice' } });
// Later, when done with the store
appStore.cleanup();

appStore.count(10); // No reactive updates occur after cleanup

Key Concepts

Deep Reactivity

All nested objects are automatically converted to reactive stores, arrays become signals, and functions remain unchanged, creating a fully reactive state tree.

import { effect } from '@hellajs/core';
import { store } from '@hellajs/store';

const userStore = store({
  profile: {
    name: 'John',
    age: 30
  },
  settings: {
    theme: 'dark'
  }
});

// All nested properties are reactive
effect(() => {
  console.log(userStore.profile.name()); // Reactive to name changes
  console.log(userStore.settings.theme()); // Reactive to theme changes
});

Readonly Options

Granular control over which properties can be modified, with full TypeScript support.

import { store } from '@hellajs/store';

const configStore = store(
  { apiUrl: 'https://api.com', theme: 'light', debug: false },
  { readonly: ['apiUrl'] } // apiUrl cannot be modified
);

configStore.theme('dark'); // ✅ Works
configStore.apiUrl('new-url'); // ❌ Doesn't update

// TypeScript enforces readonly at compile time
const readonlyConfig: Store<{ apiUrl: string; theme: string }, 'apiUrl'> = configStore;

Important Considerations

Memory Management

Call cleanup() when stores are no longer needed to prevent memory leaks.

import { store } from '@hellajs/store';

// ✅ Clean up when done
const userStore = store({ name: 'John' });
// Later...
userStore.cleanup();