@tanstack/db 0.0.16 → 0.0.18

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.
Files changed (54) hide show
  1. package/dist/cjs/SortedMap.cjs.map +1 -1
  2. package/dist/cjs/collection.cjs +53 -8
  3. package/dist/cjs/collection.cjs.map +1 -1
  4. package/dist/cjs/collection.d.cts +150 -45
  5. package/dist/cjs/deferred.cjs.map +1 -1
  6. package/dist/cjs/errors.cjs.map +1 -1
  7. package/dist/cjs/optimistic-action.cjs.map +1 -1
  8. package/dist/cjs/proxy.cjs.map +1 -1
  9. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  10. package/dist/cjs/query/builder/index.cjs.map +1 -1
  11. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  12. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  13. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  14. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  15. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  16. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  17. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  18. package/dist/cjs/query/ir.cjs.map +1 -1
  19. package/dist/cjs/query/live-query-collection.cjs +1 -1
  20. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  21. package/dist/cjs/transactions.cjs +116 -0
  22. package/dist/cjs/transactions.cjs.map +1 -1
  23. package/dist/cjs/transactions.d.cts +179 -0
  24. package/dist/cjs/types.d.cts +170 -12
  25. package/dist/esm/SortedMap.js.map +1 -1
  26. package/dist/esm/collection.d.ts +150 -45
  27. package/dist/esm/collection.js +53 -8
  28. package/dist/esm/collection.js.map +1 -1
  29. package/dist/esm/deferred.js.map +1 -1
  30. package/dist/esm/errors.js.map +1 -1
  31. package/dist/esm/optimistic-action.js.map +1 -1
  32. package/dist/esm/proxy.js.map +1 -1
  33. package/dist/esm/query/builder/functions.js.map +1 -1
  34. package/dist/esm/query/builder/index.js.map +1 -1
  35. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  36. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  37. package/dist/esm/query/compiler/group-by.js.map +1 -1
  38. package/dist/esm/query/compiler/index.js.map +1 -1
  39. package/dist/esm/query/compiler/joins.js.map +1 -1
  40. package/dist/esm/query/compiler/order-by.js.map +1 -1
  41. package/dist/esm/query/compiler/select.js.map +1 -1
  42. package/dist/esm/query/ir.js.map +1 -1
  43. package/dist/esm/query/live-query-collection.js +1 -1
  44. package/dist/esm/query/live-query-collection.js.map +1 -1
  45. package/dist/esm/transactions.d.ts +179 -0
  46. package/dist/esm/transactions.js +116 -0
  47. package/dist/esm/transactions.js.map +1 -1
  48. package/dist/esm/types.d.ts +170 -12
  49. package/package.json +2 -2
  50. package/src/collection.ts +175 -55
  51. package/src/proxy.ts +0 -1
  52. package/src/query/live-query-collection.ts +2 -1
  53. package/src/transactions.ts +179 -0
  54. package/src/types.ts +200 -27
@@ -140,26 +140,43 @@ export interface OperationConfig {
140
140
  export interface InsertConfig {
141
141
  metadata?: Record<string, unknown>;
142
142
  }
143
- export type UpdateMutationFnParams<T extends object = Record<string, unknown>> = {
143
+ export type UpdateMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = Record<string, Fn>> = {
144
144
  transaction: TransactionWithMutations<T, `update`>;
145
+ collection: Collection<T, TKey, TUtils>;
145
146
  };
146
- export type InsertMutationFnParams<T extends object = Record<string, unknown>> = {
147
+ export type InsertMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = Record<string, Fn>> = {
147
148
  transaction: TransactionWithMutations<T, `insert`>;
149
+ collection: Collection<T, TKey, TUtils>;
148
150
  };
149
- export type DeleteMutationFnParams<T extends object = Record<string, unknown>> = {
151
+ export type DeleteMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = Record<string, Fn>> = {
150
152
  transaction: TransactionWithMutations<T, `delete`>;
153
+ collection: Collection<T, TKey, TUtils>;
151
154
  };
152
- export type InsertMutationFn<T extends object = Record<string, unknown>> = (params: InsertMutationFnParams<T>) => Promise<any>;
153
- export type UpdateMutationFn<T extends object = Record<string, unknown>> = (params: UpdateMutationFnParams<T>) => Promise<any>;
154
- export type DeleteMutationFn<T extends object = Record<string, unknown>> = (params: DeleteMutationFnParams<T>) => Promise<any>;
155
+ export type InsertMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = Record<string, Fn>> = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<any>;
156
+ export type UpdateMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = Record<string, Fn>> = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<any>;
157
+ export type DeleteMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = Record<string, Fn>> = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<any>;
155
158
  /**
156
159
  * Collection status values for lifecycle management
160
+ * @example
161
+ * // Check collection status
162
+ * if (collection.status === "loading") {
163
+ * console.log("Collection is loading initial data")
164
+ * } else if (collection.status === "ready") {
165
+ * console.log("Collection is ready for use")
166
+ * }
167
+ *
168
+ * @example
169
+ * // Status transitions
170
+ * // idle → loading → initialCommit → ready
171
+ * // Any status can transition to → error or cleaned-up
157
172
  */
158
173
  export type CollectionStatus =
159
174
  /** Collection is created but sync hasn't started yet (when startSync config is false) */
160
175
  `idle`
161
176
  /** Sync has started but hasn't received the first commit yet */
162
177
  | `loading`
178
+ /** Collection is in the process of committing its first transaction */
179
+ | `initialCommit`
163
180
  /** Collection has received at least one commit and is ready for use */
164
181
  | `ready`
165
182
  /** An error occurred during sync initialization */
@@ -203,22 +220,132 @@ export interface CollectionConfig<T extends object = Record<string, unknown>, TK
203
220
  compare?: (x: T, y: T) => number;
204
221
  /**
205
222
  * Optional asynchronous handler function called before an insert operation
206
- * @param params Object containing transaction and mutation information
223
+ * @param params Object containing transaction and collection information
207
224
  * @returns Promise resolving to any value
225
+ * @example
226
+ * // Basic insert handler
227
+ * onInsert: async ({ transaction, collection }) => {
228
+ * const newItem = transaction.mutations[0].modified
229
+ * await api.createTodo(newItem)
230
+ * }
231
+ *
232
+ * @example
233
+ * // Insert handler with multiple items
234
+ * onInsert: async ({ transaction, collection }) => {
235
+ * const items = transaction.mutations.map(m => m.modified)
236
+ * await api.createTodos(items)
237
+ * }
238
+ *
239
+ * @example
240
+ * // Insert handler with error handling
241
+ * onInsert: async ({ transaction, collection }) => {
242
+ * try {
243
+ * const newItem = transaction.mutations[0].modified
244
+ * const result = await api.createTodo(newItem)
245
+ * return result
246
+ * } catch (error) {
247
+ * console.error('Insert failed:', error)
248
+ * throw error // This will cause the transaction to fail
249
+ * }
250
+ * }
251
+ *
252
+ * @example
253
+ * // Insert handler with metadata
254
+ * onInsert: async ({ transaction, collection }) => {
255
+ * const mutation = transaction.mutations[0]
256
+ * await api.createTodo(mutation.modified, {
257
+ * source: mutation.metadata?.source,
258
+ * timestamp: mutation.createdAt
259
+ * })
260
+ * }
208
261
  */
209
- onInsert?: InsertMutationFn<T>;
262
+ onInsert?: InsertMutationFn<T, TKey>;
210
263
  /**
211
264
  * Optional asynchronous handler function called before an update operation
212
- * @param params Object containing transaction and mutation information
265
+ * @param params Object containing transaction and collection information
213
266
  * @returns Promise resolving to any value
267
+ * @example
268
+ * // Basic update handler
269
+ * onUpdate: async ({ transaction, collection }) => {
270
+ * const updatedItem = transaction.mutations[0].modified
271
+ * await api.updateTodo(updatedItem.id, updatedItem)
272
+ * }
273
+ *
274
+ * @example
275
+ * // Update handler with partial updates
276
+ * onUpdate: async ({ transaction, collection }) => {
277
+ * const mutation = transaction.mutations[0]
278
+ * const changes = mutation.changes // Only the changed fields
279
+ * await api.updateTodo(mutation.original.id, changes)
280
+ * }
281
+ *
282
+ * @example
283
+ * // Update handler with multiple items
284
+ * onUpdate: async ({ transaction, collection }) => {
285
+ * const updates = transaction.mutations.map(m => ({
286
+ * id: m.key,
287
+ * changes: m.changes
288
+ * }))
289
+ * await api.updateTodos(updates)
290
+ * }
291
+ *
292
+ * @example
293
+ * // Update handler with optimistic rollback
294
+ * onUpdate: async ({ transaction, collection }) => {
295
+ * const mutation = transaction.mutations[0]
296
+ * try {
297
+ * await api.updateTodo(mutation.original.id, mutation.changes)
298
+ * } catch (error) {
299
+ * // Transaction will automatically rollback optimistic changes
300
+ * console.error('Update failed, rolling back:', error)
301
+ * throw error
302
+ * }
303
+ * }
214
304
  */
215
- onUpdate?: UpdateMutationFn<T>;
305
+ onUpdate?: UpdateMutationFn<T, TKey>;
216
306
  /**
217
307
  * Optional asynchronous handler function called before a delete operation
218
- * @param params Object containing transaction and mutation information
308
+ * @param params Object containing transaction and collection information
219
309
  * @returns Promise resolving to any value
310
+ * @example
311
+ * // Basic delete handler
312
+ * onDelete: async ({ transaction, collection }) => {
313
+ * const deletedKey = transaction.mutations[0].key
314
+ * await api.deleteTodo(deletedKey)
315
+ * }
316
+ *
317
+ * @example
318
+ * // Delete handler with multiple items
319
+ * onDelete: async ({ transaction, collection }) => {
320
+ * const keysToDelete = transaction.mutations.map(m => m.key)
321
+ * await api.deleteTodos(keysToDelete)
322
+ * }
323
+ *
324
+ * @example
325
+ * // Delete handler with confirmation
326
+ * onDelete: async ({ transaction, collection }) => {
327
+ * const mutation = transaction.mutations[0]
328
+ * const shouldDelete = await confirmDeletion(mutation.original)
329
+ * if (!shouldDelete) {
330
+ * throw new Error('Delete cancelled by user')
331
+ * }
332
+ * await api.deleteTodo(mutation.original.id)
333
+ * }
334
+ *
335
+ * @example
336
+ * // Delete handler with optimistic rollback
337
+ * onDelete: async ({ transaction, collection }) => {
338
+ * const mutation = transaction.mutations[0]
339
+ * try {
340
+ * await api.deleteTodo(mutation.original.id)
341
+ * } catch (error) {
342
+ * // Transaction will automatically rollback optimistic changes
343
+ * console.error('Delete failed, rolling back:', error)
344
+ * throw error
345
+ * }
346
+ * }
220
347
  */
221
- onDelete?: DeleteMutationFn<T>;
348
+ onDelete?: DeleteMutationFn<T, TKey>;
222
349
  }
223
350
  export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
224
351
  /**
@@ -250,4 +377,35 @@ export type KeyedNamespacedRow = [unknown, NamespacedRow];
250
377
  * a `select` clause.
251
378
  */
252
379
  export type NamespacedAndKeyedStream = IStreamBuilder<KeyedNamespacedRow>;
380
+ /**
381
+ * Function type for listening to collection changes
382
+ * @param changes - Array of change messages describing what happened
383
+ * @example
384
+ * // Basic change listener
385
+ * const listener: ChangeListener = (changes) => {
386
+ * changes.forEach(change => {
387
+ * console.log(`${change.type}: ${change.key}`, change.value)
388
+ * })
389
+ * }
390
+ *
391
+ * collection.subscribeChanges(listener)
392
+ *
393
+ * @example
394
+ * // Handle different change types
395
+ * const listener: ChangeListener<Todo> = (changes) => {
396
+ * for (const change of changes) {
397
+ * switch (change.type) {
398
+ * case 'insert':
399
+ * addToUI(change.value)
400
+ * break
401
+ * case 'update':
402
+ * updateInUI(change.key, change.value, change.previousValue)
403
+ * break
404
+ * case 'delete':
405
+ * removeFromUI(change.key)
406
+ * break
407
+ * }
408
+ * }
409
+ * }
410
+ */
253
411
  export type ChangeListener<T extends object = Record<string, unknown>, TKey extends string | number = string | number> = (changes: Array<ChangeMessage<T, TKey>>) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"SortedMap.js","sources":["../../src/SortedMap.ts"],"sourcesContent":["/**\n * A Map implementation that keeps its entries sorted based on a comparator function\n * @template TKey - The type of keys in the map\n * @template TValue - The type of values in the map\n */\nexport class SortedMap<TKey, TValue> {\n private map: Map<TKey, TValue>\n private sortedKeys: Array<TKey>\n private comparator: (a: TValue, b: TValue) => number\n\n /**\n * Creates a new SortedMap instance\n *\n * @param comparator - Optional function to compare values for sorting\n */\n constructor(comparator?: (a: TValue, b: TValue) => number) {\n this.map = new Map<TKey, TValue>()\n this.sortedKeys = []\n this.comparator = comparator || this.defaultComparator\n }\n\n /**\n * Default comparator function used when none is provided\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns -1 if a < b, 1 if a > b, 0 if equal\n */\n private defaultComparator(a: TValue, b: TValue): number {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n\n /**\n * Finds the index where a key-value pair should be inserted to maintain sort order.\n * Uses binary search to find the correct position based on the value.\n * Hence, it is in O(log n) time.\n *\n * @param key - The key to find position for\n * @param value - The value to compare against\n * @returns The index where the key should be inserted\n */\n private indexOf(value: TValue): number {\n let left = 0\n let right = this.sortedKeys.length\n\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const midKey = this.sortedKeys[mid]!\n const midValue = this.map.get(midKey)!\n const comparison = this.comparator(value, midValue)\n\n if (comparison < 0) {\n right = mid\n } else if (comparison > 0) {\n left = mid + 1\n } else {\n return mid\n }\n }\n\n return left\n }\n\n /**\n * Sets a key-value pair in the map and maintains sort order\n *\n * @param key - The key to set\n * @param value - The value to associate with the key\n * @returns This SortedMap instance for chaining\n */\n set(key: TKey, value: TValue): this {\n if (this.map.has(key)) {\n // Need to remove the old key from the sorted keys array\n const oldValue = this.map.get(key)!\n const oldIndex = this.indexOf(oldValue)\n this.sortedKeys.splice(oldIndex, 1)\n }\n\n // Insert the new key at the correct position\n const index = this.indexOf(value)\n this.sortedKeys.splice(index, 0, key)\n\n this.map.set(key, value)\n\n return this\n }\n\n /**\n * Gets a value by its key\n *\n * @param key - The key to look up\n * @returns The value associated with the key, or undefined if not found\n */\n get(key: TKey): TValue | undefined {\n return this.map.get(key)\n }\n\n /**\n * Removes a key-value pair from the map\n *\n * @param key - The key to remove\n * @returns True if the key was found and removed, false otherwise\n */\n delete(key: TKey): boolean {\n if (this.map.has(key)) {\n const oldValue = this.map.get(key)\n const index = this.indexOf(oldValue!)\n this.sortedKeys.splice(index, 1)\n return this.map.delete(key)\n }\n\n return false\n }\n\n /**\n * Checks if a key exists in the map\n *\n * @param key - The key to check\n * @returns True if the key exists, false otherwise\n */\n has(key: TKey): boolean {\n return this.map.has(key)\n }\n\n /**\n * Removes all key-value pairs from the map\n */\n clear(): void {\n this.map.clear()\n this.sortedKeys = []\n }\n\n /**\n * Gets the number of key-value pairs in the map\n */\n get size(): number {\n return this.map.size\n }\n\n /**\n * Default iterator that returns entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n *[Symbol.iterator](): IterableIterator<[TKey, TValue]> {\n for (const key of this.sortedKeys) {\n yield [key, this.map.get(key)!] as [TKey, TValue]\n }\n }\n\n /**\n * Returns an iterator for the map's entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n entries(): IterableIterator<[TKey, TValue]> {\n return this[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's keys in sorted order\n *\n * @returns An iterator for the map's keys\n */\n keys(): IterableIterator<TKey> {\n return this.sortedKeys[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's values in sorted order\n *\n * @returns An iterator for the map's values\n */\n values(): IterableIterator<TValue> {\n return function* (this: SortedMap<TKey, TValue>) {\n for (const key of this.sortedKeys) {\n yield this.map.get(key)!\n }\n }.call(this)\n }\n\n /**\n * Executes a callback function for each key-value pair in the map in sorted order\n *\n * @param callbackfn - Function to execute for each entry\n */\n forEach(\n callbackfn: (value: TValue, key: TKey, map: Map<TKey, TValue>) => void\n ): void {\n for (const key of this.sortedKeys) {\n callbackfn(this.map.get(key)!, key, this.map)\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SortedMap.js","sources":["../../src/SortedMap.ts"],"sourcesContent":["/**\n * A Map implementation that keeps its entries sorted based on a comparator function\n * @template TKey - The type of keys in the map\n * @template TValue - The type of values in the map\n */\nexport class SortedMap<TKey, TValue> {\n private map: Map<TKey, TValue>\n private sortedKeys: Array<TKey>\n private comparator: (a: TValue, b: TValue) => number\n\n /**\n * Creates a new SortedMap instance\n *\n * @param comparator - Optional function to compare values for sorting\n */\n constructor(comparator?: (a: TValue, b: TValue) => number) {\n this.map = new Map<TKey, TValue>()\n this.sortedKeys = []\n this.comparator = comparator || this.defaultComparator\n }\n\n /**\n * Default comparator function used when none is provided\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns -1 if a < b, 1 if a > b, 0 if equal\n */\n private defaultComparator(a: TValue, b: TValue): number {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n\n /**\n * Finds the index where a key-value pair should be inserted to maintain sort order.\n * Uses binary search to find the correct position based on the value.\n * Hence, it is in O(log n) time.\n *\n * @param key - The key to find position for\n * @param value - The value to compare against\n * @returns The index where the key should be inserted\n */\n private indexOf(value: TValue): number {\n let left = 0\n let right = this.sortedKeys.length\n\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const midKey = this.sortedKeys[mid]!\n const midValue = this.map.get(midKey)!\n const comparison = this.comparator(value, midValue)\n\n if (comparison < 0) {\n right = mid\n } else if (comparison > 0) {\n left = mid + 1\n } else {\n return mid\n }\n }\n\n return left\n }\n\n /**\n * Sets a key-value pair in the map and maintains sort order\n *\n * @param key - The key to set\n * @param value - The value to associate with the key\n * @returns This SortedMap instance for chaining\n */\n set(key: TKey, value: TValue): this {\n if (this.map.has(key)) {\n // Need to remove the old key from the sorted keys array\n const oldValue = this.map.get(key)!\n const oldIndex = this.indexOf(oldValue)\n this.sortedKeys.splice(oldIndex, 1)\n }\n\n // Insert the new key at the correct position\n const index = this.indexOf(value)\n this.sortedKeys.splice(index, 0, key)\n\n this.map.set(key, value)\n\n return this\n }\n\n /**\n * Gets a value by its key\n *\n * @param key - The key to look up\n * @returns The value associated with the key, or undefined if not found\n */\n get(key: TKey): TValue | undefined {\n return this.map.get(key)\n }\n\n /**\n * Removes a key-value pair from the map\n *\n * @param key - The key to remove\n * @returns True if the key was found and removed, false otherwise\n */\n delete(key: TKey): boolean {\n if (this.map.has(key)) {\n const oldValue = this.map.get(key)\n const index = this.indexOf(oldValue!)\n this.sortedKeys.splice(index, 1)\n return this.map.delete(key)\n }\n\n return false\n }\n\n /**\n * Checks if a key exists in the map\n *\n * @param key - The key to check\n * @returns True if the key exists, false otherwise\n */\n has(key: TKey): boolean {\n return this.map.has(key)\n }\n\n /**\n * Removes all key-value pairs from the map\n */\n clear(): void {\n this.map.clear()\n this.sortedKeys = []\n }\n\n /**\n * Gets the number of key-value pairs in the map\n */\n get size(): number {\n return this.map.size\n }\n\n /**\n * Default iterator that returns entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n *[Symbol.iterator](): IterableIterator<[TKey, TValue]> {\n for (const key of this.sortedKeys) {\n yield [key, this.map.get(key)!] as [TKey, TValue]\n }\n }\n\n /**\n * Returns an iterator for the map's entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n entries(): IterableIterator<[TKey, TValue]> {\n return this[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's keys in sorted order\n *\n * @returns An iterator for the map's keys\n */\n keys(): IterableIterator<TKey> {\n return this.sortedKeys[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's values in sorted order\n *\n * @returns An iterator for the map's values\n */\n values(): IterableIterator<TValue> {\n return function* (this: SortedMap<TKey, TValue>) {\n for (const key of this.sortedKeys) {\n yield this.map.get(key)!\n }\n }.call(this)\n }\n\n /**\n * Executes a callback function for each key-value pair in the map in sorted order\n *\n * @param callbackfn - Function to execute for each entry\n */\n forEach(\n callbackfn: (value: TValue, key: TKey, map: Map<TKey, TValue>) => void\n ): void {\n for (const key of this.sortedKeys) {\n callbackfn(this.map.get(key)!, key, this.map)\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { SortedMap } from './SortedMap.js';
2
2
  import { Transaction } from './transactions.js';
3
3
  import { ChangeListener, ChangeMessage, CollectionConfig, CollectionStatus, Fn, InsertConfig, OperationConfig, OptimisticChangeMessage, ResolveType, Transaction as TransactionType, UtilsRecord } from './types.js';
4
4
  import { StandardSchemaV1 } from '@standard-schema/spec';
5
- export declare const collectionsStore: Map<string, CollectionImpl<any, any>>;
5
+ export declare const collectionsStore: Map<string, CollectionImpl<any, any, {}>>;
6
6
  interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
7
7
  committed: boolean;
8
8
  operations: Array<OptimisticChangeMessage<T>>;
@@ -28,12 +28,52 @@ export interface Collection<T extends object = Record<string, unknown>, TKey ext
28
28
  * @returns A new Collection with utilities exposed both at top level and under .utils
29
29
  *
30
30
  * @example
31
- * // Using explicit type
32
- * const todos = createCollection<Todo>({
31
+ * // Pattern 1: With operation handlers (direct collection calls)
32
+ * const todos = createCollection({
33
+ * id: "todos",
34
+ * getKey: (todo) => todo.id,
35
+ * schema,
36
+ * onInsert: async ({ transaction, collection }) => {
37
+ * // Send to API
38
+ * await api.createTodo(transaction.mutations[0].modified)
39
+ * },
40
+ * onUpdate: async ({ transaction, collection }) => {
41
+ * await api.updateTodo(transaction.mutations[0].modified)
42
+ * },
43
+ * onDelete: async ({ transaction, collection }) => {
44
+ * await api.deleteTodo(transaction.mutations[0].key)
45
+ * },
46
+ * sync: { sync: () => {} }
47
+ * })
48
+ *
49
+ * // Direct usage (handlers manage transactions)
50
+ * const tx = todos.insert({ id: "1", text: "Buy milk", completed: false })
51
+ * await tx.isPersisted.promise
52
+ *
53
+ * @example
54
+ * // Pattern 2: Manual transaction management
55
+ * const todos = createCollection({
33
56
  * getKey: (todo) => todo.id,
57
+ * schema: todoSchema,
34
58
  * sync: { sync: () => {} }
35
59
  * })
36
60
  *
61
+ * // Explicit transaction usage
62
+ * const tx = createTransaction({
63
+ * mutationFn: async ({ transaction }) => {
64
+ * // Handle all mutations in transaction
65
+ * await api.saveChanges(transaction.mutations)
66
+ * }
67
+ * })
68
+ *
69
+ * tx.mutate(() => {
70
+ * todos.insert({ id: "1", text: "Buy milk" })
71
+ * todos.update("2", draft => { draft.completed = true })
72
+ * })
73
+ *
74
+ * await tx.isPersisted.promise
75
+ *
76
+ * @example
37
77
  * // Using schema for type inference (preferred as it also gives you client side validation)
38
78
  * const todoSchema = z.object({
39
79
  * id: z.string(),
@@ -47,7 +87,7 @@ export interface Collection<T extends object = Record<string, unknown>, TKey ext
47
87
  * sync: { sync: () => {} }
48
88
  * })
49
89
  *
50
- * // Note: You must provide either an explicit type or a schema, but not both
90
+ * // Note: You must provide either an explicit type or a schema, but not both.
51
91
  */
52
92
  export declare function createCollection<TExplicit = unknown, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}, TSchema extends StandardSchemaV1 = StandardSchemaV1, TFallback extends object = Record<string, unknown>>(options: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>, TKey, TSchema> & {
53
93
  utils?: TUtils;
@@ -66,7 +106,7 @@ export declare class SchemaValidationError extends Error {
66
106
  path?: ReadonlyArray<string | number | symbol>;
67
107
  }>, message?: string);
68
108
  }
69
- export declare class CollectionImpl<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
109
+ export declare class CollectionImpl<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}> {
70
110
  config: CollectionConfig<T, TKey, any>;
71
111
  transactions: SortedMap<string, Transaction<any>>;
72
112
  pendingSyncedTransactions: Array<PendingSyncedTransaction<T>>;
@@ -95,6 +135,11 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
95
135
  * Register a callback to be executed on the next commit
96
136
  * Useful for preloading collections
97
137
  * @param callback Function to call after the next commit
138
+ * @example
139
+ * collection.onFirstCommit(() => {
140
+ * console.log('Collection has received first data')
141
+ * // Safe to access collection.state now
142
+ * })
98
143
  */
99
144
  onFirstCommit(callback: () => void): void;
100
145
  id: string;
@@ -231,60 +276,78 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
231
276
  /**
232
277
  * Inserts one or more items into the collection
233
278
  * @param items - Single item or array of items to insert
234
- * @param config - Optional configuration including metadata and custom keys
235
- * @returns A TransactionType object representing the insert operation(s)
279
+ * @param config - Optional configuration including metadata
280
+ * @returns A Transaction object representing the insert operation(s)
236
281
  * @throws {SchemaValidationError} If the data fails schema validation
237
282
  * @example
238
- * // Insert a single item
239
- * insert({ text: "Buy groceries", completed: false })
283
+ * // Insert a single todo (requires onInsert handler)
284
+ * const tx = collection.insert({ id: "1", text: "Buy milk", completed: false })
285
+ * await tx.isPersisted.promise
240
286
  *
241
- * // Insert multiple items
242
- * insert([
243
- * { text: "Buy groceries", completed: false },
244
- * { text: "Walk dog", completed: false }
287
+ * @example
288
+ * // Insert multiple todos at once
289
+ * const tx = collection.insert([
290
+ * { id: "1", text: "Buy milk", completed: false },
291
+ * { id: "2", text: "Walk dog", completed: true }
245
292
  * ])
293
+ * await tx.isPersisted.promise
294
+ *
295
+ * @example
296
+ * // Insert with metadata
297
+ * const tx = collection.insert({ id: "1", text: "Buy groceries" },
298
+ * { metadata: { source: "mobile-app" } }
299
+ * )
300
+ * await tx.isPersisted.promise
246
301
  *
247
- * // Insert with custom key
248
- * insert({ text: "Buy groceries" }, { key: "grocery-task" })
302
+ * @example
303
+ * // Handle errors
304
+ * try {
305
+ * const tx = collection.insert({ id: "1", text: "New item" })
306
+ * await tx.isPersisted.promise
307
+ * console.log('Insert successful')
308
+ * } catch (error) {
309
+ * console.log('Insert failed:', error)
310
+ * }
249
311
  */
250
312
  insert: (data: T | Array<T>, config?: InsertConfig) => Transaction<Record<string, unknown>, import('./types.js').OperationType>;
251
313
  /**
252
314
  * Updates one or more items in the collection using a callback function
253
- * @param items - Single item/key or array of items/keys to update
315
+ * @param keys - Single key or array of keys to update
254
316
  * @param configOrCallback - Either update configuration or update callback
255
317
  * @param maybeCallback - Update callback if config was provided
256
318
  * @returns A Transaction object representing the update operation(s)
257
319
  * @throws {SchemaValidationError} If the updated data fails schema validation
258
320
  * @example
259
- * // Update a single item
260
- * update(todo, (draft) => { draft.completed = true })
261
- *
262
- * // Update multiple items
263
- * update([todo1, todo2], (drafts) => {
264
- * drafts.forEach(draft => { draft.completed = true })
321
+ * // Update single item by key
322
+ * const tx = collection.update("todo-1", (draft) => {
323
+ * draft.completed = true
265
324
  * })
325
+ * await tx.isPersisted.promise
266
326
  *
267
- * // Update with metadata
268
- * update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
269
- */
270
- /**
271
- * Updates one or more items in the collection using a callback function
272
- * @param ids - Single ID or array of IDs to update
273
- * @param configOrCallback - Either update configuration or update callback
274
- * @param maybeCallback - Update callback if config was provided
275
- * @returns A Transaction object representing the update operation(s)
276
- * @throws {SchemaValidationError} If the updated data fails schema validation
277
327
  * @example
278
- * // Update a single item
279
- * update("todo-1", (draft) => { draft.completed = true })
280
- *
281
328
  * // Update multiple items
282
- * update(["todo-1", "todo-2"], (drafts) => {
329
+ * const tx = collection.update(["todo-1", "todo-2"], (drafts) => {
283
330
  * drafts.forEach(draft => { draft.completed = true })
284
331
  * })
332
+ * await tx.isPersisted.promise
285
333
  *
334
+ * @example
286
335
  * // Update with metadata
287
- * update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
336
+ * const tx = collection.update("todo-1",
337
+ * { metadata: { reason: "user update" } },
338
+ * (draft) => { draft.text = "Updated text" }
339
+ * )
340
+ * await tx.isPersisted.promise
341
+ *
342
+ * @example
343
+ * // Handle errors
344
+ * try {
345
+ * const tx = collection.update("item-1", draft => { draft.value = "new" })
346
+ * await tx.isPersisted.promise
347
+ * console.log('Update successful')
348
+ * } catch (error) {
349
+ * console.log('Update failed:', error)
350
+ * }
288
351
  */
289
352
  update<TItem extends object = T>(key: Array<TKey | unknown>, callback: (drafts: Array<TItem>) => void): TransactionType;
290
353
  update<TItem extends object = T>(keys: Array<TKey | unknown>, config: OperationConfig, callback: (drafts: Array<TItem>) => void): TransactionType;
@@ -292,24 +355,50 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
292
355
  update<TItem extends object = T>(id: TKey | unknown, config: OperationConfig, callback: (draft: TItem) => void): TransactionType;
293
356
  /**
294
357
  * Deletes one or more items from the collection
295
- * @param ids - Single ID or array of IDs to delete
358
+ * @param keys - Single key or array of keys to delete
296
359
  * @param config - Optional configuration including metadata
297
- * @returns A TransactionType object representing the delete operation(s)
360
+ * @returns A Transaction object representing the delete operation(s)
298
361
  * @example
299
362
  * // Delete a single item
300
- * delete("todo-1")
363
+ * const tx = collection.delete("todo-1")
364
+ * await tx.isPersisted.promise
301
365
  *
366
+ * @example
302
367
  * // Delete multiple items
303
- * delete(["todo-1", "todo-2"])
368
+ * const tx = collection.delete(["todo-1", "todo-2"])
369
+ * await tx.isPersisted.promise
304
370
  *
371
+ * @example
305
372
  * // Delete with metadata
306
- * delete("todo-1", { metadata: { reason: "completed" } })
373
+ * const tx = collection.delete("todo-1", { metadata: { reason: "completed" } })
374
+ * await tx.isPersisted.promise
375
+ *
376
+ * @example
377
+ * // Handle errors
378
+ * try {
379
+ * const tx = collection.delete("item-1")
380
+ * await tx.isPersisted.promise
381
+ * console.log('Delete successful')
382
+ * } catch (error) {
383
+ * console.log('Delete failed:', error)
384
+ * }
307
385
  */
308
386
  delete: (keys: Array<TKey> | TKey, config?: OperationConfig) => TransactionType<any>;
309
387
  /**
310
388
  * Gets the current state of the collection as a Map
389
+ * @returns Map containing all items in the collection, with keys as identifiers
390
+ * @example
391
+ * const itemsMap = collection.state
392
+ * console.log(`Collection has ${itemsMap.size} items`)
311
393
  *
312
- * @returns A Map containing all items in the collection, with keys as identifiers
394
+ * for (const [key, item] of itemsMap) {
395
+ * console.log(`${key}: ${item.title}`)
396
+ * }
397
+ *
398
+ * // Check if specific item exists
399
+ * if (itemsMap.has("todo-1")) {
400
+ * console.log("Todo 1 exists:", itemsMap.get("todo-1"))
401
+ * }
313
402
  */
314
403
  get state(): Map<TKey, T>;
315
404
  /**
@@ -339,8 +428,24 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
339
428
  currentStateAsChanges(): Array<ChangeMessage<T>>;
340
429
  /**
341
430
  * Subscribe to changes in the collection
342
- * @param callback - A function that will be called with the changes in the collection
343
- * @returns A function that can be called to unsubscribe from the changes
431
+ * @param callback - Function called when items change
432
+ * @param options.includeInitialState - If true, immediately calls callback with current data
433
+ * @returns Unsubscribe function - Call this to stop listening for changes
434
+ * @example
435
+ * // Basic subscription
436
+ * const unsubscribe = collection.subscribeChanges((changes) => {
437
+ * changes.forEach(change => {
438
+ * console.log(`${change.type}: ${change.key}`, change.value)
439
+ * })
440
+ * })
441
+ *
442
+ * // Later: unsubscribe()
443
+ *
444
+ * @example
445
+ * // Include current state immediately
446
+ * const unsubscribe = collection.subscribeChanges((changes) => {
447
+ * updateUI(changes)
448
+ * }, { includeInitialState: true })
344
449
  */
345
450
  subscribeChanges(callback: (changes: Array<ChangeMessage<T>>) => void, { includeInitialState }?: {
346
451
  includeInitialState?: boolean;
@@ -247,7 +247,10 @@ class CollectionImpl {
247
247
  } else {
248
248
  const directOpTransaction = createTransaction({
249
249
  mutationFn: async (params) => {
250
- return this.config.onInsert(params);
250
+ return this.config.onInsert({
251
+ ...params,
252
+ collection: this
253
+ });
251
254
  }
252
255
  });
253
256
  directOpTransaction.applyMutations(mutations);
@@ -302,7 +305,10 @@ class CollectionImpl {
302
305
  const directOpTransaction = createTransaction({
303
306
  autoCommit: true,
304
307
  mutationFn: async (params) => {
305
- return this.config.onDelete(params);
308
+ return this.config.onDelete({
309
+ ...params,
310
+ collection: this
311
+ });
306
312
  }
307
313
  });
308
314
  directOpTransaction.applyMutations(mutations);
@@ -340,6 +346,11 @@ class CollectionImpl {
340
346
  * Register a callback to be executed on the next commit
341
347
  * Useful for preloading collections
342
348
  * @param callback Function to call after the next commit
349
+ * @example
350
+ * collection.onFirstCommit(() => {
351
+ * console.log('Collection has received first data')
352
+ * // Safe to access collection.state now
353
+ * })
343
354
  */
344
355
  onFirstCommit(callback) {
345
356
  this.onFirstCommitCallbacks.push(callback);
@@ -376,7 +387,8 @@ class CollectionImpl {
376
387
  }
377
388
  const validTransitions = {
378
389
  idle: [`loading`, `error`, `cleaned-up`],
379
- loading: [`ready`, `error`, `cleaned-up`],
390
+ loading: [`initialCommit`, `error`, `cleaned-up`],
391
+ initialCommit: [`ready`, `error`, `cleaned-up`],
380
392
  ready: [`cleaned-up`, `error`],
381
393
  error: [`cleaned-up`, `idle`],
382
394
  "cleaned-up": [`loading`, `error`]
@@ -458,9 +470,12 @@ class CollectionImpl {
458
470
  }
459
471
  pendingTransaction.committed = true;
460
472
  if (this._status === `loading`) {
461
- this.setStatus(`ready`);
473
+ this.setStatus(`initialCommit`);
462
474
  }
463
475
  this.commitPendingTransactions();
476
+ if (this._status === `initialCommit`) {
477
+ this.setStatus(`ready`);
478
+ }
464
479
  }
465
480
  });
466
481
  this.syncCleanupFn = typeof cleanupFn === `function` ? cleanupFn : null;
@@ -1031,7 +1046,10 @@ class CollectionImpl {
1031
1046
  }
1032
1047
  const directOpTransaction = createTransaction({
1033
1048
  mutationFn: async (params) => {
1034
- return this.config.onUpdate(params);
1049
+ return this.config.onUpdate({
1050
+ ...params,
1051
+ collection: this
1052
+ });
1035
1053
  }
1036
1054
  });
1037
1055
  directOpTransaction.applyMutations(mutations);
@@ -1042,8 +1060,19 @@ class CollectionImpl {
1042
1060
  }
1043
1061
  /**
1044
1062
  * Gets the current state of the collection as a Map
1063
+ * @returns Map containing all items in the collection, with keys as identifiers
1064
+ * @example
1065
+ * const itemsMap = collection.state
1066
+ * console.log(`Collection has ${itemsMap.size} items`)
1067
+ *
1068
+ * for (const [key, item] of itemsMap) {
1069
+ * console.log(`${key}: ${item.title}`)
1070
+ * }
1045
1071
  *
1046
- * @returns A Map containing all items in the collection, with keys as identifiers
1072
+ * // Check if specific item exists
1073
+ * if (itemsMap.has("todo-1")) {
1074
+ * console.log("Todo 1 exists:", itemsMap.get("todo-1"))
1075
+ * }
1047
1076
  */
1048
1077
  get state() {
1049
1078
  const result = /* @__PURE__ */ new Map();
@@ -1105,8 +1134,24 @@ class CollectionImpl {
1105
1134
  }
1106
1135
  /**
1107
1136
  * Subscribe to changes in the collection
1108
- * @param callback - A function that will be called with the changes in the collection
1109
- * @returns A function that can be called to unsubscribe from the changes
1137
+ * @param callback - Function called when items change
1138
+ * @param options.includeInitialState - If true, immediately calls callback with current data
1139
+ * @returns Unsubscribe function - Call this to stop listening for changes
1140
+ * @example
1141
+ * // Basic subscription
1142
+ * const unsubscribe = collection.subscribeChanges((changes) => {
1143
+ * changes.forEach(change => {
1144
+ * console.log(`${change.type}: ${change.key}`, change.value)
1145
+ * })
1146
+ * })
1147
+ *
1148
+ * // Later: unsubscribe()
1149
+ *
1150
+ * @example
1151
+ * // Include current state immediately
1152
+ * const unsubscribe = collection.subscribeChanges((changes) => {
1153
+ * updateUI(changes)
1154
+ * }, { includeInitialState: true })
1110
1155
  */
1111
1156
  subscribeChanges(callback, { includeInitialState = false } = {}) {
1112
1157
  this.addSubscriber();