4a2040f92c
This PR adds a validation mode whereby previous known-to-be-valid values can be used to speed up the validation process itself. At the same time it enables us to do fine-grained equality checking on records much more quickly than by using something like lodash isEqual, and using that we can prevent triggering effects for record updates that don't actually alter any values in the store. Here's some preliminary perf testing of average time spent in `store.put()` during some common interactions | task | before (ms) | after (ms) | | ---- | ---- | ---- | | drawing lines | 0.0403 | 0.0214 | | drawing boxes | 0.0408 | 0.0348 | | translating lines | 0.0352 | 0.0042 | | translating boxes | 0.0051 | 0.0032 | | rotating lines | 0.0312 | 0.0065 | | rotating boxes | 0.0053 | 0.0035 | | brush selecting boxes | 0.0200 | 0.0232 | | traversal with shapes | 0.0130 | 0.0108 | | traversal without shapes | 0.0201 | 0.0173 | **traversal** means moving the camera and pointer around the canvas #### Discussion At the scale of hundredths of a millisecond these .put operations are so fast that even if they became literally instantaneous the change would not be human perceptible. That said, there is an overall marked improvement here. Especially for dealing with draw shapes. These figures are also mostly in line with expectations, aside from a couple of things: - I don't understand why the `brush selecting boxes` task got slower after the change. - I don't understand why the `traversal` tasks are slower than the `translating boxes` task, both before and after. I would expect that .putting shape records would be much slower than .putting pointer/camera records (since the latter have fewer and simpler properties) ### Change Type - [x] `patch` — Bug fix ### Test Plan 1. Add a step-by-step description of how to test your PR here. 2. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Add a brief release note for your PR here.
358 lines
12 KiB
Markdown
358 lines
12 KiB
Markdown
## API Report File for "@tldraw/store"
|
|
|
|
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
|
|
|
```ts
|
|
|
|
import { Atom } from '@tldraw/state';
|
|
import { Computed } from '@tldraw/state';
|
|
|
|
// @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 (undocumented)
|
|
export function compareRecordVersions(a: RecordVersion, b: RecordVersion): -1 | 0 | 1;
|
|
|
|
// @public (undocumented)
|
|
export const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => -1 | 0 | 1;
|
|
|
|
// @public
|
|
export type ComputedCache<Data, R extends UnknownRecord> = {
|
|
get(id: IdOf<R>): Data | undefined;
|
|
};
|
|
|
|
// @public
|
|
export function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: {
|
|
migrations?: Migrations;
|
|
validator?: StoreValidator<R>;
|
|
scope: RecordScope;
|
|
}): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>;
|
|
|
|
// @public (undocumented)
|
|
export function defineMigrations<FirstVersion extends EMPTY_SYMBOL | number = EMPTY_SYMBOL, CurrentVersion extends EMPTY_SYMBOL | Exclude<number, 0> = EMPTY_SYMBOL>(opts: {
|
|
firstVersion?: CurrentVersion extends number ? FirstVersion : never;
|
|
currentVersion?: CurrentVersion;
|
|
migrators?: CurrentVersion extends number ? FirstVersion extends number ? CurrentVersion extends FirstVersion ? {
|
|
[version in Exclude<Range_2<1, CurrentVersion>, 0>]: Migration;
|
|
} : {
|
|
[version in Exclude<Range_2<FirstVersion, CurrentVersion>, FirstVersion>]: Migration;
|
|
} : {
|
|
[version in Exclude<Range_2<1, CurrentVersion>, 0>]: Migration;
|
|
} : never;
|
|
subTypeKey?: string;
|
|
subTypeMigrations?: Record<string, BaseMigrationsInfo>;
|
|
}): Migrations;
|
|
|
|
// @public
|
|
export function devFreeze<T>(object: T): T;
|
|
|
|
// @public (undocumented)
|
|
export function getRecordVersion(record: UnknownRecord, serializedSchema: SerializedSchema): RecordVersion;
|
|
|
|
// @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(): {
|
|
value: Set<T>;
|
|
diff: CollectionDiff<T>;
|
|
} | undefined;
|
|
// @public
|
|
remove(item: T): void;
|
|
}
|
|
|
|
// @public (undocumented)
|
|
export function migrate<T>({ value, migrations, fromVersion, toVersion, }: {
|
|
value: unknown;
|
|
migrations: Migrations;
|
|
fromVersion: number;
|
|
toVersion: number;
|
|
}): MigrationResult<T>;
|
|
|
|
// @public (undocumented)
|
|
export function migrateRecord<R extends UnknownRecord>({ record, migrations, fromVersion, toVersion, }: {
|
|
record: unknown;
|
|
migrations: Migrations;
|
|
fromVersion: number;
|
|
toVersion: number;
|
|
}): MigrationResult<R>;
|
|
|
|
// @public (undocumented)
|
|
export type Migration<Before = any, After = any> = {
|
|
up: (oldState: Before) => After;
|
|
down: (newState: After) => Before;
|
|
};
|
|
|
|
// @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 MigrationResult<T> = {
|
|
type: 'error';
|
|
reason: MigrationFailureReason;
|
|
} | {
|
|
type: 'success';
|
|
value: T;
|
|
};
|
|
|
|
// @public (undocumented)
|
|
export interface Migrations extends BaseMigrationsInfo {
|
|
// (undocumented)
|
|
subTypeKey?: string;
|
|
// (undocumented)
|
|
subTypeMigrations?: Record<string, BaseMigrationsInfo>;
|
|
}
|
|
|
|
// @public (undocumented)
|
|
export type RecordId<R extends UnknownRecord> = string & {
|
|
__type__: R;
|
|
};
|
|
|
|
// @public
|
|
export type RecordsDiff<R extends UnknownRecord> = {
|
|
added: Record<IdOf<R>, R>;
|
|
updated: Record<IdOf<R>, [from: R, to: R]>;
|
|
removed: Record<IdOf<R>, 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 migrations: Migrations;
|
|
readonly validator?: StoreValidator<R>;
|
|
readonly scope?: RecordScope;
|
|
});
|
|
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>;
|
|
isId(id?: string): id is IdOf<R>;
|
|
isInstance: (record?: UnknownRecord) => record is R;
|
|
// (undocumented)
|
|
readonly migrations: Migrations;
|
|
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 type RecordVersion = {
|
|
rootVersion: number;
|
|
subTypeVersion?: number;
|
|
};
|
|
|
|
// @public (undocumented)
|
|
export function reverseRecordsDiff(diff: RecordsDiff<any>): RecordsDiff<any>;
|
|
|
|
// @public (undocumented)
|
|
export interface SerializedSchema {
|
|
recordVersions: Record<string, {
|
|
version: number;
|
|
subTypeVersions: Record<string, number>;
|
|
subTypeKey: string;
|
|
} | {
|
|
version: number;
|
|
}>;
|
|
schemaVersion: number;
|
|
storeVersion: number;
|
|
}
|
|
|
|
// @public
|
|
export type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>;
|
|
|
|
// @public
|
|
export function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>;
|
|
|
|
// @public
|
|
export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|
constructor(config: {
|
|
initialData?: SerializedStore<R>;
|
|
schema: StoreSchema<R, Props>;
|
|
props: Props;
|
|
});
|
|
allRecords: () => R[];
|
|
// (undocumented)
|
|
applyDiff(diff: RecordsDiff<R>, runCallbacks?: boolean): void;
|
|
clear: () => void;
|
|
createComputedCache: <T, V extends R = R>(name: string, derive: (record: V) => T | undefined, isEqual?: ((a: V, b: V) => boolean) | undefined) => ComputedCache<T, V>;
|
|
createSelectedComputedCache: <T, J, V extends R = R>(name: string, selector: (record: V) => T | undefined, derive: (input: T) => J | undefined) => ComputedCache<J, V>;
|
|
// @internal (undocumented)
|
|
ensureStoreIsUsable(): void;
|
|
// (undocumented)
|
|
extractingChanges(fn: () => void): RecordsDiff<R>;
|
|
filterChangesByScope(change: RecordsDiff<R>, scope: RecordScope): {
|
|
added: { [K in IdOf<R>]: R; };
|
|
updated: { [K_1 in IdOf<R>]: [from: R, to: R]; };
|
|
removed: { [K in IdOf<R>]: R; };
|
|
} | null;
|
|
// (undocumented)
|
|
_flushHistory(): void;
|
|
get: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined;
|
|
// (undocumented)
|
|
getRecordType: <T extends R>(record: R) => T;
|
|
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>;
|
|
onAfterChange?: (prev: R, next: R, source: 'remote' | 'user') => void;
|
|
onAfterCreate?: (record: R, source: 'remote' | 'user') => void;
|
|
onAfterDelete?: (prev: R, source: 'remote' | 'user') => void;
|
|
onBeforeChange?: (prev: R, next: R, source: 'remote' | 'user') => R;
|
|
onBeforeCreate?: (next: R, source: 'remote' | 'user') => R;
|
|
onBeforeDelete?: (prev: R, source: 'remote' | 'user') => false | void;
|
|
// (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>;
|
|
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 StoreError = {
|
|
error: Error;
|
|
phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord';
|
|
recordBefore?: unknown;
|
|
recordAfter: unknown;
|
|
isExistingValidationIssue: boolean;
|
|
};
|
|
|
|
// @public
|
|
export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => 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)
|
|
get currentStoreVersion(): number;
|
|
// (undocumented)
|
|
migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>;
|
|
// (undocumented)
|
|
migrateStoreSnapshot(snapshot: StoreSnapshot<R>): MigrationResult<SerializedStore<R>>;
|
|
// (undocumented)
|
|
serialize(): SerializedSchema;
|
|
// (undocumented)
|
|
serializeEarliestVersion(): SerializedSchema;
|
|
// (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> = {
|
|
snapshotMigrations?: Migrations;
|
|
onValidationFailure?: (data: {
|
|
error: unknown;
|
|
store: Store<R>;
|
|
record: R;
|
|
phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord';
|
|
recordBefore: null | R;
|
|
}) => R;
|
|
createIntegrityChecker?: (store: Store<R, P>) => void;
|
|
};
|
|
|
|
// @public (undocumented)
|
|
export type StoreSnapshot<R extends UnknownRecord> = {
|
|
store: SerializedStore<R>;
|
|
schema: SerializedSchema;
|
|
};
|
|
|
|
// @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)
|
|
|
|
```
|