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]: // ...
// Methods
computed: ReadonlySignal<T>;
set(value: T): void;
update(partial: PartialDeep<T>): void;
cleanup(): void;
}
interface StoreOptions<T> {
readonly?: boolean | readonly (keyof T)[];
}
TypeScript
The store
function is fully type-safe, preserving your initial object’s structure and providing type inference for all properties and methods.
import { store } from '@hellajs/store';
interface User {
name: string;
age: number;
settings: { theme: 'dark' | 'light' };
}
const userStore = store<User>({
name: 'John Doe',
age: 30,
settings: { theme: 'dark' },
});
// userStore.name is Signal<string>
// userStore.settings.theme is Signal<'dark' | 'light'>
Basic Usage
Create a store by passing a plain JavaScript object. Access properties as signal functions to read their values, and call them with a new value to update.
import { store } from '@hellajs/store';
import { effect } from '@hellajs/core';
const user = store({
name: 'John Doe',
address: {
city: 'New York',
},
});
effect(() => {
console.log(`${user.name()} lives in ${user.address.city()}`);
});
// Logs: "John Doe lives in New York"
// Update a top-level property
user.name('Jane Doe');
// Logs: "Jane Doe lives in New York"
// Update a nested property
user.address.city('San Francisco');
// Logs: "Jane Doe lives in San Francisco"
Key Concepts
Deep Reactivity
All nested objects and arrays are automatically converted to reactive stores, creating a fully reactive state tree.
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
});
Signal Properties
Each store property becomes a signal that can be read and written using function call syntax.
const appStore = store({ count: 0, user: { name: 'Alice' } });
// Read values (getter syntax)
console.log(appStore.count()); // 0
console.log(appStore.user.name()); // 'Alice'
// Write values (setter syntax)
appStore.count(5);
appStore.user.name('Bob');
Flexible Updates
Choose between complete state replacement or partial deep updates using the update()
method.
const store = store({ user: { name: 'John', age: 30 }, ui: { theme: 'light' } });
// Partial update - only changes specified properties
store.update({ user: { name: 'Jane' } }); // age remains 30
// Complete replacement - replaces entire state
store.set({ user: { name: 'Alice', age: 25 }, ui: { theme: 'dark' } });
Computed Values
Stores expose a computed
property that is a computed signal itself, allowing you to derive state from the store’s properties reactively.
import { store } from '@hellajs/store';
import { computed } from '@hellajs/core';
const inventory = store({
items: [
{ id: 1, name: 'Widget', price: 10, quantity: 5 },
{ id: 2, name: 'Gadget', price: 25, quantity: 2 }
],
});
// Access the reactive computed signal
const totalValue = inventory.computed(() =>
inventory.items().reduce((sum, item) =>
sum + (item.price * item.quantity), 0
)
);
const lowStockItems = inventory.computed(() =>
inventory.items().filter(item => item.quantity < 3)
);
console.log(totalValue()); // 100
console.log(lowStockItems()); // [{ id: 2, ... }]
Readonly Control
Granular control over which properties can be modified, with full TypeScript support.
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'); // ❌ TypeScript error + runtime error
Important Considerations
Performance
Large stores create many signals - consider splitting into smaller, focused stores for better performance.
// ❌ Monolithic store
const appStore = store({ user: {...}, ui: {...}, data: {...} });
// ✅ Split into specialized stores
const userStore = store({ name: '', email: '' });
const uiStore = store({ theme: 'light', sidebar: false });
Memory Management
Call cleanup()
when stores are no longer needed to prevent memory leaks.
// ✅ Clean up when done
const userStore = store({ name: 'John' });
// Later...
userStore.cleanup();
Nested Updates
Always use the update()
method for partial changes to avoid replacing entire nested objects.
// ✅ Efficient partial update
store.update({ settings: { theme: 'dark' } });
// ❌ Replaces entire nested object
store.settings({ theme: 'dark', other: undefined });