@tldraw/tlstore
Advanced tools
Comparing version 2.0.0-canary.75778dd3 to 2.0.0-canary.7578fff2b179
@@ -0,1 +1,33 @@ | ||
# v2.0.0-alpha.12 (Mon Apr 03 2023) | ||
#### 🐛 Bug Fix | ||
- Make sure all types and build stuff get run in CI [#1548](https://github.com/tldraw/tldraw-lite/pull/1548) ([@SomeHats](https://github.com/SomeHats)) | ||
- add pre-commit api report generation [#1517](https://github.com/tldraw/tldraw-lite/pull/1517) ([@SomeHats](https://github.com/SomeHats)) | ||
- [chore] restore api extractor [#1500](https://github.com/tldraw/tldraw-lite/pull/1500) ([@steveruizok](https://github.com/steveruizok)) | ||
- Asset loading overhaul [#1457](https://github.com/tldraw/tldraw-lite/pull/1457) ([@SomeHats](https://github.com/SomeHats)) | ||
- David/publish good [#1488](https://github.com/tldraw/tldraw-lite/pull/1488) ([@ds300](https://github.com/ds300)) | ||
- [chore] alpha 10 [#1486](https://github.com/tldraw/tldraw-lite/pull/1486) ([@ds300](https://github.com/ds300)) | ||
- [chore] package build improvements [#1484](https://github.com/tldraw/tldraw-lite/pull/1484) ([@ds300](https://github.com/ds300)) | ||
- [chore] bump for alpha 8 [#1485](https://github.com/tldraw/tldraw-lite/pull/1485) ([@steveruizok](https://github.com/steveruizok)) | ||
- [fix] page point offset [#1483](https://github.com/tldraw/tldraw-lite/pull/1483) ([@steveruizok](https://github.com/steveruizok)) | ||
- stop using broken-af turbo for publishing [#1476](https://github.com/tldraw/tldraw-lite/pull/1476) ([@ds300](https://github.com/ds300)) | ||
- [chore] add canary release script [#1423](https://github.com/tldraw/tldraw-lite/pull/1423) ([@ds300](https://github.com/ds300) [@steveruizok](https://github.com/steveruizok)) | ||
- [chore] upgrade yarn [#1430](https://github.com/tldraw/tldraw-lite/pull/1430) ([@ds300](https://github.com/ds300)) | ||
- flush store on attach [#1449](https://github.com/tldraw/tldraw-lite/pull/1449) ([@ds300](https://github.com/ds300)) | ||
- [fix] dev version number for tldraw/tldraw [#1434](https://github.com/tldraw/tldraw-lite/pull/1434) ([@steveruizok](https://github.com/steveruizok)) | ||
- repo cleanup [#1426](https://github.com/tldraw/tldraw-lite/pull/1426) ([@steveruizok](https://github.com/steveruizok)) | ||
- [fix] use polyfill for `structuredClone` [#1408](https://github.com/tldraw/tldraw-lite/pull/1408) ([@TodePond](https://github.com/TodePond) [@steveruizok](https://github.com/steveruizok)) | ||
- Run all the tests. Fix linting for tests. [#1389](https://github.com/tldraw/tldraw-lite/pull/1389) ([@MitjaBezensek](https://github.com/MitjaBezensek)) | ||
#### Authors: 5 | ||
- alex ([@SomeHats](https://github.com/SomeHats)) | ||
- David Sheldrick ([@ds300](https://github.com/ds300)) | ||
- Lu[ke] Wilson ([@TodePond](https://github.com/TodePond)) | ||
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek)) | ||
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) | ||
--- | ||
# @tldraw/tlstore | ||
@@ -2,0 +34,0 @@ |
@@ -6,2 +6,3 @@ import { Atom } from 'signia'; | ||
* Get the type of all records in a record store. | ||
* | ||
* @public | ||
@@ -15,4 +16,5 @@ */ | ||
* @example | ||
* | ||
* ```ts | ||
* assertIdType(myId, ID<MyRecord>) | ||
* assertIdType(myId, ID<MyRecord>) | ||
* ``` | ||
@@ -22,6 +24,5 @@ * | ||
* @param type - The type of the record. | ||
* | ||
* @public | ||
*/ | ||
export declare function assertIdType<R extends BaseRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is ID<R>; | ||
export declare function assertIdType<R extends UnknownRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is IdOf<R>; | ||
@@ -38,6 +39,7 @@ declare interface BaseMigrationsInfo { | ||
* The base record that all records must extend. | ||
* | ||
* @public | ||
*/ | ||
export declare interface BaseRecord<TypeName extends string = string> { | ||
readonly id: ID<this>; | ||
export declare interface BaseRecord<TypeName extends string, Id extends ID<UnknownRecord>> { | ||
readonly id: Id; | ||
readonly typeName: TypeName; | ||
@@ -48,2 +50,3 @@ } | ||
* A diff describing the changes to a collection. | ||
* | ||
* @public | ||
@@ -57,13 +60,14 @@ */ | ||
/** @public */ | ||
export declare function compareRecordVersions(a: RecordVersion, b: RecordVersion): 0 | 1 | -1; | ||
export declare function compareRecordVersions(a: RecordVersion, b: RecordVersion): -1 | 0 | 1; | ||
/** @public */ | ||
export declare const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => number; | ||
export declare const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => -1 | 0 | 1; | ||
/** | ||
* A record store is a collection of records of different types. | ||
* | ||
* @public | ||
*/ | ||
export declare type ComputedCache<Data, R extends BaseRecord> = { | ||
get(id: ID<R>): Data | undefined; | ||
export declare type ComputedCache<Data, R extends UnknownRecord> = { | ||
get(id: IdOf<R>): Data | undefined; | ||
}; | ||
@@ -75,13 +79,14 @@ | ||
* @example | ||
* | ||
* ```ts | ||
* const Book = createRecordType<Book>('book') | ||
* const Book = createRecordType<Book>('book') | ||
* ``` | ||
* | ||
* @param typeName - The name of the type to create. | ||
* | ||
* @public | ||
*/ | ||
export declare function createRecordType<R extends BaseRecord>(typeName: R['typeName'], config: { | ||
export declare function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: { | ||
migrations?: Migrations; | ||
validator: StoreValidator<R>; | ||
validator?: StoreValidator<R>; | ||
scope: Scope; | ||
}): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>; | ||
@@ -92,8 +97,12 @@ | ||
/** @public */ | ||
export declare function defineMigrations<FirstVersion extends number, CurrentVersion extends number>({ firstVersion, currentVersion, migrators, subTypeKey, subTypeMigrations, }: { | ||
firstVersion: FirstVersion; | ||
currentVersion: CurrentVersion; | ||
migrators: { | ||
export declare 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; | ||
@@ -104,6 +113,7 @@ subTypeMigrations?: Record<string, BaseMigrationsInfo>; | ||
/** | ||
* Freeze an object when in development mode. | ||
* Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | ||
* 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 | ||
@@ -114,5 +124,3 @@ * 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 | ||
@@ -122,2 +130,4 @@ */ | ||
declare type EMPTY_SYMBOL = symbol; | ||
declare type ExtractR<T extends RecordType<any, any>> = T extends RecordType<infer S, any> ? S : never; | ||
@@ -128,18 +138,22 @@ | ||
/** @public */ | ||
export declare function getRecordVersion(record: BaseRecord, serializedSchema: SerializedSchema): RecordVersion; | ||
export declare function getRecordVersion(record: UnknownRecord, serializedSchema: SerializedSchema): RecordVersion; | ||
/** | ||
* An entry containing changes that originated either by user actions or remote changes. | ||
* | ||
* @public | ||
*/ | ||
export declare type HistoryEntry<R extends BaseRecord = BaseRecord> = { | ||
export declare type HistoryEntry<R extends UnknownRecord = UnknownRecord> = { | ||
changes: RecordsDiff<R>; | ||
source: 'user' | 'remote'; | ||
source: 'remote' | 'user'; | ||
}; | ||
/** @public */ | ||
export declare type ID<R extends BaseRecord = BaseRecord> = string & { | ||
export declare type ID<R extends UnknownRecord> = string & { | ||
__type__: R; | ||
}; | ||
/** @public */ | ||
export declare type IdOf<R extends UnknownRecord> = R['id']; | ||
/* Excluded from this release type: IncrementalSetConstructor */ | ||
@@ -156,3 +170,3 @@ | ||
/** @public */ | ||
export declare function migrateRecord<R extends BaseRecord>({ record, migrations, fromVersion, toVersion, }: { | ||
export declare function migrateRecord<R extends UnknownRecord>({ record, migrations, fromVersion, toVersion, }: { | ||
record: unknown; | ||
@@ -165,5 +179,5 @@ migrations: Migrations; | ||
/** @public */ | ||
export declare type Migration<T = any> = { | ||
up: (oldState: T) => T; | ||
down: (newState: T) => T; | ||
export declare type Migration<Before = any, After = any> = { | ||
up: (oldState: Before) => After; | ||
down: (newState: After) => Before; | ||
}; | ||
@@ -183,7 +197,7 @@ | ||
export declare type MigrationResult<T> = { | ||
type: 'error'; | ||
reason: MigrationFailureReason; | ||
} | { | ||
type: 'success'; | ||
value: T; | ||
} | { | ||
type: 'error'; | ||
reason: MigrationFailureReason; | ||
}; | ||
@@ -197,3 +211,3 @@ | ||
declare type OmitMeta<R extends BaseRecord> = R extends R ? Omit<R, 'id' | 'typeName'> : R; | ||
declare type OmitMeta<R extends UnknownRecord> = R extends R ? Omit<R, 'id' | 'typeName'> : R; | ||
@@ -204,14 +218,15 @@ declare type QueryExpression<R extends object> = { | ||
declare type Range_2<From extends number, To extends number> = To extends From ? From : To | Range_2<From, Decrement<To>>; | ||
declare type Range_2<From extends number, To extends number> = To extends From ? From : Range_2<From, Decrement<To>> | To; | ||
declare type RecFromId<K extends ID> = K extends ID<infer R> ? R : never; | ||
declare type RecFromId<K extends ID<UnknownRecord>> = K extends ID<infer R> ? R : never; | ||
/** | ||
* A diff describing the changes to a record. | ||
* | ||
* @public | ||
*/ | ||
export declare type RecordsDiff<R extends BaseRecord> = { | ||
added: Record<string, R>; | ||
updated: Record<string, [from: R, to: R]>; | ||
removed: Record<string, R>; | ||
export declare type RecordsDiff<R extends UnknownRecord> = { | ||
added: Record<IdOf<R>, R>; | ||
updated: Record<IdOf<R>, [from: R, to: R]>; | ||
removed: Record<IdOf<R>, R>; | ||
}; | ||
@@ -225,3 +240,3 @@ | ||
*/ | ||
export declare class RecordType<R extends BaseRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> { | ||
export declare class RecordType<R extends UnknownRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> { | ||
/** | ||
@@ -236,5 +251,6 @@ * The unique type associated with this record. | ||
readonly migrations: Migrations; | ||
readonly validator: StoreValidator<R> | { | ||
readonly validator: { | ||
validate: (r: unknown) => R; | ||
}; | ||
} | StoreValidator<R>; | ||
readonly scope: Scope; | ||
constructor( | ||
@@ -250,5 +266,6 @@ /** | ||
readonly migrations: Migrations; | ||
readonly validator?: StoreValidator<R> | { | ||
readonly validator?: { | ||
validate: (r: unknown) => R; | ||
}; | ||
} | StoreValidator<R>; | ||
readonly scope?: Scope; | ||
}); | ||
@@ -259,3 +276,2 @@ /** | ||
* @param properties - The properties of the record. | ||
* | ||
* @returns The new record. | ||
@@ -268,4 +284,4 @@ */ | ||
* @param record - The record to clone. | ||
* @returns The cloned record. | ||
* @public | ||
* @returns The cloned record. | ||
*/ | ||
@@ -277,2 +293,3 @@ clone(record: R): R; | ||
* @example | ||
* | ||
* ```ts | ||
@@ -282,6 +299,6 @@ * const id = recordType.createId() | ||
* | ||
* @returns The new ID. | ||
* @public | ||
* @returns The new ID. | ||
*/ | ||
createId(): ID<R>; | ||
createId(): IdOf<R>; | ||
/** | ||
@@ -291,4 +308,5 @@ * Create a new ID for this record type based on the given ID. | ||
* @example | ||
* | ||
* ```ts | ||
* const id = recordType.createCustomId("myId") | ||
* const id = recordType.createCustomId('myId') | ||
* ``` | ||
@@ -299,7 +317,15 @@ * | ||
*/ | ||
createCustomId(id: string): ID<R>; | ||
createCustomId(id: string): IdOf<R>; | ||
/** | ||
* Takes an id like `user:123` and returns the part after the colon `123` | ||
* | ||
* @param id - The id | ||
* @returns | ||
*/ | ||
parseId(id: string): IdOf<R>; | ||
/** | ||
* Check whether a record is an instance of this record type. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
@@ -312,3 +338,3 @@ * const result = recordType.isInstance(someRecord) | ||
*/ | ||
isInstance: (record?: BaseRecord) => record is R; | ||
isInstance: (record?: UnknownRecord) => record is R; | ||
/** | ||
@@ -318,4 +344,5 @@ * Check whether an id is an id of this type. | ||
* @example | ||
* | ||
* ```ts | ||
* const result = recordType.isIn("someId") | ||
* const result = recordType.isIn('someId') | ||
* ``` | ||
@@ -326,7 +353,9 @@ * | ||
*/ | ||
isId(id?: string): id is ID<R>; | ||
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. | ||
* Create a new RecordType that has the same type name as this RecordType and includes the given | ||
* default properties. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
@@ -338,10 +367,8 @@ * const authorType = createRecordType('author', () => ({ living: true })) | ||
* @param fn - A function that returns the default properties of the new RecordType. | ||
* | ||
* @returns The new RecordType. | ||
*/ | ||
withDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(createDefaultProperties: () => DefaultProps): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>; | ||
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. | ||
* 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. | ||
*/ | ||
@@ -360,26 +387,33 @@ validate(record: unknown): R; | ||
declare type RSIndex<R extends BaseRecord = BaseRecord, Property extends string & keyof R = string & keyof R> = Computed<Map<R[Property], Set<ID<R>>>, RSIndexDiff<R, Property>>; | ||
declare type RSIndex<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Computed<Map<R[Property], Set<IdOf<R>>>, RSIndexDiff<R, Property>>; | ||
declare type RSIndexDiff<R extends BaseRecord = BaseRecord, Property extends string & keyof R = string & keyof R> = Map<R[Property], CollectionDiff<ID<R>>>; | ||
declare type RSIndexDiff<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Map<R[Property], CollectionDiff<IdOf<R>>>; | ||
/** | ||
* Defines the scope of the record | ||
* | ||
* instance: 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 | ||
* */ | ||
declare type Scope = 'document' | 'instance' | 'presence'; | ||
/** @public */ | ||
export declare interface SerializedSchema { | ||
/** | ||
* Schema version is the version for this type you're looking at right now | ||
*/ | ||
/** Schema version is the version for this type you're looking at right now */ | ||
schemaVersion: number; | ||
/** | ||
* Store version is the version for the structure of the store. e.g. higher level | ||
* structure like removing or renaming a record type. | ||
* 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 | ||
*/ | ||
/** Record versions are the versions for each record type. e.g. adding a new field to a record */ | ||
recordVersions: Record<string, { | ||
version: number; | ||
subTypeVersions: Record<string, number>; | ||
subTypeKey: string; | ||
} | { | ||
version: number; | ||
subTypeVersions: Record<string, number>; | ||
subTypeKey: string; | ||
}>; | ||
@@ -395,9 +429,10 @@ } | ||
*/ | ||
export declare function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>; | ||
export declare function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>; | ||
/** | ||
* A store of records. | ||
* | ||
* @public | ||
*/ | ||
export declare class Store<R extends BaseRecord = BaseRecord, Props = unknown> { | ||
export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> { | ||
/* Excluded from this release type: atoms */ | ||
@@ -424,9 +459,7 @@ /** | ||
constructor(config: { | ||
/** | ||
* The store's initial data. | ||
*/ | ||
/** The store's initial data. */ | ||
initialData?: StoreSnapshot<R>; | ||
/** | ||
* 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. | ||
* 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. | ||
*/ | ||
@@ -443,6 +476,6 @@ schema: StoreSchema<R, Props>; | ||
private updateHistory; | ||
validate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'): void; | ||
validate(phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'): void; | ||
/** | ||
* A callback fired after a record is created. Use this to perform | ||
* related updates to other records in the store. | ||
* A callback fired after a record is created. Use this to perform related updates to other | ||
* records in the store. | ||
* | ||
@@ -485,3 +518,3 @@ * @param record - The record to be created | ||
*/ | ||
remove: (ids: ID<R>[]) => void; | ||
remove: (ids: IdOf<R>[]) => void; | ||
/** | ||
@@ -493,3 +526,3 @@ * Get the value of a store record by its id. | ||
*/ | ||
get: <K extends ID<R>>(id: K) => RecFromId<K> | undefined; | ||
get: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined; | ||
/** | ||
@@ -501,3 +534,3 @@ * Get the value of a store record by its id without updating its epoch. | ||
*/ | ||
unsafeGetWithoutCapture: <K extends ID<R>>(id: K) => RecFromId<K> | undefined; | ||
unsafeGetWithoutCapture: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined; | ||
/** | ||
@@ -511,3 +544,9 @@ * Opposite of `deserialize`. Creates a JSON payload from the record store. | ||
/** | ||
* Opposite of `serialize`. Replace the store's current records with records as defined by a simple JSON structure into the stores. | ||
* The same as `serialize`, but only serializes records with a scope of `document`. | ||
* @returns The record store snapshot as a JSON payload. | ||
*/ | ||
serializeDocumentState: () => StoreSnapshot<R>; | ||
/** | ||
* Opposite of `serialize`. Replace the store's current records with records as defined by a | ||
* simple JSON structure into the stores. | ||
* | ||
@@ -521,4 +560,4 @@ * @param snapshot - The JSON snapshot to deserialize. | ||
* | ||
* @returns An array of all values in the store. | ||
* @public | ||
* @returns An array of all values in the store. | ||
*/ | ||
@@ -533,3 +572,4 @@ allRecords: () => R[]; | ||
/** | ||
* Update a record. To update multiple records at once, use the `update` method of the `TypedStore` class. | ||
* Update a record. To update multiple records at once, use the `update` method of the | ||
* `TypedStore` class. | ||
* | ||
@@ -539,3 +579,3 @@ * @param id - The id of the record to update. | ||
*/ | ||
update: <K extends ID<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => void; | ||
update: <K extends IdOf<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => void; | ||
/** | ||
@@ -547,3 +587,3 @@ * Get whether the record store has a id. | ||
*/ | ||
has: <K extends ID<R>>(id: K) => boolean; | ||
has: <K extends IdOf<R>>(id: K) => boolean; | ||
/** | ||
@@ -583,2 +623,3 @@ * Add a new listener to the store. | ||
createSelectedComputedCache: <T, J, V extends R = R>(name: string, selector: (record: V) => T | undefined, derive: (input: T) => J | undefined) => ComputedCache<J, V>; | ||
private _integrityChecker?; | ||
/* Excluded from this release type: ensureStoreIsUsable */ | ||
@@ -593,3 +634,3 @@ private _isPossiblyCorrupted; | ||
error: Error; | ||
phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'; | ||
phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'; | ||
recordBefore?: unknown; | ||
@@ -602,14 +643,15 @@ recordAfter: unknown; | ||
* A function that will be called when the history changes. | ||
* | ||
* @public | ||
*/ | ||
export declare type StoreListener<R extends BaseRecord> = (entry: HistoryEntry<R>) => void; | ||
export declare type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void; | ||
/** | ||
* A class that provides a 'namespace' for the various kinds of indexes | ||
* one may wish to derive from the record store. | ||
* A class that provides a 'namespace' for the various kinds of indexes one may wish to derive from | ||
* the record store. | ||
*/ | ||
declare class StoreQueries<R extends BaseRecord = BaseRecord> { | ||
declare class StoreQueries<R extends UnknownRecord> { | ||
private readonly atoms; | ||
private readonly history; | ||
constructor(atoms: Atom<Record<ID<R>, Atom<R>>>, history: Atom<number, RecordsDiff<R>>); | ||
constructor(atoms: Atom<Record<IdOf<R>, Atom<R>>>, history: Atom<number, RecordsDiff<R>>); | ||
/* Excluded from this release type: indexCache */ | ||
@@ -621,3 +663,2 @@ /* Excluded from this release type: historyCache */ | ||
* @param typeName - The name of the type to filter by. | ||
* | ||
* @returns A derivation that returns the ids of all records of the given type. | ||
@@ -634,3 +675,2 @@ * @public | ||
* @param property - The name of the property. | ||
* | ||
* @public | ||
@@ -679,5 +719,5 @@ */ | ||
typeName: TypeName; | ||
}>>, name?: string): Computed<Set<ID<Extract<R, { | ||
}>>, name?: string): Computed<Set<IdOf<Extract<R, { | ||
typeName: TypeName; | ||
}>>>, CollectionDiff<ID<Extract<R, { | ||
}>>>, CollectionDiff<IdOf<Extract<R, { | ||
typeName: TypeName; | ||
@@ -693,3 +733,3 @@ }>>>>; | ||
/** @public */ | ||
export declare class StoreSchema<R extends BaseRecord, P = unknown> { | ||
export declare class StoreSchema<R extends UnknownRecord, P = unknown> { | ||
readonly types: { | ||
@@ -699,3 +739,3 @@ [Record in R as Record['typeName']]: RecordType<R, any>; | ||
private readonly options; | ||
static create<R extends BaseRecord, P = unknown>(types: { | ||
static create<R extends UnknownRecord, P = unknown>(types: { | ||
[TypeName in R['typeName']]: { | ||
@@ -707,6 +747,6 @@ createId: any; | ||
get currentStoreVersion(): number; | ||
validateRecord(store: Store<R>, record: R, phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests', recordBefore: R | null): R; | ||
migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'up' | 'down'): MigrationResult<R>; | ||
validateRecord(store: Store<R>, record: R, phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord', recordBefore: null | R): R; | ||
migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>; | ||
migrateStoreSnapshot(storeSnapshot: StoreSnapshot<R>, persistedSchema: SerializedSchema): MigrationResult<StoreSnapshot<R>>; | ||
/* Excluded from this release type: ensureStoreIsUsable */ | ||
/* Excluded from this release type: createIntegrityChecker */ | ||
serialize(): SerializedSchema; | ||
@@ -717,3 +757,3 @@ serializeEarliestVersion(): SerializedSchema; | ||
/** @public */ | ||
export declare type StoreSchemaOptions<R extends BaseRecord, P> = { | ||
export declare type StoreSchemaOptions<R extends UnknownRecord, P> = { | ||
/** @public */ | ||
@@ -726,6 +766,6 @@ snapshotMigrations?: Migrations; | ||
record: R; | ||
phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'; | ||
recordBefore: R | null; | ||
phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'; | ||
recordBefore: null | R; | ||
}) => R; | ||
/* Excluded from this release type: ensureStoreIsUsable */ | ||
/* Excluded from this release type: createIntegrityChecker */ | ||
}; | ||
@@ -735,8 +775,9 @@ | ||
* A serialized snapshot of the record store's values. | ||
* | ||
* @public | ||
*/ | ||
export declare type StoreSnapshot<R extends BaseRecord> = Record<string, R>; | ||
export declare type StoreSnapshot<R extends UnknownRecord> = Record<IdOf<R>, R>; | ||
/** @public */ | ||
export declare type StoreValidator<R extends BaseRecord> = { | ||
export declare type StoreValidator<R extends UnknownRecord> = { | ||
validate: (record: unknown) => R; | ||
@@ -746,3 +787,3 @@ }; | ||
/** @public */ | ||
export declare type StoreValidators<R extends BaseRecord> = { | ||
export declare type StoreValidators<R extends UnknownRecord> = { | ||
[K in R['typeName']]: StoreValidator<Extract<R, { | ||
@@ -753,10 +794,13 @@ typeName: K; | ||
/** @public */ | ||
export declare type UnknownRecord = BaseRecord<string, ID<UnknownRecord>>; | ||
declare type ValueMatcher<T> = { | ||
eq: T; | ||
} | { | ||
gt: number; | ||
} | { | ||
neq: T; | ||
} | { | ||
gt: number; | ||
}; | ||
export { } |
@@ -6,2 +6,6 @@ "use strict"; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __export = (target, all) => { | ||
for (var name in all) | ||
__defProp(target, name, { get: all[name], enumerable: true }); | ||
}; | ||
var __copyProps = (to, from, except, desc) => { | ||
@@ -15,7 +19,30 @@ if (from && typeof from === "object" || typeof from === "function") { | ||
}; | ||
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); | ||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
var src_exports = {}; | ||
__export(src_exports, { | ||
IncrementalSetConstructor: () => import_IncrementalSetConstructor.IncrementalSetConstructor, | ||
MigrationFailureReason: () => import_migrate.MigrationFailureReason, | ||
RecordType: () => import_RecordType.RecordType, | ||
Store: () => import_Store.Store, | ||
StoreSchema: () => import_StoreSchema.StoreSchema, | ||
assertIdType: () => import_RecordType.assertIdType, | ||
compareRecordVersions: () => import_migrate.compareRecordVersions, | ||
compareSchemas: () => import_compareSchemas.compareSchemas, | ||
createRecordType: () => import_RecordType.createRecordType, | ||
defineMigrations: () => import_migrate.defineMigrations, | ||
devFreeze: () => import_devFreeze.devFreeze, | ||
getRecordVersion: () => import_migrate.getRecordVersion, | ||
migrate: () => import_migrate.migrate, | ||
migrateRecord: () => import_migrate.migrateRecord, | ||
reverseRecordsDiff: () => import_Store.reverseRecordsDiff, | ||
squashRecordDiffs: () => import_Store.squashRecordDiffs | ||
}); | ||
module.exports = __toCommonJS(src_exports); | ||
__reExport(src_exports, require("./lib"), module.exports); | ||
var import_IncrementalSetConstructor = require("./lib/IncrementalSetConstructor"); | ||
var import_RecordType = require("./lib/RecordType"); | ||
var import_Store = require("./lib/Store"); | ||
var import_StoreSchema = require("./lib/StoreSchema"); | ||
var import_compareSchemas = require("./lib/compareSchemas"); | ||
var import_devFreeze = require("./lib/devFreeze"); | ||
var import_migrate = require("./lib/migrate"); | ||
//# sourceMappingURL=index.js.map |
@@ -58,3 +58,2 @@ "use strict"; | ||
* @param wasAlreadyPresent - Whether the item was already present in the set. | ||
* | ||
* @internal | ||
@@ -77,3 +76,2 @@ */ | ||
* @param item - The item to add. | ||
* | ||
* @public | ||
@@ -99,3 +97,2 @@ */ | ||
* @param wasAlreadyPresent - Whether the item was already present in the set. | ||
* | ||
* @internal | ||
@@ -118,3 +115,2 @@ */ | ||
* @param item - The item to remove. | ||
* | ||
* @public | ||
@@ -121,0 +117,0 @@ */ |
@@ -30,10 +30,20 @@ "use strict"; | ||
var import_BaseRecord = require("./BaseRecord"); | ||
function defineMigrations({ | ||
firstVersion, | ||
currentVersion, | ||
migrators, | ||
subTypeKey, | ||
subTypeMigrations | ||
}) { | ||
return { currentVersion, firstVersion, migrators, subTypeKey, subTypeMigrations }; | ||
function defineMigrations(opts) { | ||
const { currentVersion, firstVersion, migrators = {}, subTypeKey, subTypeMigrations } = opts; | ||
if (typeof currentVersion === "number" && typeof firstVersion === "number") { | ||
if (currentVersion === firstVersion) { | ||
throw Error(`Current version is equal to initial version.`); | ||
} else if (currentVersion < firstVersion) { | ||
throw Error(`Current version is lower than initial version.`); | ||
} | ||
} | ||
return { | ||
firstVersion: firstVersion ?? 0, | ||
// defaults | ||
currentVersion: currentVersion ?? 0, | ||
// defaults | ||
migrators, | ||
subTypeKey, | ||
subTypeMigrations | ||
}; | ||
} | ||
@@ -40,0 +50,0 @@ var MigrationFailureReason = /* @__PURE__ */ ((MigrationFailureReason2) => { |
@@ -34,2 +34,3 @@ "use strict"; | ||
this.validator = config.validator ?? { validate: (r) => r }; | ||
this.scope = config.scope ?? "document"; | ||
} | ||
@@ -39,2 +40,3 @@ createDefaultProperties; | ||
validator; | ||
scope; | ||
/** | ||
@@ -44,3 +46,2 @@ * Create a new record of this type. | ||
* @param properties - The properties of the record. | ||
* | ||
* @returns The new record. | ||
@@ -62,4 +63,4 @@ */ | ||
* @param record - The record to clone. | ||
* @returns The cloned record. | ||
* @public | ||
* @returns The cloned record. | ||
*/ | ||
@@ -73,2 +74,3 @@ clone(record) { | ||
* @example | ||
* | ||
* ```ts | ||
@@ -78,4 +80,4 @@ * const id = recordType.createId() | ||
* | ||
* @returns The new ID. | ||
* @public | ||
* @returns The new ID. | ||
*/ | ||
@@ -89,4 +91,5 @@ createId() { | ||
* @example | ||
* | ||
* ```ts | ||
* const id = recordType.createCustomId("myId") | ||
* const id = recordType.createCustomId('myId') | ||
* ``` | ||
@@ -101,5 +104,18 @@ * | ||
/** | ||
* Takes an id like `user:123` and returns the part after the colon `123` | ||
* | ||
* @param id - The id | ||
* @returns | ||
*/ | ||
parseId(id) { | ||
if (!this.isId(id)) { | ||
throw new Error(`ID "${id}" is not a valid ID for type "${this.typeName}"`); | ||
} | ||
return id.slice(this.typeName.length + 1); | ||
} | ||
/** | ||
* Check whether a record is an instance of this record type. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
@@ -119,4 +135,5 @@ * const result = recordType.isInstance(someRecord) | ||
* @example | ||
* | ||
* ```ts | ||
* const result = recordType.isIn("someId") | ||
* const result = recordType.isIn('someId') | ||
* ``` | ||
@@ -137,5 +154,7 @@ * | ||
/** | ||
* Create a new RecordType that has the same type name as this RecordType and includes the given default properties. | ||
* Create a new RecordType that has the same type name as this RecordType and includes the given | ||
* default properties. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
@@ -147,3 +166,2 @@ * const authorType = createRecordType('author', () => ({ living: true })) | ||
* @param fn - A function that returns the default properties of the new RecordType. | ||
* | ||
* @returns The new RecordType. | ||
@@ -155,9 +173,9 @@ */ | ||
migrations: this.migrations, | ||
validator: this.validator | ||
validator: this.validator, | ||
scope: this.scope | ||
}); | ||
} | ||
/** | ||
* 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. | ||
* 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. | ||
*/ | ||
@@ -172,3 +190,4 @@ validate(record) { | ||
migrations: config.migrations ?? { currentVersion: 0, firstVersion: 0, migrators: {} }, | ||
validator: config.validator | ||
validator: config.validator, | ||
scope: config.scope | ||
}); | ||
@@ -175,0 +194,0 @@ } |
@@ -26,6 +26,7 @@ "use strict"; | ||
module.exports = __toCommonJS(Store_exports); | ||
var import_utils = require("@tldraw/utils"); | ||
var import_signia = require("signia"); | ||
var import_Cache = require("./Cache"); | ||
var import_StoreQueries = require("./StoreQueries"); | ||
var import_devFreeze = require("./devFreeze"); | ||
var import_StoreQueries = require("./StoreQueries"); | ||
class Store { | ||
@@ -68,4 +69,4 @@ /** | ||
/** | ||
* A reactor that responds to changes to the history by squashing the | ||
* accumulated history and notifying listeners of the changes. | ||
* A reactor that responds to changes to the history by squashing the accumulated history and | ||
* notifying listeners of the changes. | ||
* | ||
@@ -83,4 +84,4 @@ * @internal | ||
this.atoms.set( | ||
Object.fromEntries( | ||
Object.entries(initialData).map(([id, record]) => [ | ||
(0, import_utils.objectMapFromEntries)( | ||
(0, import_utils.objectMapEntries)(initialData).map(([id, record]) => [ | ||
id, | ||
@@ -98,3 +99,3 @@ (0, import_signia.atom)("atom:" + id, this.schema.validateRecord(this, record, "initialize", null)) | ||
}, | ||
{ scheduleEffect: (cb) => requestAnimationFrame(cb) } | ||
{ scheduleEffect: (cb) => (0, import_utils.throttledRaf)(cb) } | ||
); | ||
@@ -129,4 +130,4 @@ } | ||
/** | ||
* A callback fired after a record is created. Use this to perform | ||
* related updates to other records in the store. | ||
* A callback fired after a record is created. Use this to perform related updates to other | ||
* records in the store. | ||
* | ||
@@ -293,3 +294,3 @@ * @param record - The record to be created | ||
const result = {}; | ||
for (const [id, atom2] of Object.entries(this.atoms.value)) { | ||
for (const [id, atom2] of (0, import_utils.objectMapEntries)(this.atoms.value)) { | ||
const record = atom2.value; | ||
@@ -303,3 +304,14 @@ if (typeof filter === "function" && !filter(record)) | ||
/** | ||
* Opposite of `serialize`. Replace the store's current records with records as defined by a simple JSON structure into the stores. | ||
* The same as `serialize`, but only serializes records with a scope of `document`. | ||
* @returns The record store snapshot as a JSON payload. | ||
*/ | ||
serializeDocumentState = () => { | ||
return this.serialize((r) => { | ||
const type = this.schema.types[r.typeName]; | ||
return type.scope === "document"; | ||
}); | ||
}; | ||
/** | ||
* Opposite of `serialize`. Replace the store's current records with records as defined by a | ||
* simple JSON structure into the stores. | ||
* | ||
@@ -318,7 +330,7 @@ * @param snapshot - The JSON snapshot to deserialize. | ||
* | ||
* @returns An array of all values in the store. | ||
* @public | ||
* @returns An array of all values in the store. | ||
*/ | ||
allRecords = () => { | ||
return Object.values(this.atoms.value).map((atom2) => atom2.value); | ||
return (0, import_utils.objectMapValues)(this.atoms.value).map((atom2) => atom2.value); | ||
}; | ||
@@ -331,6 +343,7 @@ /** | ||
clear = () => { | ||
this.remove(Object.keys(this.atoms.value)); | ||
this.remove((0, import_utils.objectMapKeys)(this.atoms.value)); | ||
}; | ||
/** | ||
* Update a record. To update multiple records at once, use the `update` method of the `TypedStore` class. | ||
* Update a record. To update multiple records at once, use the `update` method of the | ||
* `TypedStore` class. | ||
* | ||
@@ -409,6 +422,6 @@ * @param id - The id of the record to update. | ||
(0, import_signia.transact)(() => { | ||
const toPut = Object.values(diff.added).concat( | ||
Object.values(diff.updated).map(([_from, to]) => to) | ||
const toPut = (0, import_utils.objectMapValues)(diff.added).concat( | ||
(0, import_utils.objectMapValues)(diff.updated).map(([_from, to]) => to) | ||
); | ||
const toRemove = Object.keys(diff.removed); | ||
const toRemove = (0, import_utils.objectMapKeys)(diff.removed); | ||
if (toPut.length) { | ||
@@ -474,5 +487,7 @@ this.put(toPut); | ||
}; | ||
_integrityChecker; | ||
/** @internal */ | ||
ensureStoreIsUsable() { | ||
this.schema.ensureStoreIsUsable(this); | ||
this._integrityChecker ??= this.schema.createIntegrityChecker(this); | ||
this._integrityChecker?.(); | ||
} | ||
@@ -492,3 +507,3 @@ _isPossiblyCorrupted = false; | ||
for (const diff of diffs) { | ||
for (const [id, value] of Object.entries(diff.added)) { | ||
for (const [id, value] of (0, import_utils.objectMapEntries)(diff.added)) { | ||
if (result.removed[id]) { | ||
@@ -504,3 +519,3 @@ const original = result.removed[id]; | ||
} | ||
for (const [id, [_from, to]] of Object.entries(diff.updated)) { | ||
for (const [id, [_from, to]] of (0, import_utils.objectMapEntries)(diff.updated)) { | ||
if (result.added[id]) { | ||
@@ -520,3 +535,3 @@ result.added[id] = to; | ||
} | ||
for (const [id, value] of Object.entries(diff.removed)) { | ||
for (const [id, value] of (0, import_utils.objectMapEntries)(diff.removed)) { | ||
if (result.added[id]) { | ||
@@ -586,13 +601,3 @@ delete result.added[id]; | ||
} | ||
/** | ||
* Ensure that the store is usable. A class that extends this store should override this method. | ||
* | ||
* @param config - The configuration object. This can be any object that allows the store to validate that it is usable; the extending class should specify the type. | ||
* | ||
* @public | ||
*/ | ||
ensureStoreIsUsable(_config = {}) { | ||
return; | ||
} | ||
} | ||
//# sourceMappingURL=Store.js.map |
@@ -34,2 +34,3 @@ "use strict"; | ||
module.exports = __toCommonJS(StoreQueries_exports); | ||
var import_utils = require("@tldraw/utils"); | ||
var import_lodash = __toESM(require("lodash.isequal")); | ||
@@ -61,3 +62,2 @@ var import_signia = require("signia"); | ||
* @param typeName - The name of the type to filter by. | ||
* | ||
* @returns A derivation that returns the ids of all records of the given type. | ||
@@ -84,3 +84,3 @@ * @public | ||
for (const changes of diff) { | ||
for (const added of Object.values(changes.added)) { | ||
for (const added of (0, import_utils.objectMapValues)(changes.added)) { | ||
if (added.typeName === typeName) { | ||
@@ -101,3 +101,3 @@ if (res.removed[added.id]) { | ||
} | ||
for (const [from, to] of Object.values(changes.updated)) { | ||
for (const [from, to] of (0, import_utils.objectMapValues)(changes.updated)) { | ||
if (to.typeName === typeName) { | ||
@@ -114,3 +114,3 @@ if (res.added[to.id]) { | ||
} | ||
for (const removed of Object.values(changes.removed)) { | ||
for (const removed of (0, import_utils.objectMapValues)(changes.removed)) { | ||
if (removed.typeName === typeName) { | ||
@@ -148,3 +148,2 @@ if (res.added[removed.id]) { | ||
* @param property - The name of the property. | ||
* | ||
* @public | ||
@@ -166,3 +165,2 @@ */ | ||
* @param property - The name of the property?. | ||
* | ||
* @internal | ||
@@ -175,3 +173,3 @@ */ | ||
const res = /* @__PURE__ */ new Map(); | ||
for (const atom of Object.values(this.atoms.value)) { | ||
for (const atom of (0, import_utils.objectMapValues)(this.atoms.value)) { | ||
const record = atom.value; | ||
@@ -201,3 +199,5 @@ if (record.typeName === typeName) { | ||
if (!setConstructor) | ||
setConstructor = new import_IncrementalSetConstructor.IncrementalSetConstructor(prevValue.get(value) ?? /* @__PURE__ */ new Set()); | ||
setConstructor = new import_IncrementalSetConstructor.IncrementalSetConstructor( | ||
prevValue.get(value) ?? /* @__PURE__ */ new Set() | ||
); | ||
setConstructor.add(id); | ||
@@ -214,3 +214,3 @@ setConstructors.set(value, setConstructor); | ||
for (const changes of history) { | ||
for (const record of Object.values(changes.added)) { | ||
for (const record of (0, import_utils.objectMapValues)(changes.added)) { | ||
if (record.typeName === typeName) { | ||
@@ -221,3 +221,3 @@ const value = record[property]; | ||
} | ||
for (const [from, to] of Object.values(changes.updated)) { | ||
for (const [from, to] of (0, import_utils.objectMapValues)(changes.updated)) { | ||
if (to.typeName === typeName) { | ||
@@ -232,3 +232,3 @@ const prev = from[property]; | ||
} | ||
for (const record of Object.values(changes.removed)) { | ||
for (const record of (0, import_utils.objectMapValues)(changes.removed)) { | ||
if (record.typeName === typeName) { | ||
@@ -316,3 +316,3 @@ const value = record[property]; | ||
return new Set( | ||
Object.values(this.atoms.value).flatMap((v) => { | ||
(0, import_utils.objectMapValues)(this.atoms.value).flatMap((v) => { | ||
const r = v.value; | ||
@@ -359,3 +359,3 @@ if (r.typeName === typeName) { | ||
for (const changes of history) { | ||
for (const added of Object.values(changes.added)) { | ||
for (const added of (0, import_utils.objectMapValues)(changes.added)) { | ||
if (added.typeName === typeName && (0, import_executeQuery.objectMatchesQuery)(query, added)) { | ||
@@ -365,3 +365,3 @@ setConstructor.add(added.id); | ||
} | ||
for (const [_, updated] of Object.values(changes.updated)) { | ||
for (const [_, updated] of (0, import_utils.objectMapValues)(changes.updated)) { | ||
if (updated.typeName === typeName) { | ||
@@ -375,3 +375,3 @@ if ((0, import_executeQuery.objectMatchesQuery)(query, updated)) { | ||
} | ||
for (const removed of Object.values(changes.removed)) { | ||
for (const removed of (0, import_utils.objectMapValues)(changes.removed)) { | ||
if (removed.typeName === typeName) { | ||
@@ -378,0 +378,0 @@ setConstructor.remove(removed.id); |
@@ -87,7 +87,7 @@ "use strict"; | ||
const ourSubTypeMigrations = ourType.migrations.subTypeMigrations?.[record[ourType.migrations.subTypeKey]]; | ||
const persistedSubTypeVersion = "subTypeVersions" in persistedType ? persistedType.subTypeVersions[record[ourType.migrations.subTypeKey]] : null; | ||
const persistedSubTypeVersion = "subTypeVersions" in persistedType ? persistedType.subTypeVersions[record[ourType.migrations.subTypeKey]] : void 0; | ||
if (ourSubTypeMigrations === void 0) { | ||
return { type: "error", reason: import_migrate.MigrationFailureReason.UnrecognizedSubtype }; | ||
} | ||
if (persistedSubTypeVersion == null) { | ||
if (persistedSubTypeVersion === void 0) { | ||
return { type: "error", reason: import_migrate.MigrationFailureReason.IncompatibleSubtype }; | ||
@@ -134,3 +134,3 @@ } | ||
const updated = []; | ||
for (const r of Object.values(storeSnapshot)) { | ||
for (const r of (0, import_utils.objectMapValues)(storeSnapshot)) { | ||
const result = this.migratePersistedRecord(r, persistedSchema); | ||
@@ -152,4 +152,4 @@ if (result.type === "error") { | ||
/** @internal */ | ||
ensureStoreIsUsable(store) { | ||
this.options.ensureStoreIsUsable?.(store); | ||
createIntegrityChecker(store) { | ||
return this.options.createIntegrityChecker?.(store) ?? void 0; | ||
} | ||
@@ -156,0 +156,0 @@ serialize() { |
{ | ||
"name": "@tldraw/tlstore", | ||
"description": "A tiny little drawing app (store).", | ||
"version": "2.0.0-canary.75778dd3", | ||
"version": "2.0.0-canary.7578fff2b179", | ||
"packageManager": "yarn@3.5.0", | ||
"author": { | ||
@@ -34,16 +35,15 @@ "name": "tldraw GB Ltd.", | ||
"scripts": { | ||
"test": "yarn run -T jest", | ||
"test:coverage": "yarn run -T jest --coverage", | ||
"build:types": "yarn run -T tsx ../../scripts/build-types.ts", | ||
"build": "echo 'build should be run by turbo'", | ||
"build:package": "yarn run -T tsx ../../scripts/build-package.ts", | ||
"build:api": "yarn run -T tsx ../../scripts/build-api.ts", | ||
"test": "lazy inherit", | ||
"test-coverage": "lazy inherit", | ||
"build": "yarn run -T tsx ../../scripts/build-package.ts", | ||
"build-api": "yarn run -T tsx ../../scripts/build-api.ts", | ||
"prepack": "yarn run -T tsx ../../scripts/prepack.ts", | ||
"postpack": "../../scripts/postpack.sh", | ||
"pack-tarball": "yarn pack", | ||
"lint": "yarn run -T tsx ../../scripts/lint.ts" | ||
}, | ||
"dependencies": { | ||
"@tldraw/utils": "2.0.0-canary.75778dd3", | ||
"@tldraw/utils": "2.0.0-canary.7578fff2b179", | ||
"lodash.isequal": "^4.5.0", | ||
"nanoid": "^3.0.0" | ||
"nanoid": "4.0.2" | ||
}, | ||
@@ -56,2 +56,3 @@ "peerDependencies": { | ||
"@types/lodash.isequal": "^4.5.6", | ||
"lazyrepo": "0.0.0-alpha.26", | ||
"raf": "^3.4.1" | ||
@@ -58,0 +59,0 @@ }, |
@@ -7,3 +7,3 @@ # @tldraw/tlstore | ||
`tlstore` is used by [tldraw](https://tldraw.com) to store its data. | ||
`tlstore` is used by [tldraw](https://www.tldraw.com) to store its data. | ||
@@ -352,1 +352,5 @@ It is designed to be used with `tlstate` (@tldraw/tlstate). | ||
A diff describing the changes to a collection. | ||
## License | ||
The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com). |
@@ -1,1 +0,32 @@ | ||
export * from './lib' | ||
export type { BaseRecord, ID, IdOf, UnknownRecord } from './lib/BaseRecord' | ||
export { IncrementalSetConstructor } from './lib/IncrementalSetConstructor' | ||
export { RecordType, assertIdType, createRecordType } from './lib/RecordType' | ||
export { Store, reverseRecordsDiff, squashRecordDiffs } from './lib/Store' | ||
export type { | ||
CollectionDiff, | ||
ComputedCache, | ||
HistoryEntry, | ||
RecordsDiff, | ||
StoreError, | ||
StoreListener, | ||
StoreSnapshot, | ||
StoreValidator, | ||
StoreValidators, | ||
} from './lib/Store' | ||
export { StoreSchema } from './lib/StoreSchema' | ||
export type { SerializedSchema, StoreSchemaOptions } from './lib/StoreSchema' | ||
export { compareSchemas } from './lib/compareSchemas' | ||
export { devFreeze } from './lib/devFreeze' | ||
export { | ||
MigrationFailureReason, | ||
compareRecordVersions, | ||
defineMigrations, | ||
getRecordVersion, | ||
migrate, | ||
migrateRecord, | ||
type Migration, | ||
type MigrationResult, | ||
type Migrations, | ||
type RecordVersion, | ||
} from './lib/migrate' | ||
export type { AllRecords } from './lib/type-utils' |
/** @public */ | ||
export type ID<R extends BaseRecord = BaseRecord> = string & { __type__: R } | ||
export type ID<R extends UnknownRecord> = string & { __type__: R } | ||
/** @public */ | ||
export type IdOf<R extends UnknownRecord> = R['id'] | ||
/** | ||
* The base record that all records must extend. | ||
* | ||
* @public | ||
*/ | ||
export interface BaseRecord<TypeName extends string = string> { | ||
readonly id: ID<this> | ||
export interface BaseRecord<TypeName extends string, Id extends ID<UnknownRecord>> { | ||
readonly id: Id | ||
readonly typeName: TypeName | ||
} | ||
export type OmitMeta<R extends BaseRecord> = R extends R ? Omit<R, 'id' | 'typeName'> : R | ||
/** @public */ | ||
export type UnknownRecord = BaseRecord<string, ID<UnknownRecord>> | ||
export function isRecord(record: unknown): record is BaseRecord { | ||
export type OmitMeta<R extends UnknownRecord> = R extends R ? Omit<R, 'id' | 'typeName'> : R | ||
export function isRecord(record: unknown): record is UnknownRecord { | ||
return typeof record === 'object' && record !== null && 'id' in record && 'typeName' in record | ||
} |
import { SerializedSchema } from './StoreSchema' | ||
/** @public */ | ||
export const compareSchemas = (a: SerializedSchema, b: SerializedSchema): number => { | ||
export const compareSchemas = (a: SerializedSchema, b: SerializedSchema): 0 | 1 | -1 => { | ||
if (a.schemaVersion > b.schemaVersion) { | ||
@@ -6,0 +6,0 @@ return 1 |
/** | ||
* Freeze an object when in development mode. | ||
* Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | ||
* 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 | ||
@@ -11,5 +12,3 @@ * 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 | ||
@@ -16,0 +15,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import { BaseRecord, ID } from './BaseRecord' | ||
import { IdOf, UnknownRecord } from './BaseRecord' | ||
import { intersectSets } from './setUtils' | ||
@@ -27,7 +27,7 @@ import { StoreQueries } from './StoreQueries' | ||
export function executeQuery<R extends BaseRecord, TypeName extends R['typeName']>( | ||
export function executeQuery<R extends UnknownRecord, TypeName extends R['typeName']>( | ||
store: StoreQueries<R>, | ||
typeName: TypeName, | ||
query: QueryExpression<Extract<R, { typeName: TypeName }>> | ||
): Set<ID<Extract<R, { typeName: TypeName }>>> { | ||
): Set<IdOf<Extract<R, { typeName: TypeName }>>> { | ||
const matchIds = Object.fromEntries(Object.keys(query).map((key) => [key, new Set()])) | ||
@@ -65,3 +65,3 @@ | ||
return intersectSets(Object.values(matchIds)) as Set<ID<Extract<R, { typeName: TypeName }>>> | ||
return intersectSets(Object.values(matchIds)) as Set<IdOf<Extract<R, { typeName: TypeName }>>> | ||
} |
@@ -5,2 +5,3 @@ import { CollectionDiff } from './Store' | ||
* A class that can be used to incrementally construct a set of records. | ||
* | ||
* @internal | ||
@@ -52,3 +53,2 @@ */ | ||
* @param wasAlreadyPresent - Whether the item was already present in the set. | ||
* | ||
* @internal | ||
@@ -73,3 +73,2 @@ */ | ||
* @param item - The item to add. | ||
* | ||
* @public | ||
@@ -97,3 +96,2 @@ */ | ||
* @param wasAlreadyPresent - Whether the item was already present in the set. | ||
* | ||
* @internal | ||
@@ -120,3 +118,2 @@ */ | ||
* @param item - The item to remove. | ||
* | ||
* @public | ||
@@ -123,0 +120,0 @@ */ |
@@ -1,27 +0,47 @@ | ||
import { BaseRecord, isRecord } from './BaseRecord' | ||
import { UnknownRecord, isRecord } from './BaseRecord' | ||
import { SerializedSchema } from './StoreSchema' | ||
type EMPTY_SYMBOL = symbol | ||
/** @public */ | ||
export function defineMigrations<FirstVersion extends number, CurrentVersion extends number>({ | ||
firstVersion, | ||
currentVersion, | ||
migrators, | ||
subTypeKey, | ||
subTypeMigrations, | ||
}: { | ||
firstVersion: FirstVersion | ||
currentVersion: CurrentVersion | ||
migrators: { | ||
[version in Exclude<Range<FirstVersion, CurrentVersion>, FirstVersion>]: Migration | ||
} | ||
export function defineMigrations< | ||
FirstVersion extends number | EMPTY_SYMBOL = EMPTY_SYMBOL, | ||
CurrentVersion extends Exclude<number, 0> | EMPTY_SYMBOL = 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<1, CurrentVersion>, 0>]: Migration } | ||
: { [version in Exclude<Range<FirstVersion, CurrentVersion>, FirstVersion>]: Migration } | ||
: { [version in Exclude<Range<1, CurrentVersion>, 0>]: Migration } | ||
: never | ||
subTypeKey?: string | ||
subTypeMigrations?: Record<string, BaseMigrationsInfo> | ||
}): Migrations { | ||
return { currentVersion, firstVersion, migrators, subTypeKey, subTypeMigrations } | ||
const { currentVersion, firstVersion, migrators = {}, subTypeKey, subTypeMigrations } = opts | ||
// Some basic guards against impossible version combinations, some of which will be caught by TypeScript | ||
if (typeof currentVersion === 'number' && typeof firstVersion === 'number') { | ||
if ((currentVersion as number) === (firstVersion as number)) { | ||
throw Error(`Current version is equal to initial version.`) | ||
} else if (currentVersion < firstVersion) { | ||
throw Error(`Current version is lower than initial version.`) | ||
} | ||
} | ||
return { | ||
firstVersion: (firstVersion as number) ?? 0, // defaults | ||
currentVersion: (currentVersion as number) ?? 0, // defaults | ||
migrators, | ||
subTypeKey, | ||
subTypeMigrations, | ||
} | ||
} | ||
/** @public */ | ||
export type Migration<T = any> = { | ||
up: (oldState: T) => T | ||
down: (newState: T) => T | ||
export type Migration<Before = any, After = any> = { | ||
up: (oldState: Before) => After | ||
down: (newState: After) => Before | ||
} | ||
@@ -60,3 +80,3 @@ | ||
export function getRecordVersion( | ||
record: BaseRecord, | ||
record: UnknownRecord, | ||
serializedSchema: SerializedSchema | ||
@@ -96,3 +116,3 @@ ): RecordVersion { | ||
/** @public */ | ||
export function migrateRecord<R extends BaseRecord>({ | ||
export function migrateRecord<R extends UnknownRecord>({ | ||
record, | ||
@@ -99,0 +119,0 @@ migrations, |
import { structuredClone } from '@tldraw/utils' | ||
import { nanoid } from 'nanoid' | ||
import { BaseRecord, ID, OmitMeta } from './BaseRecord' | ||
import { IdOf, OmitMeta, UnknownRecord } from './BaseRecord' | ||
import { StoreValidator } from './Store' | ||
@@ -10,2 +10,13 @@ import { Migrations } from './migrate' | ||
/** | ||
* Defines the scope of the record | ||
* | ||
* instance: 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 type Scope = 'instance' | 'document' | 'presence' | ||
/** | ||
* A record type is a type that can be stored in a record store. It is created with | ||
@@ -17,3 +28,3 @@ * `createRecordType`. | ||
export class RecordType< | ||
R extends BaseRecord, | ||
R extends UnknownRecord, | ||
RequiredProperties extends keyof Omit<R, 'id' | 'typeName'> | ||
@@ -25,2 +36,4 @@ > { | ||
readonly scope: Scope | ||
constructor( | ||
@@ -38,2 +51,3 @@ /** | ||
readonly validator?: StoreValidator<R> | { validate: (r: unknown) => R } | ||
readonly scope?: Scope | ||
} | ||
@@ -44,2 +58,3 @@ ) { | ||
this.validator = config.validator ?? { validate: (r: unknown) => r as R } | ||
this.scope = config.scope ?? 'document' | ||
} | ||
@@ -51,3 +66,2 @@ | ||
* @param properties - The properties of the record. | ||
* | ||
* @returns The new record. | ||
@@ -73,4 +87,4 @@ */ | ||
* @param record - The record to clone. | ||
* @returns The cloned record. | ||
* @public | ||
* @returns The cloned record. | ||
*/ | ||
@@ -85,2 +99,3 @@ clone(record: R): R { | ||
* @example | ||
* | ||
* ```ts | ||
@@ -90,7 +105,7 @@ * const id = recordType.createId() | ||
* | ||
* @returns The new ID. | ||
* @public | ||
* @returns The new ID. | ||
*/ | ||
createId(): ID<R> { | ||
return (this.typeName + ':' + nanoid()) as ID<R> | ||
createId(): IdOf<R> { | ||
return (this.typeName + ':' + nanoid()) as IdOf<R> | ||
} | ||
@@ -102,4 +117,5 @@ | ||
* @example | ||
* | ||
* ```ts | ||
* const id = recordType.createCustomId("myId") | ||
* const id = recordType.createCustomId('myId') | ||
* ``` | ||
@@ -110,10 +126,25 @@ * | ||
*/ | ||
createCustomId(id: string): ID<R> { | ||
return (this.typeName + ':' + id) as ID<R> | ||
createCustomId(id: string): IdOf<R> { | ||
return (this.typeName + ':' + id) as IdOf<R> | ||
} | ||
/** | ||
* Takes an id like `user:123` and returns the part after the colon `123` | ||
* | ||
* @param id - The id | ||
* @returns | ||
*/ | ||
parseId(id: string): IdOf<R> { | ||
if (!this.isId(id)) { | ||
throw new Error(`ID "${id}" is not a valid ID for type "${this.typeName}"`) | ||
} | ||
return id.slice(this.typeName.length + 1) as IdOf<R> | ||
} | ||
/** | ||
* Check whether a record is an instance of this record type. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
@@ -126,3 +157,3 @@ * const result = recordType.isInstance(someRecord) | ||
*/ | ||
isInstance = (record?: BaseRecord): record is R => { | ||
isInstance = (record?: UnknownRecord): record is R => { | ||
return record?.typeName === this.typeName | ||
@@ -135,4 +166,5 @@ } | ||
* @example | ||
* | ||
* ```ts | ||
* const result = recordType.isIn("someId") | ||
* const result = recordType.isIn('someId') | ||
* ``` | ||
@@ -143,3 +175,3 @@ * | ||
*/ | ||
isId(id?: string): id is ID<R> { | ||
isId(id?: string): id is IdOf<R> { | ||
if (!id) return false | ||
@@ -154,5 +186,7 @@ for (let i = 0; i < this.typeName.length; i++) { | ||
/** | ||
* Create a new RecordType that has the same type name as this RecordType and includes the given default properties. | ||
* Create a new RecordType that has the same type name as this RecordType and includes the given | ||
* default properties. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
@@ -164,3 +198,2 @@ * const authorType = createRecordType('author', () => ({ living: true })) | ||
* @param fn - A function that returns the default properties of the new RecordType. | ||
* | ||
* @returns The new RecordType. | ||
@@ -175,2 +208,3 @@ */ | ||
validator: this.validator, | ||
scope: this.scope, | ||
}) | ||
@@ -180,5 +214,4 @@ } | ||
/** | ||
* 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. | ||
* 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. | ||
*/ | ||
@@ -194,16 +227,16 @@ validate(record: unknown): R { | ||
* @example | ||
* | ||
* ```ts | ||
* const Book = createRecordType<Book>('book') | ||
* const Book = createRecordType<Book>('book') | ||
* ``` | ||
* | ||
* @param typeName - The name of the type to create. | ||
* | ||
* @public | ||
*/ | ||
export function createRecordType<R extends BaseRecord>( | ||
export function createRecordType<R extends UnknownRecord>( | ||
typeName: R['typeName'], | ||
config: { | ||
migrations?: Migrations | ||
// todo: optional validations | ||
validator: StoreValidator<R> | ||
validator?: StoreValidator<R> | ||
scope: Scope | ||
} | ||
@@ -215,2 +248,3 @@ ): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> { | ||
validator: config.validator, | ||
scope: config.scope, | ||
}) | ||
@@ -223,4 +257,5 @@ } | ||
* @example | ||
* | ||
* ```ts | ||
* assertIdType(myId, ID<MyRecord>) | ||
* assertIdType(myId, ID<MyRecord>) | ||
* ``` | ||
@@ -230,9 +265,8 @@ * | ||
* @param type - The type of the record. | ||
* | ||
* @public | ||
*/ | ||
export function assertIdType<R extends BaseRecord>( | ||
export function assertIdType<R extends UnknownRecord>( | ||
id: string | undefined, | ||
type: RecordType<R, any> | ||
): asserts id is ID<R> { | ||
): asserts id is IdOf<R> { | ||
if (!id || !type.isId(id)) { | ||
@@ -239,0 +273,0 @@ throw new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`) |
@@ -1,18 +0,27 @@ | ||
import { atom, Atom, computed, Computed, Reactor, reactor, transact } from 'signia' | ||
import { BaseRecord, ID } from './BaseRecord' | ||
import { | ||
objectMapEntries, | ||
objectMapFromEntries, | ||
objectMapKeys, | ||
objectMapValues, | ||
throttledRaf, | ||
} from '@tldraw/utils' | ||
import { Atom, Computed, Reactor, atom, computed, reactor, transact } from 'signia' | ||
import { ID, IdOf, UnknownRecord } from './BaseRecord' | ||
import { Cache } from './Cache' | ||
import { devFreeze } from './devFreeze' | ||
import { RecordType } from './RecordType' | ||
import { StoreQueries } from './StoreQueries' | ||
import { StoreSchema } from './StoreSchema' | ||
import { devFreeze } from './devFreeze' | ||
type RecFromId<K extends ID> = K extends ID<infer R> ? R : never | ||
type RecFromId<K extends ID<UnknownRecord>> = K extends ID<infer R> ? R : never | ||
/** | ||
* A diff describing the changes to a record. | ||
* | ||
* @public | ||
*/ | ||
export type RecordsDiff<R extends BaseRecord> = { | ||
added: Record<string, R> | ||
updated: Record<string, [from: R, to: R]> | ||
removed: Record<string, R> | ||
export type RecordsDiff<R extends UnknownRecord> = { | ||
added: Record<IdOf<R>, R> | ||
updated: Record<IdOf<R>, [from: R, to: R]> | ||
removed: Record<IdOf<R>, R> | ||
} | ||
@@ -22,2 +31,3 @@ | ||
* A diff describing the changes to a collection. | ||
* | ||
* @public | ||
@@ -29,5 +39,6 @@ */ | ||
* An entry containing changes that originated either by user actions or remote changes. | ||
* | ||
* @public | ||
*/ | ||
export type HistoryEntry<R extends BaseRecord = BaseRecord> = { | ||
export type HistoryEntry<R extends UnknownRecord = UnknownRecord> = { | ||
changes: RecordsDiff<R> | ||
@@ -39,12 +50,14 @@ source: 'user' | 'remote' | ||
* A function that will be called when the history changes. | ||
* | ||
* @public | ||
*/ | ||
export type StoreListener<R extends BaseRecord> = (entry: HistoryEntry<R>) => void | ||
export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void | ||
/** | ||
* A record store is a collection of records of different types. | ||
* | ||
* @public | ||
*/ | ||
export type ComputedCache<Data, R extends BaseRecord> = { | ||
get(id: ID<R>): Data | undefined | ||
export type ComputedCache<Data, R extends UnknownRecord> = { | ||
get(id: IdOf<R>): Data | undefined | ||
} | ||
@@ -54,8 +67,9 @@ | ||
* A serialized snapshot of the record store's values. | ||
* | ||
* @public | ||
*/ | ||
export type StoreSnapshot<R extends BaseRecord> = Record<string, R> | ||
export type StoreSnapshot<R extends UnknownRecord> = Record<IdOf<R>, R> | ||
/** @public */ | ||
export type StoreValidator<R extends BaseRecord> = { | ||
export type StoreValidator<R extends UnknownRecord> = { | ||
validate: (record: unknown) => R | ||
@@ -65,3 +79,3 @@ } | ||
/** @public */ | ||
export type StoreValidators<R extends BaseRecord> = { | ||
export type StoreValidators<R extends UnknownRecord> = { | ||
[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>> | ||
@@ -84,5 +98,6 @@ } | ||
* A store of records. | ||
* | ||
* @public | ||
*/ | ||
export class Store<R extends BaseRecord = BaseRecord, Props = unknown> { | ||
export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> { | ||
/** | ||
@@ -94,3 +109,3 @@ * An atom containing the store's atoms. | ||
*/ | ||
private readonly atoms: Atom<Record<ID<R>, Atom<R>>> = atom('store_atoms', {}) | ||
private readonly atoms = atom('store_atoms', {} as Record<IdOf<R>, Atom<R>>) | ||
@@ -130,4 +145,4 @@ /** | ||
/** | ||
* A reactor that responds to changes to the history by squashing the | ||
* accumulated history and notifying listeners of the changes. | ||
* A reactor that responds to changes to the history by squashing the accumulated history and | ||
* notifying listeners of the changes. | ||
* | ||
@@ -143,9 +158,7 @@ * @internal | ||
constructor(config: { | ||
/** | ||
* The store's initial data. | ||
*/ | ||
/** The store's initial data. */ | ||
initialData?: StoreSnapshot<R> | ||
/** | ||
* 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. | ||
* 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. | ||
*/ | ||
@@ -162,4 +175,4 @@ schema: StoreSchema<R, Props> | ||
this.atoms.set( | ||
Object.fromEntries( | ||
Object.entries(initialData).map(([id, record]) => [ | ||
objectMapFromEntries( | ||
objectMapEntries(initialData).map(([id, record]) => [ | ||
id, | ||
@@ -180,3 +193,3 @@ atom('atom:' + id, this.schema.validateRecord(this, record, 'initialize', null)), | ||
}, | ||
{ scheduleEffect: (cb) => requestAnimationFrame(cb) } | ||
{ scheduleEffect: (cb) => throttledRaf(cb) } | ||
) | ||
@@ -216,4 +229,4 @@ } | ||
/** | ||
* A callback fired after a record is created. Use this to perform | ||
* related updates to other records in the store. | ||
* A callback fired after a record is created. Use this to perform related updates to other | ||
* records in the store. | ||
* | ||
@@ -257,7 +270,7 @@ * @param record - The record to be created | ||
transact(() => { | ||
const updates: Record<ID<R>, [from: R, to: R]> = {} | ||
const additions: Record<ID<R>, R> = {} | ||
const updates: Record<IdOf<UnknownRecord>, [from: R, to: R]> = {} | ||
const additions: Record<IdOf<UnknownRecord>, R> = {} | ||
const currentMap = this.atoms.__unsafe__getWithoutCapture() | ||
let map = null as null | Record<ID<R>, Atom<R>> | ||
let map = null as null | Record<IdOf<UnknownRecord>, Atom<R>> | ||
@@ -276,3 +289,3 @@ // Iterate through all records, creating, updating or removing as needed | ||
const recordAtom = (map ?? currentMap)[record.id] | ||
const recordAtom = (map ?? currentMap)[record.id as IdOf<R>] | ||
@@ -336,3 +349,3 @@ if (recordAtom) { | ||
updated: updates, | ||
removed: {}, | ||
removed: {} as Record<IdOf<R>, R>, | ||
}) | ||
@@ -364,3 +377,3 @@ | ||
*/ | ||
remove = (ids: ID<R>[]): void => { | ||
remove = (ids: IdOf<R>[]): void => { | ||
transact(() => { | ||
@@ -385,3 +398,3 @@ if (this.onBeforeDelete && this._runCallbacks) { | ||
if (!result) result = { ...atoms } | ||
if (!removed) removed = {} | ||
if (!removed) removed = {} as Record<IdOf<R>, R> | ||
delete result[id] | ||
@@ -396,3 +409,3 @@ removed[id] = atoms[id].value | ||
// Update the history with the removed records. | ||
this.updateHistory({ added: {}, updated: {}, removed }) | ||
this.updateHistory({ added: {}, updated: {}, removed } as RecordsDiff<R>) | ||
@@ -414,3 +427,3 @@ // If we have an onAfterChange, run it for each removed record. | ||
*/ | ||
get = <K extends ID<R>>(id: K): RecFromId<K> | undefined => { | ||
get = <K extends IdOf<R>>(id: K): RecFromId<K> | undefined => { | ||
return this.atoms.value[id]?.value as any | ||
@@ -425,3 +438,3 @@ } | ||
*/ | ||
unsafeGetWithoutCapture = <K extends ID<R>>(id: K): RecFromId<K> | undefined => { | ||
unsafeGetWithoutCapture = <K extends IdOf<R>>(id: K): RecFromId<K> | undefined => { | ||
return this.atoms.value[id]?.__unsafe__getWithoutCapture() as any | ||
@@ -437,7 +450,7 @@ } | ||
serialize = (filter?: (record: R) => boolean): StoreSnapshot<R> => { | ||
const result: Record<string, any> = {} | ||
for (const [id, atom] of Object.entries(this.atoms.value)) { | ||
const result = {} as StoreSnapshot<R> | ||
for (const [id, atom] of objectMapEntries(this.atoms.value)) { | ||
const record = atom.value | ||
if (typeof filter === 'function' && !filter(record)) continue | ||
result[id] = record | ||
result[id as IdOf<R>] = record | ||
} | ||
@@ -448,3 +461,15 @@ return result | ||
/** | ||
* Opposite of `serialize`. Replace the store's current records with records as defined by a simple JSON structure into the stores. | ||
* The same as `serialize`, but only serializes records with a scope of `document`. | ||
* @returns The record store snapshot as a JSON payload. | ||
*/ | ||
serializeDocumentState = (): StoreSnapshot<R> => { | ||
return this.serialize((r) => { | ||
const type = this.schema.types[r.typeName as R['typeName']] as RecordType<any, any> | ||
return type.scope === 'document' | ||
}) | ||
} | ||
/** | ||
* Opposite of `serialize`. Replace the store's current records with records as defined by a | ||
* simple JSON structure into the stores. | ||
* | ||
@@ -464,7 +489,7 @@ * @param snapshot - The JSON snapshot to deserialize. | ||
* | ||
* @returns An array of all values in the store. | ||
* @public | ||
* @returns An array of all values in the store. | ||
*/ | ||
allRecords = (): R[] => { | ||
return Object.values(this.atoms.value).map((atom) => atom.value) | ||
return objectMapValues(this.atoms.value).map((atom) => atom.value) | ||
} | ||
@@ -478,7 +503,8 @@ | ||
clear = (): void => { | ||
this.remove(Object.keys(this.atoms.value) as any) | ||
this.remove(objectMapKeys(this.atoms.value)) | ||
} | ||
/** | ||
* Update a record. To update multiple records at once, use the `update` method of the `TypedStore` class. | ||
* Update a record. To update multiple records at once, use the `update` method of the | ||
* `TypedStore` class. | ||
* | ||
@@ -488,3 +514,3 @@ * @param id - The id of the record to update. | ||
*/ | ||
update = <K extends ID<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => { | ||
update = <K extends IdOf<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => { | ||
const atom = this.atoms.value[id] | ||
@@ -504,3 +530,3 @@ if (!atom) { | ||
*/ | ||
has = <K extends ID<R>>(id: K): boolean => { | ||
has = <K extends IdOf<R>>(id: K): boolean => { | ||
return !!this.atoms.value[id] | ||
@@ -571,6 +597,6 @@ } | ||
transact(() => { | ||
const toPut = Object.values(diff.added).concat( | ||
Object.values(diff.updated).map(([_from, to]) => to) | ||
const toPut = objectMapValues(diff.added).concat( | ||
objectMapValues(diff.updated).map(([_from, to]) => to) | ||
) | ||
const toRemove = Object.keys(diff.removed) as ID<R>[] | ||
const toRemove = objectMapKeys(diff.removed) | ||
if (toPut.length) { | ||
@@ -601,3 +627,3 @@ this.put(toPut) | ||
return { | ||
get: (id: ID<V>) => { | ||
get: (id: IdOf<V>) => { | ||
const atom = this.atoms.value[id] | ||
@@ -629,3 +655,3 @@ if (!atom) { | ||
return { | ||
get: (id: ID<V>) => { | ||
get: (id: IdOf<V>) => { | ||
const atom = this.atoms.value[id] | ||
@@ -646,5 +672,8 @@ if (!atom) { | ||
private _integrityChecker?: () => void | undefined | ||
/** @internal */ | ||
ensureStoreIsUsable() { | ||
this.schema.ensureStoreIsUsable(this) | ||
this._integrityChecker ??= this.schema.createIntegrityChecker(this) | ||
this._integrityChecker?.() | ||
} | ||
@@ -670,7 +699,9 @@ | ||
*/ | ||
export function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T> { | ||
const result: RecordsDiff<T> = { added: {}, removed: {}, updated: {} } | ||
export function squashRecordDiffs<T extends UnknownRecord>( | ||
diffs: RecordsDiff<T>[] | ||
): RecordsDiff<T> { | ||
const result = { added: {}, removed: {}, updated: {} } as RecordsDiff<T> | ||
for (const diff of diffs) { | ||
for (const [id, value] of Object.entries(diff.added)) { | ||
for (const [id, value] of objectMapEntries(diff.added)) { | ||
if (result.removed[id]) { | ||
@@ -687,3 +718,3 @@ const original = result.removed[id] | ||
for (const [id, [_from, to]] of Object.entries(diff.updated)) { | ||
for (const [id, [_from, to]] of objectMapEntries(diff.updated)) { | ||
if (result.added[id]) { | ||
@@ -705,3 +736,3 @@ result.added[id] = to | ||
for (const [id, value] of Object.entries(diff.removed)) { | ||
for (const [id, value] of objectMapEntries(diff.removed)) { | ||
// the same record was added in this diff sequence, just drop it | ||
@@ -729,3 +760,5 @@ if (result.added[id]) { | ||
*/ | ||
function squashHistoryEntries<T extends BaseRecord>(entries: HistoryEntry<T>[]): HistoryEntry<T>[] { | ||
function squashHistoryEntries<T extends UnknownRecord>( | ||
entries: HistoryEntry<T>[] | ||
): HistoryEntry<T>[] { | ||
const result: HistoryEntry<T>[] = [] | ||
@@ -764,3 +797,3 @@ | ||
class HistoryAccumulator<T extends BaseRecord> { | ||
class HistoryAccumulator<T extends UnknownRecord> { | ||
private _history: HistoryEntry<T>[] = [] | ||
@@ -797,13 +830,2 @@ | ||
} | ||
/** | ||
* Ensure that the store is usable. A class that extends this store should override this method. | ||
* | ||
* @param config - The configuration object. This can be any object that allows the store to validate that it is usable; the extending class should specify the type. | ||
* | ||
* @public | ||
*/ | ||
ensureStoreIsUsable(_config = {} as any): void { | ||
return | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
import { objectMapValues } from '@tldraw/utils' | ||
import isEqual from 'lodash.isequal' | ||
@@ -11,3 +12,3 @@ import { | ||
} from 'signia' | ||
import { BaseRecord, ID } from './BaseRecord' | ||
import { IdOf, UnknownRecord } from './BaseRecord' | ||
import { executeQuery, objectMatchesQuery, QueryExpression } from './executeQuery' | ||
@@ -19,23 +20,23 @@ import { IncrementalSetConstructor } from './IncrementalSetConstructor' | ||
export type RSIndexDiff< | ||
R extends BaseRecord = BaseRecord, | ||
R extends UnknownRecord, | ||
Property extends string & keyof R = string & keyof R | ||
> = Map<R[Property], CollectionDiff<ID<R>>> | ||
> = Map<R[Property], CollectionDiff<IdOf<R>>> | ||
export type RSIndexMap< | ||
R extends BaseRecord = BaseRecord, | ||
R extends UnknownRecord, | ||
Property extends string & keyof R = string & keyof R | ||
> = Map<R[Property], Set<ID<R>>> | ||
> = Map<R[Property], Set<IdOf<R>>> | ||
export type RSIndex< | ||
R extends BaseRecord = BaseRecord, | ||
R extends UnknownRecord, | ||
Property extends string & keyof R = string & keyof R | ||
> = Computed<Map<R[Property], Set<ID<R>>>, RSIndexDiff<R, Property>> | ||
> = Computed<Map<R[Property], Set<IdOf<R>>>, RSIndexDiff<R, Property>> | ||
/** | ||
* A class that provides a 'namespace' for the various kinds of indexes | ||
* one may wish to derive from the record store. | ||
* A class that provides a 'namespace' for the various kinds of indexes one may wish to derive from | ||
* the record store. | ||
*/ | ||
export class StoreQueries<R extends BaseRecord = BaseRecord> { | ||
export class StoreQueries<R extends UnknownRecord> { | ||
constructor( | ||
private readonly atoms: Atom<Record<ID<R>, Atom<R>>>, | ||
private readonly atoms: Atom<Record<IdOf<R>, Atom<R>>>, | ||
private readonly history: Atom<number, RecordsDiff<R>> | ||
@@ -62,3 +63,2 @@ ) {} | ||
* @param typeName - The name of the type to filter by. | ||
* | ||
* @returns A derivation that returns the ids of all records of the given type. | ||
@@ -86,3 +86,3 @@ * @public | ||
const res: RecordsDiff<S> = { added: {}, removed: {}, updated: {} } | ||
const res = { added: {}, removed: {}, updated: {} } as RecordsDiff<S> | ||
let numAdded = 0 | ||
@@ -93,14 +93,14 @@ let numRemoved = 0 | ||
for (const changes of diff) { | ||
for (const added of Object.values(changes.added)) { | ||
for (const added of objectMapValues(changes.added)) { | ||
if (added.typeName === typeName) { | ||
if (res.removed[added.id]) { | ||
const original = res.removed[added.id] | ||
delete res.removed[added.id] | ||
if (res.removed[added.id as IdOf<S>]) { | ||
const original = res.removed[added.id as IdOf<S>] | ||
delete res.removed[added.id as IdOf<S>] | ||
numRemoved-- | ||
if (original !== added) { | ||
res.updated[added.id] = [original, added as S] | ||
res.updated[added.id as IdOf<S>] = [original, added as S] | ||
numUpdated++ | ||
} | ||
} else { | ||
res.added[added.id] = added as S | ||
res.added[added.id as IdOf<S>] = added as S | ||
numAdded++ | ||
@@ -111,10 +111,10 @@ } | ||
for (const [from, to] of Object.values(changes.updated)) { | ||
for (const [from, to] of objectMapValues(changes.updated)) { | ||
if (to.typeName === typeName) { | ||
if (res.added[to.id]) { | ||
res.added[to.id] = to as S | ||
} else if (res.updated[to.id]) { | ||
res.updated[to.id] = [res.updated[to.id][0], to as S] | ||
if (res.added[to.id as IdOf<S>]) { | ||
res.added[to.id as IdOf<S>] = to as S | ||
} else if (res.updated[to.id as IdOf<S>]) { | ||
res.updated[to.id as IdOf<S>] = [res.updated[to.id as IdOf<S>][0], to as S] | ||
} else { | ||
res.updated[to.id] = [from as S, to as S] | ||
res.updated[to.id as IdOf<S>] = [from as S, to as S] | ||
numUpdated++ | ||
@@ -125,16 +125,16 @@ } | ||
for (const removed of Object.values(changes.removed)) { | ||
for (const removed of objectMapValues(changes.removed)) { | ||
if (removed.typeName === typeName) { | ||
if (res.added[removed.id]) { | ||
if (res.added[removed.id as IdOf<S>]) { | ||
// was added during this diff sequence, so just undo the add | ||
delete res.added[removed.id] | ||
delete res.added[removed.id as IdOf<S>] | ||
numAdded-- | ||
} else if (res.updated[removed.id]) { | ||
} else if (res.updated[removed.id as IdOf<S>]) { | ||
// remove oldest version | ||
res.removed[removed.id] = res.updated[removed.id][0] | ||
delete res.updated[removed.id] | ||
res.removed[removed.id as IdOf<S>] = res.updated[removed.id as IdOf<S>][0] | ||
delete res.updated[removed.id as IdOf<S>] | ||
numUpdated-- | ||
numRemoved++ | ||
} else { | ||
res.removed[removed.id] = removed as S | ||
res.removed[removed.id as IdOf<S>] = removed as S | ||
numRemoved++ | ||
@@ -165,3 +165,2 @@ } | ||
* @param property - The name of the property. | ||
* | ||
* @public | ||
@@ -191,3 +190,2 @@ */ | ||
* @param property - The name of the property?. | ||
* | ||
* @internal | ||
@@ -207,4 +205,4 @@ */ | ||
typeHistory.value | ||
const res = new Map<S[Property], Set<ID<S>>>() | ||
for (const atom of Object.values(this.atoms.value)) { | ||
const res = new Map<S[Property], Set<IdOf<S>>>() | ||
for (const atom of objectMapValues(this.atoms.value)) { | ||
const record = atom.value | ||
@@ -216,3 +214,3 @@ if (record.typeName === typeName) { | ||
} | ||
res.get(value)!.add((record as S).id) | ||
res.get(value)!.add(record.id) | ||
} | ||
@@ -234,8 +232,10 @@ } | ||
const setConstructors = new Map<any, IncrementalSetConstructor<ID<S>>>() | ||
const setConstructors = new Map<any, IncrementalSetConstructor<IdOf<S>>>() | ||
const add = (value: S[Property], id: ID<S>) => { | ||
const add = (value: S[Property], id: IdOf<S>) => { | ||
let setConstructor = setConstructors.get(value) | ||
if (!setConstructor) | ||
setConstructor = new IncrementalSetConstructor<ID<S>>(prevValue.get(value) ?? new Set()) | ||
setConstructor = new IncrementalSetConstructor<IdOf<S>>( | ||
prevValue.get(value) ?? new Set() | ||
) | ||
setConstructor.add(id) | ||
@@ -245,5 +245,5 @@ setConstructors.set(value, setConstructor) | ||
const remove = (value: S[Property], id: ID<S>) => { | ||
const remove = (value: S[Property], id: IdOf<S>) => { | ||
let set = setConstructors.get(value) | ||
if (!set) set = new IncrementalSetConstructor<ID<S>>(prevValue.get(value) ?? new Set()) | ||
if (!set) set = new IncrementalSetConstructor<IdOf<S>>(prevValue.get(value) ?? new Set()) | ||
set.remove(id) | ||
@@ -254,9 +254,9 @@ setConstructors.set(value, set) | ||
for (const changes of history) { | ||
for (const record of Object.values(changes.added)) { | ||
for (const record of objectMapValues(changes.added)) { | ||
if (record.typeName === typeName) { | ||
const value = (record as S)[property] | ||
add(value, (record as S).id) | ||
add(value, record.id) | ||
} | ||
} | ||
for (const [from, to] of Object.values(changes.updated)) { | ||
for (const [from, to] of objectMapValues(changes.updated)) { | ||
if (to.typeName === typeName) { | ||
@@ -266,11 +266,11 @@ const prev = (from as S)[property] | ||
if (prev !== next) { | ||
remove(prev, (to as S).id) | ||
add(next, (to as S).id) | ||
remove(prev, to.id) | ||
add(next, to.id) | ||
} | ||
} | ||
} | ||
for (const record of Object.values(changes.removed)) { | ||
for (const record of objectMapValues(changes.removed)) { | ||
if (record.typeName === typeName) { | ||
const value = (record as S)[property] | ||
remove(value, (record as S).id) | ||
remove(value, record.id) | ||
} | ||
@@ -369,4 +369,4 @@ } | ||
): Computed< | ||
Set<ID<Extract<R, { typeName: TypeName }>>>, | ||
CollectionDiff<ID<Extract<R, { typeName: TypeName }>>> | ||
Set<IdOf<Extract<R, { typeName: TypeName }>>>, | ||
CollectionDiff<IdOf<Extract<R, { typeName: TypeName }>>> | ||
> { | ||
@@ -382,7 +382,7 @@ type S = Extract<R, { typeName: TypeName }> | ||
if (Object.keys(query).length === 0) { | ||
return new Set<ID<S>>( | ||
Object.values(this.atoms.value).flatMap((v) => { | ||
return new Set<IdOf<S>>( | ||
objectMapValues(this.atoms.value).flatMap((v) => { | ||
const r = v.value | ||
if (r.typeName === typeName) { | ||
return r.id as ID<S> | ||
return r.id | ||
} else { | ||
@@ -398,3 +398,3 @@ return [] | ||
const fromScratchWithDiff = (prevValue: Set<ID<S>>) => { | ||
const fromScratchWithDiff = (prevValue: Set<IdOf<S>>) => { | ||
const nextValue = fromScratch() | ||
@@ -431,24 +431,24 @@ const diff = diffSets(prevValue, nextValue) | ||
const setConstructor = new IncrementalSetConstructor<ID<S>>( | ||
const setConstructor = new IncrementalSetConstructor<IdOf<S>>( | ||
prevValue | ||
) as IncrementalSetConstructor<ID<S>> | ||
) as IncrementalSetConstructor<IdOf<S>> | ||
for (const changes of history) { | ||
for (const added of Object.values(changes.added)) { | ||
for (const added of objectMapValues(changes.added)) { | ||
if (added.typeName === typeName && objectMatchesQuery(query, added)) { | ||
setConstructor.add(added.id as ID<S>) | ||
setConstructor.add(added.id) | ||
} | ||
} | ||
for (const [_, updated] of Object.values(changes.updated)) { | ||
for (const [_, updated] of objectMapValues(changes.updated)) { | ||
if (updated.typeName === typeName) { | ||
if (objectMatchesQuery(query, updated)) { | ||
setConstructor.add(updated.id as ID<S>) | ||
setConstructor.add(updated.id) | ||
} else { | ||
setConstructor.remove(updated.id as ID<S>) | ||
setConstructor.remove(updated.id) | ||
} | ||
} | ||
} | ||
for (const removed of Object.values(changes.removed)) { | ||
for (const removed of objectMapValues(changes.removed)) { | ||
if (removed.typeName === typeName) { | ||
setConstructor.remove(removed.id as ID<S>) | ||
setConstructor.remove(removed.id) | ||
} | ||
@@ -455,0 +455,0 @@ } |
import { getOwnProperty, objectMapValues } from '@tldraw/utils' | ||
import { BaseRecord } from './BaseRecord' | ||
import { IdOf, UnknownRecord } from './BaseRecord' | ||
import { RecordType } from './RecordType' | ||
@@ -15,14 +15,10 @@ import { Store, StoreSnapshot } from './Store' | ||
export interface SerializedSchema { | ||
/** | ||
* Schema version is the version for this type you're looking at right now | ||
*/ | ||
/** Schema version is the version for this type you're looking at right now */ | ||
schemaVersion: number | ||
/** | ||
* Store version is the version for the structure of the store. e.g. higher level | ||
* structure like removing or renaming a record type. | ||
* 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 | ||
*/ | ||
/** Record versions are the versions for each record type. e.g. adding a new field to a record */ | ||
recordVersions: Record< | ||
@@ -43,3 +39,3 @@ string, | ||
/** @public */ | ||
export type StoreSchemaOptions<R extends BaseRecord, P> = { | ||
export type StoreSchemaOptions<R extends UnknownRecord, P> = { | ||
/** @public */ | ||
@@ -56,8 +52,8 @@ snapshotMigrations?: Migrations | ||
/** @internal */ | ||
ensureStoreIsUsable?: (store: Store<R, P>) => void | ||
createIntegrityChecker?: (store: Store<R, P>) => void | ||
} | ||
/** @public */ | ||
export class StoreSchema<R extends BaseRecord, P = unknown> { | ||
static create<R extends BaseRecord, P = unknown>( | ||
export class StoreSchema<R extends UnknownRecord, P = unknown> { | ||
static create<R extends UnknownRecord, P = unknown>( | ||
// HACK: making this param work with RecordType is an enormous pain | ||
@@ -159,3 +155,3 @@ // let's just settle for making sure each typeName has a corresponding RecordType | ||
? persistedType.subTypeVersions[record[ourType.migrations.subTypeKey as keyof R] as string] | ||
: null | ||
: undefined | ||
@@ -173,3 +169,3 @@ // if ourSubTypeMigrations is undefined then we don't have access to the migrations for this subtype | ||
// either way we don't know what to do with it safely, so let's return failure. | ||
if (persistedSubTypeVersion == null) { | ||
if (persistedSubTypeVersion === undefined) { | ||
return { type: 'error', reason: MigrationFailureReason.IncompatibleSubtype } | ||
@@ -231,3 +227,3 @@ } | ||
const updated: R[] = [] | ||
for (const r of Object.values(storeSnapshot)) { | ||
for (const r of objectMapValues(storeSnapshot)) { | ||
const result = this.migratePersistedRecord(r, persistedSchema) | ||
@@ -243,3 +239,3 @@ if (result.type === 'error') { | ||
for (const r of updated) { | ||
storeSnapshot[r.id] = r | ||
storeSnapshot[r.id as IdOf<R>] = r | ||
} | ||
@@ -251,4 +247,4 @@ } | ||
/** @internal */ | ||
ensureStoreIsUsable(store: Store<R, P>): void { | ||
this.options.ensureStoreIsUsable?.(store) | ||
createIntegrityChecker(store: Store<R, P>): (() => void) | undefined { | ||
return this.options.createIntegrityChecker?.(store) ?? undefined | ||
} | ||
@@ -255,0 +251,0 @@ |
@@ -7,3 +7,3 @@ import { Computed, react, RESET_VALUE, transact } from 'signia' | ||
interface Book extends BaseRecord<'book'> { | ||
interface Book extends BaseRecord<'book', ID<Book>> { | ||
title: string | ||
@@ -14,5 +14,8 @@ author: ID<Author> | ||
const Book = createRecordType<Book>('book', { validator: { validate: (book) => book as Book } }) | ||
const Book = createRecordType<Book>('book', { | ||
validator: { validate: (book) => book as Book }, | ||
scope: 'document', | ||
}) | ||
interface Author extends BaseRecord<'author'> { | ||
interface Author extends BaseRecord<'author', ID<Author>> { | ||
name: string | ||
@@ -24,2 +27,3 @@ isPseudonym: boolean | ||
validator: { validate: (author) => author as Author }, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ | ||
@@ -471,22 +475,29 @@ isPseudonym: false, | ||
it('flushes history before attaching listeners', async () => { | ||
store.put([Author.create({ name: 'J.R.R Tolkein', id: Author.createCustomId('tolkein') })]) | ||
const firstListener = jest.fn() | ||
store.listen(firstListener) | ||
expect(firstListener).toHaveBeenCalledTimes(0) | ||
try { | ||
// @ts-expect-error | ||
globalThis.__FORCE_RAF_IN_TESTS__ = true | ||
store.put([Author.create({ name: 'J.R.R Tolkein', id: Author.createCustomId('tolkein') })]) | ||
const firstListener = jest.fn() | ||
store.listen(firstListener) | ||
expect(firstListener).toHaveBeenCalledTimes(0) | ||
store.put([Author.create({ name: 'Chips McCoy', id: Author.createCustomId('chips') })]) | ||
store.put([Author.create({ name: 'Chips McCoy', id: Author.createCustomId('chips') })]) | ||
expect(firstListener).toHaveBeenCalledTimes(0) | ||
expect(firstListener).toHaveBeenCalledTimes(0) | ||
const secondListener = jest.fn() | ||
const secondListener = jest.fn() | ||
store.listen(secondListener) | ||
store.listen(secondListener) | ||
expect(firstListener).toHaveBeenCalledTimes(1) | ||
expect(secondListener).toHaveBeenCalledTimes(0) | ||
expect(firstListener).toHaveBeenCalledTimes(1) | ||
expect(secondListener).toHaveBeenCalledTimes(0) | ||
await new Promise((resolve) => requestAnimationFrame(resolve)) | ||
await new Promise((resolve) => requestAnimationFrame(resolve)) | ||
expect(firstListener).toHaveBeenCalledTimes(1) | ||
expect(secondListener).toHaveBeenCalledTimes(0) | ||
expect(firstListener).toHaveBeenCalledTimes(1) | ||
expect(secondListener).toHaveBeenCalledTimes(0) | ||
} finally { | ||
// @ts-expect-error | ||
globalThis.__FORCE_RAF_IN_TESTS__ = false | ||
} | ||
}) | ||
@@ -493,0 +504,0 @@ |
import { atom, EffectScheduler, RESET_VALUE } from 'signia' | ||
import { BaseRecord, ID } from '../BaseRecord' | ||
import { BaseRecord, ID, IdOf, UnknownRecord } from '../BaseRecord' | ||
import { executeQuery } from '../executeQuery' | ||
@@ -9,3 +9,3 @@ import { createRecordType } from '../RecordType' | ||
interface Author extends BaseRecord<'author'> { | ||
interface Author extends BaseRecord<'author', ID<Author>> { | ||
name: AuthorName | ||
@@ -25,5 +25,6 @@ age: number | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ age: 23 })) | ||
interface Book extends BaseRecord<'book'> { | ||
interface Book extends BaseRecord<'book', ID<Book>> { | ||
title: BookName | ||
@@ -43,2 +44,3 @@ authorId: ID<Author> | ||
}, | ||
scope: 'document', | ||
}) | ||
@@ -59,6 +61,6 @@ | ||
| { readonly type: 'add'; readonly record: Record } | ||
| { readonly type: 'delete'; readonly id: ID<Record> } | ||
| { readonly type: 'delete'; readonly id: IdOf<Record> } | ||
| { readonly type: 'update'; readonly record: Record } | ||
| { readonly type: 'set_book_name_query_param'; readonly bookName: BookName } | ||
| { readonly type: 'set_author_id_query_param'; readonly authorId: ID<Author> } | ||
| { readonly type: 'set_author_id_query_param'; readonly authorId: IdOf<Author> } | ||
@@ -286,7 +288,7 @@ const BOOK_NAMES = [ | ||
function recreateIndexFromDiffs(diffs: RSIndexDiff[]) { | ||
const result = new Map<string, Set<ID>>() | ||
function recreateIndexFromDiffs(diffs: RSIndexDiff<any>[]) { | ||
const result = new Map<string, Set<IdOf<UnknownRecord>>>() | ||
for (const diff of diffs) { | ||
for (const [key, changes] of diff) { | ||
const index = result.get(key) || new Set<ID>() | ||
const index = result.get(key) || new Set<IdOf<UnknownRecord>>() | ||
if (changes.added) { | ||
@@ -358,4 +360,4 @@ for (const id of changes.added) { | ||
const authorNameIndexDiffs: RSIndexDiff[] = [] | ||
const authorIdIndexDiffs: RSIndexDiff[] = [] | ||
const authorNameIndexDiffs: RSIndexDiff<Author>[] = [] | ||
const authorIdIndexDiffs: RSIndexDiff<Book>[] = [] | ||
@@ -362,0 +364,0 @@ const authorIdQueryParam = atom('authorId', Author.createCustomId('does-not-exist')) |
@@ -7,3 +7,3 @@ import { atom, RESET_VALUE } from 'signia' | ||
interface Author extends BaseRecord<'author'> { | ||
interface Author extends BaseRecord<'author', ID<Author>> { | ||
name: string | ||
@@ -23,5 +23,6 @@ age: number | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ age: 23 })) | ||
interface Book extends BaseRecord<'book'> { | ||
interface Book extends BaseRecord<'book', ID<Book>> { | ||
title: string | ||
@@ -41,2 +42,3 @@ authorId: ID<Author> | ||
}, | ||
scope: 'document', | ||
}) | ||
@@ -43,0 +45,0 @@ const authors = { |
import { assert } from '@tldraw/utils' | ||
import { BaseRecord } from '../BaseRecord' | ||
import { BaseRecord, ID } from '../BaseRecord' | ||
import { createRecordType } from '../RecordType' | ||
@@ -7,18 +7,8 @@ import { StoreSchema } from '../StoreSchema' | ||
const UserVersion = { | ||
Initial: 0, | ||
} as const | ||
/** | ||
* A user of tldraw | ||
*/ | ||
interface User extends BaseRecord<'user'> { | ||
/** A user of tldraw */ | ||
interface User extends BaseRecord<'user', ID<User>> { | ||
name: string | ||
} | ||
const userMigrations = defineMigrations({ | ||
currentVersion: UserVersion.Initial, | ||
firstVersion: UserVersion.Initial, | ||
migrators: {}, | ||
}) | ||
const userMigrations = defineMigrations({}) | ||
@@ -35,13 +25,6 @@ const User = createRecordType<User>('user', { | ||
}, | ||
scope: 'document', | ||
}) | ||
const ShapeVersion = { | ||
Initial: 0, | ||
} as const | ||
const RectangleVersion = { | ||
Initial: 0, | ||
} as const | ||
interface Shape<Props> extends BaseRecord<'shape'> { | ||
interface Shape<Props> extends BaseRecord<'shape', ID<Shape<object>>> { | ||
type: string | ||
@@ -64,13 +47,6 @@ x: number | ||
const shapeMigrations = defineMigrations({ | ||
currentVersion: ShapeVersion.Initial, | ||
firstVersion: ShapeVersion.Initial, | ||
migrators: {}, | ||
const shapeTypeMigrations = defineMigrations({ | ||
subTypeKey: 'type', | ||
subTypeMigrations: { | ||
rectangle: defineMigrations({ | ||
currentVersion: RectangleVersion.Initial, | ||
firstVersion: RectangleVersion.Initial, | ||
migrators: {}, | ||
}), | ||
rectangle: defineMigrations({}), | ||
}, | ||
@@ -80,3 +56,3 @@ }) | ||
const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', { | ||
migrations: shapeMigrations, | ||
migrations: shapeTypeMigrations, | ||
validator: { | ||
@@ -99,6 +75,7 @@ validate: (record) => { | ||
}, | ||
scope: 'document', | ||
}) | ||
// this interface only exists to be removed | ||
interface Org extends BaseRecord<'org'> { | ||
interface Org extends BaseRecord<'org', ID<Org>> { | ||
name: string | ||
@@ -108,3 +85,3 @@ } | ||
const Org = createRecordType<Org>('org', { | ||
migrations: defineMigrations({ currentVersion: 0, firstVersion: 0, migrators: {} }), | ||
migrations: defineMigrations({}), | ||
validator: { | ||
@@ -118,2 +95,3 @@ validate: (record) => { | ||
}, | ||
scope: 'document', | ||
}) | ||
@@ -128,4 +106,4 @@ | ||
{ | ||
snapshotMigrations: defineMigrations({ currentVersion: 0, firstVersion: 0, migrators: {} }), | ||
snapshotMigrations: defineMigrations({}), | ||
} | ||
) |
@@ -9,3 +9,2 @@ import { assert } from '@tldraw/utils' | ||
const UserVersion = { | ||
Initial: 0, | ||
AddLocale: 1, | ||
@@ -15,6 +14,4 @@ AddPhoneNumber: 2, | ||
/** | ||
* A user of tldraw | ||
*/ | ||
interface User extends BaseRecord<'user'> { | ||
/** A user of tldraw */ | ||
interface User extends BaseRecord<'user', ID<User>> { | ||
name: string | ||
@@ -27,3 +24,2 @@ locale: string | ||
currentVersion: UserVersion.AddPhoneNumber, | ||
firstVersion: UserVersion.Initial, | ||
migrators: { | ||
@@ -70,2 +66,3 @@ [UserVersion.AddLocale]: { | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ | ||
@@ -77,3 +74,2 @@ /* STEP 6: Add any new default values for properties here */ | ||
const ShapeVersion = { | ||
Initial: 0, | ||
AddRotation: 1, | ||
@@ -84,3 +80,2 @@ AddParent: 2, | ||
const RectangleVersion = { | ||
Initial: 0, | ||
AddOpacity: 1, | ||
@@ -90,7 +85,8 @@ } as const | ||
const OvalVersion = { | ||
Initial: 0, | ||
AddBorderStyle: 1, | ||
} as const | ||
interface Shape<Props> extends BaseRecord<'shape'> { | ||
type ShapeId = ID<Shape<object>> | ||
interface Shape<Props> extends BaseRecord<'shape', ShapeId> { | ||
type: string | ||
@@ -100,3 +96,3 @@ x: number | ||
rotation: number | ||
parentId: ID<Shape<Props>> | null | ||
parentId: ShapeId | null | ||
props: Props | ||
@@ -116,5 +112,4 @@ } | ||
const shapeMigrations = defineMigrations({ | ||
const shapeTypeMigrations = defineMigrations({ | ||
currentVersion: ShapeVersion.AddParent, | ||
firstVersion: ShapeVersion.Initial, | ||
migrators: { | ||
@@ -148,3 +143,2 @@ [ShapeVersion.AddRotation]: { | ||
currentVersion: RectangleVersion.AddOpacity, | ||
firstVersion: RectangleVersion.Initial, | ||
migrators: { | ||
@@ -171,3 +165,2 @@ [RectangleVersion.AddOpacity]: { | ||
currentVersion: OvalVersion.AddBorderStyle, | ||
firstVersion: OvalVersion.Initial, | ||
migrators: { | ||
@@ -196,3 +189,3 @@ [OvalVersion.AddBorderStyle]: { | ||
const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', { | ||
migrations: shapeMigrations, | ||
migrations: shapeTypeMigrations, | ||
validator: { | ||
@@ -209,2 +202,3 @@ validate: (record) => { | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ | ||
@@ -218,3 +212,2 @@ x: 0, | ||
const StoreVersions = { | ||
Initial: 0, | ||
RemoveOrg: 1, | ||
@@ -225,3 +218,2 @@ } | ||
currentVersion: StoreVersions.RemoveOrg, | ||
firstVersion: StoreVersions.Initial, | ||
migrators: { | ||
@@ -228,0 +220,0 @@ [StoreVersions.RemoveOrg]: { |
@@ -1,2 +0,2 @@ | ||
import { BaseRecord, ID } from '../BaseRecord' | ||
import { BaseRecord, ID, IdOf } from '../BaseRecord' | ||
import { createRecordType } from '../RecordType' | ||
@@ -6,5 +6,5 @@ import { Store, StoreSnapshot } from '../Store' | ||
interface Book extends BaseRecord<'book'> { | ||
interface Book extends BaseRecord<'book', ID<Book>> { | ||
title: string | ||
author: ID<Author> | ||
author: IdOf<Author> | ||
numPages: number | ||
@@ -25,5 +25,6 @@ } | ||
}, | ||
scope: 'document', | ||
}) | ||
interface Author extends BaseRecord<'author'> { | ||
interface Author extends BaseRecord<'author', ID<Author>> { | ||
name: string | ||
@@ -44,2 +45,3 @@ isPseudonym: boolean | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ | ||
@@ -46,0 +48,0 @@ isPseudonym: false, |
@@ -10,4 +10,5 @@ import { RecordType } from './RecordType' | ||
* Get the type of all records in a record store. | ||
* | ||
* @public | ||
*/ | ||
export type AllRecords<T extends Store<any>> = ExtractR<ExtractRecordType<T>> |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
355
547799
4
85
9441
+ Added@tldraw/utils@2.0.0-canary.7578fff2b179(transitive)
+ Addednanoid@4.0.2(transitive)
- Removed@tldraw/utils@2.0.0-canary.75778dd3(transitive)
- Removednanoid@3.3.8(transitive)
Updatednanoid@4.0.2