UNPKG

@tldraw/store

Version:

tldraw infinite canvas SDK (store).

1,138 lines (1,068 loc) • 39.1 kB
import { Atom } from '@tldraw/state'; import { Computed } from '@tldraw/state'; import { Expand } from '@tldraw/utils'; import { Result } from '@tldraw/utils'; import { Signal } from '@tldraw/state'; import { UNINITIALIZED } from '@tldraw/state'; /** * Assert whether an id correspond to a record type. * * @example * * ```ts * assertIdType(myId, "shape") * ``` * * @param id - The id to check. * @param type - The type of the record. * @public */ export declare function assertIdType<R extends UnknownRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is IdOf<R>; /** * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts. * @public */ export declare class AtomMap<K, V> implements Map<K, V> { private readonly name; private atoms; constructor(name: string, entries?: Iterable<readonly [K, V]>); /* Excluded from this release type: getAtom */ get(key: K): undefined | V; __unsafe__getWithoutCapture(key: K): undefined | V; has(key: K): boolean; __unsafe__hasWithoutCapture(key: K): boolean; set(key: K, value: V): this; update(key: K, updater: (value: V) => V): void; delete(key: K): boolean; deleteMany(keys: Iterable<K>): [K, V][]; clear(): void; entries(): Generator<[K, V], undefined, unknown>; keys(): Generator<K, undefined, unknown>; values(): Generator<V, undefined, unknown>; get size(): number; forEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void; [Symbol.iterator](): Generator<[K, V], undefined, unknown>; [Symbol.toStringTag]: string; } /** * The base record that all records must extend. * * @public */ export declare interface BaseRecord<TypeName extends string, Id extends RecordId<UnknownRecord>> { readonly id: Id; readonly typeName: TypeName; } /** @public */ export declare type ChangeSource = 'remote' | 'user'; /** * A diff describing the changes to a collection. * * @public */ export declare interface CollectionDiff<T> { added?: Set<T>; removed?: Set<T>; } /** * A record store is a collection of records of different types. * * @public */ export declare interface ComputedCache<Data, R extends UnknownRecord> { get(id: IdOf<R>): Data | undefined; } /** * Free version of {@link Store.createComputedCache}. * * @example * ```ts * const myCache = createComputedCache('myCache', (editor: Editor, shape: TLShape) => { * return editor.getSomethingExpensive(shape) * }) * * myCache.get(editor, shape.id) * ``` * * @public */ export declare function createComputedCache<Context extends StoreObject<any>, Result, Record extends StoreObjectRecordType<Context> = StoreObjectRecordType<Context>>(name: string, derive: (context: Context, record: Record) => Result | undefined, opts?: CreateComputedCacheOpts<Result, Record>): { get(context: Context, id: IdOf<Record>): Result | undefined; }; /** @public */ export declare interface CreateComputedCacheOpts<Data, R extends UnknownRecord> { areRecordsEqual?(a: R, b: R): boolean; areResultsEqual?(a: Data, b: Data): boolean; } /* Excluded from this release type: createEmptyRecordsDiff */ /** * Creates a named set of migration ids given a named set of version numbers and a sequence id. * * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API. * @public * @public */ export declare function createMigrationIds<const ID extends string, const Versions extends Record<string, number>>(sequenceId: ID, versions: Versions): { [K in keyof Versions]: `${ID}/${Versions[K]}`; }; /** * Creates a migration sequence. * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API. * @public */ export declare function createMigrationSequence({ sequence, sequenceId, retroactive, }: { retroactive?: boolean; sequence: Array<Migration | StandaloneDependsOn>; sequenceId: string; }): MigrationSequence; /* Excluded from this release type: createRecordMigrationSequence */ /** * Create a record type. * * @example * * ```ts * const Book = createRecordType<Book>('book') * ``` * * @param typeName - The name of the type to create. * @public */ export declare function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: { ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean; }; scope: RecordScope; validator?: StoreValidator<R>; }): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>; /** * Freeze an object when in development mode. Copied from * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze * * @example * * ```ts * const frozen = devFreeze({ a: 1 }) * ``` * * @param object - The object to freeze. * @returns The frozen object when in development mode, or else the object when in other modes. * @public */ export declare function devFreeze<T>(object: T): T; /** * An entry containing changes that originated either by user actions or remote changes. * * @public */ export declare interface HistoryEntry<R extends UnknownRecord = UnknownRecord> { changes: RecordsDiff<R>; source: ChangeSource; } /** @public */ export declare type IdOf<R extends UnknownRecord> = R['id']; /* Excluded from this release type: IncrementalSetConstructor */ /* Excluded from this release type: isRecordsDiffEmpty */ /** @public */ export declare interface LegacyBaseMigrationsInfo { firstVersion: number; currentVersion: number; migrators: { [version: number]: LegacyMigration; }; } /** @public */ export declare interface LegacyMigration<Before = any, After = any> { up: (oldState: Before) => After; down: (newState: After) => Before; } /** @public */ export declare interface LegacyMigrations extends LegacyBaseMigrationsInfo { subTypeKey?: string; subTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>; } /** @public */ export declare type Migration = { readonly dependsOn?: readonly MigrationId[] | undefined; readonly id: MigrationId; } & ({ readonly down?: (newState: SerializedStore<UnknownRecord>) => SerializedStore<UnknownRecord> | void; readonly scope: 'store'; readonly up: (oldState: SerializedStore<UnknownRecord>) => SerializedStore<UnknownRecord> | void; } | { readonly down?: (newState: UnknownRecord) => UnknownRecord | void; readonly filter?: (record: UnknownRecord) => boolean; readonly scope: 'record'; readonly up: (oldState: UnknownRecord) => UnknownRecord | void; }); /** @public */ export declare enum MigrationFailureReason { IncompatibleSubtype = "incompatible-subtype", UnknownType = "unknown-type", TargetVersionTooNew = "target-version-too-new", TargetVersionTooOld = "target-version-too-old", MigrationError = "migration-error", UnrecognizedSubtype = "unrecognized-subtype" } /** @public */ export declare type MigrationId = `${string}/${number}`; /** @public */ export declare type MigrationResult<T> = { reason: MigrationFailureReason; type: 'error'; } | { type: 'success'; value: T; }; /** @public */ export declare interface MigrationSequence { sequenceId: string; /** * retroactive should be true if the migrations should be applied to snapshots that were created before * this migration sequence was added to the schema. * * In general: * * - retroactive should be true when app developers create their own new migration sequences. * - retroactive should be false when library developers ship a migration sequence. When you install a library for the first time, any migrations that were added in the library before that point should generally _not_ be applied to your existing data. */ retroactive: boolean; sequence: Migration[]; } /* Excluded from this release type: parseMigrationId */ /** @public */ export declare type QueryExpression<R extends object> = { [k in keyof R & string]?: QueryValueMatcher<R[k]>; }; /** @public */ export declare type QueryValueMatcher<T> = { eq: T; } | { gt: number; } | { neq: T; }; /** @public */ export declare type RecordFromId<K extends RecordId<UnknownRecord>> = K extends RecordId<infer R> ? R : never; /** @public */ export declare type RecordId<R extends UnknownRecord> = string & { __type__: R; }; /** * Defines the scope of the record * * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating. * document: The record is persisted and synced. It is available to all store instances. * presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted. * * @public * */ export declare type RecordScope = 'document' | 'presence' | 'session'; /** * A diff describing the changes to a record. * * @public */ export declare interface RecordsDiff<R extends UnknownRecord> { added: Record<IdOf<R>, R>; updated: Record<IdOf<R>, [from: R, to: R]>; removed: Record<IdOf<R>, R>; } /** * A record type is a type that can be stored in a record store. It is created with * `createRecordType`. * * @public */ export declare class RecordType<R extends UnknownRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> { /** * The unique type associated with this record. * * @public * @readonly */ readonly typeName: R['typeName']; readonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>; readonly validator: StoreValidator<R>; readonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean; }; readonly ephemeralKeySet: ReadonlySet<string>; readonly scope: RecordScope; constructor( /** * The unique type associated with this record. * * @public * @readonly */ typeName: R['typeName'], config: { readonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>; readonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean; }; readonly scope?: RecordScope; readonly validator?: StoreValidator<R>; }); /** * Create a new record of this type. * * @param properties - The properties of the record. * @returns The new record. */ create(properties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>): R; /** * Clone a record of this type. * * @param record - The record to clone. * @returns The cloned record. * @public */ clone(record: R): R; /** * Create a new ID for this record type. * * @example * * ```ts * const id = recordType.createId() * ``` * * @returns The new ID. * @public */ createId(customUniquePart?: string): IdOf<R>; /** * Takes an id like `user:123` and returns the part after the colon `123` * * @param id - The id * @returns */ parseId(id: IdOf<R>): string; /** * Check whether a record is an instance of this record type. * * @example * * ```ts * const result = recordType.isInstance(someRecord) * ``` * * @param record - The record to check. * @returns Whether the record is an instance of this record type. */ isInstance(record?: UnknownRecord): record is R; /** * Check whether an id is an id of this type. * * @example * * ```ts * const result = recordType.isIn('someId') * ``` * * @param id - The id to check. * @returns Whether the id is an id of this type. */ isId(id?: string): id is IdOf<R>; /** * Create a new RecordType that has the same type name as this RecordType and includes the given * default properties. * * @example * * ```ts * const authorType = createRecordType('author', () => ({ living: true })) * const deadAuthorType = authorType.withDefaultProperties({ living: false }) * ``` * * @param createDefaultProperties - A function that returns the default properties of the new RecordType. * @returns The new RecordType. */ withDefaultProperties<DefaultProps extends Omit<Partial<R>, 'id' | 'typeName'>>(createDefaultProperties: () => DefaultProps): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>; /** * Check that the passed in record passes the validations for this type. Returns its input * correctly typed if it does, but throws an error otherwise. */ validate(record: unknown, recordBefore?: R): R; } /** @public */ export declare function reverseRecordsDiff(diff: RecordsDiff<any>): RecordsDiff<any>; /** @public */ export declare type RSIndex<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Computed<RSIndexMap<R, Property>, RSIndexDiff<R, Property>>; /** @public */ export declare type RSIndexDiff<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Map<R[Property], CollectionDiff<IdOf<R>>>; /** @public */ export declare type RSIndexMap<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Map<R[Property], Set<IdOf<R>>>; /** @public */ export declare type SerializedSchema = SerializedSchemaV1 | SerializedSchemaV2; /** @public */ export declare interface SerializedSchemaV1 { /** Schema version is the version for this type you're looking at right now */ schemaVersion: 1; /** * Store version is the version for the structure of the store. e.g. higher level structure like * removing or renaming a record type. */ storeVersion: number; /** Record versions are the versions for each record type. e.g. adding a new field to a record */ recordVersions: Record<string, { subTypeKey: string; subTypeVersions: Record<string, number>; version: number; } | { version: number; }>; } /** @public */ export declare interface SerializedSchemaV2 { schemaVersion: 2; sequences: { [sequenceId: string]: number; }; } /** * A serialized snapshot of the record store's values. * * @public */ export declare type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>; /** * Squash a collection of diffs into a single diff. * * @param diffs - An array of diffs to squash. * @param options - An optional object with a `mutateFirstDiff` property. If `mutateFirstDiff` is true, the first diff in the array will be mutated in-place. * @returns A single diff that represents the squashed diffs. * @public */ export declare function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[], options?: { mutateFirstDiff?: boolean; }): RecordsDiff<T>; /* Excluded from this release type: squashRecordDiffsMutable */ /** @public */ export declare interface StandaloneDependsOn { readonly dependsOn: readonly MigrationId[]; } /** * A store of records. * * @public */ export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> { /** * The random id of the store. */ readonly id: string; /* Excluded from this release type: records */ /** * An atom containing the store's history. * * @public * @readonly */ readonly history: Atom<number, RecordsDiff<R>>; /** * A StoreQueries instance for this store. * * @public * @readonly */ readonly query: StoreQueries<R>; /* Excluded from this release type: listeners */ /* Excluded from this release type: historyAccumulator */ /* Excluded from this release type: historyReactor */ /* Excluded from this release type: cancelHistoryReactor */ readonly schema: StoreSchema<R, Props>; readonly props: Props; readonly scopedTypes: { readonly [K in RecordScope]: ReadonlySet<R['typeName']>; }; readonly sideEffects: StoreSideEffects<R>; constructor(config: { /** * A map of validators for each record type. A record's validator will be called when the record * is created or updated. It should throw an error if the record is invalid. */ schema: StoreSchema<R, Props>; /** The store's initial data. */ initialData?: SerializedStore<R>; id?: string; props: Props; }); _flushHistory(): void; dispose(): void; /** * Filters out non-document changes from a diff. Returns null if there are no changes left. * @param change - the records diff * @param scope - the records scope * @returns */ 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; /** * Update the history with a diff of changes. * * @param changes - The changes to add to the history. */ private updateHistory; validate(phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'): void; /** * Add some records to the store. It's an error if they already exist. * * @param records - The records to add. * @param phaseOverride - The phase override. * @public */ put(records: R[], phaseOverride?: 'initialize'): void; /** * Remove some records from the store via their ids. * * @param ids - The ids of the records to remove. * @public */ remove(ids: IdOf<R>[]): void; /** * Get the value of a store record by its id. * * @param id - The id of the record to get. * @public */ get<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined; /** * Get the value of a store record by its id without updating its epoch. * * @param id - The id of the record to get. * @public */ unsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined; /** * Creates a JSON payload from the record store. * * @param scope - The scope of records to serialize. Defaults to 'document'. * @returns The record store snapshot as a JSON payload. */ serialize(scope?: 'all' | RecordScope): SerializedStore<R>; /** * Get a serialized snapshot of the store and its schema. * * ```ts * const snapshot = store.getStoreSnapshot() * store.loadStoreSnapshot(snapshot) * ``` * * @param scope - The scope of records to serialize. Defaults to 'document'. * * @public */ getStoreSnapshot(scope?: 'all' | RecordScope): StoreSnapshot<R>; /** * Migrate a serialized snapshot of the store and its schema. * * ```ts * const snapshot = store.getStoreSnapshot() * store.migrateSnapshot(snapshot) * ``` * * @param snapshot - The snapshot to load. * @public */ migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>; /** * Load a serialized snapshot. * * ```ts * const snapshot = store.getStoreSnapshot() * store.loadStoreSnapshot(snapshot) * ``` * * @param snapshot - The snapshot to load. * @public */ loadStoreSnapshot(snapshot: StoreSnapshot<R>): void; /** * Get an array of all values in the store. * * @returns An array of all values in the store. * @public */ allRecords(): R[]; /** * Removes all records from the store. * * @public */ clear(): void; /** * Update a record. To update multiple records at once, use the `update` method of the * `TypedStore` class. * * @param id - The id of the record to update. * @param updater - A function that updates the record. */ update<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>): void; /** * Get whether the record store has a id. * * @param id - The id of the record to check. * @public */ has<K extends IdOf<R>>(id: K): boolean; /** * Add a new listener to the store. * * @param onHistory - The listener to call when the store updates. * @param filters - Filters to apply to the listener. * @returns A function to remove the listener. */ listen(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>): () => void; private isMergingRemoteChanges; /** * Merge changes from a remote source * * @param fn - A function that merges the external changes. * @public */ mergeRemoteChanges(fn: () => void): void; /** * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result. */ extractingChanges(fn: () => void): RecordsDiff<R>; applyDiff(diff: RecordsDiff<R>, { runCallbacks, ignoreEphemeralKeys, }?: { ignoreEphemeralKeys?: boolean; runCallbacks?: boolean; }): void; /** * 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 {@link 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; }; /** * Create a computed cache. * * @param name - The name of the derivation cache. * @param derive - A function used to derive the value of the cache. * @param opts - Options for the computed cache. * @public */ createComputedCache<Result, Record extends R = R>(name: string, derive: (record: Record) => Result | undefined, opts?: CreateComputedCacheOpts<Result, Record>): ComputedCache<Result, Record>; private _integrityChecker?; /* Excluded from this release type: ensureStoreIsUsable */ private _isPossiblyCorrupted; /* Excluded from this release type: markAsPossiblyCorrupted */ /* Excluded from this release type: isPossiblyCorrupted */ private pendingAfterEvents; private addDiffForAfterEvent; private flushAtomicCallbacks; private _isInAtomicOp; /* Excluded from this release type: atomic */ /* Excluded from this release type: addHistoryInterceptor */ } /** @public */ export declare type StoreAfterChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => void; /** @public */ export declare type StoreAfterCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void; /** @public */ export declare type StoreAfterDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void; /** @public */ export declare type StoreBeforeChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => R; /** @public */ export declare type StoreBeforeCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => R; /** @public */ export declare type StoreBeforeDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => false | void; /** @public */ export declare interface StoreError { error: Error; phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'; recordBefore?: unknown; recordAfter: unknown; isExistingValidationIssue: boolean; } /** * A function that will be called when the history changes. * * @public */ export declare type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void; /** @public */ export declare interface StoreListenerFilters { source: 'all' | ChangeSource; scope: 'all' | RecordScope; } /** @public */ export declare type StoreObject<R extends UnknownRecord> = { store: Store<R>; } | Store<R>; /** @public */ export declare type StoreObjectRecordType<Context extends StoreObject<any>> = Context extends Store<infer R> ? R : Context extends { store: Store<infer R>; } ? R : never; /** @public */ export declare type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void; /** * A class that provides a 'namespace' for the various kinds of indexes one may wish to derive from * the record store. * @public */ export declare class StoreQueries<R extends UnknownRecord> { private readonly recordMap; private readonly history; constructor(recordMap: AtomMap<IdOf<R>, R>, history: Atom<number, RecordsDiff<R>>); /* Excluded from this release type: indexCache */ /* Excluded from this release type: historyCache */ /** * Create a derivation that contains the history for a given type * * @param typeName - The name of the type to filter by. * @returns A derivation that returns the ids of all records of the given type. * @public */ filterHistory<TypeName extends R['typeName']>(typeName: TypeName): Computed<number, RecordsDiff<Extract<R, { typeName: TypeName; }>>>; /** * Create a derivation that returns an index on a property for the given type. * * @param typeName - The name of the type. * @param property - The name of the property. * @public */ index<TypeName extends R['typeName'], Property extends string & keyof Extract<R, { typeName: TypeName; }>>(typeName: TypeName, property: Property): RSIndex<Extract<R, { typeName: TypeName; }>, Property>; /* Excluded from this release type: __uncached_createIndex */ /** * Create a derivation that will return a signle record matching the given query. * * It will return undefined if there is no matching record * * @param typeName - The name of the type? * @param queryCreator - A function that returns the query expression. * @param name - (optional) The name of the query. */ record<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, { typeName: TypeName; }>>, name?: string): Computed<Extract<R, { typeName: TypeName; }> | undefined>; /** * Create a derivation that will return an array of records matching the given query * * @param typeName - The name of the type? * @param queryCreator - A function that returns the query expression. * @param name - (optinal) The name of the query. */ records<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, { typeName: TypeName; }>>, name?: string): Computed<Array<Extract<R, { typeName: TypeName; }>>>; /** * Create a derivation that will return the ids of all records of the given type. * * @param typeName - The name of the type. * @param queryCreator - A function that returns the query expression. * @param name - (optinal) The name of the query. */ ids<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, { typeName: TypeName; }>>, name?: string): Computed<Set<IdOf<Extract<R, { typeName: TypeName; }>>>, CollectionDiff<IdOf<Extract<R, { typeName: TypeName; }>>>>; exec<TypeName extends R['typeName']>(typeName: TypeName, query: QueryExpression<Extract<R, { typeName: TypeName; }>>): Array<Extract<R, { typeName: TypeName; }>>; } /* Excluded from this release type: StoreRecord */ /** @public */ export declare class StoreSchema<R extends UnknownRecord, P = unknown> { readonly types: { [Record in R as Record['typeName']]: RecordType<R, any>; }; private readonly options; static create<R extends UnknownRecord, P = unknown>(types: { [TypeName in R['typeName']]: { createId: any; }; }, options?: StoreSchemaOptions<R, P>): StoreSchema<R, P>; readonly migrations: Record<string, MigrationSequence>; readonly sortedMigrations: readonly Migration[]; private readonly migrationCache; private constructor(); validateRecord(store: Store<R>, record: R, phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord', recordBefore: null | R): R; getMigrationsSince(persistedSchema: SerializedSchema): Result<Migration[], string>; migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>; migrateStoreSnapshot(snapshot: StoreSnapshot<R>, opts?: { mutateInputStore?: boolean; }): MigrationResult<SerializedStore<R>>; /* Excluded from this release type: createIntegrityChecker */ serialize(): SerializedSchemaV2; /* Excluded from this release type: serializeEarliestVersion */ /* Excluded from this release type: getType */ } /** @public */ export declare interface StoreSchemaOptions<R extends UnknownRecord, P> { migrations?: MigrationSequence[]; /** @public */ onValidationFailure?(data: StoreValidationFailure<R>): R; /* Excluded from this release type: createIntegrityChecker */ } /** * The side effect manager (aka a "correct state enforcer") is responsible * for making sure that the editor's state is always correct. This includes * things like: deleting a shape if its parent is deleted; unbinding * arrows when their binding target is deleted; etc. * * @public */ export declare class StoreSideEffects<R extends UnknownRecord> { private readonly store; constructor(store: Store<R>); private _beforeCreateHandlers; private _afterCreateHandlers; private _beforeChangeHandlers; private _afterChangeHandlers; private _beforeDeleteHandlers; private _afterDeleteHandlers; private _operationCompleteHandlers; private _isEnabled; /* Excluded from this release type: isEnabled */ /* Excluded from this release type: setIsEnabled */ /* Excluded from this release type: handleBeforeCreate */ /* Excluded from this release type: handleAfterCreate */ /* Excluded from this release type: handleBeforeChange */ /* Excluded from this release type: handleAfterChange */ /* Excluded from this release type: handleBeforeDelete */ /* Excluded from this release type: handleAfterDelete */ /* Excluded from this release type: handleOperationComplete */ /* Excluded from this release type: register */ /** * Register a handler to be called before a record of a certain type is created. Return a * modified record from the handler to change the record that will be created. * * Use this handle only to modify the creation of the record itself. If you want to trigger a * side-effect on a different record (for example, moving one shape when another is created), * use {@link StoreSideEffects.registerAfterCreateHandler} instead. * * @example * ```ts * editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => { * // only modify shapes created by the user * if (source !== 'user') return shape * * //by default, arrow shapes have no label. Let's make sure they always have a label. * if (shape.type === 'arrow') { * return {...shape, props: {...shape.props, text: 'an arrow'}} * } * * // other shapes get returned unmodified * return shape * }) * ``` * * @param typeName - The type of record to listen for * @param handler - The handler to call * * @returns A callback that removes the handler. */ registerBeforeCreateHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeCreateHandler<R & { typeName: T; }>): () => void; /** * Register a handler to be called after a record is created. This is useful for side-effects * that would update _other_ records. If you want to modify the record being created use * {@link StoreSideEffects.registerBeforeCreateHandler} instead. * * @example * ```ts * editor.sideEffects.registerAfterCreateHandler('page', (page, source) => { * // Automatically create a shape when a page is created * editor.createShape<TLTextShape>({ * id: createShapeId(), * type: 'text', * props: { richText: toRichText(page.name) }, * }) * }) * ``` * * @param typeName - The type of record to listen for * @param handler - The handler to call * * @returns A callback that removes the handler. */ registerAfterCreateHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterCreateHandler<R & { typeName: T; }>): () => void; /** * Register a handler to be called before a record is changed. The handler is given the old and * new record - you can return a modified record to apply a different update, or the old record * to block the update entirely. * * Use this handler only for intercepting updates to the record itself. If you want to update * other records in response to a change, use * {@link StoreSideEffects.registerAfterChangeHandler} instead. * * @example * ```ts * editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next, source) => { * if (next.isLocked && !prev.isLocked) { * // prevent shapes from ever being locked: * return prev * } * // other types of change are allowed * return next * }) * ``` * * @param typeName - The type of record to listen for * @param handler - The handler to call * * @returns A callback that removes the handler. */ registerBeforeChangeHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeChangeHandler<R & { typeName: T; }>): () => void; /** * Register a handler to be called after a record is changed. This is useful for side-effects * that would update _other_ records - if you want to modify the record being changed, use * {@link StoreSideEffects.registerBeforeChangeHandler} instead. * * @example * ```ts * editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => { * if (next.props.color === 'red') { * // there can only be one red shape at a time: * const otherRedShapes = editor.getCurrentPageShapes().filter(s => s.props.color === 'red' && s.id !== next.id) * editor.updateShapes(otherRedShapes.map(s => ({...s, props: {...s.props, color: 'blue'}}))) * } * }) * ``` * * @param typeName - The type of record to listen for * @param handler - The handler to call * * @returns A callback that removes the handler. */ registerAfterChangeHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterChangeHandler<R & { typeName: T; }>): () => void; /** * Register a handler to be called before a record is deleted. The handler can return `false` to * prevent the deletion. * * Use this handler only for intercepting deletions of the record itself. If you want to do * something to other records in response to a deletion, use * {@link StoreSideEffects.registerAfterDeleteHandler} instead. * * @example * ```ts * editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => { * if (shape.props.color === 'red') { * // prevent red shapes from being deleted * return false * } * }) * ``` * * @param typeName - The type of record to listen for * @param handler - The handler to call * * @returns A callback that removes the handler. */ registerBeforeDeleteHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeDeleteHandler<R & { typeName: T; }>): () => void; /** * Register a handler to be called after a record is deleted. This is useful for side-effects * that would update _other_ records - if you want to block the deletion of the record itself, * use {@link StoreSideEffects.registerBeforeDeleteHandler} instead. * * @example * ```ts * editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => { * // if the last shape in a frame is deleted, delete the frame too: * const parentFrame = editor.getShape(shape.parentId) * if (!parentFrame || parentFrame.type !== 'frame') return * * const siblings = editor.getSortedChildIdsForParent(parentFrame) * if (siblings.length === 0) { * editor.deleteShape(parentFrame.id) * } * }) * ``` * * @param typeName - The type of record to listen for * @param handler - The handler to call * * @returns A callback that removes the handler. */ registerAfterDeleteHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterDeleteHandler<R & { typeName: T; }>): () => void; /** * Register a handler to be called when a store completes an atomic operation. * * @example * ```ts * let count = 0 * * editor.sideEffects.registerOperationCompleteHandler(() => count++) * * editor.selectAll() * expect(count).toBe(1) * * editor.store.atomic(() => { * editor.selectNone() * editor.selectAll() * }) * * expect(count).toBe(2) * ``` * * @param handler - The handler to call * * @returns A callback that removes the handler. * * @public */ registerOperationCompleteHandler(handler: StoreOperationCompleteHandler): () => void; } /** @public */ export declare interface StoreSnapshot<R extends UnknownRecord> { store: SerializedStore<R>; schema: SerializedSchema; } /** @public */ export declare interface StoreValidationFailure<R extends UnknownRecord> { error: unknown; store: Store<R>; record: R; phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'; recordBefore: null | R; } /** @public */ export declare interface StoreValidator<R extends UnknownRecord> { validate(record: unknown): R; validateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R; } /** @public */ export declare type StoreValidators<R extends UnknownRecord> = { [K in R['typeName']]: StoreValidator<Extract<R, { typeName: K; }>>; }; /** @public */ export declare type UnknownRecord = BaseRecord<string, RecordId<UnknownRecord>>; export { }