@tldraw/store 5.5.0-canary.fa8dc460ac73 → 5.5.0-canary.fdb2d37fcb2a
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 +8 -5
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/StoreQueries.js +10 -7
- package/dist-cjs/lib/StoreQueries.js.map +2 -2
- package/dist-cjs/lib/StoreSideEffects.js +22 -30
- package/dist-cjs/lib/StoreSideEffects.js.map +2 -2
- package/dist-esm/index.d.mts +8 -5
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/StoreQueries.mjs +10 -7
- package/dist-esm/lib/StoreQueries.mjs.map +2 -2
- package/dist-esm/lib/StoreSideEffects.mjs +22 -30
- package/dist-esm/lib/StoreSideEffects.mjs.map +2 -2
- package/package.json +4 -4
- package/src/lib/StoreQueries.test.ts +75 -0
- package/src/lib/StoreQueries.ts +15 -7
- package/src/lib/StoreSideEffects.test.ts +66 -0
- package/src/lib/StoreSideEffects.ts +24 -30
package/dist-cjs/index.d.ts
CHANGED
|
@@ -2126,17 +2126,19 @@ export declare class StoreQueries<R extends UnknownRecord> {
|
|
|
2126
2126
|
* have been added, updated, or removed.
|
|
2127
2127
|
*
|
|
2128
2128
|
* @param typeName - The type name to filter the history by
|
|
2129
|
-
* @returns A computed value containing
|
|
2129
|
+
* @returns A computed value containing a change counter and diffs of changes for the specified type
|
|
2130
2130
|
*
|
|
2131
2131
|
* @example
|
|
2132
2132
|
* ```ts
|
|
2133
2133
|
* // Track changes to book records only
|
|
2134
2134
|
* const bookHistory = store.query.filterHistory('book')
|
|
2135
2135
|
*
|
|
2136
|
-
* //
|
|
2137
|
-
* react('book-changes', () => {
|
|
2138
|
-
* const
|
|
2139
|
-
*
|
|
2136
|
+
* // Log the books added since the last run
|
|
2137
|
+
* react('book-changes', (lastReactedEpoch) => {
|
|
2138
|
+
* const diffs = bookHistory.getDiffSince(lastReactedEpoch)
|
|
2139
|
+
* // no diffs to give, e.g. on the first run: rebuild anything derived from scratch
|
|
2140
|
+
* if (diffs === RESET_VALUE) return
|
|
2141
|
+
* for (const diff of diffs) console.log('Added:', Object.keys(diff.added))
|
|
2140
2142
|
* })
|
|
2141
2143
|
* ```
|
|
2142
2144
|
*
|
|
@@ -2599,6 +2601,7 @@ export declare class StoreSideEffects<R extends UnknownRecord> {
|
|
|
2599
2601
|
private _afterDeleteHandlers;
|
|
2600
2602
|
private _operationCompleteHandlers;
|
|
2601
2603
|
private _isEnabled;
|
|
2604
|
+
private addHandler;
|
|
2602
2605
|
/* Excluded from this release type: isEnabled */
|
|
2603
2606
|
/* Excluded from this release type: setIsEnabled */
|
|
2604
2607
|
/* Excluded from this release type: handleBeforeCreate */
|
package/dist-cjs/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var import_StoreSchema = require("./lib/StoreSchema");
|
|
|
56
56
|
var import_StoreSideEffects = require("./lib/StoreSideEffects");
|
|
57
57
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
58
58
|
"@tldraw/store",
|
|
59
|
-
"5.5.0-canary.
|
|
59
|
+
"5.5.0-canary.fdb2d37fcb2a",
|
|
60
60
|
"cjs"
|
|
61
61
|
);
|
|
62
62
|
//# sourceMappingURL=index.js.map
|
|
@@ -94,17 +94,19 @@ class StoreQueries {
|
|
|
94
94
|
* have been added, updated, or removed.
|
|
95
95
|
*
|
|
96
96
|
* @param typeName - The type name to filter the history by
|
|
97
|
-
* @returns A computed value containing
|
|
97
|
+
* @returns A computed value containing a change counter and diffs of changes for the specified type
|
|
98
98
|
*
|
|
99
99
|
* @example
|
|
100
100
|
* ```ts
|
|
101
101
|
* // Track changes to book records only
|
|
102
102
|
* const bookHistory = store.query.filterHistory('book')
|
|
103
103
|
*
|
|
104
|
-
* //
|
|
105
|
-
* react('book-changes', () => {
|
|
106
|
-
* const
|
|
107
|
-
*
|
|
104
|
+
* // Log the books added since the last run
|
|
105
|
+
* react('book-changes', (lastReactedEpoch) => {
|
|
106
|
+
* const diffs = bookHistory.getDiffSince(lastReactedEpoch)
|
|
107
|
+
* // no diffs to give, e.g. on the first run: rebuild anything derived from scratch
|
|
108
|
+
* if (diffs === RESET_VALUE) return
|
|
109
|
+
* for (const diff of diffs) console.log('Added:', Object.keys(diff.added))
|
|
108
110
|
* })
|
|
109
111
|
* ```
|
|
110
112
|
*
|
|
@@ -120,12 +122,13 @@ class StoreQueries {
|
|
|
120
122
|
if ((0, import_state.isUninitialized)(lastValue)) {
|
|
121
123
|
return this.history.get();
|
|
122
124
|
}
|
|
125
|
+
const next = Math.max(this.history.get(), lastValue + 1);
|
|
123
126
|
const diff = this.history.getDiffSince(lastComputedEpoch);
|
|
124
|
-
if (diff === import_state.RESET_VALUE) return
|
|
127
|
+
if (diff === import_state.RESET_VALUE) return next;
|
|
125
128
|
const res = { added: {}, removed: {}, updated: {} };
|
|
126
129
|
const size = (0, import_RecordsDiff.squashRecordDiffsMutableByType)(res, diff, typeName);
|
|
127
130
|
if (size) {
|
|
128
|
-
return (0, import_state.withDiff)(
|
|
131
|
+
return (0, import_state.withDiff)(next, res);
|
|
129
132
|
} else {
|
|
130
133
|
return lastValue;
|
|
131
134
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/StoreQueries.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n\tAtom,\n\tcomputed,\n\tComputed,\n\tEMPTY_ARRAY,\n\tisUninitialized,\n\tRESET_VALUE,\n\twithDiff,\n} from '@tldraw/state'\nimport { areArraysShallowEqual, isEqual, objectMapValues } from '@tldraw/utils'\nimport { AtomMap } from './AtomMap'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { executeQuery, objectMatchesQuery, QueryExpression } from './executeQuery'\nimport { IncrementalSetConstructor } from './IncrementalSetConstructor'\nimport { hasAnyKey, RecordsDiff, squashRecordDiffsMutableByType } from './RecordsDiff'\nimport { diffSets } from './setUtils'\nimport { CollectionDiff } from './Store'\n\n/**\n * A type representing the diff of changes to a reactive store index.\n * Maps property values to the collection differences for record IDs that have that property value.\n *\n * @example\n * ```ts\n * // For an index on book titles, the diff might look like:\n * const titleIndexDiff: RSIndexDiff<Book, 'title'> = new Map([\n * ['The Lathe of Heaven', { added: new Set(['book:1']), removed: new Set() }],\n * ['Animal Farm', { added: new Set(), removed: new Set(['book:2']) }]\n * ])\n * ```\n *\n * @public\n */\nexport type RSIndexDiff<R extends UnknownRecord> = Map<any, CollectionDiff<IdOf<R>>>\n\n/**\n * A type representing a reactive store index as a map from property values to sets of record IDs.\n * This is used to efficiently look up records by a specific property value.\n *\n * @example\n * ```ts\n * // Index mapping book titles to the IDs of books with that title\n * const titleIndex: RSIndexMap<Book, 'title'> = new Map([\n * ['The Lathe of Heaven', new Set(['book:1'])],\n * ['Animal Farm', new Set(['book:2', 'book:3'])]\n * ])\n * ```\n *\n * @public\n */\nexport type RSIndexMap<R extends UnknownRecord> = Map<any, Set<IdOf<R>>>\n\n/**\n * A reactive computed index that provides efficient lookups of records by property values.\n * Returns a computed value containing an RSIndexMap with diffs for change tracking.\n *\n * @example\n * ```ts\n * // Create an index on book authors\n * const authorIndex: RSIndex<Book, 'authorId'> = store.query.index('book', 'authorId')\n *\n * // Get all books by a specific author\n * const leguinBooks = authorIndex.get().get('author:leguin')\n * ```\n *\n * @public\n */\nexport type RSIndex<R extends UnknownRecord> = Computed<RSIndexMap<R>, RSIndexDiff<R>>\n\n/**\n * A class that provides reactive querying capabilities for a record store.\n * Offers methods to create indexes, filter records, and perform efficient lookups with automatic cache management.\n * All queries are reactive and will automatically update when the underlying store data changes.\n *\n * @example\n * ```ts\n * // Create a store with books\n * const store = new Store({ schema: StoreSchema.create({ book: Book, author: Author }) })\n *\n * // Get reactive queries for books\n * const booksByAuthor = store.query.index('book', 'authorId')\n * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))\n * ```\n *\n * @public\n */\nexport class StoreQueries<R extends UnknownRecord> {\n\t/**\n\t * Creates a new StoreQueries instance.\n\t *\n\t * recordMap - The atom map containing all records in the store\n\t * history - The atom tracking the store's change history with diffs\n\t *\n\t * @internal\n\t */\n\tconstructor(\n\t\tprivate readonly recordMap: AtomMap<IdOf<R>, R>,\n\t\tprivate readonly history: Atom<number, RecordsDiff<R>>\n\t) {}\n\n\t/**\n\t * A cache of derivations (indexes).\n\t *\n\t * @internal\n\t */\n\tprivate indexCache = new Map<string, RSIndex<R>>()\n\n\t/**\n\t * A cache of derivations (filtered histories).\n\t *\n\t * @internal\n\t */\n\tprivate historyCache = new Map<string, Computed<number, RecordsDiff<R>>>()\n\n\t/**\n\t * @internal\n\t */\n\tpublic getAllIdsForType<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName\n\t): Set<IdOf<Extract<R, { typeName: TypeName }>>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\t\tconst ids = new Set<IdOf<S>>()\n\t\tfor (const record of this.recordMap.values()) {\n\t\t\tif (record.typeName === typeName) {\n\t\t\t\tids.add(record.id as IdOf<S>)\n\t\t\t}\n\t\t}\n\t\treturn ids\n\t}\n\n\t/**\n\t * @internal\n\t */\n\tpublic getRecordById<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tid: IdOf<Extract<R, { typeName: TypeName }>>\n\t): Extract<R, { typeName: TypeName }> | undefined {\n\t\tconst record = this.recordMap.get(id as IdOf<R>)\n\t\tif (record && record.typeName === typeName) {\n\t\t\treturn record as Extract<R, { typeName: TypeName }>\n\t\t}\n\t\treturn undefined\n\t}\n\n\t/**\n\t * Helper to extract nested property value using pre-split path parts.\n\t * @internal\n\t */\n\tprivate getNestedValue(obj: any, pathParts: string[]): any {\n\t\tlet current = obj\n\t\tfor (const part of pathParts) {\n\t\t\tif (current == null || typeof current !== 'object') return undefined\n\t\t\tcurrent = current[part]\n\t\t}\n\t\treturn current\n\t}\n\n\t/**\n\t * Creates a reactive computed that tracks the change history for records of a specific type.\n\t * The returned computed provides incremental diffs showing what records of the given type\n\t * have been added, updated, or removed.\n\t *\n\t * @param typeName - The type name to filter the history by\n\t * @returns A computed value containing the current epoch and diffs of changes for the specified type\n\t *\n\t * @example\n\t * ```ts\n\t * // Track changes to book records only\n\t * const bookHistory = store.query.filterHistory('book')\n\t *\n\t * // React to book changes\n\t * react('book-changes', () => {\n\t * const currentEpoch = bookHistory.get()\n\t * console.log('Books updated at epoch:', currentEpoch)\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic filterHistory<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName\n\t): Computed<number, RecordsDiff<Extract<R, { typeName: TypeName }>>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\n\t\tif (this.historyCache.has(typeName)) {\n\t\t\treturn this.historyCache.get(typeName) as any\n\t\t}\n\n\t\tconst filtered = computed<number, RecordsDiff<S>>(\n\t\t\t'filterHistory:' + typeName,\n\t\t\t(lastValue, lastComputedEpoch) => {\n\t\t\t\tif (isUninitialized(lastValue)) {\n\t\t\t\t\treturn this.history.get()\n\t\t\t\t}\n\n\t\t\t\tconst diff = this.history.getDiffSince(lastComputedEpoch)\n\t\t\t\tif (diff === RESET_VALUE) return this.history.get()\n\n\t\t\t\tconst res = { added: {}, removed: {}, updated: {} } as RecordsDiff<S>\n\t\t\t\tconst size = squashRecordDiffsMutableByType(res, diff, typeName)\n\n\t\t\t\tif (size) {\n\t\t\t\t\treturn withDiff(this.history.get(), res)\n\t\t\t\t} else {\n\t\t\t\t\treturn lastValue\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ historyLength: 100 }\n\t\t)\n\n\t\tthis.historyCache.set(typeName, filtered)\n\n\t\treturn filtered\n\t}\n\n\t/**\n\t * Creates a reactive index that maps property values to sets of record IDs for efficient lookups.\n\t * The index automatically updates when records are added, updated, or removed, and results are cached\n\t * for performance.\n\t *\n\t * Supports nested property paths using backslash separator (e.g., 'metadata\\\\sessionId').\n\t *\n\t * @param typeName - The type name of records to index\n\t * @param path - The property name or backslash-delimited path to index by\n\t * @returns A reactive computed containing the index map with change diffs\n\t *\n\t * @example\n\t * ```ts\n\t * // Create an index of books by author ID\n\t * const booksByAuthor = store.query.index('book', 'authorId')\n\t *\n\t * // Get all books by a specific author\n\t * const authorBooks = booksByAuthor.get().get('author:leguin')\n\t * console.log(authorBooks) // Set<RecordId<Book>>\n\t *\n\t * // Index by nested property using backslash separator\n\t * const booksBySession = store.query.index('book', 'metadata\\\\sessionId')\n\t * const sessionBooks = booksBySession.get().get('session:alpha')\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic index<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tpath: string\n\t): RSIndex<Extract<R, { typeName: TypeName }>> {\n\t\tconst cacheKey = typeName + ':' + path\n\n\t\tif (this.indexCache.has(cacheKey)) {\n\t\t\treturn this.indexCache.get(cacheKey) as any\n\t\t}\n\n\t\tconst index = this.__uncached_createIndex(typeName, path)\n\n\t\tthis.indexCache.set(cacheKey, index as any)\n\n\t\treturn index\n\t}\n\n\t/**\n\t * Creates a new index without checking the cache. This method performs the actual work\n\t * of building the reactive index computation that tracks property values to record ID sets.\n\t *\n\t * Supports nested property paths using backslash separator.\n\t *\n\t * @param typeName - The type name of records to index\n\t * @param path - The property name or backslash-delimited path to index by\n\t * @returns A reactive computed containing the index map with change diffs\n\t *\n\t * @internal\n\t */\n\t__uncached_createIndex<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tpath: string\n\t): RSIndex<Extract<R, { typeName: TypeName }>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\n\t\tconst typeHistory = this.filterHistory(typeName)\n\n\t\t// Create closure for efficient property value extraction\n\t\tconst pathParts = path.split('\\\\')\n\t\tconst getPropertyValue =\n\t\t\tpathParts.length > 1\n\t\t\t\t? (obj: S) => this.getNestedValue(obj, pathParts)\n\t\t\t\t: (obj: S) => obj[path as keyof S]\n\n\t\tconst fromScratch = () => {\n\t\t\t// deref typeHistory early so that the first time the incremental version runs\n\t\t\t// it gets a diff to work with instead of having to bail to this from-scratch version\n\t\t\ttypeHistory.get()\n\t\t\tconst res = new Map<any, Set<IdOf<S>>>()\n\t\t\tfor (const record of this.recordMap.values()) {\n\t\t\t\tif (record.typeName === typeName) {\n\t\t\t\t\tconst value = getPropertyValue(record as S)\n\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\tif (!res.has(value)) {\n\t\t\t\t\t\t\tres.set(value, new Set())\n\t\t\t\t\t\t}\n\t\t\t\t\t\tres.get(value)!.add(record.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn res\n\t\t}\n\n\t\treturn computed<RSIndexMap<S>, RSIndexDiff<S>>(\n\t\t\t'index:' + typeName + ':' + path,\n\t\t\t(prevValue, lastComputedEpoch) => {\n\t\t\t\tif (isUninitialized(prevValue)) return fromScratch()\n\n\t\t\t\tconst history = typeHistory.getDiffSince(lastComputedEpoch)\n\t\t\t\tif (history === RESET_VALUE) {\n\t\t\t\t\treturn fromScratch()\n\t\t\t\t}\n\n\t\t\t\tconst setConstructors = new Map<any, IncrementalSetConstructor<IdOf<S>>>()\n\n\t\t\t\tconst add = (value: any, id: IdOf<S>) => {\n\t\t\t\t\tlet setConstructor = setConstructors.get(value)\n\t\t\t\t\tif (!setConstructor)\n\t\t\t\t\t\tsetConstructor = new IncrementalSetConstructor<IdOf<S>>(\n\t\t\t\t\t\t\tprevValue.get(value) ?? new Set()\n\t\t\t\t\t\t)\n\t\t\t\t\tsetConstructor.add(id)\n\t\t\t\t\tsetConstructors.set(value, setConstructor)\n\t\t\t\t}\n\n\t\t\t\tconst remove = (value: any, id: IdOf<S>) => {\n\t\t\t\t\tlet set = setConstructors.get(value)\n\t\t\t\t\tif (!set) set = new IncrementalSetConstructor<IdOf<S>>(prevValue.get(value) ?? new Set())\n\t\t\t\t\tset.remove(id)\n\t\t\t\t\tsetConstructors.set(value, set)\n\t\t\t\t}\n\n\t\t\t\tfor (const changes of history) {\n\t\t\t\t\tfor (const record of objectMapValues(changes.added)) {\n\t\t\t\t\t\tif (record.typeName === typeName) {\n\t\t\t\t\t\t\tconst value = getPropertyValue(record as S)\n\t\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\t\tadd(value, record.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const [from, to] of objectMapValues(changes.updated)) {\n\t\t\t\t\t\tif (to.typeName === typeName) {\n\t\t\t\t\t\t\tconst prev = getPropertyValue(from as S)\n\t\t\t\t\t\t\tconst next = getPropertyValue(to as S)\n\t\t\t\t\t\t\tif (prev !== next) {\n\t\t\t\t\t\t\t\tif (prev !== undefined) {\n\t\t\t\t\t\t\t\t\tremove(prev, to.id)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (next !== undefined) {\n\t\t\t\t\t\t\t\t\tadd(next, to.id)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const record of objectMapValues(changes.removed)) {\n\t\t\t\t\t\tif (record.typeName === typeName) {\n\t\t\t\t\t\t\tconst value = getPropertyValue(record as S)\n\t\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\t\tremove(value, record.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet nextValue: undefined | RSIndexMap<S> = undefined\n\t\t\t\tlet nextDiff: undefined | RSIndexDiff<S> = undefined\n\n\t\t\t\tfor (const [value, setConstructor] of setConstructors) {\n\t\t\t\t\tconst result = setConstructor.get()\n\t\t\t\t\tif (!result) continue\n\t\t\t\t\tif (!nextValue) nextValue = new Map(prevValue)\n\t\t\t\t\tif (!nextDiff) nextDiff = new Map()\n\t\t\t\t\tif (result.value.size === 0) {\n\t\t\t\t\t\tnextValue.delete(value)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnextValue.set(value, result.value)\n\t\t\t\t\t}\n\t\t\t\t\tnextDiff.set(value, result.diff)\n\t\t\t\t}\n\n\t\t\t\tif (nextValue && nextDiff) {\n\t\t\t\t\treturn withDiff(nextValue, nextDiff)\n\t\t\t\t}\n\n\t\t\t\treturn prevValue\n\t\t\t},\n\t\t\t{ historyLength: 100 }\n\t\t)\n\t}\n\n\t/**\n\t * Creates a reactive query that returns the first record matching the given query criteria.\n\t * Returns undefined if no matching record is found. The query automatically updates\n\t * when records change.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param queryCreator - Function that returns the query expression object to match against\n\t * @param name - Optional name for the query computation (used for debugging)\n\t * @returns A computed value containing the first matching record or undefined\n\t *\n\t * @example\n\t * ```ts\n\t * // Find the first book with a specific title\n\t * const bookLatheOfHeaven = store.query.record('book', () => ({ title: { eq: 'The Lathe of Heaven' } }))\n\t * console.log(bookLatheOfHeaven.get()?.title) // 'The Lathe of Heaven' or undefined\n\t *\n\t * // Find any book in stock\n\t * const anyInStockBook = store.query.record('book', () => ({ inStock: { eq: true } }))\n\t * ```\n\t *\n\t * @public\n\t */\n\trecord<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tqueryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),\n\t\tname = 'record:' + typeName + ':' + queryCreator.toString()\n\t): Computed<Extract<R, { typeName: TypeName }> | undefined> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\t\tconst ids = this.ids(typeName, queryCreator, name)\n\n\t\treturn computed<S | undefined>(name, () => {\n\t\t\tfor (const id of ids.get()) {\n\t\t\t\treturn this.recordMap.get(id) as S | undefined\n\t\t\t}\n\t\t\treturn undefined\n\t\t})\n\t}\n\n\t/**\n\t * Creates a reactive query that returns an array of all records matching the given query criteria.\n\t * The array automatically updates when records are added, updated, or removed.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param queryCreator - Function that returns the query expression object to match against\n\t * @param name - Optional name for the query computation (used for debugging)\n\t * @returns A computed value containing an array of all matching records\n\t *\n\t * @example\n\t * ```ts\n\t * // Get all books in stock\n\t * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))\n\t * console.log(inStockBooks.get()) // Book[]\n\t *\n\t * // Get all books by a specific author\n\t * const leguinBooks = store.query.records('book', () => ({ authorId: { eq: 'author:leguin' } }))\n\t *\n\t * // Get all books (no filter)\n\t * const allBooks = store.query.records('book')\n\t * ```\n\t *\n\t * @public\n\t */\n\trecords<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tqueryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),\n\t\tname = 'records:' + typeName + ':' + queryCreator.toString()\n\t): Computed<Array<Extract<R, { typeName: TypeName }>>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\t\tconst ids = this.ids(typeName, queryCreator, 'ids:' + name)\n\n\t\treturn computed<S[]>(\n\t\t\tname,\n\t\t\t() => {\n\t\t\t\treturn Array.from(ids.get(), (id) => this.recordMap.get(id) as S)\n\t\t\t},\n\t\t\t{\n\t\t\t\tisEqual: areArraysShallowEqual,\n\t\t\t}\n\t\t)\n\t}\n\n\t/**\n\t * Creates a reactive query that returns a set of record IDs matching the given query criteria.\n\t * This is more efficient than `records()` when you only need the IDs and not the full record objects.\n\t * The set automatically updates with collection diffs when records change.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param queryCreator - Function that returns the query expression object to match against\n\t * @param name - Optional name for the query computation (used for debugging)\n\t * @returns A computed value containing a set of matching record IDs with collection diffs\n\t *\n\t * @example\n\t * ```ts\n\t * // Get IDs of all books in stock\n\t * const inStockBookIds = store.query.ids('book', () => ({ inStock: { eq: true } }))\n\t * console.log(inStockBookIds.get()) // Set<RecordId<Book>>\n\t *\n\t * // Get all book IDs (no filter)\n\t * const allBookIds = store.query.ids('book')\n\t *\n\t * // Use with other queries for efficient lookups\n\t * const authorBookIds = store.query.ids('book', () => ({ authorId: { eq: 'author:leguin' } }))\n\t * ```\n\t *\n\t * @public\n\t */\n\tids<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tqueryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),\n\t\tname = 'ids:' + typeName + ':' + queryCreator.toString()\n\t): Computed<\n\t\tSet<IdOf<Extract<R, { typeName: TypeName }>>>,\n\t\tCollectionDiff<IdOf<Extract<R, { typeName: TypeName }>>>\n\t> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\n\t\tconst typeHistory = this.filterHistory(typeName)\n\n\t\tconst fromScratch = () => {\n\t\t\t// deref type history early to allow first incremental update to use diffs\n\t\t\ttypeHistory.get()\n\t\t\tconst query: QueryExpression<S> = queryCreator()\n\t\t\tif (!hasAnyKey(query)) {\n\t\t\t\treturn this.getAllIdsForType(typeName)\n\t\t\t}\n\n\t\t\treturn executeQuery(this, typeName, query)\n\t\t}\n\n\t\tconst fromScratchWithDiff = (prevValue: Set<IdOf<S>>) => {\n\t\t\tconst nextValue = fromScratch()\n\t\t\tconst diff = diffSets(prevValue, nextValue)\n\t\t\tif (diff) {\n\t\t\t\treturn withDiff(nextValue, diff)\n\t\t\t} else {\n\t\t\t\treturn prevValue\n\t\t\t}\n\t\t}\n\t\t// Wrapped rather than passed straight through: a computed's derive function receives\n\t\t// `(previousValue, lastComputedEpoch)`, which a queryCreator with optional parameters\n\t\t// would otherwise misread.\n\t\tconst cachedQuery = computed('ids_query:' + name, () => queryCreator(), {\n\t\t\tisEqual,\n\t\t})\n\n\t\treturn computed(\n\t\t\t'query:' + name,\n\t\t\t(prevValue, lastComputedEpoch) => {\n\t\t\t\tconst query = cachedQuery.get()\n\t\t\t\tif (isUninitialized(prevValue)) {\n\t\t\t\t\treturn fromScratch()\n\t\t\t\t}\n\n\t\t\t\t// if the query changed since last time this ran then we need to start again\n\t\t\t\tif (lastComputedEpoch < cachedQuery.lastChangedEpoch) {\n\t\t\t\t\treturn fromScratchWithDiff(prevValue)\n\t\t\t\t}\n\n\t\t\t\t// otherwise iterate over the changes from the store and apply them to the previous value if needed\n\t\t\t\tconst history = typeHistory.getDiffSince(lastComputedEpoch)\n\t\t\t\tif (history === RESET_VALUE) {\n\t\t\t\t\treturn fromScratchWithDiff(prevValue)\n\t\t\t\t}\n\n\t\t\t\tconst setConstructor = new IncrementalSetConstructor<IdOf<S>>(\n\t\t\t\t\tprevValue\n\t\t\t\t) as IncrementalSetConstructor<IdOf<S>>\n\n\t\t\t\tfor (const changes of history) {\n\t\t\t\t\tfor (const added of objectMapValues(changes.added)) {\n\t\t\t\t\t\tif (added.typeName === typeName && objectMatchesQuery(query, added)) {\n\t\t\t\t\t\t\tsetConstructor.add(added.id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const [_, updated] of objectMapValues(changes.updated)) {\n\t\t\t\t\t\tif (updated.typeName === typeName) {\n\t\t\t\t\t\t\tif (objectMatchesQuery(query, updated)) {\n\t\t\t\t\t\t\t\tsetConstructor.add(updated.id)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetConstructor.remove(updated.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const removed of objectMapValues(changes.removed)) {\n\t\t\t\t\t\tif (removed.typeName === typeName) {\n\t\t\t\t\t\t\tsetConstructor.remove(removed.id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst result = setConstructor.get()\n\t\t\t\tif (!result) {\n\t\t\t\t\treturn prevValue\n\t\t\t\t}\n\n\t\t\t\treturn withDiff(result.value, result.diff)\n\t\t\t},\n\t\t\t{ historyLength: 50 }\n\t\t)\n\t}\n\n\t/**\n\t * Executes a one-time query against the current store state and returns matching records.\n\t * This is a non-reactive query that returns results immediately without creating a computed value.\n\t * Use this when you need a snapshot of data at a specific point in time.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param query - The query expression object to match against\n\t * @returns An array of records that match the query at the current moment\n\t *\n\t * @example\n\t * ```ts\n\t * // Get current in-stock books (non-reactive)\n\t * const currentInStockBooks = store.query.exec('book', { inStock: { eq: true } })\n\t * console.log(currentInStockBooks) // Book[]\n\t *\n\t * // Unlike records(), this won't update when the data changes\n\t * const staticBookList = store.query.exec('book', { authorId: { eq: 'author:leguin' } })\n\t * ```\n\t *\n\t * @public\n\t */\n\texec<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tquery: QueryExpression<Extract<R, { typeName: TypeName }>>\n\t): Array<Extract<R, { typeName: TypeName }>> {\n\t\tconst ids = executeQuery(this, typeName, query)\n\t\tif (ids.size === 0) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\t\treturn Array.from(ids, (id) => this.recordMap.get(id) as Extract<R, { typeName: TypeName }>)\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAQO;AACP,mBAAgE;AAGhE,0BAAkE;AAClE,uCAA0C;AAC1C,yBAAuE;AACvE,sBAAyB;AAuElB,MAAM,aAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASlD,YACkB,WACA,SAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQV,aAAa,oBAAI,IAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzC,eAAe,oBAAI,IAA8C;AAAA;AAAA;AAAA;AAAA,EAKlE,iBACN,UACgD;AAEhD,UAAM,MAAM,oBAAI,IAAa;AAC7B,eAAW,UAAU,KAAK,UAAU,OAAO,GAAG;AAC7C,UAAI,OAAO,aAAa,UAAU;AACjC,YAAI,IAAI,OAAO,EAAa;AAAA,MAC7B;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKO,cACN,UACA,IACiD;AACjD,UAAM,SAAS,KAAK,UAAU,IAAI,EAAa;AAC/C,QAAI,UAAU,OAAO,aAAa,UAAU;AAC3C,aAAO;AAAA,IACR;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,KAAU,WAA0B;AAC1D,QAAI,UAAU;AACd,eAAW,QAAQ,WAAW;AAC7B,UAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,gBAAU,QAAQ,IAAI;AAAA,IACvB;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n\tAtom,\n\tcomputed,\n\tComputed,\n\tEMPTY_ARRAY,\n\tisUninitialized,\n\tRESET_VALUE,\n\twithDiff,\n} from '@tldraw/state'\nimport { areArraysShallowEqual, isEqual, objectMapValues } from '@tldraw/utils'\nimport { AtomMap } from './AtomMap'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { executeQuery, objectMatchesQuery, QueryExpression } from './executeQuery'\nimport { IncrementalSetConstructor } from './IncrementalSetConstructor'\nimport { hasAnyKey, RecordsDiff, squashRecordDiffsMutableByType } from './RecordsDiff'\nimport { diffSets } from './setUtils'\nimport { CollectionDiff } from './Store'\n\n/**\n * A type representing the diff of changes to a reactive store index.\n * Maps property values to the collection differences for record IDs that have that property value.\n *\n * @example\n * ```ts\n * // For an index on book titles, the diff might look like:\n * const titleIndexDiff: RSIndexDiff<Book, 'title'> = new Map([\n * ['The Lathe of Heaven', { added: new Set(['book:1']), removed: new Set() }],\n * ['Animal Farm', { added: new Set(), removed: new Set(['book:2']) }]\n * ])\n * ```\n *\n * @public\n */\nexport type RSIndexDiff<R extends UnknownRecord> = Map<any, CollectionDiff<IdOf<R>>>\n\n/**\n * A type representing a reactive store index as a map from property values to sets of record IDs.\n * This is used to efficiently look up records by a specific property value.\n *\n * @example\n * ```ts\n * // Index mapping book titles to the IDs of books with that title\n * const titleIndex: RSIndexMap<Book, 'title'> = new Map([\n * ['The Lathe of Heaven', new Set(['book:1'])],\n * ['Animal Farm', new Set(['book:2', 'book:3'])]\n * ])\n * ```\n *\n * @public\n */\nexport type RSIndexMap<R extends UnknownRecord> = Map<any, Set<IdOf<R>>>\n\n/**\n * A reactive computed index that provides efficient lookups of records by property values.\n * Returns a computed value containing an RSIndexMap with diffs for change tracking.\n *\n * @example\n * ```ts\n * // Create an index on book authors\n * const authorIndex: RSIndex<Book, 'authorId'> = store.query.index('book', 'authorId')\n *\n * // Get all books by a specific author\n * const leguinBooks = authorIndex.get().get('author:leguin')\n * ```\n *\n * @public\n */\nexport type RSIndex<R extends UnknownRecord> = Computed<RSIndexMap<R>, RSIndexDiff<R>>\n\n/**\n * A class that provides reactive querying capabilities for a record store.\n * Offers methods to create indexes, filter records, and perform efficient lookups with automatic cache management.\n * All queries are reactive and will automatically update when the underlying store data changes.\n *\n * @example\n * ```ts\n * // Create a store with books\n * const store = new Store({ schema: StoreSchema.create({ book: Book, author: Author }) })\n *\n * // Get reactive queries for books\n * const booksByAuthor = store.query.index('book', 'authorId')\n * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))\n * ```\n *\n * @public\n */\nexport class StoreQueries<R extends UnknownRecord> {\n\t/**\n\t * Creates a new StoreQueries instance.\n\t *\n\t * recordMap - The atom map containing all records in the store\n\t * history - The atom tracking the store's change history with diffs\n\t *\n\t * @internal\n\t */\n\tconstructor(\n\t\tprivate readonly recordMap: AtomMap<IdOf<R>, R>,\n\t\tprivate readonly history: Atom<number, RecordsDiff<R>>\n\t) {}\n\n\t/**\n\t * A cache of derivations (indexes).\n\t *\n\t * @internal\n\t */\n\tprivate indexCache = new Map<string, RSIndex<R>>()\n\n\t/**\n\t * A cache of derivations (filtered histories).\n\t *\n\t * @internal\n\t */\n\tprivate historyCache = new Map<string, Computed<number, RecordsDiff<R>>>()\n\n\t/**\n\t * @internal\n\t */\n\tpublic getAllIdsForType<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName\n\t): Set<IdOf<Extract<R, { typeName: TypeName }>>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\t\tconst ids = new Set<IdOf<S>>()\n\t\tfor (const record of this.recordMap.values()) {\n\t\t\tif (record.typeName === typeName) {\n\t\t\t\tids.add(record.id as IdOf<S>)\n\t\t\t}\n\t\t}\n\t\treturn ids\n\t}\n\n\t/**\n\t * @internal\n\t */\n\tpublic getRecordById<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tid: IdOf<Extract<R, { typeName: TypeName }>>\n\t): Extract<R, { typeName: TypeName }> | undefined {\n\t\tconst record = this.recordMap.get(id as IdOf<R>)\n\t\tif (record && record.typeName === typeName) {\n\t\t\treturn record as Extract<R, { typeName: TypeName }>\n\t\t}\n\t\treturn undefined\n\t}\n\n\t/**\n\t * Helper to extract nested property value using pre-split path parts.\n\t * @internal\n\t */\n\tprivate getNestedValue(obj: any, pathParts: string[]): any {\n\t\tlet current = obj\n\t\tfor (const part of pathParts) {\n\t\t\tif (current == null || typeof current !== 'object') return undefined\n\t\t\tcurrent = current[part]\n\t\t}\n\t\treturn current\n\t}\n\n\t/**\n\t * Creates a reactive computed that tracks the change history for records of a specific type.\n\t * The returned computed provides incremental diffs showing what records of the given type\n\t * have been added, updated, or removed.\n\t *\n\t * @param typeName - The type name to filter the history by\n\t * @returns A computed value containing a change counter and diffs of changes for the specified type\n\t *\n\t * @example\n\t * ```ts\n\t * // Track changes to book records only\n\t * const bookHistory = store.query.filterHistory('book')\n\t *\n\t * // Log the books added since the last run\n\t * react('book-changes', (lastReactedEpoch) => {\n\t * const diffs = bookHistory.getDiffSince(lastReactedEpoch)\n\t * // no diffs to give, e.g. on the first run: rebuild anything derived from scratch\n\t * if (diffs === RESET_VALUE) return\n\t * for (const diff of diffs) console.log('Added:', Object.keys(diff.added))\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic filterHistory<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName\n\t): Computed<number, RecordsDiff<Extract<R, { typeName: TypeName }>>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\n\t\tif (this.historyCache.has(typeName)) {\n\t\t\treturn this.historyCache.get(typeName) as any\n\t\t}\n\n\t\tconst filtered = computed<number, RecordsDiff<S>>(\n\t\t\t'filterHistory:' + typeName,\n\t\t\t(lastValue, lastComputedEpoch) => {\n\t\t\t\tif (isUninitialized(lastValue)) {\n\t\t\t\t\treturn this.history.get()\n\t\t\t\t}\n\n\t\t\t\t// The value tracks the store's history counter, but it must strictly increase on every\n\t\t\t\t// reset or relevant change: after a rolled-back transaction the counter can come back\n\t\t\t\t// round to `lastValue`, and an unchanged value would hide the reset from downstream\n\t\t\t\t// indexes and queries, leaving them stale for good.\n\t\t\t\tconst next = Math.max(this.history.get(), lastValue + 1)\n\n\t\t\t\tconst diff = this.history.getDiffSince(lastComputedEpoch)\n\t\t\t\tif (diff === RESET_VALUE) return next\n\n\t\t\t\tconst res = { added: {}, removed: {}, updated: {} } as RecordsDiff<S>\n\t\t\t\tconst size = squashRecordDiffsMutableByType(res, diff, typeName)\n\n\t\t\t\tif (size) {\n\t\t\t\t\treturn withDiff(next, res)\n\t\t\t\t} else {\n\t\t\t\t\treturn lastValue\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ historyLength: 100 }\n\t\t)\n\n\t\tthis.historyCache.set(typeName, filtered)\n\n\t\treturn filtered\n\t}\n\n\t/**\n\t * Creates a reactive index that maps property values to sets of record IDs for efficient lookups.\n\t * The index automatically updates when records are added, updated, or removed, and results are cached\n\t * for performance.\n\t *\n\t * Supports nested property paths using backslash separator (e.g., 'metadata\\\\sessionId').\n\t *\n\t * @param typeName - The type name of records to index\n\t * @param path - The property name or backslash-delimited path to index by\n\t * @returns A reactive computed containing the index map with change diffs\n\t *\n\t * @example\n\t * ```ts\n\t * // Create an index of books by author ID\n\t * const booksByAuthor = store.query.index('book', 'authorId')\n\t *\n\t * // Get all books by a specific author\n\t * const authorBooks = booksByAuthor.get().get('author:leguin')\n\t * console.log(authorBooks) // Set<RecordId<Book>>\n\t *\n\t * // Index by nested property using backslash separator\n\t * const booksBySession = store.query.index('book', 'metadata\\\\sessionId')\n\t * const sessionBooks = booksBySession.get().get('session:alpha')\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic index<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tpath: string\n\t): RSIndex<Extract<R, { typeName: TypeName }>> {\n\t\tconst cacheKey = typeName + ':' + path\n\n\t\tif (this.indexCache.has(cacheKey)) {\n\t\t\treturn this.indexCache.get(cacheKey) as any\n\t\t}\n\n\t\tconst index = this.__uncached_createIndex(typeName, path)\n\n\t\tthis.indexCache.set(cacheKey, index as any)\n\n\t\treturn index\n\t}\n\n\t/**\n\t * Creates a new index without checking the cache. This method performs the actual work\n\t * of building the reactive index computation that tracks property values to record ID sets.\n\t *\n\t * Supports nested property paths using backslash separator.\n\t *\n\t * @param typeName - The type name of records to index\n\t * @param path - The property name or backslash-delimited path to index by\n\t * @returns A reactive computed containing the index map with change diffs\n\t *\n\t * @internal\n\t */\n\t__uncached_createIndex<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tpath: string\n\t): RSIndex<Extract<R, { typeName: TypeName }>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\n\t\tconst typeHistory = this.filterHistory(typeName)\n\n\t\t// Create closure for efficient property value extraction\n\t\tconst pathParts = path.split('\\\\')\n\t\tconst getPropertyValue =\n\t\t\tpathParts.length > 1\n\t\t\t\t? (obj: S) => this.getNestedValue(obj, pathParts)\n\t\t\t\t: (obj: S) => obj[path as keyof S]\n\n\t\tconst fromScratch = () => {\n\t\t\t// deref typeHistory early so that the first time the incremental version runs\n\t\t\t// it gets a diff to work with instead of having to bail to this from-scratch version\n\t\t\ttypeHistory.get()\n\t\t\tconst res = new Map<any, Set<IdOf<S>>>()\n\t\t\tfor (const record of this.recordMap.values()) {\n\t\t\t\tif (record.typeName === typeName) {\n\t\t\t\t\tconst value = getPropertyValue(record as S)\n\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\tif (!res.has(value)) {\n\t\t\t\t\t\t\tres.set(value, new Set())\n\t\t\t\t\t\t}\n\t\t\t\t\t\tres.get(value)!.add(record.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn res\n\t\t}\n\n\t\treturn computed<RSIndexMap<S>, RSIndexDiff<S>>(\n\t\t\t'index:' + typeName + ':' + path,\n\t\t\t(prevValue, lastComputedEpoch) => {\n\t\t\t\tif (isUninitialized(prevValue)) return fromScratch()\n\n\t\t\t\tconst history = typeHistory.getDiffSince(lastComputedEpoch)\n\t\t\t\tif (history === RESET_VALUE) {\n\t\t\t\t\treturn fromScratch()\n\t\t\t\t}\n\n\t\t\t\tconst setConstructors = new Map<any, IncrementalSetConstructor<IdOf<S>>>()\n\n\t\t\t\tconst add = (value: any, id: IdOf<S>) => {\n\t\t\t\t\tlet setConstructor = setConstructors.get(value)\n\t\t\t\t\tif (!setConstructor)\n\t\t\t\t\t\tsetConstructor = new IncrementalSetConstructor<IdOf<S>>(\n\t\t\t\t\t\t\tprevValue.get(value) ?? new Set()\n\t\t\t\t\t\t)\n\t\t\t\t\tsetConstructor.add(id)\n\t\t\t\t\tsetConstructors.set(value, setConstructor)\n\t\t\t\t}\n\n\t\t\t\tconst remove = (value: any, id: IdOf<S>) => {\n\t\t\t\t\tlet set = setConstructors.get(value)\n\t\t\t\t\tif (!set) set = new IncrementalSetConstructor<IdOf<S>>(prevValue.get(value) ?? new Set())\n\t\t\t\t\tset.remove(id)\n\t\t\t\t\tsetConstructors.set(value, set)\n\t\t\t\t}\n\n\t\t\t\tfor (const changes of history) {\n\t\t\t\t\tfor (const record of objectMapValues(changes.added)) {\n\t\t\t\t\t\tif (record.typeName === typeName) {\n\t\t\t\t\t\t\tconst value = getPropertyValue(record as S)\n\t\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\t\tadd(value, record.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const [from, to] of objectMapValues(changes.updated)) {\n\t\t\t\t\t\tif (to.typeName === typeName) {\n\t\t\t\t\t\t\tconst prev = getPropertyValue(from as S)\n\t\t\t\t\t\t\tconst next = getPropertyValue(to as S)\n\t\t\t\t\t\t\tif (prev !== next) {\n\t\t\t\t\t\t\t\tif (prev !== undefined) {\n\t\t\t\t\t\t\t\t\tremove(prev, to.id)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (next !== undefined) {\n\t\t\t\t\t\t\t\t\tadd(next, to.id)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const record of objectMapValues(changes.removed)) {\n\t\t\t\t\t\tif (record.typeName === typeName) {\n\t\t\t\t\t\t\tconst value = getPropertyValue(record as S)\n\t\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\t\tremove(value, record.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet nextValue: undefined | RSIndexMap<S> = undefined\n\t\t\t\tlet nextDiff: undefined | RSIndexDiff<S> = undefined\n\n\t\t\t\tfor (const [value, setConstructor] of setConstructors) {\n\t\t\t\t\tconst result = setConstructor.get()\n\t\t\t\t\tif (!result) continue\n\t\t\t\t\tif (!nextValue) nextValue = new Map(prevValue)\n\t\t\t\t\tif (!nextDiff) nextDiff = new Map()\n\t\t\t\t\tif (result.value.size === 0) {\n\t\t\t\t\t\tnextValue.delete(value)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnextValue.set(value, result.value)\n\t\t\t\t\t}\n\t\t\t\t\tnextDiff.set(value, result.diff)\n\t\t\t\t}\n\n\t\t\t\tif (nextValue && nextDiff) {\n\t\t\t\t\treturn withDiff(nextValue, nextDiff)\n\t\t\t\t}\n\n\t\t\t\treturn prevValue\n\t\t\t},\n\t\t\t{ historyLength: 100 }\n\t\t)\n\t}\n\n\t/**\n\t * Creates a reactive query that returns the first record matching the given query criteria.\n\t * Returns undefined if no matching record is found. The query automatically updates\n\t * when records change.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param queryCreator - Function that returns the query expression object to match against\n\t * @param name - Optional name for the query computation (used for debugging)\n\t * @returns A computed value containing the first matching record or undefined\n\t *\n\t * @example\n\t * ```ts\n\t * // Find the first book with a specific title\n\t * const bookLatheOfHeaven = store.query.record('book', () => ({ title: { eq: 'The Lathe of Heaven' } }))\n\t * console.log(bookLatheOfHeaven.get()?.title) // 'The Lathe of Heaven' or undefined\n\t *\n\t * // Find any book in stock\n\t * const anyInStockBook = store.query.record('book', () => ({ inStock: { eq: true } }))\n\t * ```\n\t *\n\t * @public\n\t */\n\trecord<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tqueryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),\n\t\tname = 'record:' + typeName + ':' + queryCreator.toString()\n\t): Computed<Extract<R, { typeName: TypeName }> | undefined> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\t\tconst ids = this.ids(typeName, queryCreator, name)\n\n\t\treturn computed<S | undefined>(name, () => {\n\t\t\tfor (const id of ids.get()) {\n\t\t\t\treturn this.recordMap.get(id) as S | undefined\n\t\t\t}\n\t\t\treturn undefined\n\t\t})\n\t}\n\n\t/**\n\t * Creates a reactive query that returns an array of all records matching the given query criteria.\n\t * The array automatically updates when records are added, updated, or removed.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param queryCreator - Function that returns the query expression object to match against\n\t * @param name - Optional name for the query computation (used for debugging)\n\t * @returns A computed value containing an array of all matching records\n\t *\n\t * @example\n\t * ```ts\n\t * // Get all books in stock\n\t * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))\n\t * console.log(inStockBooks.get()) // Book[]\n\t *\n\t * // Get all books by a specific author\n\t * const leguinBooks = store.query.records('book', () => ({ authorId: { eq: 'author:leguin' } }))\n\t *\n\t * // Get all books (no filter)\n\t * const allBooks = store.query.records('book')\n\t * ```\n\t *\n\t * @public\n\t */\n\trecords<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tqueryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),\n\t\tname = 'records:' + typeName + ':' + queryCreator.toString()\n\t): Computed<Array<Extract<R, { typeName: TypeName }>>> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\t\tconst ids = this.ids(typeName, queryCreator, 'ids:' + name)\n\n\t\treturn computed<S[]>(\n\t\t\tname,\n\t\t\t() => {\n\t\t\t\treturn Array.from(ids.get(), (id) => this.recordMap.get(id) as S)\n\t\t\t},\n\t\t\t{\n\t\t\t\tisEqual: areArraysShallowEqual,\n\t\t\t}\n\t\t)\n\t}\n\n\t/**\n\t * Creates a reactive query that returns a set of record IDs matching the given query criteria.\n\t * This is more efficient than `records()` when you only need the IDs and not the full record objects.\n\t * The set automatically updates with collection diffs when records change.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param queryCreator - Function that returns the query expression object to match against\n\t * @param name - Optional name for the query computation (used for debugging)\n\t * @returns A computed value containing a set of matching record IDs with collection diffs\n\t *\n\t * @example\n\t * ```ts\n\t * // Get IDs of all books in stock\n\t * const inStockBookIds = store.query.ids('book', () => ({ inStock: { eq: true } }))\n\t * console.log(inStockBookIds.get()) // Set<RecordId<Book>>\n\t *\n\t * // Get all book IDs (no filter)\n\t * const allBookIds = store.query.ids('book')\n\t *\n\t * // Use with other queries for efficient lookups\n\t * const authorBookIds = store.query.ids('book', () => ({ authorId: { eq: 'author:leguin' } }))\n\t * ```\n\t *\n\t * @public\n\t */\n\tids<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tqueryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),\n\t\tname = 'ids:' + typeName + ':' + queryCreator.toString()\n\t): Computed<\n\t\tSet<IdOf<Extract<R, { typeName: TypeName }>>>,\n\t\tCollectionDiff<IdOf<Extract<R, { typeName: TypeName }>>>\n\t> {\n\t\ttype S = Extract<R, { typeName: TypeName }>\n\n\t\tconst typeHistory = this.filterHistory(typeName)\n\n\t\tconst fromScratch = () => {\n\t\t\t// deref type history early to allow first incremental update to use diffs\n\t\t\ttypeHistory.get()\n\t\t\tconst query: QueryExpression<S> = queryCreator()\n\t\t\tif (!hasAnyKey(query)) {\n\t\t\t\treturn this.getAllIdsForType(typeName)\n\t\t\t}\n\n\t\t\treturn executeQuery(this, typeName, query)\n\t\t}\n\n\t\tconst fromScratchWithDiff = (prevValue: Set<IdOf<S>>) => {\n\t\t\tconst nextValue = fromScratch()\n\t\t\tconst diff = diffSets(prevValue, nextValue)\n\t\t\tif (diff) {\n\t\t\t\treturn withDiff(nextValue, diff)\n\t\t\t} else {\n\t\t\t\treturn prevValue\n\t\t\t}\n\t\t}\n\t\t// Wrapped rather than passed straight through: a computed's derive function receives\n\t\t// `(previousValue, lastComputedEpoch)`, which a queryCreator with optional parameters\n\t\t// would otherwise misread.\n\t\tconst cachedQuery = computed('ids_query:' + name, () => queryCreator(), {\n\t\t\tisEqual,\n\t\t})\n\n\t\treturn computed(\n\t\t\t'query:' + name,\n\t\t\t(prevValue, lastComputedEpoch) => {\n\t\t\t\tconst query = cachedQuery.get()\n\t\t\t\tif (isUninitialized(prevValue)) {\n\t\t\t\t\treturn fromScratch()\n\t\t\t\t}\n\n\t\t\t\t// if the query changed since last time this ran then we need to start again\n\t\t\t\tif (lastComputedEpoch < cachedQuery.lastChangedEpoch) {\n\t\t\t\t\treturn fromScratchWithDiff(prevValue)\n\t\t\t\t}\n\n\t\t\t\t// otherwise iterate over the changes from the store and apply them to the previous value if needed\n\t\t\t\tconst history = typeHistory.getDiffSince(lastComputedEpoch)\n\t\t\t\tif (history === RESET_VALUE) {\n\t\t\t\t\treturn fromScratchWithDiff(prevValue)\n\t\t\t\t}\n\n\t\t\t\tconst setConstructor = new IncrementalSetConstructor<IdOf<S>>(\n\t\t\t\t\tprevValue\n\t\t\t\t) as IncrementalSetConstructor<IdOf<S>>\n\n\t\t\t\tfor (const changes of history) {\n\t\t\t\t\tfor (const added of objectMapValues(changes.added)) {\n\t\t\t\t\t\tif (added.typeName === typeName && objectMatchesQuery(query, added)) {\n\t\t\t\t\t\t\tsetConstructor.add(added.id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const [_, updated] of objectMapValues(changes.updated)) {\n\t\t\t\t\t\tif (updated.typeName === typeName) {\n\t\t\t\t\t\t\tif (objectMatchesQuery(query, updated)) {\n\t\t\t\t\t\t\t\tsetConstructor.add(updated.id)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetConstructor.remove(updated.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const removed of objectMapValues(changes.removed)) {\n\t\t\t\t\t\tif (removed.typeName === typeName) {\n\t\t\t\t\t\t\tsetConstructor.remove(removed.id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst result = setConstructor.get()\n\t\t\t\tif (!result) {\n\t\t\t\t\treturn prevValue\n\t\t\t\t}\n\n\t\t\t\treturn withDiff(result.value, result.diff)\n\t\t\t},\n\t\t\t{ historyLength: 50 }\n\t\t)\n\t}\n\n\t/**\n\t * Executes a one-time query against the current store state and returns matching records.\n\t * This is a non-reactive query that returns results immediately without creating a computed value.\n\t * Use this when you need a snapshot of data at a specific point in time.\n\t *\n\t * @param typeName - The type name of records to query\n\t * @param query - The query expression object to match against\n\t * @returns An array of records that match the query at the current moment\n\t *\n\t * @example\n\t * ```ts\n\t * // Get current in-stock books (non-reactive)\n\t * const currentInStockBooks = store.query.exec('book', { inStock: { eq: true } })\n\t * console.log(currentInStockBooks) // Book[]\n\t *\n\t * // Unlike records(), this won't update when the data changes\n\t * const staticBookList = store.query.exec('book', { authorId: { eq: 'author:leguin' } })\n\t * ```\n\t *\n\t * @public\n\t */\n\texec<TypeName extends R['typeName']>(\n\t\ttypeName: TypeName,\n\t\tquery: QueryExpression<Extract<R, { typeName: TypeName }>>\n\t): Array<Extract<R, { typeName: TypeName }>> {\n\t\tconst ids = executeQuery(this, typeName, query)\n\t\tif (ids.size === 0) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\t\treturn Array.from(ids, (id) => this.recordMap.get(id) as Extract<R, { typeName: TypeName }>)\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -39,6 +39,15 @@ class StoreSideEffects {
|
|
|
39
39
|
_afterDeleteHandlers = {};
|
|
40
40
|
_operationCompleteHandlers = [];
|
|
41
41
|
_isEnabled = true;
|
|
42
|
+
// Handler arrays are replaced, never mutated in place: dispatch iterates the array it looked up,
|
|
43
|
+
// so a handler that removes itself (or registers another) while running must not shift the
|
|
44
|
+
// entries out from under that iteration.
|
|
45
|
+
addHandler(handlers, typeName, handler) {
|
|
46
|
+
handlers[typeName] = [...handlers[typeName] ?? [], handler];
|
|
47
|
+
return () => {
|
|
48
|
+
handlers[typeName] = withoutFirst(handlers[typeName] ?? [], handler);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
42
51
|
/**
|
|
43
52
|
* Checks whether side effects are currently enabled.
|
|
44
53
|
* When disabled, all side effect handlers are bypassed.
|
|
@@ -270,10 +279,7 @@ class StoreSideEffects {
|
|
|
270
279
|
* @returns A callback that removes the handler.
|
|
271
280
|
*/
|
|
272
281
|
registerBeforeCreateHandler(typeName, handler) {
|
|
273
|
-
|
|
274
|
-
if (!handlers) this._beforeCreateHandlers[typeName] = [];
|
|
275
|
-
this._beforeCreateHandlers[typeName].push(handler);
|
|
276
|
-
return () => remove(this._beforeCreateHandlers[typeName], handler);
|
|
282
|
+
return this.addHandler(this._beforeCreateHandlers, typeName, handler);
|
|
277
283
|
}
|
|
278
284
|
/**
|
|
279
285
|
* Register a handler to be called after a record is created. This is useful for side-effects
|
|
@@ -298,10 +304,7 @@ class StoreSideEffects {
|
|
|
298
304
|
* @returns A callback that removes the handler.
|
|
299
305
|
*/
|
|
300
306
|
registerAfterCreateHandler(typeName, handler) {
|
|
301
|
-
|
|
302
|
-
if (!handlers) this._afterCreateHandlers[typeName] = [];
|
|
303
|
-
this._afterCreateHandlers[typeName].push(handler);
|
|
304
|
-
return () => remove(this._afterCreateHandlers[typeName], handler);
|
|
307
|
+
return this.addHandler(this._afterCreateHandlers, typeName, handler);
|
|
305
308
|
}
|
|
306
309
|
/**
|
|
307
310
|
* Register a handler to be called before a record is changed. The handler is given the old and
|
|
@@ -330,10 +333,7 @@ class StoreSideEffects {
|
|
|
330
333
|
* @returns A callback that removes the handler.
|
|
331
334
|
*/
|
|
332
335
|
registerBeforeChangeHandler(typeName, handler) {
|
|
333
|
-
|
|
334
|
-
if (!handlers) this._beforeChangeHandlers[typeName] = [];
|
|
335
|
-
this._beforeChangeHandlers[typeName].push(handler);
|
|
336
|
-
return () => remove(this._beforeChangeHandlers[typeName], handler);
|
|
336
|
+
return this.addHandler(this._beforeChangeHandlers, typeName, handler);
|
|
337
337
|
}
|
|
338
338
|
/**
|
|
339
339
|
* Register a handler to be called after a record is changed. This is useful for side-effects
|
|
@@ -357,10 +357,7 @@ class StoreSideEffects {
|
|
|
357
357
|
* @returns A callback that removes the handler.
|
|
358
358
|
*/
|
|
359
359
|
registerAfterChangeHandler(typeName, handler) {
|
|
360
|
-
|
|
361
|
-
if (!handlers) this._afterChangeHandlers[typeName] = [];
|
|
362
|
-
this._afterChangeHandlers[typeName].push(handler);
|
|
363
|
-
return () => remove(this._afterChangeHandlers[typeName], handler);
|
|
360
|
+
return this.addHandler(this._afterChangeHandlers, typeName, handler);
|
|
364
361
|
}
|
|
365
362
|
/**
|
|
366
363
|
* Register a handler to be called before a record is deleted. The handler can return `false` to
|
|
@@ -386,10 +383,7 @@ class StoreSideEffects {
|
|
|
386
383
|
* @returns A callback that removes the handler.
|
|
387
384
|
*/
|
|
388
385
|
registerBeforeDeleteHandler(typeName, handler) {
|
|
389
|
-
|
|
390
|
-
if (!handlers) this._beforeDeleteHandlers[typeName] = [];
|
|
391
|
-
this._beforeDeleteHandlers[typeName].push(handler);
|
|
392
|
-
return () => remove(this._beforeDeleteHandlers[typeName], handler);
|
|
386
|
+
return this.addHandler(this._beforeDeleteHandlers, typeName, handler);
|
|
393
387
|
}
|
|
394
388
|
/**
|
|
395
389
|
* Register a handler to be called after a record is deleted. This is useful for side-effects
|
|
@@ -416,10 +410,7 @@ class StoreSideEffects {
|
|
|
416
410
|
* @returns A callback that removes the handler.
|
|
417
411
|
*/
|
|
418
412
|
registerAfterDeleteHandler(typeName, handler) {
|
|
419
|
-
|
|
420
|
-
if (!handlers) this._afterDeleteHandlers[typeName] = [];
|
|
421
|
-
this._afterDeleteHandlers[typeName].push(handler);
|
|
422
|
-
return () => remove(this._afterDeleteHandlers[typeName], handler);
|
|
413
|
+
return this.addHandler(this._afterDeleteHandlers, typeName, handler);
|
|
423
414
|
}
|
|
424
415
|
/**
|
|
425
416
|
* Register a handler to be called when a store completes an atomic operation.
|
|
@@ -448,14 +439,15 @@ class StoreSideEffects {
|
|
|
448
439
|
* @public
|
|
449
440
|
*/
|
|
450
441
|
registerOperationCompleteHandler(handler) {
|
|
451
|
-
this._operationCompleteHandlers.
|
|
452
|
-
return () =>
|
|
442
|
+
this._operationCompleteHandlers = [...this._operationCompleteHandlers, handler];
|
|
443
|
+
return () => {
|
|
444
|
+
this._operationCompleteHandlers = withoutFirst(this._operationCompleteHandlers, handler);
|
|
445
|
+
};
|
|
453
446
|
}
|
|
454
447
|
}
|
|
455
|
-
function
|
|
448
|
+
function withoutFirst(array, item) {
|
|
456
449
|
const index = array.indexOf(item);
|
|
457
|
-
if (index
|
|
458
|
-
|
|
459
|
-
}
|
|
450
|
+
if (index < 0) return array;
|
|
451
|
+
return [...array.slice(0, index), ...array.slice(index + 1)];
|
|
460
452
|
}
|
|
461
453
|
//# sourceMappingURL=StoreSideEffects.js.map
|
|
@@ -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({\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
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA0MO,MAAM,iBAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtD,YAA6B,OAAiB;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA,EAErB,wBAA6E,CAAC;AAAA,EAC9E,uBAA2E,CAAC;AAAA,EAC5E,wBAA6E,CAAC;AAAA,EAC9E,uBAA2E,CAAC;AAAA,EAC5E,wBAA6E,CAAC;AAAA,EAC9E,uBAA2E,CAAC;AAAA,EAC5E,6BAA8D,CAAC;AAAA,EAE/D,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
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\n\t// Handler arrays are replaced, never mutated in place: dispatch iterates the array it looked up,\n\t// so a handler that removes itself (or registers another) while running must not shift the\n\t// entries out from under that iteration.\n\tprivate addHandler<H>(handlers: { [K in string]?: H[] }, typeName: string, handler: H) {\n\t\thandlers[typeName] = [...(handlers[typeName] ?? []), handler]\n\t\treturn () => {\n\t\t\thandlers[typeName] = withoutFirst(handlers[typeName] ?? [], handler)\n\t\t}\n\t}\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\treturn this.addHandler(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\treturn this.addHandler(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\treturn this.addHandler(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\treturn this.addHandler(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\treturn this.addHandler(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\treturn this.addHandler(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\t// copy on write, see addHandler\n\t\tthis._operationCompleteHandlers = [...this._operationCompleteHandlers, handler]\n\t\treturn () => {\n\t\t\tthis._operationCompleteHandlers = withoutFirst(this._operationCompleteHandlers, handler)\n\t\t}\n\t}\n}\n\nfunction withoutFirst<T>(array: T[], item: T): T[] {\n\tconst index = array.indexOf(item)\n\tif (index < 0) return array\n\treturn [...array.slice(0, index), ...array.slice(index + 1)]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.d.mts
CHANGED
|
@@ -2126,17 +2126,19 @@ export declare class StoreQueries<R extends UnknownRecord> {
|
|
|
2126
2126
|
* have been added, updated, or removed.
|
|
2127
2127
|
*
|
|
2128
2128
|
* @param typeName - The type name to filter the history by
|
|
2129
|
-
* @returns A computed value containing
|
|
2129
|
+
* @returns A computed value containing a change counter and diffs of changes for the specified type
|
|
2130
2130
|
*
|
|
2131
2131
|
* @example
|
|
2132
2132
|
* ```ts
|
|
2133
2133
|
* // Track changes to book records only
|
|
2134
2134
|
* const bookHistory = store.query.filterHistory('book')
|
|
2135
2135
|
*
|
|
2136
|
-
* //
|
|
2137
|
-
* react('book-changes', () => {
|
|
2138
|
-
* const
|
|
2139
|
-
*
|
|
2136
|
+
* // Log the books added since the last run
|
|
2137
|
+
* react('book-changes', (lastReactedEpoch) => {
|
|
2138
|
+
* const diffs = bookHistory.getDiffSince(lastReactedEpoch)
|
|
2139
|
+
* // no diffs to give, e.g. on the first run: rebuild anything derived from scratch
|
|
2140
|
+
* if (diffs === RESET_VALUE) return
|
|
2141
|
+
* for (const diff of diffs) console.log('Added:', Object.keys(diff.added))
|
|
2140
2142
|
* })
|
|
2141
2143
|
* ```
|
|
2142
2144
|
*
|
|
@@ -2599,6 +2601,7 @@ export declare class StoreSideEffects<R extends UnknownRecord> {
|
|
|
2599
2601
|
private _afterDeleteHandlers;
|
|
2600
2602
|
private _operationCompleteHandlers;
|
|
2601
2603
|
private _isEnabled;
|
|
2604
|
+
private addHandler;
|
|
2602
2605
|
/* Excluded from this release type: isEnabled */
|
|
2603
2606
|
/* Excluded from this release type: setIsEnabled */
|
|
2604
2607
|
/* Excluded from this release type: handleBeforeCreate */
|
package/dist-esm/index.mjs
CHANGED
|
@@ -77,17 +77,19 @@ class StoreQueries {
|
|
|
77
77
|
* have been added, updated, or removed.
|
|
78
78
|
*
|
|
79
79
|
* @param typeName - The type name to filter the history by
|
|
80
|
-
* @returns A computed value containing
|
|
80
|
+
* @returns A computed value containing a change counter and diffs of changes for the specified type
|
|
81
81
|
*
|
|
82
82
|
* @example
|
|
83
83
|
* ```ts
|
|
84
84
|
* // Track changes to book records only
|
|
85
85
|
* const bookHistory = store.query.filterHistory('book')
|
|
86
86
|
*
|
|
87
|
-
* //
|
|
88
|
-
* react('book-changes', () => {
|
|
89
|
-
* const
|
|
90
|
-
*
|
|
87
|
+
* // Log the books added since the last run
|
|
88
|
+
* react('book-changes', (lastReactedEpoch) => {
|
|
89
|
+
* const diffs = bookHistory.getDiffSince(lastReactedEpoch)
|
|
90
|
+
* // no diffs to give, e.g. on the first run: rebuild anything derived from scratch
|
|
91
|
+
* if (diffs === RESET_VALUE) return
|
|
92
|
+
* for (const diff of diffs) console.log('Added:', Object.keys(diff.added))
|
|
91
93
|
* })
|
|
92
94
|
* ```
|
|
93
95
|
*
|
|
@@ -103,12 +105,13 @@ class StoreQueries {
|
|
|
103
105
|
if (isUninitialized(lastValue)) {
|
|
104
106
|
return this.history.get();
|
|
105
107
|
}
|
|
108
|
+
const next = Math.max(this.history.get(), lastValue + 1);
|
|
106
109
|
const diff = this.history.getDiffSince(lastComputedEpoch);
|
|
107
|
-
if (diff === RESET_VALUE) return
|
|
110
|
+
if (diff === RESET_VALUE) return next;
|
|
108
111
|
const res = { added: {}, removed: {}, updated: {} };
|
|
109
112
|
const size = squashRecordDiffsMutableByType(res, diff, typeName);
|
|
110
113
|
if (size) {
|
|
111
|
-
return withDiff(
|
|
114
|
+
return withDiff(next, res);
|
|
112
115
|
} else {
|
|
113
116
|
return lastValue;
|
|
114
117
|
}
|