tldraw/packages/store/api-report.md
alex ab807afda3
Store-level "operation end" event (#3748)
This adds a store-level "operation end" event which fires at the end of
atomic operations. It includes some other changes too:

- The `SideEffectManager` now lives in & is a property of the store as
`StoreSideEffects`. One benefit to this is that instead of overriding
methods on the store to register side effects (meaning the store can
only ever be used in one place) the store now calls directly into the
side effect manager, which is responsible for dealing with any other
callbacks
- The history manager's "batch complete" event is gone, in favour of
this new event. We were using the batch complete event for only one
thing, calling `onChildrenChange` - which meant it wasn't getting called
for undo/redo events, which aren't part of a batch. `onChildrenChange`
is now called after each atomic store operation affecting children.

I've also added a rough pin example which shows (kinda messily) how you
might use the operation complete handler to traverse a graph of bindings
and resolve constraints between them.

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature

### Release Notes

#### Breaking changes
`editor.registerBatchCompleteHandler` has been replaced with
`editor.registerOperationCompleteHandler`
2024-05-14 09:42:41 +00:00

17 KiB

API Report File for "@tldraw/store"

Do not edit this file. It is a report generated by API Extractor.


import { Atom } from '@tldraw/state';
import { Computed } from '@tldraw/state';
import { Result } from '@tldraw/utils';

// @public
export type AllRecords<T extends Store<any>> = ExtractR<ExtractRecordType<T>>;

// @public
export function assertIdType<R extends UnknownRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is IdOf<R>;

// @public
export interface BaseRecord<TypeName extends string, Id extends RecordId<UnknownRecord>> {
    // (undocumented)
    readonly id: Id;
    // (undocumented)
    readonly typeName: TypeName;
}

// @public
export type CollectionDiff<T> = {
    added?: Set<T>;
    removed?: Set<T>;
};

// @public
export type ComputedCache<Data, R extends UnknownRecord> = {
    get(id: IdOf<R>): Data | undefined;
};

// @public
export function createComputedCache<Context extends StoreContext<any>, Result, Record extends ContextRecordType<Context> = ContextRecordType<Context>>(name: string, derive: (context: Context, record: Record) => Result | undefined, isEqual?: (a: Record, b: Record) => boolean): {
    get(context: Context, id: IdOf<Record>): Result | undefined;
};

// @internal (undocumented)
export function createEmptyRecordsDiff<R extends UnknownRecord>(): RecordsDiff<R>;

// @public
export function createMigrationIds<const ID extends string, const Versions extends Record<string, number>>(sequenceId: ID, versions: Versions): {
    [K in keyof Versions]: `${ID}/${Versions[K]}`;
};

// @public
export function createMigrationSequence({ sequence, sequenceId, retroactive, }: {
    retroactive?: boolean;
    sequence: Array<Migration | StandaloneDependsOn>;
    sequenceId: string;
}): MigrationSequence;

// @internal (undocumented)
export function createRecordMigrationSequence(opts: {
    filter?: (record: UnknownRecord) => boolean;
    recordType: string;
    retroactive?: boolean;
    sequence: Omit<Extract<Migration, {
        scope: 'record';
    }>, 'scope'>[];
    sequenceId: string;
}): MigrationSequence;

// @public
export 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'>>;

// @public @deprecated (undocumented)
export function defineMigrations(opts: {
    currentVersion?: number;
    firstVersion?: number;
    migrators?: Record<number, LegacyMigration>;
    subTypeKey?: string;
    subTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>;
}): LegacyMigrations;

// @public
export function devFreeze<T>(object: T): T;

// @public
export type HistoryEntry<R extends UnknownRecord = UnknownRecord> = {
    changes: RecordsDiff<R>;
    source: ChangeSource;
};

// @public (undocumented)
export type IdOf<R extends UnknownRecord> = R['id'];

// @internal
export class IncrementalSetConstructor<T> {
    constructor(
    previousValue: Set<T>);
    // @public
    add(item: T): void;
    // @public
    get(): {
        diff: CollectionDiff<T>;
        value: Set<T>;
    } | undefined;
    // @public
    remove(item: T): void;
}

// @internal
export function isRecordsDiffEmpty<T extends UnknownRecord>(diff: RecordsDiff<T>): boolean;

// @public (undocumented)
export type LegacyMigration<Before = any, After = any> = {
    down: (newState: After) => Before;
    up: (oldState: Before) => After;
};

// @public (undocumented)
export interface LegacyMigrations extends LegacyBaseMigrationsInfo {
    // (undocumented)
    subTypeKey?: string;
    // (undocumented)
    subTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>;
}

// @public (undocumented)
export 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 (undocumented)
export enum MigrationFailureReason {
    // (undocumented)
    IncompatibleSubtype = "incompatible-subtype",
    // (undocumented)
    MigrationError = "migration-error",
    // (undocumented)
    TargetVersionTooNew = "target-version-too-new",
    // (undocumented)
    TargetVersionTooOld = "target-version-too-old",
    // (undocumented)
    UnknownType = "unknown-type",
    // (undocumented)
    UnrecognizedSubtype = "unrecognized-subtype"
}

// @public (undocumented)
export type MigrationId = `${string}/${number}`;

// @public (undocumented)
export type MigrationResult<T> = {
    reason: MigrationFailureReason;
    type: 'error';
} | {
    type: 'success';
    value: T;
};

// @public (undocumented)
export interface MigrationSequence {
    retroactive: boolean;
    // (undocumented)
    sequence: Migration[];
    // (undocumented)
    sequenceId: string;
}

// @internal (undocumented)
export function parseMigrationId(id: MigrationId): {
    sequenceId: string;
    version: number;
};

// @public (undocumented)
export type RecordId<R extends UnknownRecord> = string & {
    __type__: R;
};

// @public
export type RecordsDiff<R extends UnknownRecord> = {
    added: Record<IdOf<R>, R>;
    removed: Record<IdOf<R>, R>;
    updated: Record<IdOf<R>, [from: R, to: R]>;
};

// @public
export class RecordType<R extends UnknownRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> {
    constructor(
    typeName: R['typeName'], config: {
        readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>;
        readonly ephemeralKeys?: {
            readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
        };
        readonly scope?: RecordScope;
        readonly validator?: StoreValidator<R>;
    });
    clone(record: R): R;
    create(properties: Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>): R;
    // @deprecated
    createCustomId(id: string): IdOf<R>;
    // (undocumented)
    readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>;
    createId(customUniquePart?: string): IdOf<R>;
    // (undocumented)
    readonly ephemeralKeys?: {
        readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
    };
    // (undocumented)
    readonly ephemeralKeySet: ReadonlySet<string>;
    isId(id?: string): id is IdOf<R>;
    isInstance: (record?: UnknownRecord) => record is R;
    parseId(id: IdOf<R>): string;
    // (undocumented)
    readonly scope: RecordScope;
    readonly typeName: R['typeName'];
    validate(record: unknown, recordBefore?: R): R;
    // (undocumented)
    readonly validator: StoreValidator<R>;
    withDefaultProperties<DefaultProps extends Omit<Partial<R>, 'id' | 'typeName'>>(createDefaultProperties: () => DefaultProps): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>;
}

// @public (undocumented)
export function reverseRecordsDiff(diff: RecordsDiff<any>): RecordsDiff<any>;

// @public (undocumented)
export type SerializedSchema = SerializedSchemaV1 | SerializedSchemaV2;

// @public (undocumented)
export interface SerializedSchemaV1 {
    recordVersions: Record<string, {
        subTypeKey: string;
        subTypeVersions: Record<string, number>;
        version: number;
    } | {
        version: number;
    }>;
    schemaVersion: 1;
    storeVersion: number;
}

// @public (undocumented)
export interface SerializedSchemaV2 {
    // (undocumented)
    schemaVersion: 2;
    // (undocumented)
    sequences: {
        [sequenceId: string]: number;
    };
}

// @public
export type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>;

// @public
export function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>;

// @internal
export function squashRecordDiffsMutable<T extends UnknownRecord>(target: RecordsDiff<T>, diffs: RecordsDiff<T>[]): void;

// @public (undocumented)
export type StandaloneDependsOn = {
    readonly dependsOn: readonly MigrationId[];
};

// @public
export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
    constructor(config: {
        schema: StoreSchema<R, Props>;
        initialData?: SerializedStore<R>;
        id?: string;
        props: Props;
    });
    // @internal (undocumented)
    addHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void): () => void;
    allRecords: () => R[];
    // (undocumented)
    applyDiff(diff: RecordsDiff<R>, { runCallbacks, ignoreEphemeralKeys, }?: {
        ignoreEphemeralKeys?: boolean;
        runCallbacks?: boolean;
    }): void;
    // @internal (undocumented)
    atomic<T>(fn: () => T, runCallbacks?: boolean): T;
    clear: () => void;
    createComputedCache: <Result, Record extends R = R>(name: string, derive: (record: Record) => Result | undefined, isEqual?: ((a: Record, b: Record) => boolean) | undefined) => ComputedCache<Result, Record>;
    createSelectedComputedCache: <Selection, Result, Record extends R = R>(name: string, selector: (record: Record) => Selection | undefined, derive: (input: Selection) => Result | undefined) => ComputedCache<Result, Record>;
    // @internal (undocumented)
    ensureStoreIsUsable(): void;
    extractingChanges(fn: () => void): RecordsDiff<R>;
    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;
    // (undocumented)
    _flushHistory(): void;
    get: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined;
    getSnapshot(scope?: 'all' | RecordScope): StoreSnapshot<R>;
    has: <K extends IdOf<R>>(id: K) => boolean;
    readonly history: Atom<number, RecordsDiff<R>>;
    readonly id: string;
    // @internal (undocumented)
    isPossiblyCorrupted(): boolean;
    listen: (onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>) => () => void;
    loadSnapshot(snapshot: StoreSnapshot<R>): void;
    // @internal (undocumented)
    markAsPossiblyCorrupted(): void;
    mergeRemoteChanges: (fn: () => void) => void;
    migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>;
    // (undocumented)
    readonly props: Props;
    put: (records: R[], phaseOverride?: 'initialize') => void;
    readonly query: StoreQueries<R>;
    remove: (ids: IdOf<R>[]) => void;
    // (undocumented)
    readonly schema: StoreSchema<R, Props>;
    // (undocumented)
    readonly scopedTypes: {
        readonly [K in RecordScope]: ReadonlySet<R['typeName']>;
    };
    serialize: (scope?: 'all' | RecordScope) => SerializedStore<R>;
    // (undocumented)
    readonly sideEffects: StoreSideEffects<R>;
    unsafeGetWithoutCapture: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined;
    update: <K extends IdOf<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => void;
    // (undocumented)
    validate(phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'): void;
}

// @public (undocumented)
export type StoreAfterChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => void;

// @public (undocumented)
export type StoreAfterCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;

// @public (undocumented)
export type StoreAfterDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;

// @public (undocumented)
export type StoreBeforeChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => R;

// @public (undocumented)
export type StoreBeforeCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => R;

// @public (undocumented)
export type StoreBeforeDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => false | void;

// @public (undocumented)
export type StoreError = {
    error: Error;
    isExistingValidationIssue: boolean;
    phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord';
    recordAfter: unknown;
    recordBefore?: unknown;
};

// @public
export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void;

// @public (undocumented)
export type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void;

// @public (undocumented)
export class StoreSchema<R extends UnknownRecord, P = unknown> {
    // (undocumented)
    static create<R extends UnknownRecord, P = unknown>(types: {
        [TypeName in R['typeName']]: {
            createId: any;
        };
    }, options?: StoreSchemaOptions<R, P>): StoreSchema<R, P>;
    // @internal (undocumented)
    createIntegrityChecker(store: Store<R, P>): (() => void) | undefined;
    // (undocumented)
    getMigrationsSince(persistedSchema: SerializedSchema): Result<Migration[], string>;
    // @internal (undocumented)
    getType(typeName: string): RecordType<R, any>;
    // (undocumented)
    migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>;
    // (undocumented)
    migrateStoreSnapshot(snapshot: StoreSnapshot<R>): MigrationResult<SerializedStore<R>>;
    // (undocumented)
    readonly migrations: Record<string, MigrationSequence>;
    // (undocumented)
    serialize(): SerializedSchemaV2;
    // @deprecated (undocumented)
    serializeEarliestVersion(): SerializedSchema;
    // (undocumented)
    readonly sortedMigrations: readonly Migration[];
    // (undocumented)
    readonly types: {
        [Record in R as Record['typeName']]: RecordType<R, any>;
    };
    // (undocumented)
    validateRecord(store: Store<R>, record: R, phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord', recordBefore: null | R): R;
}

// @public (undocumented)
export type StoreSchemaOptions<R extends UnknownRecord, P> = {
    createIntegrityChecker?: (store: Store<R, P>) => void;
    onValidationFailure?: (data: {
        error: unknown;
        phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord';
        record: R;
        recordBefore: null | R;
        store: Store<R>;
    }) => R;
    migrations?: MigrationSequence[];
};

// @public
export class StoreSideEffects<R extends UnknownRecord> {
    constructor(store: Store<R>);
    // @internal (undocumented)
    handleAfterChange(prev: R, next: R, source: 'remote' | 'user'): void;
    // @internal (undocumented)
    handleAfterCreate(record: R, source: 'remote' | 'user'): void;
    // @internal (undocumented)
    handleAfterDelete(record: R, source: 'remote' | 'user'): void;
    // @internal (undocumented)
    handleBeforeChange(prev: R, next: R, source: 'remote' | 'user'): R;
    // @internal (undocumented)
    handleBeforeCreate(record: R, source: 'remote' | 'user'): R;
    // @internal (undocumented)
    handleBeforeDelete(record: R, source: 'remote' | 'user'): boolean;
    // @internal (undocumented)
    handleOperationComplete(source: 'remote' | 'user'): void;
    // @internal (undocumented)
    isEnabled(): boolean;
    // @internal
    register(handlersByType: {
        [T in R as T['typeName']]?: {
            afterChange?: StoreAfterChangeHandler<T>;
            afterCreate?: StoreAfterCreateHandler<T>;
            afterDelete?: StoreAfterDeleteHandler<T>;
            beforeChange?: StoreBeforeChangeHandler<T>;
            beforeCreate?: StoreBeforeCreateHandler<T>;
            beforeDelete?: StoreBeforeDeleteHandler<T>;
        };
    }): () => void;
    registerAfterChangeHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterChangeHandler<R & {
        typeName: T;
    }>): () => void;
    registerAfterCreateHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterCreateHandler<R & {
        typeName: T;
    }>): () => void;
    registerAfterDeleteHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterDeleteHandler<R & {
        typeName: T;
    }>): () => void;
    registerBeforeChangeHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeChangeHandler<R & {
        typeName: T;
    }>): () => void;
    registerBeforeCreateHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeCreateHandler<R & {
        typeName: T;
    }>): () => void;
    registerBeforeDeleteHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeDeleteHandler<R & {
        typeName: T;
    }>): () => void;
    registerOperationCompleteHandler(handler: StoreOperationCompleteHandler): () => void;
    // @internal (undocumented)
    setIsEnabled(enabled: boolean): void;
}

// @public (undocumented)
export type StoreSnapshot<R extends UnknownRecord> = {
    schema: SerializedSchema;
    store: SerializedStore<R>;
};

// @public (undocumented)
export type StoreValidator<R extends UnknownRecord> = {
    validate: (record: unknown) => R;
    validateUsingKnownGoodVersion?: (knownGoodVersion: R, record: unknown) => R;
};

// @public (undocumented)
export type StoreValidators<R extends UnknownRecord> = {
    [K in R['typeName']]: StoreValidator<Extract<R, {
        typeName: K;
    }>>;
};

// @public (undocumented)
export type UnknownRecord = BaseRecord<string, RecordId<UnknownRecord>>;

// (No @packageDocumentation comment for this package)