Table 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

NameDescription

config

{
  props: Props
  id?: string
  schema: StoreSchema<R, Props>
  initialData?: SerializedStore<R>
}

Configuration object for the store


Properties

history

readonly

An atom containing the store's history.

readonly history: Atom<number, RecordsDiff<R>>

id

readonly

The unique identifier of the store instance.

readonly id: string

props

readonly

Custom properties associated with this store instance.

readonly props: Props

query

readonly

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

readonly

The schema that defines the structure and validation rules for records in this store.

readonly schema: StoreSchema<R, Props>

scopedTypes

readonly

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

readonly

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
  }
): void

Parameters

NameDescription

diff

{ runCallbacks, ignoreEphemeralKeys, }

{
  ignoreEphemeralKeys?: boolean
  runCallbacks?: boolean
}

Returns

void

clear( )

Remove all records from the store.

clear(): void

Example

store.clear()
console.log(store.allRecords().length) // 0

createCache( )

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

NameDescription

create

(
  id: IdOf<Record>,
  recordSignal: Signal<R>
) => Signal<Result>

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

NameDescription

name

string

The name of the derivation cache.

derive

(record: Record) => Result | undefined

A function used to derive the value of the cache.

opts

CreateComputedCacheOpts<Result, Record>

Options for the computed cache.

Returns

ComputedCache<Result, Record>

dispose( )

dispose(): void

extractingChanges( )

Run fn and return a RecordsDiff of the changes that occurred as a result.

extractingChanges(fn: () => void): RecordsDiff<R>

Parameters

NameDescription

fn

() => void

Returns


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] }
} | null

Parameters

NameDescription

change

the records diff

scope

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] }
} | null

get( )

Get a record by its ID. This creates a reactive subscription to the record.

get<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined

Example

const book = store.get(bookId)
if (book) {
  console.log(book.title)
}

Parameters

NameDescription

id

K

The ID of the record to get

Returns

RecordFromId<K> | undefined

The 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

NameDescription

scope

'all' | RecordScope

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): boolean

Example

if (store.has(bookId)) {
  console.log('Book exists!')
}

Parameters

NameDescription

id

K

The ID of the record to check

Returns

boolean

True 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>
): () => void

Example

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

NameDescription

onHistory

The listener function to call when changes occur

filters

Optional filters to control when the listener is called

Returns

() => void

A 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>): void

Example

const snapshot = JSON.parse(localStorage.getItem('myApp'))
store.loadStoreSnapshot(snapshot)

Parameters

NameDescription

snapshot

The snapshot to load

Returns

void

mergeRemoteChanges( )

Merge changes from a remote source. Changes made within the provided function will be marked with source 'remote' instead of 'user'.

mergeRemoteChanges(fn: () => void): void

Example

// Changes from sync/collaboration
store.mergeRemoteChanges(() => {
  store.put(remoteRecords)
  store.remove(deletedIds)
})

Parameters

NameDescription

fn

() => void

A function that applies the remote changes

Returns

void

migrateSnapshot( )

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

NameDescription

snapshot

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'): void

Example

// 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

NameDescription

records

R[]

The records to add or update

phaseOverride

'initialize'

Override the validation phase (used internally)

Returns

void

remove( )

Remove records from the store by their IDs.

remove(ids: IdOf<R>[]): void

Example

// Remove a single record
store.remove([book.id])

// Remove multiple records
store.remove([book1.id, book2.id, book3.id])

Parameters

NameDescription

ids

IdOf<R>[]

The IDs of the records to remove

Returns

void

serialize( )

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

NameDescription

scope

'all' | RecordScope

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> | undefined

Example

// Won't trigger reactive updates when this record changes
const book = store.unsafeGetWithoutCapture(bookId)

Parameters

NameDescription

id

K

The ID of the record to get

Returns

RecordFromId<K> | undefined

The 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>
): void

Example

store.update(book.id, (book) => ({
  ...book,
  title: 'Updated Title',
}))

Parameters

NameDescription

id

K

The ID of the record to update

updater

(record: RecordFromId<K>) => RecordFromId<K>

A function that receives the current record and returns the updated record

Returns

void

validate( )

validate(
  phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'
): void

Parameters

NameDescription

phase

  | 'createRecord'
  | 'initialize'
  | 'tests'
  | 'updateRecord'

Returns

void

Prev
RecordType
Next
StoreQueries