@tldraw/tlstore
Advanced tools
Comparing version 2.0.0-canary.e312ea047 to 2.0.0-canary.e3cf05f408a8
@@ -24,3 +24,3 @@ import { Atom } from 'signia'; | ||
*/ | ||
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>; | ||
@@ -40,4 +40,4 @@ declare interface BaseMigrationsInfo { | ||
*/ | ||
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; | ||
@@ -60,3 +60,3 @@ } | ||
/** @public */ | ||
export declare const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => number; | ||
export declare const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => -1 | 0 | 1; | ||
@@ -68,4 +68,4 @@ /** | ||
*/ | ||
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; | ||
}; | ||
@@ -85,5 +85,6 @@ | ||
*/ | ||
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'>>; | ||
@@ -94,8 +95,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; | ||
@@ -121,2 +126,4 @@ subTypeMigrations?: Record<string, BaseMigrationsInfo>; | ||
declare type EMPTY_SYMBOL = symbol; | ||
declare type ExtractR<T extends RecordType<any, any>> = T extends RecordType<infer S, any> ? S : never; | ||
@@ -127,3 +134,3 @@ | ||
/** @public */ | ||
export declare function getRecordVersion(record: BaseRecord, serializedSchema: SerializedSchema): RecordVersion; | ||
export declare function getRecordVersion(record: UnknownRecord, serializedSchema: SerializedSchema): RecordVersion; | ||
@@ -135,3 +142,3 @@ /** | ||
*/ | ||
export declare type HistoryEntry<R extends BaseRecord = BaseRecord> = { | ||
export declare type HistoryEntry<R extends UnknownRecord = UnknownRecord> = { | ||
changes: RecordsDiff<R>; | ||
@@ -142,6 +149,9 @@ 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 */ | ||
@@ -158,3 +168,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; | ||
@@ -167,5 +177,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; | ||
}; | ||
@@ -198,3 +208,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; | ||
@@ -207,3 +217,3 @@ declare type QueryExpression<R extends object> = { | ||
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; | ||
@@ -215,6 +225,6 @@ /** | ||
*/ | ||
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>; | ||
}; | ||
@@ -228,3 +238,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'>> { | ||
/** | ||
@@ -242,2 +252,3 @@ * The unique type associated with this record. | ||
} | StoreValidator<R>; | ||
readonly scope: Scope; | ||
constructor( | ||
@@ -256,2 +267,3 @@ /** | ||
} | StoreValidator<R>; | ||
readonly scope?: Scope; | ||
}); | ||
@@ -285,3 +297,3 @@ /** | ||
*/ | ||
createId(): ID<R>; | ||
createId(): IdOf<R>; | ||
/** | ||
@@ -299,3 +311,3 @@ * Create a new ID for this record type based on the given ID. | ||
*/ | ||
createCustomId(id: string): ID<R>; | ||
createCustomId(id: string): IdOf<R>; | ||
/** | ||
@@ -307,3 +319,3 @@ * Takes an id like `user:123` and returns the part after the colon `123` | ||
*/ | ||
parseId(id: string): ID<R>; | ||
parseId(id: string): IdOf<R>; | ||
/** | ||
@@ -321,3 +333,3 @@ * Check whether a record is an instance of this record type. | ||
*/ | ||
isInstance: (record?: BaseRecord) => record is R; | ||
isInstance: (record?: UnknownRecord) => record is R; | ||
/** | ||
@@ -335,3 +347,3 @@ * Check whether an id is an id of this type. | ||
*/ | ||
isId(id?: string): id is ID<R>; | ||
isId(id?: string): id is IdOf<R>; | ||
/** | ||
@@ -368,6 +380,17 @@ * Create a new RecordType that has the same type name as this RecordType and includes the given | ||
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 */ | ||
@@ -399,3 +422,3 @@ export declare interface SerializedSchema { | ||
*/ | ||
export declare function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>; | ||
export declare function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>; | ||
@@ -407,3 +430,3 @@ /** | ||
*/ | ||
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 */ | ||
@@ -487,3 +510,3 @@ /** | ||
*/ | ||
remove: (ids: ID<R>[]) => void; | ||
remove: (ids: IdOf<R>[]) => void; | ||
/** | ||
@@ -495,3 +518,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; | ||
/** | ||
@@ -503,3 +526,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; | ||
/** | ||
@@ -513,2 +536,7 @@ * Opposite of `deserialize`. Creates a JSON payload from the record store. | ||
/** | ||
* 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 | ||
@@ -541,3 +569,3 @@ * simple JSON structure into the stores. | ||
*/ | ||
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; | ||
/** | ||
@@ -549,3 +577,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; | ||
/** | ||
@@ -585,2 +613,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 */ | ||
@@ -606,3 +635,3 @@ private _isPossiblyCorrupted; | ||
*/ | ||
export declare type StoreListener<R extends BaseRecord> = (entry: HistoryEntry<R>) => void; | ||
export declare type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void; | ||
@@ -613,6 +642,6 @@ /** | ||
*/ | ||
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 */ | ||
@@ -678,5 +707,5 @@ /* Excluded from this release type: historyCache */ | ||
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; | ||
@@ -692,3 +721,3 @@ }>>>>; | ||
/** @public */ | ||
export declare class StoreSchema<R extends BaseRecord, P = unknown> { | ||
export declare class StoreSchema<R extends UnknownRecord, P = unknown> { | ||
readonly types: { | ||
@@ -698,3 +727,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']]: { | ||
@@ -709,3 +738,3 @@ createId: any; | ||
migrateStoreSnapshot(storeSnapshot: StoreSnapshot<R>, persistedSchema: SerializedSchema): MigrationResult<StoreSnapshot<R>>; | ||
/* Excluded from this release type: ensureStoreIsUsable */ | ||
/* Excluded from this release type: createIntegrityChecker */ | ||
serialize(): SerializedSchema; | ||
@@ -716,3 +745,3 @@ serializeEarliestVersion(): SerializedSchema; | ||
/** @public */ | ||
export declare type StoreSchemaOptions<R extends BaseRecord, P> = { | ||
export declare type StoreSchemaOptions<R extends UnknownRecord, P> = { | ||
/** @public */ | ||
@@ -728,3 +757,3 @@ snapshotMigrations?: Migrations; | ||
}) => R; | ||
/* Excluded from this release type: ensureStoreIsUsable */ | ||
/* Excluded from this release type: createIntegrityChecker */ | ||
}; | ||
@@ -737,6 +766,6 @@ | ||
*/ | ||
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 +775,3 @@ }; | ||
/** @public */ | ||
export declare type StoreValidators<R extends BaseRecord> = { | ||
export declare type StoreValidators<R extends UnknownRecord> = { | ||
[K in R['typeName']]: StoreValidator<Extract<R, { | ||
@@ -753,2 +782,5 @@ typeName: K; | ||
/** @public */ | ||
export declare type UnknownRecord = BaseRecord<string, ID<UnknownRecord>>; | ||
declare type ValueMatcher<T> = { | ||
@@ -755,0 +787,0 @@ eq: T; |
@@ -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 |
@@ -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; | ||
/** | ||
@@ -162,3 +164,4 @@ * Create a new record of this type. | ||
migrations: this.migrations, | ||
validator: this.validator | ||
validator: this.validator, | ||
scope: this.scope | ||
}); | ||
@@ -178,3 +181,4 @@ } | ||
migrations: config.migrations ?? { currentVersion: 0, firstVersion: 0, migrators: {} }, | ||
validator: config.validator | ||
validator: config.validator, | ||
scope: config.scope | ||
}); | ||
@@ -181,0 +185,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 { | ||
@@ -82,4 +83,4 @@ /** | ||
this.atoms.set( | ||
Object.fromEntries( | ||
Object.entries(initialData).map(([id, record]) => [ | ||
(0, import_utils.objectMapFromEntries)( | ||
(0, import_utils.objectMapEntries)(initialData).map(([id, record]) => [ | ||
id, | ||
@@ -97,3 +98,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) } | ||
); | ||
@@ -291,3 +292,3 @@ } | ||
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; | ||
@@ -301,2 +302,12 @@ if (typeof filter === "function" && !filter(record)) | ||
/** | ||
* 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 | ||
@@ -321,3 +332,3 @@ * simple JSON structure into the stores. | ||
allRecords = () => { | ||
return Object.values(this.atoms.value).map((atom2) => atom2.value); | ||
return (0, import_utils.objectMapValues)(this.atoms.value).map((atom2) => atom2.value); | ||
}; | ||
@@ -330,3 +341,3 @@ /** | ||
clear = () => { | ||
this.remove(Object.keys(this.atoms.value)); | ||
this.remove((0, import_utils.objectMapKeys)(this.atoms.value)); | ||
}; | ||
@@ -409,6 +420,6 @@ /** | ||
(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 +485,7 @@ this.put(toPut); | ||
}; | ||
_integrityChecker; | ||
/** @internal */ | ||
ensureStoreIsUsable() { | ||
this.schema.ensureStoreIsUsable(this); | ||
this._integrityChecker ??= this.schema.createIntegrityChecker(this); | ||
this._integrityChecker?.(); | ||
} | ||
@@ -492,3 +505,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 +517,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 +533,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 +599,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")); | ||
@@ -82,3 +83,3 @@ var import_signia = require("signia"); | ||
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) { | ||
@@ -99,3 +100,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) { | ||
@@ -112,3 +113,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) { | ||
@@ -169,3 +170,3 @@ if (res.added[removed.id]) { | ||
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; | ||
@@ -195,3 +196,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); | ||
@@ -208,3 +211,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) { | ||
@@ -215,3 +218,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) { | ||
@@ -226,3 +229,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) { | ||
@@ -310,3 +313,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; | ||
@@ -353,3 +356,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)) { | ||
@@ -359,3 +362,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) { | ||
@@ -369,3 +372,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) { | ||
@@ -372,0 +375,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.e312ea047", | ||
"version": "2.0.0-canary.e3cf05f408a8", | ||
"packageManager": "yarn@3.5.0", | ||
"author": { | ||
@@ -34,15 +35,15 @@ "name": "tldraw GB Ltd.", | ||
"scripts": { | ||
"test": "yarn run -T jest", | ||
"test:coverage": "yarn run -T jest --coverage", | ||
"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.e312ea047", | ||
"@tldraw/utils": "2.0.0-canary.e3cf05f408a8", | ||
"lodash.isequal": "^4.5.0", | ||
"nanoid": "^3.0.0" | ||
"nanoid": "4.0.2" | ||
}, | ||
@@ -55,2 +56,3 @@ "peerDependencies": { | ||
"@types/lodash.isequal": "^4.5.6", | ||
"lazyrepo": "0.0.0-alpha.26", | ||
"raf": "^3.4.1" | ||
@@ -57,0 +59,0 @@ }, |
@@ -351,1 +351,5 @@ # @tldraw/tlstore | ||
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'] | ||
/** | ||
@@ -9,11 +12,14 @@ * The base record that all records must extend. | ||
*/ | ||
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 |
@@ -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 }>>> | ||
} |
@@ -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' | ||
} | ||
@@ -90,4 +105,4 @@ | ||
*/ | ||
createId(): ID<R> { | ||
return (this.typeName + ':' + nanoid()) as ID<R> | ||
createId(): IdOf<R> { | ||
return (this.typeName + ':' + nanoid()) as IdOf<R> | ||
} | ||
@@ -107,4 +122,4 @@ | ||
*/ | ||
createCustomId(id: string): ID<R> { | ||
return (this.typeName + ':' + id) as ID<R> | ||
createCustomId(id: string): IdOf<R> { | ||
return (this.typeName + ':' + id) as IdOf<R> | ||
} | ||
@@ -118,3 +133,3 @@ | ||
*/ | ||
parseId(id: string): ID<R> { | ||
parseId(id: string): IdOf<R> { | ||
if (!this.isId(id)) { | ||
@@ -124,3 +139,3 @@ throw new Error(`ID "${id}" is not a valid ID for type "${this.typeName}"`) | ||
return id.slice(this.typeName.length + 1) as ID<R> | ||
return id.slice(this.typeName.length + 1) as IdOf<R> | ||
} | ||
@@ -140,3 +155,3 @@ | ||
*/ | ||
isInstance = (record?: BaseRecord): record is R => { | ||
isInstance = (record?: UnknownRecord): record is R => { | ||
return record?.typeName === this.typeName | ||
@@ -157,3 +172,3 @@ } | ||
*/ | ||
isId(id?: string): id is ID<R> { | ||
isId(id?: string): id is IdOf<R> { | ||
if (!id) return false | ||
@@ -188,2 +203,3 @@ for (let i = 0; i < this.typeName.length; i++) { | ||
validator: this.validator, | ||
scope: this.scope, | ||
}) | ||
@@ -213,8 +229,8 @@ } | ||
*/ | ||
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 | ||
} | ||
@@ -226,2 +242,3 @@ ): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> { | ||
validator: config.validator, | ||
scope: config.scope, | ||
}) | ||
@@ -243,6 +260,6 @@ } | ||
*/ | ||
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)) { | ||
@@ -249,0 +266,0 @@ throw new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`) |
@@ -1,9 +0,17 @@ | ||
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 | ||
@@ -15,6 +23,6 @@ /** | ||
*/ | ||
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> | ||
} | ||
@@ -34,3 +42,3 @@ | ||
*/ | ||
export type HistoryEntry<R extends BaseRecord = BaseRecord> = { | ||
export type HistoryEntry<R extends UnknownRecord = UnknownRecord> = { | ||
changes: RecordsDiff<R> | ||
@@ -45,3 +53,3 @@ source: 'user' | 'remote' | ||
*/ | ||
export type StoreListener<R extends BaseRecord> = (entry: HistoryEntry<R>) => void | ||
export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void | ||
@@ -53,4 +61,4 @@ /** | ||
*/ | ||
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 | ||
} | ||
@@ -63,6 +71,6 @@ | ||
*/ | ||
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 | ||
@@ -72,3 +80,3 @@ } | ||
/** @public */ | ||
export type StoreValidators<R extends BaseRecord> = { | ||
export type StoreValidators<R extends UnknownRecord> = { | ||
[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>> | ||
@@ -94,3 +102,3 @@ } | ||
*/ | ||
export class Store<R extends BaseRecord = BaseRecord, Props = unknown> { | ||
export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> { | ||
/** | ||
@@ -102,3 +110,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>>) | ||
@@ -166,4 +174,4 @@ /** | ||
this.atoms.set( | ||
Object.fromEntries( | ||
Object.entries(initialData).map(([id, record]) => [ | ||
objectMapFromEntries( | ||
objectMapEntries(initialData).map(([id, record]) => [ | ||
id, | ||
@@ -184,3 +192,3 @@ atom('atom:' + id, this.schema.validateRecord(this, record, 'initialize', null)), | ||
}, | ||
{ scheduleEffect: (cb) => requestAnimationFrame(cb) } | ||
{ scheduleEffect: (cb) => throttledRaf(cb) } | ||
) | ||
@@ -260,7 +268,7 @@ } | ||
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>> | ||
@@ -279,3 +287,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>] | ||
@@ -339,3 +347,3 @@ if (recordAtom) { | ||
updated: updates, | ||
removed: {}, | ||
removed: {} as Record<IdOf<R>, R>, | ||
}) | ||
@@ -367,3 +375,3 @@ | ||
*/ | ||
remove = (ids: ID<R>[]): void => { | ||
remove = (ids: IdOf<R>[]): void => { | ||
transact(() => { | ||
@@ -388,3 +396,3 @@ if (this.onBeforeDelete && this._runCallbacks) { | ||
if (!result) result = { ...atoms } | ||
if (!removed) removed = {} | ||
if (!removed) removed = {} as Record<IdOf<R>, R> | ||
delete result[id] | ||
@@ -399,3 +407,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>) | ||
@@ -417,3 +425,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 | ||
@@ -428,3 +436,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 | ||
@@ -440,7 +448,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 | ||
} | ||
@@ -451,2 +459,13 @@ return result | ||
/** | ||
* 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 | ||
@@ -472,3 +491,3 @@ * simple JSON structure into the stores. | ||
allRecords = (): R[] => { | ||
return Object.values(this.atoms.value).map((atom) => atom.value) | ||
return objectMapValues(this.atoms.value).map((atom) => atom.value) | ||
} | ||
@@ -482,3 +501,3 @@ | ||
clear = (): void => { | ||
this.remove(Object.keys(this.atoms.value) as any) | ||
this.remove(objectMapKeys(this.atoms.value)) | ||
} | ||
@@ -493,3 +512,3 @@ | ||
*/ | ||
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] | ||
@@ -509,3 +528,3 @@ if (!atom) { | ||
*/ | ||
has = <K extends ID<R>>(id: K): boolean => { | ||
has = <K extends IdOf<R>>(id: K): boolean => { | ||
return !!this.atoms.value[id] | ||
@@ -576,6 +595,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) { | ||
@@ -606,3 +625,3 @@ this.put(toPut) | ||
return { | ||
get: (id: ID<V>) => { | ||
get: (id: IdOf<V>) => { | ||
const atom = this.atoms.value[id] | ||
@@ -634,3 +653,3 @@ if (!atom) { | ||
return { | ||
get: (id: ID<V>) => { | ||
get: (id: IdOf<V>) => { | ||
const atom = this.atoms.value[id] | ||
@@ -651,5 +670,8 @@ if (!atom) { | ||
private _integrityChecker?: () => void | undefined | ||
/** @internal */ | ||
ensureStoreIsUsable() { | ||
this.schema.ensureStoreIsUsable(this) | ||
this._integrityChecker ??= this.schema.createIntegrityChecker(this) | ||
this._integrityChecker?.() | ||
} | ||
@@ -675,7 +697,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]) { | ||
@@ -692,3 +716,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]) { | ||
@@ -710,3 +734,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 | ||
@@ -734,3 +758,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>[] = [] | ||
@@ -769,3 +795,3 @@ | ||
class HistoryAccumulator<T extends BaseRecord> { | ||
class HistoryAccumulator<T extends UnknownRecord> { | ||
private _history: HistoryEntry<T>[] = [] | ||
@@ -802,13 +828,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,15 +20,15 @@ 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>> | ||
@@ -38,5 +39,5 @@ /** | ||
*/ | ||
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>> | ||
@@ -85,3 +86,3 @@ ) {} | ||
const res: RecordsDiff<S> = { added: {}, removed: {}, updated: {} } | ||
const res = { added: {}, removed: {}, updated: {} } as RecordsDiff<S> | ||
let numAdded = 0 | ||
@@ -92,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++ | ||
@@ -110,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++ | ||
@@ -124,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++ | ||
@@ -202,4 +203,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 | ||
@@ -211,3 +212,3 @@ if (record.typeName === typeName) { | ||
} | ||
res.get(value)!.add((record as S).id) | ||
res.get(value)!.add(record.id) | ||
} | ||
@@ -229,8 +230,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) | ||
@@ -240,5 +243,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) | ||
@@ -249,9 +252,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) { | ||
@@ -261,11 +264,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) | ||
} | ||
@@ -364,4 +367,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 }>>> | ||
> { | ||
@@ -377,7 +380,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 { | ||
@@ -393,3 +396,3 @@ return [] | ||
const fromScratchWithDiff = (prevValue: Set<ID<S>>) => { | ||
const fromScratchWithDiff = (prevValue: Set<IdOf<S>>) => { | ||
const nextValue = fromScratch() | ||
@@ -426,24 +429,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) | ||
} | ||
@@ -450,0 +453,0 @@ } |
import { getOwnProperty, objectMapValues } from '@tldraw/utils' | ||
import { BaseRecord } from './BaseRecord' | ||
import { IdOf, UnknownRecord } from './BaseRecord' | ||
import { RecordType } from './RecordType' | ||
@@ -38,3 +38,3 @@ import { Store, StoreSnapshot } from './Store' | ||
/** @public */ | ||
export type StoreSchemaOptions<R extends BaseRecord, P> = { | ||
export type StoreSchemaOptions<R extends UnknownRecord, P> = { | ||
/** @public */ | ||
@@ -51,8 +51,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 | ||
@@ -154,3 +154,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 | ||
@@ -168,3 +168,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 } | ||
@@ -226,3 +226,3 @@ } | ||
const updated: R[] = [] | ||
for (const r of Object.values(storeSnapshot)) { | ||
for (const r of objectMapValues(storeSnapshot)) { | ||
const result = this.migratePersistedRecord(r, persistedSchema) | ||
@@ -238,3 +238,3 @@ if (result.type === 'error') { | ||
for (const r of updated) { | ||
storeSnapshot[r.id] = r | ||
storeSnapshot[r.id as IdOf<R>] = r | ||
} | ||
@@ -246,4 +246,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 | ||
} | ||
@@ -250,0 +250,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,16 +7,8 @@ import { StoreSchema } from '../StoreSchema' | ||
const UserVersion = { | ||
Initial: 0, | ||
} as const | ||
/** A user of tldraw */ | ||
interface User extends BaseRecord<'user'> { | ||
interface User extends BaseRecord<'user', ID<User>> { | ||
name: string | ||
} | ||
const userMigrations = defineMigrations({ | ||
currentVersion: UserVersion.Initial, | ||
firstVersion: UserVersion.Initial, | ||
migrators: {}, | ||
}) | ||
const userMigrations = defineMigrations({}) | ||
@@ -33,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 | ||
@@ -62,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({}), | ||
}, | ||
@@ -78,3 +56,3 @@ }) | ||
const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', { | ||
migrations: shapeMigrations, | ||
migrations: shapeTypeMigrations, | ||
validator: { | ||
@@ -97,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 | ||
@@ -106,3 +85,3 @@ } | ||
const Org = createRecordType<Org>('org', { | ||
migrations: defineMigrations({ currentVersion: 0, firstVersion: 0, migrators: {} }), | ||
migrations: defineMigrations({}), | ||
validator: { | ||
@@ -116,2 +95,3 @@ validate: (record) => { | ||
}, | ||
scope: 'document', | ||
}) | ||
@@ -126,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, | ||
@@ -16,3 +15,3 @@ AddPhoneNumber: 2, | ||
/** A user of tldraw */ | ||
interface User extends BaseRecord<'user'> { | ||
interface User extends BaseRecord<'user', ID<User>> { | ||
name: string | ||
@@ -25,3 +24,2 @@ locale: string | ||
currentVersion: UserVersion.AddPhoneNumber, | ||
firstVersion: UserVersion.Initial, | ||
migrators: { | ||
@@ -68,2 +66,3 @@ [UserVersion.AddLocale]: { | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ | ||
@@ -75,3 +74,2 @@ /* STEP 6: Add any new default values for properties here */ | ||
const ShapeVersion = { | ||
Initial: 0, | ||
AddRotation: 1, | ||
@@ -82,3 +80,2 @@ AddParent: 2, | ||
const RectangleVersion = { | ||
Initial: 0, | ||
AddOpacity: 1, | ||
@@ -88,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 | ||
@@ -98,3 +96,3 @@ x: number | ||
rotation: number | ||
parentId: ID<Shape<Props>> | null | ||
parentId: ShapeId | null | ||
props: Props | ||
@@ -114,5 +112,4 @@ } | ||
const shapeMigrations = defineMigrations({ | ||
const shapeTypeMigrations = defineMigrations({ | ||
currentVersion: ShapeVersion.AddParent, | ||
firstVersion: ShapeVersion.Initial, | ||
migrators: { | ||
@@ -146,3 +143,2 @@ [ShapeVersion.AddRotation]: { | ||
currentVersion: RectangleVersion.AddOpacity, | ||
firstVersion: RectangleVersion.Initial, | ||
migrators: { | ||
@@ -169,3 +165,2 @@ [RectangleVersion.AddOpacity]: { | ||
currentVersion: OvalVersion.AddBorderStyle, | ||
firstVersion: OvalVersion.Initial, | ||
migrators: { | ||
@@ -194,3 +189,3 @@ [OvalVersion.AddBorderStyle]: { | ||
const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', { | ||
migrations: shapeMigrations, | ||
migrations: shapeTypeMigrations, | ||
validator: { | ||
@@ -207,2 +202,3 @@ validate: (record) => { | ||
}, | ||
scope: 'document', | ||
}).withDefaultProperties(() => ({ | ||
@@ -216,3 +212,2 @@ x: 0, | ||
const StoreVersions = { | ||
Initial: 0, | ||
RemoveOrg: 1, | ||
@@ -223,3 +218,2 @@ } | ||
currentVersion: StoreVersions.RemoveOrg, | ||
firstVersion: StoreVersions.Initial, | ||
migrators: { | ||
@@ -226,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, |
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
547799
9441
355
4
85
+ Added@tldraw/utils@2.0.0-canary.e3cf05f408a8(transitive)
+ Addednanoid@4.0.2(transitive)
- Removed@tldraw/utils@2.0.0-canary.e312ea047(transitive)
- Removednanoid@3.3.8(transitive)
Updatednanoid@4.0.2