Store
See source codeTable of contents
A reactive store that manages collections of typed records.
The Store is the central container for your application's data, providing:
- Reactive state management with automatic updates
- Type-safe record operations
- History tracking and change notifications
- Schema validation and migrations
- Side effects and business logic hooks
- Efficient querying and indexing
class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {}Example
// Create a store with schema
const schema = StoreSchema.create({
book: Book,
author: Author,
})
const store = new Store({
schema,
props: {},
})
// Add records
const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })
store.put([book])
// Listen to changes
store.listen((entry) => {
console.log('Changes:', entry.changes)
})Constructor
Creates a new Store instance.
Example
const store = new Store({
schema: StoreSchema.create({ book: Book }),
props: { appName: 'MyLibrary' },
initialData: savedData,
})Parameters
| Name | Description |
|---|---|
| Configuration object for the store |
Properties
history
An atom containing the store's history.
readonly history: Atom<number, RecordsDiff<R>>id
The unique identifier of the store instance.
readonly id: stringprops
Custom properties associated with this store instance.
readonly props: Propsquery
Reactive queries and indexes for efficiently accessing store data. Provides methods for filtering, indexing, and subscribing to subsets of records.
readonly query: StoreQueries<R>Example
// Create an index by a property
const booksByAuthor = store.query.index('book', 'author')
// Get records matching criteria
const inStockBooks = store.query.records('book', () => ({
inStock: { eq: true },
}))schema
The schema that defines the structure and validation rules for records in this store.
readonly schema: StoreSchema<R, Props>scopedTypes
A mapping of record scopes to the set of record type names that belong to each scope. Used to filter records by their persistence and synchronization behavior.
readonly scopedTypes: {
readonly [K in RecordScope]: ReadonlySet<R['typeName']>
}sideEffects
Side effects manager that handles lifecycle events for record operations. Allows registration of callbacks for create, update, delete, and validation events.
readonly sideEffects: StoreSideEffects<R>Example
store.sideEffects.registerAfterCreateHandler('book', (book) => {
console.log('Book created:', book.title)
})Methods
allRecords( )
Get an array of all records in the store.
allRecords(): R[]Example
const allRecords = store.allRecords()
const books = allRecords.filter((r) => r.typeName === 'book')applyDiff( )
applyDiff(
diff: RecordsDiff<R>,
{
runCallbacks,
ignoreEphemeralKeys,
}?: {
ignoreEphemeralKeys?: boolean
runCallbacks?: boolean
}
): voidParameters
| Name | Description |
|---|---|
| |
| |
Returns
voidclear( )
Remove all records from the store.
clear(): voidExample
store.clear()
console.log(store.allRecords().length) // 0createCache( )
Create a cache based on values in the store. Pass in a function that takes and ID and a signal for the underlying record. Return a signal (usually a computed) for the cached value. For simple derivations, use Store.createComputedCache. This function is useful if you need more precise control over intermediate values.
createCache<Result, Record extends R = R>(
create: (id: IdOf<Record>, recordSignal: Signal<R>) => Signal<Result>
): {
get: (id: IdOf<Record>) => Result | undefined
}Parameters
Returns
{
get: (id: IdOf<Record>) => Result | undefined
}createComputedCache( )
Create a computed cache.
createComputedCache<Result, Record extends R = R>(
name: string,
derive: (record: Record) => Result | undefined,
opts?: CreateComputedCacheOpts<Result, Record>
): ComputedCache<Result, Record>Parameters
| Name | Description |
|---|---|
| The name of the derivation cache. |
| A function used to derive the value of the cache. |
| Options for the computed cache. |
Returns
ComputedCache<Result, Record>dispose( )
dispose(): voidextractingChanges( )
Run fn and return a RecordsDiff of the changes that occurred as a result.
extractingChanges(fn: () => void): RecordsDiff<R>Parameters
| Name | Description |
|---|---|
| |
Returns
RecordsDiff<R>filterChangesByScope( )
Filters out non-document changes from a diff. Returns null if there are no changes left.
filterChangesByScope(
change: RecordsDiff<R>,
scope: RecordScope
): {
added: { [K in IdOf<R>]: R }
removed: { [K in IdOf<R>]: R }
updated: { [K_1 in IdOf<R>]: [from: R, to: R] }
} | nullParameters
| Name | Description |
|---|---|
| the records diff |
| the records scope |
Returns
{
added: { [K in IdOf<R>]: R }
removed: { [K in IdOf<R>]: R }
updated: { [K_1 in IdOf<R>]: [from: R, to: R] }
} | nullget( )
Get a record by its ID. This creates a reactive subscription to the record.
get<K extends IdOf<R>>(id: K): RecordFromId<K> | undefinedExample
const book = store.get(bookId)
if (book) {
console.log(book.title)
}Parameters
| Name | Description |
|---|---|
| The ID of the record to get |
Returns
RecordFromId<K> | undefinedThe record if it exists, undefined otherwise
getStoreSnapshot( )
Get a serialized snapshot of the store and its schema. This includes both the data and schema information needed for proper migration.
getStoreSnapshot(scope?: 'all' | RecordScope): StoreSnapshot<R>Example
const snapshot = store.getStoreSnapshot()
localStorage.setItem('myApp', JSON.stringify(snapshot))
// Later...
const saved = JSON.parse(localStorage.getItem('myApp'))
store.loadStoreSnapshot(saved)Parameters
| Name | Description |
|---|---|
| The scope of records to serialize. Defaults to 'document' |
Returns
A snapshot containing both store data and schema information
has( )
Check whether a record with the given ID exists in the store.
has<K extends IdOf<R>>(id: K): booleanExample
if (store.has(bookId)) {
console.log('Book exists!')
}Parameters
| Name | Description |
|---|---|
| The ID of the record to check |
Returns
booleanTrue if the record exists, false otherwise
listen( )
Add a listener that will be called when the store changes. Returns a function to remove the listener.
listen(
onHistory: StoreListener<R>,
filters?: Partial<StoreListenerFilters>
): () => voidExample
const removeListener = store.listen((entry) => {
console.log('Changes:', entry.changes)
console.log('Source:', entry.source)
})
// Listen only to user changes to document records
const removeDocumentListener = store.listen(
(entry) => console.log('Document changed:', entry),
{ source: 'user', scope: 'document' }
)
// Later, remove the listener
removeListener()Parameters
| Name | Description |
|---|---|
| The listener function to call when changes occur |
| Optional filters to control when the listener is called |
Returns
() => voidA function that removes the listener when called
loadStoreSnapshot( )
Load a serialized snapshot into the store, replacing all current data. The snapshot will be automatically migrated to the current schema version if needed.
loadStoreSnapshot(snapshot: StoreSnapshot<R>): voidExample
const snapshot = JSON.parse(localStorage.getItem('myApp'))
store.loadStoreSnapshot(snapshot)Parameters
| Name | Description |
|---|---|
| The snapshot to load |
Returns
voidmergeRemoteChanges( )
Merge changes from a remote source. Changes made within the provided function will be marked with source 'remote' instead of 'user'.
mergeRemoteChanges(fn: () => void): voidExample
// Changes from sync/collaboration
store.mergeRemoteChanges(() => {
store.put(remoteRecords)
store.remove(deletedIds)
})Parameters
| Name | Description |
|---|---|
| A function that applies the remote changes |
Returns
voidmigrateSnapshot( )
Migrate a serialized snapshot to the current schema version. This applies any necessary migrations to bring old data up to date.
migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>Example
const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))
const migratedSnapshot = store.migrateSnapshot(oldSnapshot)Parameters
| Name | Description |
|---|---|
| The snapshot to migrate |
Returns
The migrated snapshot with current schema version
put( )
Add or update records in the store. If a record with the same ID already exists, it will be updated. Otherwise, a new record will be created.
put(records: R[], phaseOverride?: 'initialize'): voidExample
// Add new records
const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })
store.put([book])
// Update existing record
store.put([{ ...book, title: 'The Lathe of Heaven' }])Parameters
| Name | Description |
|---|---|
| The records to add or update |
| Override the validation phase (used internally) |
Returns
voidremove( )
Remove records from the store by their IDs.
remove(ids: IdOf<R>[]): voidExample
// Remove a single record
store.remove([book.id])
// Remove multiple records
store.remove([book1.id, book2.id, book3.id])Parameters
| Name | Description |
|---|---|
| The IDs of the records to remove |
Returns
voidserialize( )
Serialize the store's records to a plain JavaScript object. Only includes records matching the specified scope.
serialize(scope?: 'all' | RecordScope): SerializedStore<R>Example
// Serialize only document records (default)
const documentData = store.serialize('document')
// Serialize all records
const allData = store.serialize('all')Parameters
| Name | Description |
|---|---|
| The scope of records to serialize. Defaults to 'document' |
Returns
The serialized store data
unsafeGetWithoutCapture( )
Get a record by its ID without creating a reactive subscription. Use this when you need to access a record but don't want reactive updates.
unsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefinedExample
// Won't trigger reactive updates when this record changes
const book = store.unsafeGetWithoutCapture(bookId)Parameters
| Name | Description |
|---|---|
| The ID of the record to get |
Returns
RecordFromId<K> | undefinedThe record if it exists, undefined otherwise
update( )
Update a single record using an updater function. To update multiple records at once,
use the update method of the TypedStore class.
update<K extends IdOf<R>>(
id: K,
updater: (record: RecordFromId<K>) => RecordFromId<K>
): voidExample
store.update(book.id, (book) => ({
...book,
title: 'Updated Title',
}))Parameters
| Name | Description |
|---|---|
| The ID of the record to update |
| A function that receives the current record and returns the updated record |
Returns
voidvalidate( )
validate(
phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'
): voidParameters
| Name | Description |
|---|---|
| |
Returns
void