@tldraw/store
Version:
tldraw infinite canvas SDK (store).
1,138 lines (1,068 loc) • 39.1 kB
TypeScript
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 { }