@tanstack/db 0.4.16 → 0.4.17

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 (48) hide show
  1. package/dist/cjs/duplicate-instance-check.d.cts +1 -0
  2. package/dist/cjs/errors.cjs +38 -0
  3. package/dist/cjs/errors.cjs.map +1 -1
  4. package/dist/cjs/errors.d.cts +6 -0
  5. package/dist/cjs/index.cjs +2 -0
  6. package/dist/cjs/index.cjs.map +1 -1
  7. package/dist/cjs/optimistic-action.cjs +6 -1
  8. package/dist/cjs/optimistic-action.cjs.map +1 -1
  9. package/dist/cjs/paced-mutations.cjs.map +1 -1
  10. package/dist/cjs/paced-mutations.d.cts +2 -2
  11. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
  12. package/dist/cjs/strategies/debounceStrategy.d.cts +4 -1
  13. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
  14. package/dist/cjs/strategies/throttleStrategy.d.cts +8 -2
  15. package/dist/cjs/transactions.cjs +3 -1
  16. package/dist/cjs/transactions.cjs.map +1 -1
  17. package/dist/cjs/transactions.d.cts +3 -1
  18. package/dist/cjs/utils/type-guards.cjs +7 -0
  19. package/dist/cjs/utils/type-guards.cjs.map +1 -0
  20. package/dist/cjs/utils/type-guards.d.cts +6 -0
  21. package/dist/esm/duplicate-instance-check.d.ts +1 -0
  22. package/dist/esm/errors.d.ts +6 -0
  23. package/dist/esm/errors.js +38 -0
  24. package/dist/esm/errors.js.map +1 -1
  25. package/dist/esm/index.js +3 -1
  26. package/dist/esm/optimistic-action.js +6 -1
  27. package/dist/esm/optimistic-action.js.map +1 -1
  28. package/dist/esm/paced-mutations.d.ts +2 -2
  29. package/dist/esm/paced-mutations.js.map +1 -1
  30. package/dist/esm/strategies/debounceStrategy.d.ts +4 -1
  31. package/dist/esm/strategies/debounceStrategy.js.map +1 -1
  32. package/dist/esm/strategies/throttleStrategy.d.ts +8 -2
  33. package/dist/esm/strategies/throttleStrategy.js.map +1 -1
  34. package/dist/esm/transactions.d.ts +3 -1
  35. package/dist/esm/transactions.js +3 -1
  36. package/dist/esm/transactions.js.map +1 -1
  37. package/dist/esm/utils/type-guards.d.ts +6 -0
  38. package/dist/esm/utils/type-guards.js +7 -0
  39. package/dist/esm/utils/type-guards.js.map +1 -0
  40. package/package.json +1 -1
  41. package/src/duplicate-instance-check.ts +32 -0
  42. package/src/errors.ts +35 -0
  43. package/src/optimistic-action.ts +7 -2
  44. package/src/paced-mutations.ts +2 -2
  45. package/src/strategies/debounceStrategy.ts +4 -1
  46. package/src/strategies/throttleStrategy.ts +8 -2
  47. package/src/transactions.ts +5 -1
  48. package/src/utils/type-guards.ts +12 -0
package/src/errors.ts CHANGED
@@ -41,6 +41,32 @@ export class SchemaValidationError extends TanStackDBError {
41
41
  }
42
42
  }
43
43
 
44
+ // Module Instance Errors
45
+ export class DuplicateDbInstanceError extends TanStackDBError {
46
+ constructor() {
47
+ super(
48
+ `Multiple instances of @tanstack/db detected!\n\n` +
49
+ `This causes transaction context to be lost because each instance maintains ` +
50
+ `its own transaction stack.\n\n` +
51
+ `Common causes:\n` +
52
+ `1. Different versions of @tanstack/db installed\n` +
53
+ `2. Incompatible peer dependency versions in packages\n` +
54
+ `3. Module resolution issues in bundler configuration\n\n` +
55
+ `To fix:\n` +
56
+ `1. Check installed versions: npm list @tanstack/db (or pnpm/yarn list)\n` +
57
+ `2. Force a single version using package manager overrides:\n` +
58
+ ` - npm: "overrides" in package.json\n` +
59
+ ` - pnpm: "pnpm.overrides" in package.json\n` +
60
+ ` - yarn: "resolutions" in package.json\n` +
61
+ `3. Clear node_modules and lockfile, then reinstall\n\n` +
62
+ `To temporarily disable this check (not recommended):\n` +
63
+ `Set environment variable: TANSTACK_DB_DISABLE_DUP_CHECK=1\n\n` +
64
+ `See: https://tanstack.com/db/latest/docs/troubleshooting#duplicate-instances`
65
+ )
66
+ this.name = `DuplicateDbInstanceError`
67
+ }
68
+ }
69
+
44
70
  // Collection Configuration Errors
45
71
  export class CollectionConfigurationError extends TanStackDBError {
46
72
  constructor(message: string) {
@@ -229,6 +255,15 @@ export class MissingMutationFunctionError extends TransactionError {
229
255
  }
230
256
  }
231
257
 
258
+ export class OnMutateMustBeSynchronousError extends TransactionError {
259
+ constructor() {
260
+ super(
261
+ `onMutate must be synchronous and cannot return a promise. Remove async/await or returned promises from onMutate.`
262
+ )
263
+ this.name = `OnMutateMustBeSynchronousError`
264
+ }
265
+ }
266
+
232
267
  export class TransactionNotPendingMutateError extends TransactionError {
233
268
  constructor() {
234
269
  super(
@@ -1,4 +1,6 @@
1
1
  import { createTransaction } from "./transactions"
2
+ import { OnMutateMustBeSynchronousError } from "./errors"
3
+ import { isPromiseLike } from "./utils/type-guards"
2
4
  import type { CreateOptimisticActionsOptions, Transaction } from "./types"
3
5
 
4
6
  /**
@@ -67,8 +69,11 @@ export function createOptimisticAction<TVariables = unknown>(
67
69
  // Execute the transaction. The mutationFn is called once mutate()
68
70
  // is finished.
69
71
  transaction.mutate(() => {
70
- // Call onMutate with variables to apply optimistic updates
71
- onMutate(variables)
72
+ const maybePromise = onMutate(variables) as unknown
73
+
74
+ if (isPromiseLike(maybePromise)) {
75
+ throw new OnMutateMustBeSynchronousError()
76
+ }
72
77
  })
73
78
 
74
79
  return transaction
@@ -53,7 +53,7 @@ export interface PacedMutationsConfig<
53
53
  * // Apply optimistic update immediately
54
54
  * collection.update(id, draft => { draft.text = text })
55
55
  * },
56
- * mutationFn: async (text, { transaction }) => {
56
+ * mutationFn: async ({ transaction }) => {
57
57
  * await api.save(transaction.mutations)
58
58
  * },
59
59
  * strategy: debounceStrategy({ wait: 500 })
@@ -73,7 +73,7 @@ export interface PacedMutationsConfig<
73
73
  * onMutate: ({ text }) => {
74
74
  * collection.insert({ id: uuid(), text, completed: false })
75
75
  * },
76
- * mutationFn: async ({ text }, { transaction }) => {
76
+ * mutationFn: async ({ transaction }) => {
77
77
  * await api.save(transaction.mutations)
78
78
  * },
79
79
  * strategy: queueStrategy({
@@ -14,7 +14,10 @@ import type { Transaction } from "../transactions"
14
14
  *
15
15
  * @example
16
16
  * ```ts
17
- * const mutate = useSerializedTransaction({
17
+ * const mutate = usePacedMutations({
18
+ * onMutate: (value) => {
19
+ * collection.update(id, draft => { draft.value = value })
20
+ * },
18
21
  * mutationFn: async ({ transaction }) => {
19
22
  * await api.save(transaction.mutations)
20
23
  * },
@@ -16,7 +16,10 @@ import type { Transaction } from "../transactions"
16
16
  * @example
17
17
  * ```ts
18
18
  * // Throttle slider updates to every 200ms
19
- * const mutate = useSerializedTransaction({
19
+ * const mutate = usePacedMutations({
20
+ * onMutate: (volume) => {
21
+ * settingsCollection.update('volume', draft => { draft.value = volume })
22
+ * },
20
23
  * mutationFn: async ({ transaction }) => {
21
24
  * await api.updateVolume(transaction.mutations)
22
25
  * },
@@ -27,7 +30,10 @@ import type { Transaction } from "../transactions"
27
30
  * @example
28
31
  * ```ts
29
32
  * // Throttle with leading and trailing execution
30
- * const mutate = useSerializedTransaction({
33
+ * const mutate = usePacedMutations({
34
+ * onMutate: (data) => {
35
+ * collection.update(id, draft => { Object.assign(draft, data) })
36
+ * },
31
37
  * mutationFn: async ({ transaction }) => {
32
38
  * await api.save(transaction.mutations)
33
39
  * },
@@ -1,4 +1,5 @@
1
1
  import { createDeferred } from "./deferred"
2
+ import "./duplicate-instance-check"
2
3
  import {
3
4
  MissingMutationFunctionError,
4
5
  TransactionAlreadyCompletedRollbackError,
@@ -244,7 +245,9 @@ class Transaction<T extends object = Record<string, unknown>> {
244
245
 
245
246
  /**
246
247
  * Execute collection operations within this transaction
247
- * @param callback - Function containing collection operations to group together
248
+ * @param callback - Function containing collection operations to group together. If the
249
+ * callback returns a Promise, the transaction context will remain active until the promise
250
+ * settles, allowing optimistic writes after `await` boundaries.
248
251
  * @returns This transaction for chaining
249
252
  * @example
250
253
  * // Group multiple operations
@@ -287,6 +290,7 @@ class Transaction<T extends object = Record<string, unknown>> {
287
290
  }
288
291
 
289
292
  registerTransaction(this)
293
+
290
294
  try {
291
295
  callback()
292
296
  } finally {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Type guard to check if a value is promise-like (has a `.then` method)
3
+ * @param value - The value to check
4
+ * @returns True if the value is promise-like, false otherwise
5
+ */
6
+ export function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
7
+ return (
8
+ !!value &&
9
+ (typeof value === `object` || typeof value === `function`) &&
10
+ typeof (value as { then?: unknown }).then === `function`
11
+ )
12
+ }