@tanstack/db 0.0.1 → 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kyle Mathews
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **A reactive client store for building super fast apps on sync**
4
4
 
5
- TanStack DB extends TanStack Query with collections, live queries and transactional mutations that keep your UI reactive, consistent and blazing fast 🔥
5
+ TanStack DB extends TanStack Query with collections, live queries and optimistic mutations that keep your UI reactive, consistent and blazing fast 🔥
6
6
 
7
7
  <p>
8
8
  <a href="https://x.com/intent/post?text=TanStack%20DB&url=https://tanstack.com/db">
@@ -315,8 +315,12 @@ class Collection {
315
315
  prevDepVals
316
316
  }) => {
317
317
  const prevDerivedState = (prevDepVals == null ? void 0 : prevDepVals[0]) ?? /* @__PURE__ */ new Map();
318
+ const prevOptimisticOperations = (prevDepVals == null ? void 0 : prevDepVals[1]) ?? [];
318
319
  const changedKeys = new Set(this.syncedKeys);
319
320
  optimisticOperations.flat().filter((op) => op.isActive).forEach((op) => changedKeys.add(op.key));
321
+ prevOptimisticOperations.flat().forEach((op) => {
322
+ changedKeys.add(op.key);
323
+ });
320
324
  if (changedKeys.size === 0) {
321
325
  return [];
322
326
  }
@@ -331,12 +335,16 @@ class Collection {
331
335
  } else if (!prevDerivedState.has(key) && derivedState.has(key)) {
332
336
  changes.push({ type: `insert`, key, value: derivedState.get(key) });
333
337
  } else if (prevDerivedState.has(key) && derivedState.has(key)) {
334
- changes.push({
335
- type: `update`,
336
- key,
337
- value: derivedState.get(key),
338
- previousValue: prevDerivedState.get(key)
339
- });
338
+ const value = derivedState.get(key);
339
+ const previousValue = prevDerivedState.get(key);
340
+ if (value !== previousValue) {
341
+ changes.push({
342
+ type: `update`,
343
+ key,
344
+ value,
345
+ previousValue
346
+ });
347
+ }
340
348
  }
341
349
  }
342
350
  this.syncedKeys.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"collection.cjs","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(new Map<string, Collection<any>>())\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<Collection<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<Collection<T>> {\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<Collection<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n next.set(\n config.id,\n new Collection<T>({\n id: config.id,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<Collection<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<Collection<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class Collection<T extends object = Record<string, unknown>> {\n public transactions: Store<SortedMap<string, Transaction>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // WeakMap to associate objects with their keys\n public objectKeyMap = new WeakMap<object, string>()\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = crypto.randomUUID()\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config?: CollectionConfig<T>) {\n if (!config?.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, Transaction>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations.map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<string, unknown>\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n const optimisticKeys = new Set<string>()\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n optimisticKeys.add(operation.key)\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n // Update object => key mappings\n optimisticKeys.forEach((key) => {\n if (combined.has(key)) {\n this.objectKeyMap.set(combined.get(key)!, key)\n }\n })\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({\n type: `update`,\n key,\n value: derivedState.get(key)!,\n previousValue: prevDerivedState.get(key),\n })\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (message: ChangeMessage<T>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n const keys = new Set<string>()\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n keys.add(operation.key)\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n keys.forEach((key) => {\n const curValue = this.state.get(key)\n if (curValue) {\n this.objectKeyMap.set(curValue, key)\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\n\n const standardSchema = this.ensureStandardSchema(this.config.schema)\n\n // For updates, we need to merge with the existing data before validation\n if (type === `update` && key) {\n // Get the existing data for this key\n const existingData = this.state.get(key)\n\n if (\n existingData &&\n data &&\n typeof data === `object` &&\n typeof existingData === `object`\n ) {\n // Merge the update with the existing data\n const mergedData = { ...existingData, ...data }\n\n // Validate the merged data\n const result = standardSchema[`~standard`].validate(mergedData)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n // Return the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\n }\n }\n\n // For inserts or updates without existing data, validate the data directly\n const result = standardSchema[`~standard`].validate(data)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n return result.value as T\n }\n\n private generateKey(data: unknown): string {\n const str = JSON.stringify(data)\n let h = 0\n\n for (let i = 0; i < str.length; i++) {\n h = (Math.imul(31, h) + str.charCodeAt(i)) | 0\n }\n\n return `${this.id}/${Math.abs(h).toString(36)}`\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A Transaction object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.insert`\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n let keys: Array<string>\n if (config?.key) {\n const configKeys = Array.isArray(config.key) ? config.key : [config.key]\n // If keys are provided, ensure we have the right number or allow sparse array\n if (Array.isArray(config.key) && configKeys.length > items.length) {\n throw new Error(`More keys provided than items to insert`)\n }\n keys = items.map((_, i) => configKeys[i] ?? this.generateKey(items[i]))\n } else {\n // No keys provided, generate for all items\n keys = items.map((item) => this.generateKey(item))\n }\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n update<TItem extends object = T>(\n item: TItem,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: Array<TItem>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: TItem | Array<TItem>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof items === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\n\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.update`\n }\n\n const isArray = Array.isArray(items)\n const itemsArray = Array.isArray(items) ? items : [items]\n const callback =\n typeof configOrCallback === `function` ? configOrCallback : maybeCallback!\n const config =\n typeof configOrCallback === `function` ? {} : configOrCallback\n\n const keys = itemsArray.map((item) => {\n if (typeof item === `object` && (item as unknown) !== null) {\n const key = this.objectKeyMap.get(item)\n if (key === undefined) {\n throw new Error(`Object not found in collection`)\n }\n return key\n }\n throw new Error(`Invalid item type for update - must be an object`)\n })\n\n // Get the current objects or empty objects if they don't exist\n const currentObjects = keys.map((key) => ({\n ...(this.state.get(key) || {}),\n })) as Array<TItem>\n\n let changesArray\n if (isArray) {\n // Use the proxy to track changes for all objects\n changesArray = withArrayChangeTracking(\n currentObjects,\n callback as (draft: Array<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = keys\n .map((key, index) => {\n const changes = changesArray[index]\n\n // Skip items with no changes\n if (!changes || Object.keys(changes).length === 0) {\n return null\n }\n\n // Validate the changes for this item\n const validatedData = this.validateData(changes, `update`, key)\n\n return {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: {\n ...(this.state.get(key) || {}),\n ...validatedData,\n } as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param items - Single item/key or array of items/keys to delete\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(todo)\n *\n * // Delete multiple items\n * delete([todo1, todo2])\n *\n * // Delete with metadata\n * delete(todo, { metadata: { reason: \"completed\" } })\n */\n delete = (\n items: Array<T | string> | T | string,\n config?: OperationConfig\n ) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.delete`\n }\n\n const itemsArray = Array.isArray(items) ? items : [items]\n const mutations: Array<PendingMutation<T>> = []\n\n for (const item of itemsArray) {\n let key: string\n if (typeof item === `object` && (item as unknown) !== null) {\n const objectKey = this.objectKeyMap.get(item)\n if (objectKey === undefined) {\n throw new Error(\n `Object not found in collection: ${JSON.stringify(item)}`\n )\n }\n key = objectKey\n } else if (typeof item === `string`) {\n key = item\n } else {\n throw new Error(\n `Invalid item type for delete - must be an object or string key`\n )\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: { _deleted: true },\n changes: { _deleted: true },\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n }\n\n // Delete object => key mapping.\n mutations.forEach((mutation) => {\n const curValue = this.state.get(mutation.key)\n if (curValue) {\n this.objectKeyMap.delete(curValue)\n }\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["Store","batch","config","getActiveTransaction","SortedMap","Derived","transactions","result","withArrayChangeTracking","withChangeTracking"],"mappings":"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"}
1
+ {"version":3,"file":"collection.cjs","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(new Map<string, Collection<any>>())\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<Collection<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<Collection<T>> {\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<Collection<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n next.set(\n config.id,\n new Collection<T>({\n id: config.id,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<Collection<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<Collection<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class Collection<T extends object = Record<string, unknown>> {\n public transactions: Store<SortedMap<string, Transaction>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // WeakMap to associate objects with their keys\n public objectKeyMap = new WeakMap<object, string>()\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = crypto.randomUUID()\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config?: CollectionConfig<T>) {\n if (!config?.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, Transaction>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations.map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<string, unknown>\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n const optimisticKeys = new Set<string>()\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n optimisticKeys.add(operation.key)\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n // Update object => key mappings\n optimisticKeys.forEach((key) => {\n if (combined.has(key)) {\n this.objectKeyMap.set(combined.get(key)!, key)\n }\n })\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const prevOptimisticOperations = prevDepVals?.[1] ?? []\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n prevOptimisticOperations.flat().forEach((op) => {\n changedKeys.add(op.key)\n })\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n const value = derivedState.get(key)!\n const previousValue = prevDerivedState.get(key)\n if (value !== previousValue) {\n // Comparing objects by reference as records are not mutated\n changes.push({\n type: `update`,\n key,\n value,\n previousValue,\n })\n }\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (message: ChangeMessage<T>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n const keys = new Set<string>()\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n keys.add(operation.key)\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n keys.forEach((key) => {\n const curValue = this.state.get(key)\n if (curValue) {\n this.objectKeyMap.set(curValue, key)\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\n\n const standardSchema = this.ensureStandardSchema(this.config.schema)\n\n // For updates, we need to merge with the existing data before validation\n if (type === `update` && key) {\n // Get the existing data for this key\n const existingData = this.state.get(key)\n\n if (\n existingData &&\n data &&\n typeof data === `object` &&\n typeof existingData === `object`\n ) {\n // Merge the update with the existing data\n const mergedData = { ...existingData, ...data }\n\n // Validate the merged data\n const result = standardSchema[`~standard`].validate(mergedData)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n // Return the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\n }\n }\n\n // For inserts or updates without existing data, validate the data directly\n const result = standardSchema[`~standard`].validate(data)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n return result.value as T\n }\n\n private generateKey(data: unknown): string {\n const str = JSON.stringify(data)\n let h = 0\n\n for (let i = 0; i < str.length; i++) {\n h = (Math.imul(31, h) + str.charCodeAt(i)) | 0\n }\n\n return `${this.id}/${Math.abs(h).toString(36)}`\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A Transaction object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.insert`\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n let keys: Array<string>\n if (config?.key) {\n const configKeys = Array.isArray(config.key) ? config.key : [config.key]\n // If keys are provided, ensure we have the right number or allow sparse array\n if (Array.isArray(config.key) && configKeys.length > items.length) {\n throw new Error(`More keys provided than items to insert`)\n }\n keys = items.map((_, i) => configKeys[i] ?? this.generateKey(items[i]))\n } else {\n // No keys provided, generate for all items\n keys = items.map((item) => this.generateKey(item))\n }\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n update<TItem extends object = T>(\n item: TItem,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: Array<TItem>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: TItem | Array<TItem>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof items === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\n\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.update`\n }\n\n const isArray = Array.isArray(items)\n const itemsArray = Array.isArray(items) ? items : [items]\n const callback =\n typeof configOrCallback === `function` ? configOrCallback : maybeCallback!\n const config =\n typeof configOrCallback === `function` ? {} : configOrCallback\n\n const keys = itemsArray.map((item) => {\n if (typeof item === `object` && (item as unknown) !== null) {\n const key = this.objectKeyMap.get(item)\n if (key === undefined) {\n throw new Error(`Object not found in collection`)\n }\n return key\n }\n throw new Error(`Invalid item type for update - must be an object`)\n })\n\n // Get the current objects or empty objects if they don't exist\n const currentObjects = keys.map((key) => ({\n ...(this.state.get(key) || {}),\n })) as Array<TItem>\n\n let changesArray\n if (isArray) {\n // Use the proxy to track changes for all objects\n changesArray = withArrayChangeTracking(\n currentObjects,\n callback as (draft: Array<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = keys\n .map((key, index) => {\n const changes = changesArray[index]\n\n // Skip items with no changes\n if (!changes || Object.keys(changes).length === 0) {\n return null\n }\n\n // Validate the changes for this item\n const validatedData = this.validateData(changes, `update`, key)\n\n return {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: {\n ...(this.state.get(key) || {}),\n ...validatedData,\n } as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param items - Single item/key or array of items/keys to delete\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(todo)\n *\n * // Delete multiple items\n * delete([todo1, todo2])\n *\n * // Delete with metadata\n * delete(todo, { metadata: { reason: \"completed\" } })\n */\n delete = (\n items: Array<T | string> | T | string,\n config?: OperationConfig\n ) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.delete`\n }\n\n const itemsArray = Array.isArray(items) ? items : [items]\n const mutations: Array<PendingMutation<T>> = []\n\n for (const item of itemsArray) {\n let key: string\n if (typeof item === `object` && (item as unknown) !== null) {\n const objectKey = this.objectKeyMap.get(item)\n if (objectKey === undefined) {\n throw new Error(\n `Object not found in collection: ${JSON.stringify(item)}`\n )\n }\n key = objectKey\n } else if (typeof item === `string`) {\n key = item\n } else {\n throw new Error(\n `Invalid item type for delete - must be an object or string key`\n )\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: { _deleted: true },\n changes: { _deleted: true },\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n }\n\n // Delete object => key mapping.\n mutations.forEach((mutation) => {\n const curValue = this.state.get(mutation.key)\n if (curValue) {\n this.objectKeyMap.delete(curValue)\n }\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["Store","batch","config","getActiveTransaction","SortedMap","Derived","transactions","result","withArrayChangeTracking","withChangeTracking"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const store = require("@tanstack/store");
3
4
  const deferred = require("./deferred.cjs");
4
5
  function generateUUID() {
5
6
  if (typeof crypto !== `undefined` && typeof crypto.randomUUID === `function`) {
@@ -118,8 +119,10 @@ class Transaction {
118
119
  }
119
120
  try {
120
121
  await this.mutationFn({ transaction: this });
121
- this.setState(`completed`);
122
- this.touchCollection();
122
+ store.batch(() => {
123
+ this.setState(`completed`);
124
+ this.touchCollection();
125
+ });
123
126
  this.isPersisted.resolve(this);
124
127
  } catch (error) {
125
128
  this.error = {
@@ -1 +1 @@
1
- {"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nlet transactionStack: Array<Transaction> = []\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same keys\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationKeys = new Set()\n this.mutations.forEach((m) => mutationKeys.add(m.key))\n transactions.forEach(\n (t) =>\n t.state === `pending` &&\n t.mutations.some((m) => mutationKeys.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n )\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n this.mutations.forEach((mutation) => {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n })\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n }\n\n // Run mutationFn\n try {\n await this.mutationFn({ transaction: this })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":["createDeferred"],"mappings":"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"}
1
+ {"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { batch } from \"@tanstack/store\"\nimport { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nlet transactionStack: Array<Transaction> = []\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same keys\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationKeys = new Set()\n this.mutations.forEach((m) => mutationKeys.add(m.key))\n transactions.forEach(\n (t) =>\n t.state === `pending` &&\n t.mutations.some((m) => mutationKeys.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n )\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n this.mutations.forEach((mutation) => {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n })\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n }\n\n // Run mutationFn\n try {\n await this.mutationFn({ transaction: this })\n\n batch(() => {\n this.setState(`completed`)\n this.touchCollection()\n })\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":["createDeferred","batch"],"mappings":"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"}
@@ -313,8 +313,12 @@ class Collection {
313
313
  prevDepVals
314
314
  }) => {
315
315
  const prevDerivedState = (prevDepVals == null ? void 0 : prevDepVals[0]) ?? /* @__PURE__ */ new Map();
316
+ const prevOptimisticOperations = (prevDepVals == null ? void 0 : prevDepVals[1]) ?? [];
316
317
  const changedKeys = new Set(this.syncedKeys);
317
318
  optimisticOperations.flat().filter((op) => op.isActive).forEach((op) => changedKeys.add(op.key));
319
+ prevOptimisticOperations.flat().forEach((op) => {
320
+ changedKeys.add(op.key);
321
+ });
318
322
  if (changedKeys.size === 0) {
319
323
  return [];
320
324
  }
@@ -329,12 +333,16 @@ class Collection {
329
333
  } else if (!prevDerivedState.has(key) && derivedState.has(key)) {
330
334
  changes.push({ type: `insert`, key, value: derivedState.get(key) });
331
335
  } else if (prevDerivedState.has(key) && derivedState.has(key)) {
332
- changes.push({
333
- type: `update`,
334
- key,
335
- value: derivedState.get(key),
336
- previousValue: prevDerivedState.get(key)
337
- });
336
+ const value = derivedState.get(key);
337
+ const previousValue = prevDerivedState.get(key);
338
+ if (value !== previousValue) {
339
+ changes.push({
340
+ type: `update`,
341
+ key,
342
+ value,
343
+ previousValue
344
+ });
345
+ }
338
346
  }
339
347
  }
340
348
  this.syncedKeys.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"collection.js","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(new Map<string, Collection<any>>())\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<Collection<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<Collection<T>> {\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<Collection<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n next.set(\n config.id,\n new Collection<T>({\n id: config.id,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<Collection<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<Collection<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class Collection<T extends object = Record<string, unknown>> {\n public transactions: Store<SortedMap<string, Transaction>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // WeakMap to associate objects with their keys\n public objectKeyMap = new WeakMap<object, string>()\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = crypto.randomUUID()\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config?: CollectionConfig<T>) {\n if (!config?.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, Transaction>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations.map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<string, unknown>\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n const optimisticKeys = new Set<string>()\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n optimisticKeys.add(operation.key)\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n // Update object => key mappings\n optimisticKeys.forEach((key) => {\n if (combined.has(key)) {\n this.objectKeyMap.set(combined.get(key)!, key)\n }\n })\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({\n type: `update`,\n key,\n value: derivedState.get(key)!,\n previousValue: prevDerivedState.get(key),\n })\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (message: ChangeMessage<T>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n const keys = new Set<string>()\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n keys.add(operation.key)\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n keys.forEach((key) => {\n const curValue = this.state.get(key)\n if (curValue) {\n this.objectKeyMap.set(curValue, key)\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\n\n const standardSchema = this.ensureStandardSchema(this.config.schema)\n\n // For updates, we need to merge with the existing data before validation\n if (type === `update` && key) {\n // Get the existing data for this key\n const existingData = this.state.get(key)\n\n if (\n existingData &&\n data &&\n typeof data === `object` &&\n typeof existingData === `object`\n ) {\n // Merge the update with the existing data\n const mergedData = { ...existingData, ...data }\n\n // Validate the merged data\n const result = standardSchema[`~standard`].validate(mergedData)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n // Return the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\n }\n }\n\n // For inserts or updates without existing data, validate the data directly\n const result = standardSchema[`~standard`].validate(data)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n return result.value as T\n }\n\n private generateKey(data: unknown): string {\n const str = JSON.stringify(data)\n let h = 0\n\n for (let i = 0; i < str.length; i++) {\n h = (Math.imul(31, h) + str.charCodeAt(i)) | 0\n }\n\n return `${this.id}/${Math.abs(h).toString(36)}`\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A Transaction object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.insert`\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n let keys: Array<string>\n if (config?.key) {\n const configKeys = Array.isArray(config.key) ? config.key : [config.key]\n // If keys are provided, ensure we have the right number or allow sparse array\n if (Array.isArray(config.key) && configKeys.length > items.length) {\n throw new Error(`More keys provided than items to insert`)\n }\n keys = items.map((_, i) => configKeys[i] ?? this.generateKey(items[i]))\n } else {\n // No keys provided, generate for all items\n keys = items.map((item) => this.generateKey(item))\n }\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n update<TItem extends object = T>(\n item: TItem,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: Array<TItem>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: TItem | Array<TItem>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof items === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\n\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.update`\n }\n\n const isArray = Array.isArray(items)\n const itemsArray = Array.isArray(items) ? items : [items]\n const callback =\n typeof configOrCallback === `function` ? configOrCallback : maybeCallback!\n const config =\n typeof configOrCallback === `function` ? {} : configOrCallback\n\n const keys = itemsArray.map((item) => {\n if (typeof item === `object` && (item as unknown) !== null) {\n const key = this.objectKeyMap.get(item)\n if (key === undefined) {\n throw new Error(`Object not found in collection`)\n }\n return key\n }\n throw new Error(`Invalid item type for update - must be an object`)\n })\n\n // Get the current objects or empty objects if they don't exist\n const currentObjects = keys.map((key) => ({\n ...(this.state.get(key) || {}),\n })) as Array<TItem>\n\n let changesArray\n if (isArray) {\n // Use the proxy to track changes for all objects\n changesArray = withArrayChangeTracking(\n currentObjects,\n callback as (draft: Array<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = keys\n .map((key, index) => {\n const changes = changesArray[index]\n\n // Skip items with no changes\n if (!changes || Object.keys(changes).length === 0) {\n return null\n }\n\n // Validate the changes for this item\n const validatedData = this.validateData(changes, `update`, key)\n\n return {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: {\n ...(this.state.get(key) || {}),\n ...validatedData,\n } as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param items - Single item/key or array of items/keys to delete\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(todo)\n *\n * // Delete multiple items\n * delete([todo1, todo2])\n *\n * // Delete with metadata\n * delete(todo, { metadata: { reason: \"completed\" } })\n */\n delete = (\n items: Array<T | string> | T | string,\n config?: OperationConfig\n ) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.delete`\n }\n\n const itemsArray = Array.isArray(items) ? items : [items]\n const mutations: Array<PendingMutation<T>> = []\n\n for (const item of itemsArray) {\n let key: string\n if (typeof item === `object` && (item as unknown) !== null) {\n const objectKey = this.objectKeyMap.get(item)\n if (objectKey === undefined) {\n throw new Error(\n `Object not found in collection: ${JSON.stringify(item)}`\n )\n }\n key = objectKey\n } else if (typeof item === `string`) {\n key = item\n } else {\n throw new Error(\n `Invalid item type for delete - must be an object or string key`\n )\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: { _deleted: true },\n changes: { _deleted: true },\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n }\n\n // Delete object => key mapping.\n mutations.forEach((mutation) => {\n const curValue = this.state.get(mutation.key)\n if (curValue) {\n this.objectKeyMap.delete(curValue)\n }\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["config","result"],"mappings":"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"}
1
+ {"version":3,"file":"collection.js","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(new Map<string, Collection<any>>())\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<Collection<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<Collection<T>> {\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<Collection<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n next.set(\n config.id,\n new Collection<T>({\n id: config.id,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<Collection<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<Collection<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class Collection<T extends object = Record<string, unknown>> {\n public transactions: Store<SortedMap<string, Transaction>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // WeakMap to associate objects with their keys\n public objectKeyMap = new WeakMap<object, string>()\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = crypto.randomUUID()\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config?: CollectionConfig<T>) {\n if (!config?.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, Transaction>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations.map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<string, unknown>\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n const optimisticKeys = new Set<string>()\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n optimisticKeys.add(operation.key)\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n // Update object => key mappings\n optimisticKeys.forEach((key) => {\n if (combined.has(key)) {\n this.objectKeyMap.set(combined.get(key)!, key)\n }\n })\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const prevOptimisticOperations = prevDepVals?.[1] ?? []\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n prevOptimisticOperations.flat().forEach((op) => {\n changedKeys.add(op.key)\n })\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n const value = derivedState.get(key)!\n const previousValue = prevDerivedState.get(key)\n if (value !== previousValue) {\n // Comparing objects by reference as records are not mutated\n changes.push({\n type: `update`,\n key,\n value,\n previousValue,\n })\n }\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (message: ChangeMessage<T>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n const keys = new Set<string>()\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n keys.add(operation.key)\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n keys.forEach((key) => {\n const curValue = this.state.get(key)\n if (curValue) {\n this.objectKeyMap.set(curValue, key)\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\n\n const standardSchema = this.ensureStandardSchema(this.config.schema)\n\n // For updates, we need to merge with the existing data before validation\n if (type === `update` && key) {\n // Get the existing data for this key\n const existingData = this.state.get(key)\n\n if (\n existingData &&\n data &&\n typeof data === `object` &&\n typeof existingData === `object`\n ) {\n // Merge the update with the existing data\n const mergedData = { ...existingData, ...data }\n\n // Validate the merged data\n const result = standardSchema[`~standard`].validate(mergedData)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n // Return the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\n }\n }\n\n // For inserts or updates without existing data, validate the data directly\n const result = standardSchema[`~standard`].validate(data)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new TypeError(`Schema validation must be synchronous`)\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n return result.value as T\n }\n\n private generateKey(data: unknown): string {\n const str = JSON.stringify(data)\n let h = 0\n\n for (let i = 0; i < str.length; i++) {\n h = (Math.imul(31, h) + str.charCodeAt(i)) | 0\n }\n\n return `${this.id}/${Math.abs(h).toString(36)}`\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A Transaction object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.insert`\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n let keys: Array<string>\n if (config?.key) {\n const configKeys = Array.isArray(config.key) ? config.key : [config.key]\n // If keys are provided, ensure we have the right number or allow sparse array\n if (Array.isArray(config.key) && configKeys.length > items.length) {\n throw new Error(`More keys provided than items to insert`)\n }\n keys = items.map((_, i) => configKeys[i] ?? this.generateKey(items[i]))\n } else {\n // No keys provided, generate for all items\n keys = items.map((item) => this.generateKey(item))\n }\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n update<TItem extends object = T>(\n item: TItem,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: Array<TItem>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): Transaction\n\n update<TItem extends object = T>(\n items: TItem | Array<TItem>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof items === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\n\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.update`\n }\n\n const isArray = Array.isArray(items)\n const itemsArray = Array.isArray(items) ? items : [items]\n const callback =\n typeof configOrCallback === `function` ? configOrCallback : maybeCallback!\n const config =\n typeof configOrCallback === `function` ? {} : configOrCallback\n\n const keys = itemsArray.map((item) => {\n if (typeof item === `object` && (item as unknown) !== null) {\n const key = this.objectKeyMap.get(item)\n if (key === undefined) {\n throw new Error(`Object not found in collection`)\n }\n return key\n }\n throw new Error(`Invalid item type for update - must be an object`)\n })\n\n // Get the current objects or empty objects if they don't exist\n const currentObjects = keys.map((key) => ({\n ...(this.state.get(key) || {}),\n })) as Array<TItem>\n\n let changesArray\n if (isArray) {\n // Use the proxy to track changes for all objects\n changesArray = withArrayChangeTracking(\n currentObjects,\n callback as (draft: Array<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = keys\n .map((key, index) => {\n const changes = changesArray[index]\n\n // Skip items with no changes\n if (!changes || Object.keys(changes).length === 0) {\n return null\n }\n\n // Validate the changes for this item\n const validatedData = this.validateData(changes, `update`, key)\n\n return {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: {\n ...(this.state.get(key) || {}),\n ...validatedData,\n } as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param items - Single item/key or array of items/keys to delete\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(todo)\n *\n * // Delete multiple items\n * delete([todo1, todo2])\n *\n * // Delete with metadata\n * delete(todo, { metadata: { reason: \"completed\" } })\n */\n delete = (\n items: Array<T | string> | T | string,\n config?: OperationConfig\n ) => {\n const transaction = getActiveTransaction()\n if (typeof transaction === `undefined`) {\n throw `no transaction found when calling collection.delete`\n }\n\n const itemsArray = Array.isArray(items) ? items : [items]\n const mutations: Array<PendingMutation<T>> = []\n\n for (const item of itemsArray) {\n let key: string\n if (typeof item === `object` && (item as unknown) !== null) {\n const objectKey = this.objectKeyMap.get(item)\n if (objectKey === undefined) {\n throw new Error(\n `Object not found in collection: ${JSON.stringify(item)}`\n )\n }\n key = objectKey\n } else if (typeof item === `string`) {\n key = item\n } else {\n throw new Error(\n `Invalid item type for delete - must be an object or string key`\n )\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(key) || {}) as Record<string, unknown>,\n modified: { _deleted: true },\n changes: { _deleted: true },\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(key) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n\n mutations.push(mutation)\n }\n\n // Delete object => key mapping.\n mutations.forEach((mutation) => {\n const curValue = this.state.get(mutation.key)\n if (curValue) {\n this.objectKeyMap.delete(curValue)\n }\n })\n\n transaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(transaction.id, transaction)\n return sortedMap\n })\n\n return transaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["config","result"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { batch } from "@tanstack/store";
1
2
  import { createDeferred } from "./deferred.js";
2
3
  function generateUUID() {
3
4
  if (typeof crypto !== `undefined` && typeof crypto.randomUUID === `function`) {
@@ -116,8 +117,10 @@ class Transaction {
116
117
  }
117
118
  try {
118
119
  await this.mutationFn({ transaction: this });
119
- this.setState(`completed`);
120
- this.touchCollection();
120
+ batch(() => {
121
+ this.setState(`completed`);
122
+ this.touchCollection();
123
+ });
121
124
  this.isPersisted.resolve(this);
122
125
  } catch (error) {
123
126
  this.error = {
@@ -1 +1 @@
1
- {"version":3,"file":"transactions.js","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nlet transactionStack: Array<Transaction> = []\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same keys\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationKeys = new Set()\n this.mutations.forEach((m) => mutationKeys.add(m.key))\n transactions.forEach(\n (t) =>\n t.state === `pending` &&\n t.mutations.some((m) => mutationKeys.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n )\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n this.mutations.forEach((mutation) => {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n })\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n }\n\n // Run mutationFn\n try {\n await this.mutationFn({ transaction: this })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transactions.js","sources":["../../src/transactions.ts"],"sourcesContent":["import { batch } from \"@tanstack/store\"\nimport { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nlet transactionStack: Array<Transaction> = []\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same keys\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationKeys = new Set()\n this.mutations.forEach((m) => mutationKeys.add(m.key))\n transactions.forEach(\n (t) =>\n t.state === `pending` &&\n t.mutations.some((m) => mutationKeys.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n )\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n this.mutations.forEach((mutation) => {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n })\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n }\n\n // Run mutationFn\n try {\n await this.mutationFn({ transaction: this })\n\n batch(() => {\n this.setState(`completed`)\n this.touchCollection()\n })\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/db",
3
3
  "description": "A reactive client store for building super fast apps on sync",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "dependencies": {
6
6
  "@electric-sql/d2ts": "^0.1.5",
7
7
  "@standard-schema/spec": "^1.0.0",
@@ -29,7 +29,6 @@
29
29
  ],
30
30
  "main": "dist/cjs/index.cjs",
31
31
  "module": "dist/esm/index.js",
32
- "packageManager": "pnpm@10.5.2",
33
32
  "peerDependencies": {
34
33
  "typescript": ">=4.7"
35
34
  },
@@ -45,13 +44,13 @@
45
44
  "optimistic",
46
45
  "typescript"
47
46
  ],
47
+ "sideEffects": false,
48
+ "type": "module",
49
+ "types": "dist/esm/index.d.ts",
48
50
  "scripts": {
49
51
  "build": "vite build",
50
52
  "dev": "vite build --watch",
51
53
  "lint": "eslint . --fix",
52
54
  "test": "npx vitest --run"
53
- },
54
- "sideEffects": false,
55
- "type": "module",
56
- "types": "dist/esm/index.d.ts"
57
- }
55
+ }
56
+ }
package/src/collection.ts CHANGED
@@ -288,11 +288,15 @@ export class Collection<T extends object = Record<string, unknown>> {
288
288
  prevDepVals,
289
289
  }) => {
290
290
  const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()
291
+ const prevOptimisticOperations = prevDepVals?.[1] ?? []
291
292
  const changedKeys = new Set(this.syncedKeys)
292
293
  optimisticOperations
293
294
  .flat()
294
295
  .filter((op) => op.isActive)
295
296
  .forEach((op) => changedKeys.add(op.key))
297
+ prevOptimisticOperations.flat().forEach((op) => {
298
+ changedKeys.add(op.key)
299
+ })
296
300
 
297
301
  if (changedKeys.size === 0) {
298
302
  return []
@@ -309,12 +313,17 @@ export class Collection<T extends object = Record<string, unknown>> {
309
313
  } else if (!prevDerivedState.has(key) && derivedState.has(key)) {
310
314
  changes.push({ type: `insert`, key, value: derivedState.get(key)! })
311
315
  } else if (prevDerivedState.has(key) && derivedState.has(key)) {
312
- changes.push({
313
- type: `update`,
314
- key,
315
- value: derivedState.get(key)!,
316
- previousValue: prevDerivedState.get(key),
317
- })
316
+ const value = derivedState.get(key)!
317
+ const previousValue = prevDerivedState.get(key)
318
+ if (value !== previousValue) {
319
+ // Comparing objects by reference as records are not mutated
320
+ changes.push({
321
+ type: `update`,
322
+ key,
323
+ value,
324
+ previousValue,
325
+ })
326
+ }
318
327
  }
319
328
  }
320
329
 
@@ -1,3 +1,4 @@
1
+ import { batch } from "@tanstack/store"
1
2
  import { createDeferred } from "./deferred"
2
3
  import type { Deferred } from "./deferred"
3
4
  import type {
@@ -178,8 +179,10 @@ export class Transaction {
178
179
  try {
179
180
  await this.mutationFn({ transaction: this })
180
181
 
181
- this.setState(`completed`)
182
- this.touchCollection()
182
+ batch(() => {
183
+ this.setState(`completed`)
184
+ this.touchCollection()
185
+ })
183
186
 
184
187
  this.isPersisted.resolve(this)
185
188
  } catch (error) {