Open-Source Wikis

/

tldraw

/

Features

/

Store and migrations

tldraw/tldraw

Store and migrations

Active contributors: Steve Ruiz, David Sheldrick

Purpose

How tldraw's reactive document state actually works: records, validators, side-effects, snapshots, and the migration system that keeps old documents loadable as the schema evolves.

The reactive store

graph LR
    Editor -->|put / remove| Store
    Store -->|fire before| Hooks1[before hooks]
    Hooks1 --> Store
    Store -->|update atoms| AtomMap
    AtomMap -->|invalidate| Computed
    Computed -->|notify| Subscribers
    Store -->|emit diff| History[HistoryManager]
    Store -->|emit diff| Sync[TLSyncClient]
    Store -->|fire after| Hooks2[after hooks]

The store is a typed map of records keyed by RecordId. Entries live in AtomMap instances so reads are tracked. Writes go through validation, fire side-effects, then mutate the atoms inside a transact so all downstream computeds invalidate together. The diff is emitted to listeners (history, sync, debug).

Records, scopes, and what syncs

Each record type has a scope (document | session | presence):

  • document records persist to disk and sync across clients. TLShape, TLBinding, TLAsset, TLPage, TLDocument.
  • session records are local to the editor instance. TLInstance, TLInstancePageState, TLPointer, TLCamera. They are still in the store, but never sent over the wire and never written to a .tldr snapshot.
  • presence records sync but are not persisted. TLInstancePresence.

The scope is set when the record type is defined (RecordType.createCustomRecordType({ scope: 'session' })).

The schema

createTLSchema() (in packages/tlschema/src/createTLSchema.ts) builds a StoreSchema from:

  • The list of shape utils (from the SDK or your custom ones).
  • The list of binding utils.
  • The list of style props.

The schema knows about every record type and migration. Pass it to new Store({ schema, ... }) and the store can validate and migrate.

Migrations

Schema changes ship migrations:

  • Record-level: defined inside the record file (e.g., packages/tlschema/src/shapes/TLArrowShape.ts). Versions are numbered; each migration takes a record from version N to N+1 (or back).
  • Store-level: defined in packages/tlschema/src/store-migrations.ts. Used for changes that span records.
graph LR
    OldSnapshot[snapshot v3] --> Migrate[migrate.ts runner]
    Migrate -->|record migrations| Store
    Migrate -->|store migrations| Store
    Store --> NewState[live state at v5]

Tests in packages/tlschema/src/migrations.test.ts (66 KB) snapshot every migration's behavior. Adding a new migration means adding a new test case here.

Side-effects

packages/store/src/lib/StoreSideEffects.ts exposes a registration API:

editor.store.sideEffects.registerBeforeChangeHandler(
  'shape',
  (prev, next, source) => {
    // mutate `next` to enforce invariants (e.g., clamp x to grid)
  }
);
editor.store.sideEffects.registerAfterCreateHandler(
  'shape',
  (record, source) => {
    // react to a new record (e.g., create an undo entry)
  }
);

The editor uses these heavily — most of defaultSideEffects.ts is reaction code that keeps derived records (page state, presence) consistent with shape edits.

Snapshots

const snapshot = editor.store.getStoreSnapshot(); // serialize
editor.store.loadSnapshot(snapshot); // hydrate, run migrations

Snapshots include the schema version so cross-version loads run the right migrations. The dotcom and VS Code apps both round-trip via this mechanism for .tldr files.

Queries

store.query returns a StoreQueries instance. Common patterns:

  • store.query.records('shape') — reactive array of all records of a type.
  • store.query.ids('shape', () => ({ parentId: { eq: pageId } })) — set of matching ids.
  • store.query.exec('shape', { parentId: { eq: pageId } }) — non-reactive snapshot.

The query AST is small — it supports equality, set membership, and a simple gt/lt predicate for indexed numeric fields. executeQuery.ts implements it.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Store and migrations – tldraw wiki | Factory