@mocanvas/store · class

Store

declare class Store<R extends UnknownRecord = UnknownRecord, Props = unknown>

A reactive, transactional collection of records.

  • one atom per record, so consumers subscribe to exactly what they read

  • per-type id sets maintained incrementally

  • writes are batched; listeners receive one squashed diff per outermost operation

  • records are frozen on write


Methods · 18

allRecords(): R[]

All records (reactive over every record and every type's membership).

applyDiff(diff: RecordsDiff<R>, options?: {

Apply a diff (e.g. from extractingChanges or reverseRecordsDiff). With ignoreEphemeralKeys, ephemeral keys of updated records keep their current store values instead of the diff's.

atomic<T>(fn: () => T, options?: {

Run fn as one operation: side effects' operationComplete handlers run once at the end, and listeners get a single squashed history entry.

clear(): void

Remove every record.

constructor(options: StoreOptions<R, Props>)
dispose(): void
extractingChanges(fn: () => void): RecordsDiff<R>

Run fn and return the squashed diff of everything it changed. Listeners are still notified.

getScope(typeName: string): RecordScope

Scope of a record type; unknown types are document.

getStoreSnapshot(scope?: RecordScope | "all"): StoreSnapshot<R>
getTypeIndex(typeName: string): TypeIndex<R>

@internal

isDisposed(): boolean
listen(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>): () => void

Subscribe to history entries. Called after each outermost operation with the squashed changes, filtered by source and record scope.

loadStoreSnapshot(snapshot: StoreSnapshot<R>): void

Replace the store's contents with a snapshot (migrating it first). Existing records in document scope and in every scope present in the snapshot are removed unless the snapshot contains them; other scopes are left alone. Side effects do not run; listeners are notified.

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

Changes made inside fn are reported to listeners with source remote.

migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>

Bring a snapshot saved by an older document up to this store's schema, without loading it.

Every migration sequence the schema knows is run — including the ones createStore derives from the shape and binding utils, so a board saved before a prop existed is backfilled here rather than failing validation on load. The input is not mutated: the result is a new snapshot carrying this schema's serialized version, ready for Store.loadStoreSnapshot (or for a caller that wants to inspect the migrated records first).

A snapshot that cannot be migrated — an unknown schema version, a sequence from a NEWER build than this one, a migration that throws — raises rather than returning half-migrated data, so a caller can fail closed on it.

put(records: readonly R[], phaseOverride?: StoreValidationPhase): void

Insert or update records. Records are validated, passed through before* side effects, frozen, and written. after* side effects run once every record in the call has been written.

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

Remove records by id. Missing ids are ignored. beforeDelete handlers may veto.

serialize(scope?: RecordScope | "all"): SerializedStore<R>

Plain-object snapshot of the records in scope (default document).


Properties · 12

createComputedCache<T, K extends IdOf<R> = IdOf<R>>(name: string, derive: (record: RecordFromId<K>) => T, options?: {

A per-record derived value, recomputed only when that record changes. Entries are dropped automatically when records are removed.

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

Get a record (reactive: subscribes to the record, or to its type's membership when absent).

has<K extends IdOf<R>>(id: K): boolean
readonly history: Atom<number, RecordsDiff<R>>

Bumped once per completed operation that changed something.

The counter itself carries no information; the diffs do. The atom keeps a bounded history of the squashed RecordsDiff of each operation, so a derived collection can ask history.getDiffSince(epoch) and patch itself instead of rebuilding. store.query.filterHistory(typeName) is the same thing narrowed to one record type.

readonly id: string
readonly props: Props
readonly query: StoreQueries<R>
readonly schema: StoreSchema<R, Props>
readonly scopedTypes: {
readonly sideEffects: StoreSideEffects<R>
unsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined

Get a record without registering a reactive dependency.

update<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>): void

Update one record with a function. No-op when the record does not exist.

mocanvas is built by Symbio. Free for non-commercial, non-profit and development use; commercial use needs an agreement. Not affiliated with or endorsed by tldraw.