Store
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
| 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: string;props
Custom properties associated with this store instance.
readonly props: Props;query
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;
},
): void;Parameters
| Name | Description |
|---|---|
| |
| |
Returns
void;clear( )
Remove all records from the store.
clear(): void;Example
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(): void;extractingChanges( )
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 in IdOf<R>]: [from: R, to: R] };
} | null;Parameters
| Name | Description |
|---|---|
| the records diff |
| 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
| Name | Description |
|---|---|
| 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
| Name | Description |
|---|---|
| The scope of records to serialize. Defaults to 'document' |
Returns
StoreSnapshot<R>;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
| Name | Description |
|---|---|
| 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
| Name | Description |
|---|---|
| The listener function to call when changes occur |
| 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
| Name | Description |
|---|---|
| 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
| Name | Description |
|---|---|
| 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
| Name | Description |
|---|---|
| The snapshot to migrate |
Returns
StoreSnapshot<R>;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
| Name | Description |
|---|---|
| The records to add or update |
| 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
| Name | Description |
|---|---|
| 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
| Name | Description |
|---|---|
| The scope of records to serialize. Defaults to 'document' |
Returns
SerializedStore<R>;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
| Name | Description |
|---|---|
| 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
| Name | Description |
|---|---|
| The ID of the record to update |
| A function that receives the current record and returns the updated record |
Returns
void;validate( )
validate(
phase: "createRecord" | "initialize" | "tests" | "updateRecord",
): void;Parameters
| Name | Description |
|---|---|
| |
Returns
void;