@tldraw/store 4.2.0-next.f100cedfc45b → 4.3.0-canary.03ae87dcc44b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +1 -1
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/RecordType.js.map +2 -2
- package/dist-cjs/lib/StoreSideEffects.js +1 -1
- package/dist-cjs/lib/StoreSideEffects.js.map +1 -1
- package/dist-esm/index.d.mts +1 -1
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/RecordType.mjs.map +2 -2
- package/dist-esm/lib/StoreSideEffects.mjs +1 -1
- package/dist-esm/lib/StoreSideEffects.mjs.map +1 -1
- package/package.json +3 -3
- package/src/lib/RecordType.ts +1 -1
- package/src/lib/StoreSideEffects.ts +1 -1
package/dist-cjs/index.d.ts
CHANGED
|
@@ -2588,7 +2588,7 @@ export declare class StoreSideEffects<R extends UnknownRecord> {
|
|
|
2588
2588
|
* ```ts
|
|
2589
2589
|
* editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
|
|
2590
2590
|
* // Automatically create a shape when a page is created
|
|
2591
|
-
* editor.createShape
|
|
2591
|
+
* editor.createShape({
|
|
2592
2592
|
* id: createShapeId(),
|
|
2593
2593
|
* type: 'text',
|
|
2594
2594
|
* props: { richText: toRichText(page.name) },
|
package/dist-cjs/index.js
CHANGED
|
@@ -54,7 +54,7 @@ var import_StoreSchema = require("./lib/StoreSchema");
|
|
|
54
54
|
var import_StoreSideEffects = require("./lib/StoreSideEffects");
|
|
55
55
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
56
56
|
"@tldraw/store",
|
|
57
|
-
"4.
|
|
57
|
+
"4.3.0-canary.03ae87dcc44b",
|
|
58
58
|
"cjs"
|
|
59
59
|
);
|
|
60
60
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/RecordType.ts"],
|
|
4
|
-
"sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * 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.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAoE;AAkC7D,MAAM,WAGX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2CD,YAOiB,UAChB,QAUC;AAXe;AAYhB,SAAK,0BAA0B,OAAO;AACtC,SAAK,YAAY,OAAO,aAAa,EAAE,UAAU,CAAC,MAAe,EAAO;AACxE,SAAK,QAAQ,OAAO,SAAS;AAC7B,SAAK,gBAAgB,OAAO;AAE5B,UAAM,kBAAkB,oBAAI,IAAY;AACxC,QAAI,OAAO,eAAe;AACzB,iBAAW,CAAC,KAAK,WAAW,SAAK,+BAAiB,OAAO,aAAa,GAAG;AACxE,YAAI,YAAa,iBAAgB,IAAI,
|
|
4
|
+
"sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * 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.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key as string)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAoE;AAkC7D,MAAM,WAGX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2CD,YAOiB,UAChB,QAUC;AAXe;AAYhB,SAAK,0BAA0B,OAAO;AACtC,SAAK,YAAY,OAAO,aAAa,EAAE,UAAU,CAAC,MAAe,EAAO;AACxE,SAAK,QAAQ,OAAO,SAAS;AAC7B,SAAK,gBAAgB,OAAO;AAE5B,UAAM,kBAAkB,oBAAI,IAAY;AACxC,QAAI,OAAO,eAAe;AACzB,iBAAW,CAAC,KAAK,WAAW,SAAK,+BAAiB,OAAO,aAAa,GAAG;AACxE,YAAI,YAAa,iBAAgB,IAAI,GAAa;AAAA,MACnD;AAAA,IACD;AACA,SAAK,kBAAkB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EArES;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiET,OACC,YACI;AACJ,UAAM,SAAS;AAAA,MACd,GAAG,KAAK,wBAAwB;AAAA,MAChC,IAAI,QAAQ,aAAa,WAAW,KAAK,KAAK,SAAS;AAAA,IACxD;AAEA,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,GAAG;AAChD,UAAI,MAAM,QAAW;AACpB,eAAO,CAAC,IAAI;AAAA,MACb;AAAA,IACD;AAEA,WAAO,WAAW,KAAK;AAEvB,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,QAAc;AACnB,WAAO,EAAE,OAAG,8BAAgB,MAAM,GAAG,IAAI,KAAK,SAAS,EAAE;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,SAAS,kBAAoC;AAC5C,WAAQ,KAAK,WAAW,OAAO,wBAAoB,uBAAS;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,QAAQ,IAAqB;AAC5B,QAAI,CAAC,KAAK,KAAK,EAAE,GAAG;AACnB,YAAM,IAAI,MAAM,OAAO,EAAE,iCAAiC,KAAK,QAAQ,GAAG;AAAA,IAC3E;AAEA,WAAO,GAAG,MAAM,KAAK,SAAS,SAAS,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,WAAW,QAAqC;AAC/C,WAAO,QAAQ,aAAa,KAAK;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,KAAK,IAA4B;AAChC,QAAI,CAAC,GAAI,QAAO;AAChB,aAAS,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;AAC9C,UAAI,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,EAAG,QAAO;AAAA,IACxC;AAEA,WAAO,GAAG,KAAK,SAAS,MAAM,MAAM;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,sBACC,yBACiE;AACjE,WAAO,IAAI,WAA+D,KAAK,UAAU;AAAA,MACxF;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,eAAe,KAAK;AAAA,IACrB,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,SAAS,QAAiB,cAAqB;AAC9C,QAAI,gBAAgB,KAAK,UAAU,+BAA+B;AACjE,aAAO,KAAK,UAAU,8BAA8B,cAAc,MAAM;AAAA,IACzE;AACA,WAAO,KAAK,UAAU,SAAS,MAAM;AAAA,EACtC;AACD;AA4BO,SAAS,iBACf,UACA,QAKkD;AAClD,SAAO,IAAI,WAAgD,UAAU;AAAA,IACpE,yBAAyB,OAAO,CAAC;AAAA,IACjC,WAAW,OAAO;AAAA,IAClB,OAAO,OAAO;AAAA,IACd,eAAe,OAAO;AAAA,EACvB,CAAC;AACF;AAeO,SAAS,aACf,IACA,MACwB;AACxB,MAAI,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,GAAG;AAC1B,UAAM,IAAI,MAAM,UAAU,KAAK,UAAU,EAAE,CAAC,mBAAmB,KAAK,QAAQ,KAAK;AAAA,EAClF;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -283,7 +283,7 @@ class StoreSideEffects {
|
|
|
283
283
|
* ```ts
|
|
284
284
|
* editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
|
|
285
285
|
* // Automatically create a shape when a page is created
|
|
286
|
-
* editor.createShape
|
|
286
|
+
* editor.createShape({
|
|
287
287
|
* id: createShapeId(),
|
|
288
288
|
* type: 'text',
|
|
289
289
|
* props: { richText: toRichText(page.name) },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/StoreSideEffects.ts"],
|
|
4
|
-
"sourcesContent": ["import { UnknownRecord } from './BaseRecord'\nimport { Store } from './Store'\n\n/**\n * Handler function called before a record is created in the store.\n * The handler receives the record to be created and can return a modified version.\n * Use this to validate, transform, or modify records before they are added to the store.\n *\n * @param record - The record about to be created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record to actually create (may be modified)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeCreateHandler<MyRecord> = (record, source) => {\n * // Ensure all user-created records have a timestamp\n * if (source === 'user' && !record.createdAt) {\n * return { ...record, createdAt: Date.now() }\n * }\n * return record\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully created in the store.\n * Use this for side effects that should happen after record creation, such as updating\n * related records or triggering notifications.\n *\n * @param record - The record that was created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterCreateHandler<BookRecord> = (book, source) => {\n * if (source === 'user') {\n * console.log(`New book added: ${book.title}`)\n * updateAuthorBookCount(book.authorId)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is updated in the store.\n * The handler receives the current and new versions of the record and can return\n * a modified version or the original to prevent the change.\n *\n * @param prev - The current version of the record in the store\n * @param next - The proposed new version of the record\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record version to actually store (may be modified or the original to block change)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Prevent shapes from being moved outside the canvas bounds\n * if (next.x < 0 || next.y < 0) {\n * return prev // Block the change\n * }\n * return next\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully updated in the store.\n * Use this for side effects that should happen after record changes, such as\n * updating related records or maintaining consistency constraints.\n *\n * @param prev - The previous version of the record\n * @param next - The new version of the record that was stored\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Update connected arrows when a shape moves\n * if (prev.x !== next.x || prev.y !== next.y) {\n * updateConnectedArrows(next.id)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is deleted from the store.\n * The handler can return `false` to prevent the deletion from occurring.\n *\n * @param record - The record about to be deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns `false` to prevent deletion, `void` or any other value to allow it\n *\n * @example\n * ```ts\n * const handler: StoreBeforeDeleteHandler<BookRecord> = (book, source) => {\n * // Prevent deletion of books that are currently checked out\n * if (book.isCheckedOut) {\n * console.warn('Cannot delete checked out book')\n * return false\n * }\n * // Allow deletion for other books\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void | false\n/**\n * Handler function called after a record has been successfully deleted from the store.\n * Use this for cleanup operations and maintaining referential integrity.\n *\n * @param record - The record that was deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterDeleteHandler<ShapeRecord> = (shape, source) => {\n * // Clean up arrows that were connected to this shape\n * const connectedArrows = findArrowsConnectedTo(shape.id)\n * store.remove(connectedArrows.map(arrow => arrow.id))\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n\n/**\n * Handler function called when a store operation (atomic transaction) completes.\n * This is useful for performing actions after a batch of changes has been applied,\n * such as triggering saves or sending notifications.\n *\n * @param source - Whether the operation originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreOperationCompleteHandler = (source) => {\n * if (source === 'user') {\n * // Auto-save after user operations complete\n * saveStoreSnapshot()\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void\n\n/**\n * The side effect manager (aka a \"correct state enforcer\") is responsible\n * for making sure that the store's state is always correct and consistent. This includes\n * things like: deleting a shape if its parent is deleted; unbinding\n * arrows when their binding target is deleted; maintaining referential integrity; etc.\n *\n * Side effects are organized into lifecycle hooks that run before and after\n * record operations (create, change, delete), allowing you to validate data,\n * transform records, and maintain business rules.\n *\n * @example\n * ```ts\n * const sideEffects = new StoreSideEffects(store)\n *\n * // Ensure arrows are deleted when their target shape is deleted\n * sideEffects.registerAfterDeleteHandler('shape', (shape) => {\n * const arrows = store.query.records('arrow', () => ({\n * toId: { eq: shape.id }\n * })).get()\n * store.remove(arrows.map(arrow => arrow.id))\n * })\n * ```\n *\n * @public\n */\nexport class StoreSideEffects<R extends UnknownRecord> {\n\t/**\n\t * Creates a new side effects manager for the given store.\n\t *\n\t * store - The store instance to manage side effects for\n\t */\n\tconstructor(private readonly store: Store<R>) {}\n\n\tprivate _beforeCreateHandlers: { [K in string]?: StoreBeforeCreateHandler<any>[] } = {}\n\tprivate _afterCreateHandlers: { [K in string]?: StoreAfterCreateHandler<any>[] } = {}\n\tprivate _beforeChangeHandlers: { [K in string]?: StoreBeforeChangeHandler<any>[] } = {}\n\tprivate _afterChangeHandlers: { [K in string]?: StoreAfterChangeHandler<any>[] } = {}\n\tprivate _beforeDeleteHandlers: { [K in string]?: StoreBeforeDeleteHandler<any>[] } = {}\n\tprivate _afterDeleteHandlers: { [K in string]?: StoreAfterDeleteHandler<any>[] } = {}\n\tprivate _operationCompleteHandlers: StoreOperationCompleteHandler[] = []\n\n\tprivate _isEnabled = true\n\t/**\n\t * Checks whether side effects are currently enabled.\n\t * When disabled, all side effect handlers are bypassed.\n\t *\n\t * @returns `true` if side effects are enabled, `false` otherwise\n\t * @internal\n\t */\n\tisEnabled() {\n\t\treturn this._isEnabled\n\t}\n\t/**\n\t * Enables or disables side effects processing.\n\t * When disabled, no side effect handlers will be called.\n\t *\n\t * @param enabled - Whether to enable or disable side effects\n\t * @internal\n\t */\n\tsetIsEnabled(enabled: boolean) {\n\t\tthis._isEnabled = enabled\n\t}\n\n\t/**\n\t * Processes all registered 'before create' handlers for a record.\n\t * Handlers are called in registration order and can transform the record.\n\t *\n\t * @param record - The record about to be created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually create\n\t * @internal\n\t */\n\thandleBeforeCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return record\n\n\t\tconst handlers = this._beforeCreateHandlers[record.typeName] as StoreBeforeCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = record\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn record\n\t}\n\n\t/**\n\t * Processes all registered 'after create' handlers for a record.\n\t * Handlers are called in registration order after the record is created.\n\t *\n\t * @param record - The record that was created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterCreateHandlers[record.typeName] as StoreAfterCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before change' handlers for a record.\n\t * Handlers are called in registration order and can modify or block the change.\n\t *\n\t * @param prev - The current version of the record\n\t * @param next - The proposed new version of the record\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually store\n\t * @internal\n\t */\n\thandleBeforeChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return next\n\n\t\tconst handlers = this._beforeChangeHandlers[next.typeName] as StoreBeforeChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = next\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(prev, r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn next\n\t}\n\n\t/**\n\t * Processes all registered 'after change' handlers for a record.\n\t * Handlers are called in registration order after the record is updated.\n\t *\n\t * @param prev - The previous version of the record\n\t * @param next - The new version of the record that was stored\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterChangeHandlers[next.typeName] as StoreAfterChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(prev, next, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before delete' handlers for a record.\n\t * If any handler returns `false`, the deletion is prevented.\n\t *\n\t * @param record - The record about to be deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns `true` to allow deletion, `false` to prevent it\n\t * @internal\n\t */\n\thandleBeforeDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return true\n\n\t\tconst handlers = this._beforeDeleteHandlers[record.typeName] as StoreBeforeDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tif (handler(record, source) === false) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t/**\n\t * Processes all registered 'after delete' handlers for a record.\n\t * Handlers are called in registration order after the record is deleted.\n\t *\n\t * @param record - The record that was deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterDeleteHandlers[record.typeName] as StoreAfterDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered operation complete handlers.\n\t * Called after an atomic store operation finishes.\n\t *\n\t * @param source - Whether the operation originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleOperationComplete(source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tfor (const handler of this._operationCompleteHandlers) {\n\t\t\thandler(source)\n\t\t}\n\t}\n\n\t/**\n\t * Internal helper for registering multiple side effect handlers at once and keeping them organized.\n\t * This provides a convenient way to register handlers for multiple record types and lifecycle events\n\t * in a single call, returning a single cleanup function.\n\t *\n\t * @param handlersByType - An object mapping record type names to their respective handlers\n\t * @returns A function that removes all registered handlers when called\n\t *\n\t * @example\n\t * ```ts\n\t * const cleanup = sideEffects.register({\n\t * shape: {\n\t * afterDelete: (shape) => console.log('Shape deleted:', shape.id),\n\t * beforeChange: (prev, next) => ({ ...next, lastModified: Date.now() })\n\t * },\n\t * arrow: {\n\t * afterCreate: (arrow) => updateConnectedShapes(arrow)\n\t * }\n\t * })\n\t *\n\t * // Later, remove all handlers\n\t * cleanup()\n\t * ```\n\t *\n\t * @internal\n\t */\n\tregister(handlersByType: {\n\t\t[T in R as T['typeName']]?: {\n\t\t\tbeforeCreate?: StoreBeforeCreateHandler<T>\n\t\t\tafterCreate?: StoreAfterCreateHandler<T>\n\t\t\tbeforeChange?: StoreBeforeChangeHandler<T>\n\t\t\tafterChange?: StoreAfterChangeHandler<T>\n\t\t\tbeforeDelete?: StoreBeforeDeleteHandler<T>\n\t\t\tafterDelete?: StoreAfterDeleteHandler<T>\n\t\t}\n\t}) {\n\t\tconst disposes: (() => void)[] = []\n\t\tfor (const [type, handlers] of Object.entries(handlersByType) as any) {\n\t\t\tif (handlers?.beforeCreate) {\n\t\t\t\tdisposes.push(this.registerBeforeCreateHandler(type, handlers.beforeCreate))\n\t\t\t}\n\t\t\tif (handlers?.afterCreate) {\n\t\t\t\tdisposes.push(this.registerAfterCreateHandler(type, handlers.afterCreate))\n\t\t\t}\n\t\t\tif (handlers?.beforeChange) {\n\t\t\t\tdisposes.push(this.registerBeforeChangeHandler(type, handlers.beforeChange))\n\t\t\t}\n\t\t\tif (handlers?.afterChange) {\n\t\t\t\tdisposes.push(this.registerAfterChangeHandler(type, handlers.afterChange))\n\t\t\t}\n\t\t\tif (handlers?.beforeDelete) {\n\t\t\t\tdisposes.push(this.registerBeforeDeleteHandler(type, handlers.beforeDelete))\n\t\t\t}\n\t\t\tif (handlers?.afterDelete) {\n\t\t\t\tdisposes.push(this.registerAfterDeleteHandler(type, handlers.afterDelete))\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const dispose of disposes) dispose()\n\t\t}\n\t}\n\n\t/**\n\t * Register a handler to be called before a record of a certain type is created. Return a\n\t * modified record from the handler to change the record that will be created.\n\t *\n\t * Use this handle only to modify the creation of the record itself. If you want to trigger a\n\t * side-effect on a different record (for example, moving one shape when another is created),\n\t * use {@link StoreSideEffects.registerAfterCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => {\n\t * // only modify shapes created by the user\n\t * if (source !== 'user') return shape\n\t *\n\t * //by default, arrow shapes have no label. Let's make sure they always have a label.\n\t * if (shape.type === 'arrow') {\n\t * return {...shape, props: {...shape.props, text: 'an arrow'}}\n\t * }\n\t *\n\t * // other shapes get returned unmodified\n\t * return shape\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeCreateHandlers[typeName] as StoreBeforeCreateHandler<any>[]\n\t\tif (!handlers) this._beforeCreateHandlers[typeName] = []\n\t\tthis._beforeCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is created. This is useful for side-effects\n\t * that would update _other_ records. If you want to modify the record being created use\n\t * {@link StoreSideEffects.registerBeforeCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {\n\t * // Automatically create a shape when a page is created\n\t * editor.createShape<TLTextShape>({\n\t * id: createShapeId(),\n\t * type: 'text',\n\t * props: { richText: toRichText(page.name) },\n\t * })\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterCreateHandlers[typeName] as StoreAfterCreateHandler<any>[]\n\t\tif (!handlers) this._afterCreateHandlers[typeName] = []\n\t\tthis._afterCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._afterCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is changed. The handler is given the old and\n\t * new record - you can return a modified record to apply a different update, or the old record\n\t * to block the update entirely.\n\t *\n\t * Use this handler only for intercepting updates to the record itself. If you want to update\n\t * other records in response to a change, use\n\t * {@link StoreSideEffects.registerAfterChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next, source) => {\n\t * if (next.isLocked && !prev.isLocked) {\n\t * // prevent shapes from ever being locked:\n\t * return prev\n\t * }\n\t * // other types of change are allowed\n\t * return next\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeChangeHandlers[typeName] as StoreBeforeChangeHandler<any>[]\n\t\tif (!handlers) this._beforeChangeHandlers[typeName] = []\n\t\tthis._beforeChangeHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is changed. This is useful for side-effects\n\t * that would update _other_ records - if you want to modify the record being changed, use\n\t * {@link StoreSideEffects.registerBeforeChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => {\n\t * if (next.props.color === 'red') {\n\t * // there can only be one red shape at a time:\n\t * const otherRedShapes = editor.getCurrentPageShapes().filter(s => s.props.color === 'red' && s.id !== next.id)\n\t * editor.updateShapes(otherRedShapes.map(s => ({...s, props: {...s.props, color: 'blue'}})))\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterChangeHandlers[typeName] as StoreAfterChangeHandler<any>[]\n\t\tif (!handlers) this._afterChangeHandlers[typeName] = []\n\t\tthis._afterChangeHandlers[typeName]!.push(handler as StoreAfterChangeHandler<any>)\n\t\treturn () => remove(this._afterChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is deleted. The handler can return `false` to\n\t * prevent the deletion.\n\t *\n\t * Use this handler only for intercepting deletions of the record itself. If you want to do\n\t * something to other records in response to a deletion, use\n\t * {@link StoreSideEffects.registerAfterDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => {\n\t * if (shape.props.color === 'red') {\n\t * // prevent red shapes from being deleted\n\t * \t return false\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeDeleteHandlers[typeName] as StoreBeforeDeleteHandler<any>[]\n\t\tif (!handlers) this._beforeDeleteHandlers[typeName] = []\n\t\tthis._beforeDeleteHandlers[typeName]!.push(handler as StoreBeforeDeleteHandler<any>)\n\t\treturn () => remove(this._beforeDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is deleted. This is useful for side-effects\n\t * that would update _other_ records - if you want to block the deletion of the record itself,\n\t * use {@link StoreSideEffects.registerBeforeDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => {\n\t * // if the last shape in a frame is deleted, delete the frame too:\n\t * const parentFrame = editor.getShape(shape.parentId)\n\t * if (!parentFrame || parentFrame.type !== 'frame') return\n\t *\n\t * const siblings = editor.getSortedChildIdsForParent(parentFrame)\n\t * if (siblings.length === 0) {\n\t * editor.deleteShape(parentFrame.id)\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterDeleteHandlers[typeName] as StoreAfterDeleteHandler<any>[]\n\t\tif (!handlers) this._afterDeleteHandlers[typeName] = []\n\t\tthis._afterDeleteHandlers[typeName]!.push(handler as StoreAfterDeleteHandler<any>)\n\t\treturn () => remove(this._afterDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called when a store completes an atomic operation.\n\t *\n\t * @example\n\t * ```ts\n\t * let count = 0\n\t *\n\t * editor.sideEffects.registerOperationCompleteHandler(() => count++)\n\t *\n\t * editor.selectAll()\n\t * expect(count).toBe(1)\n\t *\n\t * editor.store.atomic(() => {\n\t *\teditor.selectNone()\n\t * \teditor.selectAll()\n\t * })\n\t *\n\t * expect(count).toBe(2)\n\t * ```\n\t *\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t *\n\t * @public\n\t */\n\tregisterOperationCompleteHandler(handler: StoreOperationCompleteHandler) {\n\t\tthis._operationCompleteHandlers.push(handler)\n\t\treturn () => remove(this._operationCompleteHandlers, handler)\n\t}\n}\n\nfunction remove(array: any[], item: any) {\n\tconst index = array.indexOf(item)\n\tif (index >= 0) {\n\t\tarray.splice(index, 1)\n\t}\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { UnknownRecord } from './BaseRecord'\nimport { Store } from './Store'\n\n/**\n * Handler function called before a record is created in the store.\n * The handler receives the record to be created and can return a modified version.\n * Use this to validate, transform, or modify records before they are added to the store.\n *\n * @param record - The record about to be created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record to actually create (may be modified)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeCreateHandler<MyRecord> = (record, source) => {\n * // Ensure all user-created records have a timestamp\n * if (source === 'user' && !record.createdAt) {\n * return { ...record, createdAt: Date.now() }\n * }\n * return record\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully created in the store.\n * Use this for side effects that should happen after record creation, such as updating\n * related records or triggering notifications.\n *\n * @param record - The record that was created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterCreateHandler<BookRecord> = (book, source) => {\n * if (source === 'user') {\n * console.log(`New book added: ${book.title}`)\n * updateAuthorBookCount(book.authorId)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is updated in the store.\n * The handler receives the current and new versions of the record and can return\n * a modified version or the original to prevent the change.\n *\n * @param prev - The current version of the record in the store\n * @param next - The proposed new version of the record\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record version to actually store (may be modified or the original to block change)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Prevent shapes from being moved outside the canvas bounds\n * if (next.x < 0 || next.y < 0) {\n * return prev // Block the change\n * }\n * return next\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully updated in the store.\n * Use this for side effects that should happen after record changes, such as\n * updating related records or maintaining consistency constraints.\n *\n * @param prev - The previous version of the record\n * @param next - The new version of the record that was stored\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Update connected arrows when a shape moves\n * if (prev.x !== next.x || prev.y !== next.y) {\n * updateConnectedArrows(next.id)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is deleted from the store.\n * The handler can return `false` to prevent the deletion from occurring.\n *\n * @param record - The record about to be deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns `false` to prevent deletion, `void` or any other value to allow it\n *\n * @example\n * ```ts\n * const handler: StoreBeforeDeleteHandler<BookRecord> = (book, source) => {\n * // Prevent deletion of books that are currently checked out\n * if (book.isCheckedOut) {\n * console.warn('Cannot delete checked out book')\n * return false\n * }\n * // Allow deletion for other books\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void | false\n/**\n * Handler function called after a record has been successfully deleted from the store.\n * Use this for cleanup operations and maintaining referential integrity.\n *\n * @param record - The record that was deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterDeleteHandler<ShapeRecord> = (shape, source) => {\n * // Clean up arrows that were connected to this shape\n * const connectedArrows = findArrowsConnectedTo(shape.id)\n * store.remove(connectedArrows.map(arrow => arrow.id))\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n\n/**\n * Handler function called when a store operation (atomic transaction) completes.\n * This is useful for performing actions after a batch of changes has been applied,\n * such as triggering saves or sending notifications.\n *\n * @param source - Whether the operation originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreOperationCompleteHandler = (source) => {\n * if (source === 'user') {\n * // Auto-save after user operations complete\n * saveStoreSnapshot()\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void\n\n/**\n * The side effect manager (aka a \"correct state enforcer\") is responsible\n * for making sure that the store's state is always correct and consistent. This includes\n * things like: deleting a shape if its parent is deleted; unbinding\n * arrows when their binding target is deleted; maintaining referential integrity; etc.\n *\n * Side effects are organized into lifecycle hooks that run before and after\n * record operations (create, change, delete), allowing you to validate data,\n * transform records, and maintain business rules.\n *\n * @example\n * ```ts\n * const sideEffects = new StoreSideEffects(store)\n *\n * // Ensure arrows are deleted when their target shape is deleted\n * sideEffects.registerAfterDeleteHandler('shape', (shape) => {\n * const arrows = store.query.records('arrow', () => ({\n * toId: { eq: shape.id }\n * })).get()\n * store.remove(arrows.map(arrow => arrow.id))\n * })\n * ```\n *\n * @public\n */\nexport class StoreSideEffects<R extends UnknownRecord> {\n\t/**\n\t * Creates a new side effects manager for the given store.\n\t *\n\t * store - The store instance to manage side effects for\n\t */\n\tconstructor(private readonly store: Store<R>) {}\n\n\tprivate _beforeCreateHandlers: { [K in string]?: StoreBeforeCreateHandler<any>[] } = {}\n\tprivate _afterCreateHandlers: { [K in string]?: StoreAfterCreateHandler<any>[] } = {}\n\tprivate _beforeChangeHandlers: { [K in string]?: StoreBeforeChangeHandler<any>[] } = {}\n\tprivate _afterChangeHandlers: { [K in string]?: StoreAfterChangeHandler<any>[] } = {}\n\tprivate _beforeDeleteHandlers: { [K in string]?: StoreBeforeDeleteHandler<any>[] } = {}\n\tprivate _afterDeleteHandlers: { [K in string]?: StoreAfterDeleteHandler<any>[] } = {}\n\tprivate _operationCompleteHandlers: StoreOperationCompleteHandler[] = []\n\n\tprivate _isEnabled = true\n\t/**\n\t * Checks whether side effects are currently enabled.\n\t * When disabled, all side effect handlers are bypassed.\n\t *\n\t * @returns `true` if side effects are enabled, `false` otherwise\n\t * @internal\n\t */\n\tisEnabled() {\n\t\treturn this._isEnabled\n\t}\n\t/**\n\t * Enables or disables side effects processing.\n\t * When disabled, no side effect handlers will be called.\n\t *\n\t * @param enabled - Whether to enable or disable side effects\n\t * @internal\n\t */\n\tsetIsEnabled(enabled: boolean) {\n\t\tthis._isEnabled = enabled\n\t}\n\n\t/**\n\t * Processes all registered 'before create' handlers for a record.\n\t * Handlers are called in registration order and can transform the record.\n\t *\n\t * @param record - The record about to be created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually create\n\t * @internal\n\t */\n\thandleBeforeCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return record\n\n\t\tconst handlers = this._beforeCreateHandlers[record.typeName] as StoreBeforeCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = record\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn record\n\t}\n\n\t/**\n\t * Processes all registered 'after create' handlers for a record.\n\t * Handlers are called in registration order after the record is created.\n\t *\n\t * @param record - The record that was created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterCreateHandlers[record.typeName] as StoreAfterCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before change' handlers for a record.\n\t * Handlers are called in registration order and can modify or block the change.\n\t *\n\t * @param prev - The current version of the record\n\t * @param next - The proposed new version of the record\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually store\n\t * @internal\n\t */\n\thandleBeforeChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return next\n\n\t\tconst handlers = this._beforeChangeHandlers[next.typeName] as StoreBeforeChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = next\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(prev, r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn next\n\t}\n\n\t/**\n\t * Processes all registered 'after change' handlers for a record.\n\t * Handlers are called in registration order after the record is updated.\n\t *\n\t * @param prev - The previous version of the record\n\t * @param next - The new version of the record that was stored\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterChangeHandlers[next.typeName] as StoreAfterChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(prev, next, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before delete' handlers for a record.\n\t * If any handler returns `false`, the deletion is prevented.\n\t *\n\t * @param record - The record about to be deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns `true` to allow deletion, `false` to prevent it\n\t * @internal\n\t */\n\thandleBeforeDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return true\n\n\t\tconst handlers = this._beforeDeleteHandlers[record.typeName] as StoreBeforeDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tif (handler(record, source) === false) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t/**\n\t * Processes all registered 'after delete' handlers for a record.\n\t * Handlers are called in registration order after the record is deleted.\n\t *\n\t * @param record - The record that was deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterDeleteHandlers[record.typeName] as StoreAfterDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered operation complete handlers.\n\t * Called after an atomic store operation finishes.\n\t *\n\t * @param source - Whether the operation originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleOperationComplete(source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tfor (const handler of this._operationCompleteHandlers) {\n\t\t\thandler(source)\n\t\t}\n\t}\n\n\t/**\n\t * Internal helper for registering multiple side effect handlers at once and keeping them organized.\n\t * This provides a convenient way to register handlers for multiple record types and lifecycle events\n\t * in a single call, returning a single cleanup function.\n\t *\n\t * @param handlersByType - An object mapping record type names to their respective handlers\n\t * @returns A function that removes all registered handlers when called\n\t *\n\t * @example\n\t * ```ts\n\t * const cleanup = sideEffects.register({\n\t * shape: {\n\t * afterDelete: (shape) => console.log('Shape deleted:', shape.id),\n\t * beforeChange: (prev, next) => ({ ...next, lastModified: Date.now() })\n\t * },\n\t * arrow: {\n\t * afterCreate: (arrow) => updateConnectedShapes(arrow)\n\t * }\n\t * })\n\t *\n\t * // Later, remove all handlers\n\t * cleanup()\n\t * ```\n\t *\n\t * @internal\n\t */\n\tregister(handlersByType: {\n\t\t[T in R as T['typeName']]?: {\n\t\t\tbeforeCreate?: StoreBeforeCreateHandler<T>\n\t\t\tafterCreate?: StoreAfterCreateHandler<T>\n\t\t\tbeforeChange?: StoreBeforeChangeHandler<T>\n\t\t\tafterChange?: StoreAfterChangeHandler<T>\n\t\t\tbeforeDelete?: StoreBeforeDeleteHandler<T>\n\t\t\tafterDelete?: StoreAfterDeleteHandler<T>\n\t\t}\n\t}) {\n\t\tconst disposes: (() => void)[] = []\n\t\tfor (const [type, handlers] of Object.entries(handlersByType) as any) {\n\t\t\tif (handlers?.beforeCreate) {\n\t\t\t\tdisposes.push(this.registerBeforeCreateHandler(type, handlers.beforeCreate))\n\t\t\t}\n\t\t\tif (handlers?.afterCreate) {\n\t\t\t\tdisposes.push(this.registerAfterCreateHandler(type, handlers.afterCreate))\n\t\t\t}\n\t\t\tif (handlers?.beforeChange) {\n\t\t\t\tdisposes.push(this.registerBeforeChangeHandler(type, handlers.beforeChange))\n\t\t\t}\n\t\t\tif (handlers?.afterChange) {\n\t\t\t\tdisposes.push(this.registerAfterChangeHandler(type, handlers.afterChange))\n\t\t\t}\n\t\t\tif (handlers?.beforeDelete) {\n\t\t\t\tdisposes.push(this.registerBeforeDeleteHandler(type, handlers.beforeDelete))\n\t\t\t}\n\t\t\tif (handlers?.afterDelete) {\n\t\t\t\tdisposes.push(this.registerAfterDeleteHandler(type, handlers.afterDelete))\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const dispose of disposes) dispose()\n\t\t}\n\t}\n\n\t/**\n\t * Register a handler to be called before a record of a certain type is created. Return a\n\t * modified record from the handler to change the record that will be created.\n\t *\n\t * Use this handle only to modify the creation of the record itself. If you want to trigger a\n\t * side-effect on a different record (for example, moving one shape when another is created),\n\t * use {@link StoreSideEffects.registerAfterCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => {\n\t * // only modify shapes created by the user\n\t * if (source !== 'user') return shape\n\t *\n\t * //by default, arrow shapes have no label. Let's make sure they always have a label.\n\t * if (shape.type === 'arrow') {\n\t * return {...shape, props: {...shape.props, text: 'an arrow'}}\n\t * }\n\t *\n\t * // other shapes get returned unmodified\n\t * return shape\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeCreateHandlers[typeName] as StoreBeforeCreateHandler<any>[]\n\t\tif (!handlers) this._beforeCreateHandlers[typeName] = []\n\t\tthis._beforeCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is created. This is useful for side-effects\n\t * that would update _other_ records. If you want to modify the record being created use\n\t * {@link StoreSideEffects.registerBeforeCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {\n\t * // Automatically create a shape when a page is created\n\t * editor.createShape({\n\t * id: createShapeId(),\n\t * type: 'text',\n\t * props: { richText: toRichText(page.name) },\n\t * })\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterCreateHandlers[typeName] as StoreAfterCreateHandler<any>[]\n\t\tif (!handlers) this._afterCreateHandlers[typeName] = []\n\t\tthis._afterCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._afterCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is changed. The handler is given the old and\n\t * new record - you can return a modified record to apply a different update, or the old record\n\t * to block the update entirely.\n\t *\n\t * Use this handler only for intercepting updates to the record itself. If you want to update\n\t * other records in response to a change, use\n\t * {@link StoreSideEffects.registerAfterChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next, source) => {\n\t * if (next.isLocked && !prev.isLocked) {\n\t * // prevent shapes from ever being locked:\n\t * return prev\n\t * }\n\t * // other types of change are allowed\n\t * return next\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeChangeHandlers[typeName] as StoreBeforeChangeHandler<any>[]\n\t\tif (!handlers) this._beforeChangeHandlers[typeName] = []\n\t\tthis._beforeChangeHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is changed. This is useful for side-effects\n\t * that would update _other_ records - if you want to modify the record being changed, use\n\t * {@link StoreSideEffects.registerBeforeChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => {\n\t * if (next.props.color === 'red') {\n\t * // there can only be one red shape at a time:\n\t * const otherRedShapes = editor.getCurrentPageShapes().filter(s => s.props.color === 'red' && s.id !== next.id)\n\t * editor.updateShapes(otherRedShapes.map(s => ({...s, props: {...s.props, color: 'blue'}})))\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterChangeHandlers[typeName] as StoreAfterChangeHandler<any>[]\n\t\tif (!handlers) this._afterChangeHandlers[typeName] = []\n\t\tthis._afterChangeHandlers[typeName]!.push(handler as StoreAfterChangeHandler<any>)\n\t\treturn () => remove(this._afterChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is deleted. The handler can return `false` to\n\t * prevent the deletion.\n\t *\n\t * Use this handler only for intercepting deletions of the record itself. If you want to do\n\t * something to other records in response to a deletion, use\n\t * {@link StoreSideEffects.registerAfterDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => {\n\t * if (shape.props.color === 'red') {\n\t * // prevent red shapes from being deleted\n\t * \t return false\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeDeleteHandlers[typeName] as StoreBeforeDeleteHandler<any>[]\n\t\tif (!handlers) this._beforeDeleteHandlers[typeName] = []\n\t\tthis._beforeDeleteHandlers[typeName]!.push(handler as StoreBeforeDeleteHandler<any>)\n\t\treturn () => remove(this._beforeDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is deleted. This is useful for side-effects\n\t * that would update _other_ records - if you want to block the deletion of the record itself,\n\t * use {@link StoreSideEffects.registerBeforeDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => {\n\t * // if the last shape in a frame is deleted, delete the frame too:\n\t * const parentFrame = editor.getShape(shape.parentId)\n\t * if (!parentFrame || parentFrame.type !== 'frame') return\n\t *\n\t * const siblings = editor.getSortedChildIdsForParent(parentFrame)\n\t * if (siblings.length === 0) {\n\t * editor.deleteShape(parentFrame.id)\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterDeleteHandlers[typeName] as StoreAfterDeleteHandler<any>[]\n\t\tif (!handlers) this._afterDeleteHandlers[typeName] = []\n\t\tthis._afterDeleteHandlers[typeName]!.push(handler as StoreAfterDeleteHandler<any>)\n\t\treturn () => remove(this._afterDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called when a store completes an atomic operation.\n\t *\n\t * @example\n\t * ```ts\n\t * let count = 0\n\t *\n\t * editor.sideEffects.registerOperationCompleteHandler(() => count++)\n\t *\n\t * editor.selectAll()\n\t * expect(count).toBe(1)\n\t *\n\t * editor.store.atomic(() => {\n\t *\teditor.selectNone()\n\t * \teditor.selectAll()\n\t * })\n\t *\n\t * expect(count).toBe(2)\n\t * ```\n\t *\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t *\n\t * @public\n\t */\n\tregisterOperationCompleteHandler(handler: StoreOperationCompleteHandler) {\n\t\tthis._operationCompleteHandlers.push(handler)\n\t\treturn () => remove(this._operationCompleteHandlers, handler)\n\t}\n}\n\nfunction remove(array: any[], item: any) {\n\tconst index = array.indexOf(item)\n\tif (index >= 0) {\n\t\tarray.splice(index, 1)\n\t}\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.d.mts
CHANGED
|
@@ -2588,7 +2588,7 @@ export declare class StoreSideEffects<R extends UnknownRecord> {
|
|
|
2588
2588
|
* ```ts
|
|
2589
2589
|
* editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
|
|
2590
2590
|
* // Automatically create a shape when a page is created
|
|
2591
|
-
* editor.createShape
|
|
2591
|
+
* editor.createShape({
|
|
2592
2592
|
* id: createShapeId(),
|
|
2593
2593
|
* type: 'text',
|
|
2594
2594
|
* props: { richText: toRichText(page.name) },
|
package/dist-esm/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/RecordType.ts"],
|
|
4
|
-
"sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * 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.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAiB,kBAAkB,iBAAiB,gBAAgB;AAkC7D,MAAM,WAGX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2CD,YAOiB,UAChB,QAUC;AAXe;AAYhB,SAAK,0BAA0B,OAAO;AACtC,SAAK,YAAY,OAAO,aAAa,EAAE,UAAU,CAAC,MAAe,EAAO;AACxE,SAAK,QAAQ,OAAO,SAAS;AAC7B,SAAK,gBAAgB,OAAO;AAE5B,UAAM,kBAAkB,oBAAI,IAAY;AACxC,QAAI,OAAO,eAAe;AACzB,iBAAW,CAAC,KAAK,WAAW,KAAK,iBAAiB,OAAO,aAAa,GAAG;AACxE,YAAI,YAAa,iBAAgB,IAAI,
|
|
4
|
+
"sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * 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.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key as string)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -260,7 +260,7 @@ class StoreSideEffects {
|
|
|
260
260
|
* ```ts
|
|
261
261
|
* editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
|
|
262
262
|
* // Automatically create a shape when a page is created
|
|
263
|
-
* editor.createShape
|
|
263
|
+
* editor.createShape({
|
|
264
264
|
* id: createShapeId(),
|
|
265
265
|
* type: 'text',
|
|
266
266
|
* props: { richText: toRichText(page.name) },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/StoreSideEffects.ts"],
|
|
4
|
-
"sourcesContent": ["import { UnknownRecord } from './BaseRecord'\nimport { Store } from './Store'\n\n/**\n * Handler function called before a record is created in the store.\n * The handler receives the record to be created and can return a modified version.\n * Use this to validate, transform, or modify records before they are added to the store.\n *\n * @param record - The record about to be created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record to actually create (may be modified)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeCreateHandler<MyRecord> = (record, source) => {\n * // Ensure all user-created records have a timestamp\n * if (source === 'user' && !record.createdAt) {\n * return { ...record, createdAt: Date.now() }\n * }\n * return record\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully created in the store.\n * Use this for side effects that should happen after record creation, such as updating\n * related records or triggering notifications.\n *\n * @param record - The record that was created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterCreateHandler<BookRecord> = (book, source) => {\n * if (source === 'user') {\n * console.log(`New book added: ${book.title}`)\n * updateAuthorBookCount(book.authorId)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is updated in the store.\n * The handler receives the current and new versions of the record and can return\n * a modified version or the original to prevent the change.\n *\n * @param prev - The current version of the record in the store\n * @param next - The proposed new version of the record\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record version to actually store (may be modified or the original to block change)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Prevent shapes from being moved outside the canvas bounds\n * if (next.x < 0 || next.y < 0) {\n * return prev // Block the change\n * }\n * return next\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully updated in the store.\n * Use this for side effects that should happen after record changes, such as\n * updating related records or maintaining consistency constraints.\n *\n * @param prev - The previous version of the record\n * @param next - The new version of the record that was stored\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Update connected arrows when a shape moves\n * if (prev.x !== next.x || prev.y !== next.y) {\n * updateConnectedArrows(next.id)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is deleted from the store.\n * The handler can return `false` to prevent the deletion from occurring.\n *\n * @param record - The record about to be deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns `false` to prevent deletion, `void` or any other value to allow it\n *\n * @example\n * ```ts\n * const handler: StoreBeforeDeleteHandler<BookRecord> = (book, source) => {\n * // Prevent deletion of books that are currently checked out\n * if (book.isCheckedOut) {\n * console.warn('Cannot delete checked out book')\n * return false\n * }\n * // Allow deletion for other books\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void | false\n/**\n * Handler function called after a record has been successfully deleted from the store.\n * Use this for cleanup operations and maintaining referential integrity.\n *\n * @param record - The record that was deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterDeleteHandler<ShapeRecord> = (shape, source) => {\n * // Clean up arrows that were connected to this shape\n * const connectedArrows = findArrowsConnectedTo(shape.id)\n * store.remove(connectedArrows.map(arrow => arrow.id))\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n\n/**\n * Handler function called when a store operation (atomic transaction) completes.\n * This is useful for performing actions after a batch of changes has been applied,\n * such as triggering saves or sending notifications.\n *\n * @param source - Whether the operation originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreOperationCompleteHandler = (source) => {\n * if (source === 'user') {\n * // Auto-save after user operations complete\n * saveStoreSnapshot()\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void\n\n/**\n * The side effect manager (aka a \"correct state enforcer\") is responsible\n * for making sure that the store's state is always correct and consistent. This includes\n * things like: deleting a shape if its parent is deleted; unbinding\n * arrows when their binding target is deleted; maintaining referential integrity; etc.\n *\n * Side effects are organized into lifecycle hooks that run before and after\n * record operations (create, change, delete), allowing you to validate data,\n * transform records, and maintain business rules.\n *\n * @example\n * ```ts\n * const sideEffects = new StoreSideEffects(store)\n *\n * // Ensure arrows are deleted when their target shape is deleted\n * sideEffects.registerAfterDeleteHandler('shape', (shape) => {\n * const arrows = store.query.records('arrow', () => ({\n * toId: { eq: shape.id }\n * })).get()\n * store.remove(arrows.map(arrow => arrow.id))\n * })\n * ```\n *\n * @public\n */\nexport class StoreSideEffects<R extends UnknownRecord> {\n\t/**\n\t * Creates a new side effects manager for the given store.\n\t *\n\t * store - The store instance to manage side effects for\n\t */\n\tconstructor(private readonly store: Store<R>) {}\n\n\tprivate _beforeCreateHandlers: { [K in string]?: StoreBeforeCreateHandler<any>[] } = {}\n\tprivate _afterCreateHandlers: { [K in string]?: StoreAfterCreateHandler<any>[] } = {}\n\tprivate _beforeChangeHandlers: { [K in string]?: StoreBeforeChangeHandler<any>[] } = {}\n\tprivate _afterChangeHandlers: { [K in string]?: StoreAfterChangeHandler<any>[] } = {}\n\tprivate _beforeDeleteHandlers: { [K in string]?: StoreBeforeDeleteHandler<any>[] } = {}\n\tprivate _afterDeleteHandlers: { [K in string]?: StoreAfterDeleteHandler<any>[] } = {}\n\tprivate _operationCompleteHandlers: StoreOperationCompleteHandler[] = []\n\n\tprivate _isEnabled = true\n\t/**\n\t * Checks whether side effects are currently enabled.\n\t * When disabled, all side effect handlers are bypassed.\n\t *\n\t * @returns `true` if side effects are enabled, `false` otherwise\n\t * @internal\n\t */\n\tisEnabled() {\n\t\treturn this._isEnabled\n\t}\n\t/**\n\t * Enables or disables side effects processing.\n\t * When disabled, no side effect handlers will be called.\n\t *\n\t * @param enabled - Whether to enable or disable side effects\n\t * @internal\n\t */\n\tsetIsEnabled(enabled: boolean) {\n\t\tthis._isEnabled = enabled\n\t}\n\n\t/**\n\t * Processes all registered 'before create' handlers for a record.\n\t * Handlers are called in registration order and can transform the record.\n\t *\n\t * @param record - The record about to be created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually create\n\t * @internal\n\t */\n\thandleBeforeCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return record\n\n\t\tconst handlers = this._beforeCreateHandlers[record.typeName] as StoreBeforeCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = record\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn record\n\t}\n\n\t/**\n\t * Processes all registered 'after create' handlers for a record.\n\t * Handlers are called in registration order after the record is created.\n\t *\n\t * @param record - The record that was created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterCreateHandlers[record.typeName] as StoreAfterCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before change' handlers for a record.\n\t * Handlers are called in registration order and can modify or block the change.\n\t *\n\t * @param prev - The current version of the record\n\t * @param next - The proposed new version of the record\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually store\n\t * @internal\n\t */\n\thandleBeforeChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return next\n\n\t\tconst handlers = this._beforeChangeHandlers[next.typeName] as StoreBeforeChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = next\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(prev, r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn next\n\t}\n\n\t/**\n\t * Processes all registered 'after change' handlers for a record.\n\t * Handlers are called in registration order after the record is updated.\n\t *\n\t * @param prev - The previous version of the record\n\t * @param next - The new version of the record that was stored\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterChangeHandlers[next.typeName] as StoreAfterChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(prev, next, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before delete' handlers for a record.\n\t * If any handler returns `false`, the deletion is prevented.\n\t *\n\t * @param record - The record about to be deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns `true` to allow deletion, `false` to prevent it\n\t * @internal\n\t */\n\thandleBeforeDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return true\n\n\t\tconst handlers = this._beforeDeleteHandlers[record.typeName] as StoreBeforeDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tif (handler(record, source) === false) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t/**\n\t * Processes all registered 'after delete' handlers for a record.\n\t * Handlers are called in registration order after the record is deleted.\n\t *\n\t * @param record - The record that was deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterDeleteHandlers[record.typeName] as StoreAfterDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered operation complete handlers.\n\t * Called after an atomic store operation finishes.\n\t *\n\t * @param source - Whether the operation originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleOperationComplete(source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tfor (const handler of this._operationCompleteHandlers) {\n\t\t\thandler(source)\n\t\t}\n\t}\n\n\t/**\n\t * Internal helper for registering multiple side effect handlers at once and keeping them organized.\n\t * This provides a convenient way to register handlers for multiple record types and lifecycle events\n\t * in a single call, returning a single cleanup function.\n\t *\n\t * @param handlersByType - An object mapping record type names to their respective handlers\n\t * @returns A function that removes all registered handlers when called\n\t *\n\t * @example\n\t * ```ts\n\t * const cleanup = sideEffects.register({\n\t * shape: {\n\t * afterDelete: (shape) => console.log('Shape deleted:', shape.id),\n\t * beforeChange: (prev, next) => ({ ...next, lastModified: Date.now() })\n\t * },\n\t * arrow: {\n\t * afterCreate: (arrow) => updateConnectedShapes(arrow)\n\t * }\n\t * })\n\t *\n\t * // Later, remove all handlers\n\t * cleanup()\n\t * ```\n\t *\n\t * @internal\n\t */\n\tregister(handlersByType: {\n\t\t[T in R as T['typeName']]?: {\n\t\t\tbeforeCreate?: StoreBeforeCreateHandler<T>\n\t\t\tafterCreate?: StoreAfterCreateHandler<T>\n\t\t\tbeforeChange?: StoreBeforeChangeHandler<T>\n\t\t\tafterChange?: StoreAfterChangeHandler<T>\n\t\t\tbeforeDelete?: StoreBeforeDeleteHandler<T>\n\t\t\tafterDelete?: StoreAfterDeleteHandler<T>\n\t\t}\n\t}) {\n\t\tconst disposes: (() => void)[] = []\n\t\tfor (const [type, handlers] of Object.entries(handlersByType) as any) {\n\t\t\tif (handlers?.beforeCreate) {\n\t\t\t\tdisposes.push(this.registerBeforeCreateHandler(type, handlers.beforeCreate))\n\t\t\t}\n\t\t\tif (handlers?.afterCreate) {\n\t\t\t\tdisposes.push(this.registerAfterCreateHandler(type, handlers.afterCreate))\n\t\t\t}\n\t\t\tif (handlers?.beforeChange) {\n\t\t\t\tdisposes.push(this.registerBeforeChangeHandler(type, handlers.beforeChange))\n\t\t\t}\n\t\t\tif (handlers?.afterChange) {\n\t\t\t\tdisposes.push(this.registerAfterChangeHandler(type, handlers.afterChange))\n\t\t\t}\n\t\t\tif (handlers?.beforeDelete) {\n\t\t\t\tdisposes.push(this.registerBeforeDeleteHandler(type, handlers.beforeDelete))\n\t\t\t}\n\t\t\tif (handlers?.afterDelete) {\n\t\t\t\tdisposes.push(this.registerAfterDeleteHandler(type, handlers.afterDelete))\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const dispose of disposes) dispose()\n\t\t}\n\t}\n\n\t/**\n\t * Register a handler to be called before a record of a certain type is created. Return a\n\t * modified record from the handler to change the record that will be created.\n\t *\n\t * Use this handle only to modify the creation of the record itself. If you want to trigger a\n\t * side-effect on a different record (for example, moving one shape when another is created),\n\t * use {@link StoreSideEffects.registerAfterCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => {\n\t * // only modify shapes created by the user\n\t * if (source !== 'user') return shape\n\t *\n\t * //by default, arrow shapes have no label. Let's make sure they always have a label.\n\t * if (shape.type === 'arrow') {\n\t * return {...shape, props: {...shape.props, text: 'an arrow'}}\n\t * }\n\t *\n\t * // other shapes get returned unmodified\n\t * return shape\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeCreateHandlers[typeName] as StoreBeforeCreateHandler<any>[]\n\t\tif (!handlers) this._beforeCreateHandlers[typeName] = []\n\t\tthis._beforeCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is created. This is useful for side-effects\n\t * that would update _other_ records. If you want to modify the record being created use\n\t * {@link StoreSideEffects.registerBeforeCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {\n\t * // Automatically create a shape when a page is created\n\t * editor.createShape<TLTextShape>({\n\t * id: createShapeId(),\n\t * type: 'text',\n\t * props: { richText: toRichText(page.name) },\n\t * })\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterCreateHandlers[typeName] as StoreAfterCreateHandler<any>[]\n\t\tif (!handlers) this._afterCreateHandlers[typeName] = []\n\t\tthis._afterCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._afterCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is changed. The handler is given the old and\n\t * new record - you can return a modified record to apply a different update, or the old record\n\t * to block the update entirely.\n\t *\n\t * Use this handler only for intercepting updates to the record itself. If you want to update\n\t * other records in response to a change, use\n\t * {@link StoreSideEffects.registerAfterChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next, source) => {\n\t * if (next.isLocked && !prev.isLocked) {\n\t * // prevent shapes from ever being locked:\n\t * return prev\n\t * }\n\t * // other types of change are allowed\n\t * return next\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeChangeHandlers[typeName] as StoreBeforeChangeHandler<any>[]\n\t\tif (!handlers) this._beforeChangeHandlers[typeName] = []\n\t\tthis._beforeChangeHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is changed. This is useful for side-effects\n\t * that would update _other_ records - if you want to modify the record being changed, use\n\t * {@link StoreSideEffects.registerBeforeChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => {\n\t * if (next.props.color === 'red') {\n\t * // there can only be one red shape at a time:\n\t * const otherRedShapes = editor.getCurrentPageShapes().filter(s => s.props.color === 'red' && s.id !== next.id)\n\t * editor.updateShapes(otherRedShapes.map(s => ({...s, props: {...s.props, color: 'blue'}})))\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterChangeHandlers[typeName] as StoreAfterChangeHandler<any>[]\n\t\tif (!handlers) this._afterChangeHandlers[typeName] = []\n\t\tthis._afterChangeHandlers[typeName]!.push(handler as StoreAfterChangeHandler<any>)\n\t\treturn () => remove(this._afterChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is deleted. The handler can return `false` to\n\t * prevent the deletion.\n\t *\n\t * Use this handler only for intercepting deletions of the record itself. If you want to do\n\t * something to other records in response to a deletion, use\n\t * {@link StoreSideEffects.registerAfterDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => {\n\t * if (shape.props.color === 'red') {\n\t * // prevent red shapes from being deleted\n\t * \t return false\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeDeleteHandlers[typeName] as StoreBeforeDeleteHandler<any>[]\n\t\tif (!handlers) this._beforeDeleteHandlers[typeName] = []\n\t\tthis._beforeDeleteHandlers[typeName]!.push(handler as StoreBeforeDeleteHandler<any>)\n\t\treturn () => remove(this._beforeDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is deleted. This is useful for side-effects\n\t * that would update _other_ records - if you want to block the deletion of the record itself,\n\t * use {@link StoreSideEffects.registerBeforeDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => {\n\t * // if the last shape in a frame is deleted, delete the frame too:\n\t * const parentFrame = editor.getShape(shape.parentId)\n\t * if (!parentFrame || parentFrame.type !== 'frame') return\n\t *\n\t * const siblings = editor.getSortedChildIdsForParent(parentFrame)\n\t * if (siblings.length === 0) {\n\t * editor.deleteShape(parentFrame.id)\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterDeleteHandlers[typeName] as StoreAfterDeleteHandler<any>[]\n\t\tif (!handlers) this._afterDeleteHandlers[typeName] = []\n\t\tthis._afterDeleteHandlers[typeName]!.push(handler as StoreAfterDeleteHandler<any>)\n\t\treturn () => remove(this._afterDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called when a store completes an atomic operation.\n\t *\n\t * @example\n\t * ```ts\n\t * let count = 0\n\t *\n\t * editor.sideEffects.registerOperationCompleteHandler(() => count++)\n\t *\n\t * editor.selectAll()\n\t * expect(count).toBe(1)\n\t *\n\t * editor.store.atomic(() => {\n\t *\teditor.selectNone()\n\t * \teditor.selectAll()\n\t * })\n\t *\n\t * expect(count).toBe(2)\n\t * ```\n\t *\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t *\n\t * @public\n\t */\n\tregisterOperationCompleteHandler(handler: StoreOperationCompleteHandler) {\n\t\tthis._operationCompleteHandlers.push(handler)\n\t\treturn () => remove(this._operationCompleteHandlers, handler)\n\t}\n}\n\nfunction remove(array: any[], item: any) {\n\tconst index = array.indexOf(item)\n\tif (index >= 0) {\n\t\tarray.splice(index, 1)\n\t}\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { UnknownRecord } from './BaseRecord'\nimport { Store } from './Store'\n\n/**\n * Handler function called before a record is created in the store.\n * The handler receives the record to be created and can return a modified version.\n * Use this to validate, transform, or modify records before they are added to the store.\n *\n * @param record - The record about to be created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record to actually create (may be modified)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeCreateHandler<MyRecord> = (record, source) => {\n * // Ensure all user-created records have a timestamp\n * if (source === 'user' && !record.createdAt) {\n * return { ...record, createdAt: Date.now() }\n * }\n * return record\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully created in the store.\n * Use this for side effects that should happen after record creation, such as updating\n * related records or triggering notifications.\n *\n * @param record - The record that was created\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterCreateHandler<BookRecord> = (book, source) => {\n * if (source === 'user') {\n * console.log(`New book added: ${book.title}`)\n * updateAuthorBookCount(book.authorId)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterCreateHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is updated in the store.\n * The handler receives the current and new versions of the record and can return\n * a modified version or the original to prevent the change.\n *\n * @param prev - The current version of the record in the store\n * @param next - The proposed new version of the record\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns The record version to actually store (may be modified or the original to block change)\n *\n * @example\n * ```ts\n * const handler: StoreBeforeChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Prevent shapes from being moved outside the canvas bounds\n * if (next.x < 0 || next.y < 0) {\n * return prev // Block the change\n * }\n * return next\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => R\n/**\n * Handler function called after a record has been successfully updated in the store.\n * Use this for side effects that should happen after record changes, such as\n * updating related records or maintaining consistency constraints.\n *\n * @param prev - The previous version of the record\n * @param next - The new version of the record that was stored\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterChangeHandler<ShapeRecord> = (prev, next, source) => {\n * // Update connected arrows when a shape moves\n * if (prev.x !== next.x || prev.y !== next.y) {\n * updateConnectedArrows(next.id)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterChangeHandler<R extends UnknownRecord> = (\n\tprev: R,\n\tnext: R,\n\tsource: 'remote' | 'user'\n) => void\n/**\n * Handler function called before a record is deleted from the store.\n * The handler can return `false` to prevent the deletion from occurring.\n *\n * @param record - The record about to be deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n * @returns `false` to prevent deletion, `void` or any other value to allow it\n *\n * @example\n * ```ts\n * const handler: StoreBeforeDeleteHandler<BookRecord> = (book, source) => {\n * // Prevent deletion of books that are currently checked out\n * if (book.isCheckedOut) {\n * console.warn('Cannot delete checked out book')\n * return false\n * }\n * // Allow deletion for other books\n * }\n * ```\n *\n * @public\n */\nexport type StoreBeforeDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void | false\n/**\n * Handler function called after a record has been successfully deleted from the store.\n * Use this for cleanup operations and maintaining referential integrity.\n *\n * @param record - The record that was deleted\n * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreAfterDeleteHandler<ShapeRecord> = (shape, source) => {\n * // Clean up arrows that were connected to this shape\n * const connectedArrows = findArrowsConnectedTo(shape.id)\n * store.remove(connectedArrows.map(arrow => arrow.id))\n * }\n * ```\n *\n * @public\n */\nexport type StoreAfterDeleteHandler<R extends UnknownRecord> = (\n\trecord: R,\n\tsource: 'remote' | 'user'\n) => void\n\n/**\n * Handler function called when a store operation (atomic transaction) completes.\n * This is useful for performing actions after a batch of changes has been applied,\n * such as triggering saves or sending notifications.\n *\n * @param source - Whether the operation originated from 'user' interaction or 'remote' synchronization\n *\n * @example\n * ```ts\n * const handler: StoreOperationCompleteHandler = (source) => {\n * if (source === 'user') {\n * // Auto-save after user operations complete\n * saveStoreSnapshot()\n * }\n * }\n * ```\n *\n * @public\n */\nexport type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void\n\n/**\n * The side effect manager (aka a \"correct state enforcer\") is responsible\n * for making sure that the store's state is always correct and consistent. This includes\n * things like: deleting a shape if its parent is deleted; unbinding\n * arrows when their binding target is deleted; maintaining referential integrity; etc.\n *\n * Side effects are organized into lifecycle hooks that run before and after\n * record operations (create, change, delete), allowing you to validate data,\n * transform records, and maintain business rules.\n *\n * @example\n * ```ts\n * const sideEffects = new StoreSideEffects(store)\n *\n * // Ensure arrows are deleted when their target shape is deleted\n * sideEffects.registerAfterDeleteHandler('shape', (shape) => {\n * const arrows = store.query.records('arrow', () => ({\n * toId: { eq: shape.id }\n * })).get()\n * store.remove(arrows.map(arrow => arrow.id))\n * })\n * ```\n *\n * @public\n */\nexport class StoreSideEffects<R extends UnknownRecord> {\n\t/**\n\t * Creates a new side effects manager for the given store.\n\t *\n\t * store - The store instance to manage side effects for\n\t */\n\tconstructor(private readonly store: Store<R>) {}\n\n\tprivate _beforeCreateHandlers: { [K in string]?: StoreBeforeCreateHandler<any>[] } = {}\n\tprivate _afterCreateHandlers: { [K in string]?: StoreAfterCreateHandler<any>[] } = {}\n\tprivate _beforeChangeHandlers: { [K in string]?: StoreBeforeChangeHandler<any>[] } = {}\n\tprivate _afterChangeHandlers: { [K in string]?: StoreAfterChangeHandler<any>[] } = {}\n\tprivate _beforeDeleteHandlers: { [K in string]?: StoreBeforeDeleteHandler<any>[] } = {}\n\tprivate _afterDeleteHandlers: { [K in string]?: StoreAfterDeleteHandler<any>[] } = {}\n\tprivate _operationCompleteHandlers: StoreOperationCompleteHandler[] = []\n\n\tprivate _isEnabled = true\n\t/**\n\t * Checks whether side effects are currently enabled.\n\t * When disabled, all side effect handlers are bypassed.\n\t *\n\t * @returns `true` if side effects are enabled, `false` otherwise\n\t * @internal\n\t */\n\tisEnabled() {\n\t\treturn this._isEnabled\n\t}\n\t/**\n\t * Enables or disables side effects processing.\n\t * When disabled, no side effect handlers will be called.\n\t *\n\t * @param enabled - Whether to enable or disable side effects\n\t * @internal\n\t */\n\tsetIsEnabled(enabled: boolean) {\n\t\tthis._isEnabled = enabled\n\t}\n\n\t/**\n\t * Processes all registered 'before create' handlers for a record.\n\t * Handlers are called in registration order and can transform the record.\n\t *\n\t * @param record - The record about to be created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually create\n\t * @internal\n\t */\n\thandleBeforeCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return record\n\n\t\tconst handlers = this._beforeCreateHandlers[record.typeName] as StoreBeforeCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = record\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn record\n\t}\n\n\t/**\n\t * Processes all registered 'after create' handlers for a record.\n\t * Handlers are called in registration order after the record is created.\n\t *\n\t * @param record - The record that was created\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterCreate(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterCreateHandlers[record.typeName] as StoreAfterCreateHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before change' handlers for a record.\n\t * Handlers are called in registration order and can modify or block the change.\n\t *\n\t * @param prev - The current version of the record\n\t * @param next - The proposed new version of the record\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns The potentially modified record to actually store\n\t * @internal\n\t */\n\thandleBeforeChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return next\n\n\t\tconst handlers = this._beforeChangeHandlers[next.typeName] as StoreBeforeChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tlet r = next\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tr = handler(prev, r, source)\n\t\t\t}\n\t\t\treturn r\n\t\t}\n\n\t\treturn next\n\t}\n\n\t/**\n\t * Processes all registered 'after change' handlers for a record.\n\t * Handlers are called in registration order after the record is updated.\n\t *\n\t * @param prev - The previous version of the record\n\t * @param next - The new version of the record that was stored\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterChange(prev: R, next: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterChangeHandlers[next.typeName] as StoreAfterChangeHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(prev, next, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered 'before delete' handlers for a record.\n\t * If any handler returns `false`, the deletion is prevented.\n\t *\n\t * @param record - The record about to be deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @returns `true` to allow deletion, `false` to prevent it\n\t * @internal\n\t */\n\thandleBeforeDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return true\n\n\t\tconst handlers = this._beforeDeleteHandlers[record.typeName] as StoreBeforeDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tif (handler(record, source) === false) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t/**\n\t * Processes all registered 'after delete' handlers for a record.\n\t * Handlers are called in registration order after the record is deleted.\n\t *\n\t * @param record - The record that was deleted\n\t * @param source - Whether the change originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleAfterDelete(record: R, source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tconst handlers = this._afterDeleteHandlers[record.typeName] as StoreAfterDeleteHandler<R>[]\n\t\tif (handlers) {\n\t\t\tfor (const handler of handlers) {\n\t\t\t\thandler(record, source)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes all registered operation complete handlers.\n\t * Called after an atomic store operation finishes.\n\t *\n\t * @param source - Whether the operation originated from 'user' or 'remote'\n\t * @internal\n\t */\n\thandleOperationComplete(source: 'remote' | 'user') {\n\t\tif (!this._isEnabled) return\n\n\t\tfor (const handler of this._operationCompleteHandlers) {\n\t\t\thandler(source)\n\t\t}\n\t}\n\n\t/**\n\t * Internal helper for registering multiple side effect handlers at once and keeping them organized.\n\t * This provides a convenient way to register handlers for multiple record types and lifecycle events\n\t * in a single call, returning a single cleanup function.\n\t *\n\t * @param handlersByType - An object mapping record type names to their respective handlers\n\t * @returns A function that removes all registered handlers when called\n\t *\n\t * @example\n\t * ```ts\n\t * const cleanup = sideEffects.register({\n\t * shape: {\n\t * afterDelete: (shape) => console.log('Shape deleted:', shape.id),\n\t * beforeChange: (prev, next) => ({ ...next, lastModified: Date.now() })\n\t * },\n\t * arrow: {\n\t * afterCreate: (arrow) => updateConnectedShapes(arrow)\n\t * }\n\t * })\n\t *\n\t * // Later, remove all handlers\n\t * cleanup()\n\t * ```\n\t *\n\t * @internal\n\t */\n\tregister(handlersByType: {\n\t\t[T in R as T['typeName']]?: {\n\t\t\tbeforeCreate?: StoreBeforeCreateHandler<T>\n\t\t\tafterCreate?: StoreAfterCreateHandler<T>\n\t\t\tbeforeChange?: StoreBeforeChangeHandler<T>\n\t\t\tafterChange?: StoreAfterChangeHandler<T>\n\t\t\tbeforeDelete?: StoreBeforeDeleteHandler<T>\n\t\t\tafterDelete?: StoreAfterDeleteHandler<T>\n\t\t}\n\t}) {\n\t\tconst disposes: (() => void)[] = []\n\t\tfor (const [type, handlers] of Object.entries(handlersByType) as any) {\n\t\t\tif (handlers?.beforeCreate) {\n\t\t\t\tdisposes.push(this.registerBeforeCreateHandler(type, handlers.beforeCreate))\n\t\t\t}\n\t\t\tif (handlers?.afterCreate) {\n\t\t\t\tdisposes.push(this.registerAfterCreateHandler(type, handlers.afterCreate))\n\t\t\t}\n\t\t\tif (handlers?.beforeChange) {\n\t\t\t\tdisposes.push(this.registerBeforeChangeHandler(type, handlers.beforeChange))\n\t\t\t}\n\t\t\tif (handlers?.afterChange) {\n\t\t\t\tdisposes.push(this.registerAfterChangeHandler(type, handlers.afterChange))\n\t\t\t}\n\t\t\tif (handlers?.beforeDelete) {\n\t\t\t\tdisposes.push(this.registerBeforeDeleteHandler(type, handlers.beforeDelete))\n\t\t\t}\n\t\t\tif (handlers?.afterDelete) {\n\t\t\t\tdisposes.push(this.registerAfterDeleteHandler(type, handlers.afterDelete))\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const dispose of disposes) dispose()\n\t\t}\n\t}\n\n\t/**\n\t * Register a handler to be called before a record of a certain type is created. Return a\n\t * modified record from the handler to change the record that will be created.\n\t *\n\t * Use this handle only to modify the creation of the record itself. If you want to trigger a\n\t * side-effect on a different record (for example, moving one shape when another is created),\n\t * use {@link StoreSideEffects.registerAfterCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => {\n\t * // only modify shapes created by the user\n\t * if (source !== 'user') return shape\n\t *\n\t * //by default, arrow shapes have no label. Let's make sure they always have a label.\n\t * if (shape.type === 'arrow') {\n\t * return {...shape, props: {...shape.props, text: 'an arrow'}}\n\t * }\n\t *\n\t * // other shapes get returned unmodified\n\t * return shape\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeCreateHandlers[typeName] as StoreBeforeCreateHandler<any>[]\n\t\tif (!handlers) this._beforeCreateHandlers[typeName] = []\n\t\tthis._beforeCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is created. This is useful for side-effects\n\t * that would update _other_ records. If you want to modify the record being created use\n\t * {@link StoreSideEffects.registerBeforeCreateHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {\n\t * // Automatically create a shape when a page is created\n\t * editor.createShape({\n\t * id: createShapeId(),\n\t * type: 'text',\n\t * props: { richText: toRichText(page.name) },\n\t * })\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterCreateHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterCreateHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterCreateHandlers[typeName] as StoreAfterCreateHandler<any>[]\n\t\tif (!handlers) this._afterCreateHandlers[typeName] = []\n\t\tthis._afterCreateHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._afterCreateHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is changed. The handler is given the old and\n\t * new record - you can return a modified record to apply a different update, or the old record\n\t * to block the update entirely.\n\t *\n\t * Use this handler only for intercepting updates to the record itself. If you want to update\n\t * other records in response to a change, use\n\t * {@link StoreSideEffects.registerAfterChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next, source) => {\n\t * if (next.isLocked && !prev.isLocked) {\n\t * // prevent shapes from ever being locked:\n\t * return prev\n\t * }\n\t * // other types of change are allowed\n\t * return next\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeChangeHandlers[typeName] as StoreBeforeChangeHandler<any>[]\n\t\tif (!handlers) this._beforeChangeHandlers[typeName] = []\n\t\tthis._beforeChangeHandlers[typeName]!.push(handler)\n\t\treturn () => remove(this._beforeChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is changed. This is useful for side-effects\n\t * that would update _other_ records - if you want to modify the record being changed, use\n\t * {@link StoreSideEffects.registerBeforeChangeHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => {\n\t * if (next.props.color === 'red') {\n\t * // there can only be one red shape at a time:\n\t * const otherRedShapes = editor.getCurrentPageShapes().filter(s => s.props.color === 'red' && s.id !== next.id)\n\t * editor.updateShapes(otherRedShapes.map(s => ({...s, props: {...s.props, color: 'blue'}})))\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterChangeHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterChangeHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterChangeHandlers[typeName] as StoreAfterChangeHandler<any>[]\n\t\tif (!handlers) this._afterChangeHandlers[typeName] = []\n\t\tthis._afterChangeHandlers[typeName]!.push(handler as StoreAfterChangeHandler<any>)\n\t\treturn () => remove(this._afterChangeHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called before a record is deleted. The handler can return `false` to\n\t * prevent the deletion.\n\t *\n\t * Use this handler only for intercepting deletions of the record itself. If you want to do\n\t * something to other records in response to a deletion, use\n\t * {@link StoreSideEffects.registerAfterDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => {\n\t * if (shape.props.color === 'red') {\n\t * // prevent red shapes from being deleted\n\t * \t return false\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterBeforeDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreBeforeDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._beforeDeleteHandlers[typeName] as StoreBeforeDeleteHandler<any>[]\n\t\tif (!handlers) this._beforeDeleteHandlers[typeName] = []\n\t\tthis._beforeDeleteHandlers[typeName]!.push(handler as StoreBeforeDeleteHandler<any>)\n\t\treturn () => remove(this._beforeDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called after a record is deleted. This is useful for side-effects\n\t * that would update _other_ records - if you want to block the deletion of the record itself,\n\t * use {@link StoreSideEffects.registerBeforeDeleteHandler} instead.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => {\n\t * // if the last shape in a frame is deleted, delete the frame too:\n\t * const parentFrame = editor.getShape(shape.parentId)\n\t * if (!parentFrame || parentFrame.type !== 'frame') return\n\t *\n\t * const siblings = editor.getSortedChildIdsForParent(parentFrame)\n\t * if (siblings.length === 0) {\n\t * editor.deleteShape(parentFrame.id)\n\t * }\n\t * })\n\t * ```\n\t *\n\t * @param typeName - The type of record to listen for\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t */\n\tregisterAfterDeleteHandler<T extends R['typeName']>(\n\t\ttypeName: T,\n\t\thandler: StoreAfterDeleteHandler<R & { typeName: T }>\n\t) {\n\t\tconst handlers = this._afterDeleteHandlers[typeName] as StoreAfterDeleteHandler<any>[]\n\t\tif (!handlers) this._afterDeleteHandlers[typeName] = []\n\t\tthis._afterDeleteHandlers[typeName]!.push(handler as StoreAfterDeleteHandler<any>)\n\t\treturn () => remove(this._afterDeleteHandlers[typeName]!, handler)\n\t}\n\n\t/**\n\t * Register a handler to be called when a store completes an atomic operation.\n\t *\n\t * @example\n\t * ```ts\n\t * let count = 0\n\t *\n\t * editor.sideEffects.registerOperationCompleteHandler(() => count++)\n\t *\n\t * editor.selectAll()\n\t * expect(count).toBe(1)\n\t *\n\t * editor.store.atomic(() => {\n\t *\teditor.selectNone()\n\t * \teditor.selectAll()\n\t * })\n\t *\n\t * expect(count).toBe(2)\n\t * ```\n\t *\n\t * @param handler - The handler to call\n\t *\n\t * @returns A callback that removes the handler.\n\t *\n\t * @public\n\t */\n\tregisterOperationCompleteHandler(handler: StoreOperationCompleteHandler) {\n\t\tthis._operationCompleteHandlers.push(handler)\n\t\treturn () => remove(this._operationCompleteHandlers, handler)\n\t}\n}\n\nfunction remove(array: any[], item: any) {\n\tconst index = array.indexOf(item)\n\tif (index >= 0) {\n\t\tarray.splice(index, 1)\n\t}\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/store",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (store).",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.0-canary.03ae87dcc44b",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"context": "yarn run -T tsx ../../internal/scripts/context.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@tldraw/state": "4.
|
|
48
|
-
"@tldraw/utils": "4.
|
|
47
|
+
"@tldraw/state": "4.3.0-canary.03ae87dcc44b",
|
|
48
|
+
"@tldraw/utils": "4.3.0-canary.03ae87dcc44b"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^18.2.0 || ^19.0.0"
|
package/src/lib/RecordType.ts
CHANGED
|
@@ -105,7 +105,7 @@ export class RecordType<
|
|
|
105
105
|
const ephemeralKeySet = new Set<string>()
|
|
106
106
|
if (config.ephemeralKeys) {
|
|
107
107
|
for (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {
|
|
108
|
-
if (isEphemeral) ephemeralKeySet.add(key)
|
|
108
|
+
if (isEphemeral) ephemeralKeySet.add(key as string)
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
this.ephemeralKeySet = ephemeralKeySet
|
|
@@ -493,7 +493,7 @@ export class StoreSideEffects<R extends UnknownRecord> {
|
|
|
493
493
|
* ```ts
|
|
494
494
|
* editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
|
|
495
495
|
* // Automatically create a shape when a page is created
|
|
496
|
-
* editor.createShape
|
|
496
|
+
* editor.createShape({
|
|
497
497
|
* id: createShapeId(),
|
|
498
498
|
* type: 'text',
|
|
499
499
|
* props: { richText: toRichText(page.name) },
|