Store

See source code
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

RecordsDiff<R>;

{ 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

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 in IdOf<R>]: [from: R, to: R] };
} | null;

Parameters

NameDescription

change

RecordsDiff<R>;

the records diff

scope

the records scope

Returns

{
  added: { [K in IdOf<R>]: R };
  removed: { [K in IdOf<R>]: R };
  updated: { [K 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

Partial<StoreListenerFilters>;

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