@tanstack/db 0.4.16 → 0.4.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 (75) hide show
  1. package/dist/cjs/collection/changes.cjs +1 -1
  2. package/dist/cjs/collection/changes.cjs.map +1 -1
  3. package/dist/cjs/collection/subscription.cjs +55 -5
  4. package/dist/cjs/collection/subscription.cjs.map +1 -1
  5. package/dist/cjs/collection/subscription.d.cts +5 -2
  6. package/dist/cjs/duplicate-instance-check.d.cts +1 -0
  7. package/dist/cjs/errors.cjs +46 -0
  8. package/dist/cjs/errors.cjs.map +1 -1
  9. package/dist/cjs/errors.d.cts +14 -0
  10. package/dist/cjs/index.cjs +3 -0
  11. package/dist/cjs/index.cjs.map +1 -1
  12. package/dist/cjs/optimistic-action.cjs +6 -1
  13. package/dist/cjs/optimistic-action.cjs.map +1 -1
  14. package/dist/cjs/paced-mutations.cjs.map +1 -1
  15. package/dist/cjs/paced-mutations.d.cts +2 -2
  16. package/dist/cjs/query/compiler/index.cjs +40 -0
  17. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  18. package/dist/cjs/query/compiler/order-by.cjs +3 -6
  19. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  20. package/dist/cjs/query/live/collection-subscriber.cjs +20 -34
  21. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  22. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
  23. package/dist/cjs/strategies/debounceStrategy.d.cts +4 -1
  24. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
  25. package/dist/cjs/strategies/throttleStrategy.d.cts +8 -2
  26. package/dist/cjs/transactions.cjs +3 -1
  27. package/dist/cjs/transactions.cjs.map +1 -1
  28. package/dist/cjs/transactions.d.cts +3 -1
  29. package/dist/cjs/utils/type-guards.cjs +7 -0
  30. package/dist/cjs/utils/type-guards.cjs.map +1 -0
  31. package/dist/cjs/utils/type-guards.d.cts +6 -0
  32. package/dist/esm/collection/changes.js +1 -1
  33. package/dist/esm/collection/changes.js.map +1 -1
  34. package/dist/esm/collection/subscription.d.ts +5 -2
  35. package/dist/esm/collection/subscription.js +56 -6
  36. package/dist/esm/collection/subscription.js.map +1 -1
  37. package/dist/esm/duplicate-instance-check.d.ts +1 -0
  38. package/dist/esm/errors.d.ts +14 -0
  39. package/dist/esm/errors.js +46 -0
  40. package/dist/esm/errors.js.map +1 -1
  41. package/dist/esm/index.js +4 -1
  42. package/dist/esm/optimistic-action.js +6 -1
  43. package/dist/esm/optimistic-action.js.map +1 -1
  44. package/dist/esm/paced-mutations.d.ts +2 -2
  45. package/dist/esm/paced-mutations.js.map +1 -1
  46. package/dist/esm/query/compiler/index.js +41 -1
  47. package/dist/esm/query/compiler/index.js.map +1 -1
  48. package/dist/esm/query/compiler/order-by.js +3 -6
  49. package/dist/esm/query/compiler/order-by.js.map +1 -1
  50. package/dist/esm/query/live/collection-subscriber.js +20 -34
  51. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  52. package/dist/esm/strategies/debounceStrategy.d.ts +4 -1
  53. package/dist/esm/strategies/debounceStrategy.js.map +1 -1
  54. package/dist/esm/strategies/throttleStrategy.d.ts +8 -2
  55. package/dist/esm/strategies/throttleStrategy.js.map +1 -1
  56. package/dist/esm/transactions.d.ts +3 -1
  57. package/dist/esm/transactions.js +3 -1
  58. package/dist/esm/transactions.js.map +1 -1
  59. package/dist/esm/utils/type-guards.d.ts +6 -0
  60. package/dist/esm/utils/type-guards.js +7 -0
  61. package/dist/esm/utils/type-guards.js.map +1 -0
  62. package/package.json +1 -1
  63. package/src/collection/changes.ts +1 -1
  64. package/src/collection/subscription.ts +82 -6
  65. package/src/duplicate-instance-check.ts +32 -0
  66. package/src/errors.ts +51 -0
  67. package/src/optimistic-action.ts +7 -2
  68. package/src/paced-mutations.ts +2 -2
  69. package/src/query/compiler/index.ts +74 -0
  70. package/src/query/compiler/order-by.ts +3 -5
  71. package/src/query/live/collection-subscriber.ts +26 -72
  72. package/src/strategies/debounceStrategy.ts +4 -1
  73. package/src/strategies/throttleStrategy.ts +8 -2
  74. package/src/transactions.ts +5 -1
  75. package/src/utils/type-guards.ts +12 -0
@@ -63,7 +63,7 @@ class CollectionChangesManager {
63
63
  }
64
64
  });
65
65
  if (options.includeInitialState) {
66
- subscription$1.requestSnapshot();
66
+ subscription$1.requestSnapshot({ trackLoadSubsetPromise: false });
67
67
  }
68
68
  this.changeSubscriptions.add(subscription$1);
69
69
  return subscription$1;
@@ -1 +1 @@
1
- {"version":3,"file":"changes.cjs","sources":["../../../src/collection/changes.ts"],"sourcesContent":["import { NegativeActiveSubscribersError } from \"../errors\"\nimport { CollectionSubscription } from \"./subscription.js\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { ChangeMessage, SubscribeChangesOptions } from \"../types\"\nimport type { CollectionLifecycleManager } from \"./lifecycle.js\"\nimport type { CollectionSyncManager } from \"./sync.js\"\nimport type { CollectionEventsManager } from \"./events.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\nexport class CollectionChangesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n\n public activeSubscribersCount = 0\n public changeSubscriptions = new Set<CollectionSubscription>()\n public batchedEvents: Array<ChangeMessage<TOutput, TKey>> = []\n public shouldBatchEvents = false\n\n /**\n * Creates a new CollectionChangesManager instance\n */\n constructor() {}\n\n public setDeps(deps: {\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n }) {\n this.lifecycle = deps.lifecycle\n this.sync = deps.sync\n this.events = deps.events\n this.collection = deps.collection\n }\n\n /**\n * Emit an empty ready event to notify subscribers that the collection is ready\n * This bypasses the normal empty array check in emitEvents\n */\n public emitEmptyReadyEvent(): void {\n // Emit empty array directly to all subscribers\n for (const subscription of this.changeSubscriptions) {\n subscription.emitEvents([])\n }\n }\n\n /**\n * Emit events either immediately or batch them for later emission\n */\n public emitEvents(\n changes: Array<ChangeMessage<TOutput, TKey>>,\n forceEmit = false\n ): void {\n // Skip batching for user actions (forceEmit=true) to keep UI responsive\n if (this.shouldBatchEvents && !forceEmit) {\n // Add events to the batch\n this.batchedEvents.push(...changes)\n return\n }\n\n // Either we're not batching, or we're forcing emission (user action or ending batch cycle)\n let eventsToEmit = changes\n\n if (forceEmit) {\n // Force emit is used to end a batch (e.g. after a sync commit). Combine any\n // buffered optimistic events with the final changes so subscribers see the\n // whole picture, even if the sync diff is empty.\n if (this.batchedEvents.length > 0) {\n eventsToEmit = [...this.batchedEvents, ...changes]\n }\n this.batchedEvents = []\n this.shouldBatchEvents = false\n }\n\n if (eventsToEmit.length === 0) {\n return\n }\n\n // Emit to all listeners\n for (const subscription of this.changeSubscriptions) {\n subscription.emitEvents(eventsToEmit)\n }\n }\n\n /**\n * Subscribe to changes in the collection\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<TOutput>>) => void,\n options: SubscribeChangesOptions = {}\n ): CollectionSubscription {\n // Start sync and track subscriber\n this.addSubscriber()\n\n const subscription = new CollectionSubscription(this.collection, callback, {\n ...options,\n onUnsubscribe: () => {\n this.removeSubscriber()\n this.changeSubscriptions.delete(subscription)\n },\n })\n\n if (options.includeInitialState) {\n subscription.requestSnapshot()\n }\n\n // Add to batched listeners\n this.changeSubscriptions.add(subscription)\n\n return subscription\n }\n\n /**\n * Increment the active subscribers count and start sync if needed\n */\n private addSubscriber(): void {\n const previousSubscriberCount = this.activeSubscribersCount\n this.activeSubscribersCount++\n this.lifecycle.cancelGCTimer()\n\n // Start sync if collection was cleaned up\n if (\n this.lifecycle.status === `cleaned-up` ||\n this.lifecycle.status === `idle`\n ) {\n this.sync.startSync()\n }\n\n this.events.emitSubscribersChange(\n this.activeSubscribersCount,\n previousSubscriberCount\n )\n }\n\n /**\n * Decrement the active subscribers count and start GC timer if needed\n */\n private removeSubscriber(): void {\n const previousSubscriberCount = this.activeSubscribersCount\n this.activeSubscribersCount--\n\n if (this.activeSubscribersCount === 0) {\n this.lifecycle.startGCTimer()\n } else if (this.activeSubscribersCount < 0) {\n throw new NegativeActiveSubscribersError()\n }\n\n this.events.emitSubscribersChange(\n this.activeSubscribersCount,\n previousSubscriberCount\n )\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public cleanup(): void {\n this.batchedEvents = []\n this.shouldBatchEvents = false\n }\n}\n"],"names":["subscription","CollectionSubscription","NegativeActiveSubscribersError"],"mappings":";;;;AASO,MAAM,yBAKX;AAAA;AAAA;AAAA;AAAA,EAcA,cAAc;AARd,SAAO,yBAAyB;AAChC,SAAO,0CAA0B,IAAA;AACjC,SAAO,gBAAqD,CAAA;AAC5D,SAAO,oBAAoB;AAAA,EAKZ;AAAA,EAER,QAAQ,MAKZ;AACD,SAAK,YAAY,KAAK;AACtB,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AACnB,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,sBAA4B;AAEjC,eAAWA,iBAAgB,KAAK,qBAAqB;AACnD,MAAAA,cAAa,WAAW,EAAE;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,WACL,SACA,YAAY,OACN;AAEN,QAAI,KAAK,qBAAqB,CAAC,WAAW;AAExC,WAAK,cAAc,KAAK,GAAG,OAAO;AAClC;AAAA,IACF;AAGA,QAAI,eAAe;AAEnB,QAAI,WAAW;AAIb,UAAI,KAAK,cAAc,SAAS,GAAG;AACjC,uBAAe,CAAC,GAAG,KAAK,eAAe,GAAG,OAAO;AAAA,MACnD;AACA,WAAK,gBAAgB,CAAA;AACrB,WAAK,oBAAoB;AAAA,IAC3B;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B;AAAA,IACF;AAGA,eAAWA,iBAAgB,KAAK,qBAAqB;AACnD,MAAAA,cAAa,WAAW,YAAY;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,iBACL,UACA,UAAmC,IACX;AAExB,SAAK,cAAA;AAEL,UAAMA,iBAAe,IAAIC,aAAAA,uBAAuB,KAAK,YAAY,UAAU;AAAA,MACzE,GAAG;AAAA,MACH,eAAe,MAAM;AACnB,aAAK,iBAAA;AACL,aAAK,oBAAoB,OAAOD,cAAY;AAAA,MAC9C;AAAA,IAAA,CACD;AAED,QAAI,QAAQ,qBAAqB;AAC/BA,qBAAa,gBAAA;AAAA,IACf;AAGA,SAAK,oBAAoB,IAAIA,cAAY;AAEzC,WAAOA;AAAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,UAAM,0BAA0B,KAAK;AACrC,SAAK;AACL,SAAK,UAAU,cAAA;AAGf,QACE,KAAK,UAAU,WAAW,gBAC1B,KAAK,UAAU,WAAW,QAC1B;AACA,WAAK,KAAK,UAAA;AAAA,IACZ;AAEA,SAAK,OAAO;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,UAAM,0BAA0B,KAAK;AACrC,SAAK;AAEL,QAAI,KAAK,2BAA2B,GAAG;AACrC,WAAK,UAAU,aAAA;AAAA,IACjB,WAAW,KAAK,yBAAyB,GAAG;AAC1C,YAAM,IAAIE,OAAAA,+BAAA;AAAA,IACZ;AAEA,SAAK,OAAO;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UAAgB;AACrB,SAAK,gBAAgB,CAAA;AACrB,SAAK,oBAAoB;AAAA,EAC3B;AACF;;"}
1
+ {"version":3,"file":"changes.cjs","sources":["../../../src/collection/changes.ts"],"sourcesContent":["import { NegativeActiveSubscribersError } from \"../errors\"\nimport { CollectionSubscription } from \"./subscription.js\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { ChangeMessage, SubscribeChangesOptions } from \"../types\"\nimport type { CollectionLifecycleManager } from \"./lifecycle.js\"\nimport type { CollectionSyncManager } from \"./sync.js\"\nimport type { CollectionEventsManager } from \"./events.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\nexport class CollectionChangesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n\n public activeSubscribersCount = 0\n public changeSubscriptions = new Set<CollectionSubscription>()\n public batchedEvents: Array<ChangeMessage<TOutput, TKey>> = []\n public shouldBatchEvents = false\n\n /**\n * Creates a new CollectionChangesManager instance\n */\n constructor() {}\n\n public setDeps(deps: {\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n }) {\n this.lifecycle = deps.lifecycle\n this.sync = deps.sync\n this.events = deps.events\n this.collection = deps.collection\n }\n\n /**\n * Emit an empty ready event to notify subscribers that the collection is ready\n * This bypasses the normal empty array check in emitEvents\n */\n public emitEmptyReadyEvent(): void {\n // Emit empty array directly to all subscribers\n for (const subscription of this.changeSubscriptions) {\n subscription.emitEvents([])\n }\n }\n\n /**\n * Emit events either immediately or batch them for later emission\n */\n public emitEvents(\n changes: Array<ChangeMessage<TOutput, TKey>>,\n forceEmit = false\n ): void {\n // Skip batching for user actions (forceEmit=true) to keep UI responsive\n if (this.shouldBatchEvents && !forceEmit) {\n // Add events to the batch\n this.batchedEvents.push(...changes)\n return\n }\n\n // Either we're not batching, or we're forcing emission (user action or ending batch cycle)\n let eventsToEmit = changes\n\n if (forceEmit) {\n // Force emit is used to end a batch (e.g. after a sync commit). Combine any\n // buffered optimistic events with the final changes so subscribers see the\n // whole picture, even if the sync diff is empty.\n if (this.batchedEvents.length > 0) {\n eventsToEmit = [...this.batchedEvents, ...changes]\n }\n this.batchedEvents = []\n this.shouldBatchEvents = false\n }\n\n if (eventsToEmit.length === 0) {\n return\n }\n\n // Emit to all listeners\n for (const subscription of this.changeSubscriptions) {\n subscription.emitEvents(eventsToEmit)\n }\n }\n\n /**\n * Subscribe to changes in the collection\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<TOutput>>) => void,\n options: SubscribeChangesOptions = {}\n ): CollectionSubscription {\n // Start sync and track subscriber\n this.addSubscriber()\n\n const subscription = new CollectionSubscription(this.collection, callback, {\n ...options,\n onUnsubscribe: () => {\n this.removeSubscriber()\n this.changeSubscriptions.delete(subscription)\n },\n })\n\n if (options.includeInitialState) {\n subscription.requestSnapshot({ trackLoadSubsetPromise: false })\n }\n\n // Add to batched listeners\n this.changeSubscriptions.add(subscription)\n\n return subscription\n }\n\n /**\n * Increment the active subscribers count and start sync if needed\n */\n private addSubscriber(): void {\n const previousSubscriberCount = this.activeSubscribersCount\n this.activeSubscribersCount++\n this.lifecycle.cancelGCTimer()\n\n // Start sync if collection was cleaned up\n if (\n this.lifecycle.status === `cleaned-up` ||\n this.lifecycle.status === `idle`\n ) {\n this.sync.startSync()\n }\n\n this.events.emitSubscribersChange(\n this.activeSubscribersCount,\n previousSubscriberCount\n )\n }\n\n /**\n * Decrement the active subscribers count and start GC timer if needed\n */\n private removeSubscriber(): void {\n const previousSubscriberCount = this.activeSubscribersCount\n this.activeSubscribersCount--\n\n if (this.activeSubscribersCount === 0) {\n this.lifecycle.startGCTimer()\n } else if (this.activeSubscribersCount < 0) {\n throw new NegativeActiveSubscribersError()\n }\n\n this.events.emitSubscribersChange(\n this.activeSubscribersCount,\n previousSubscriberCount\n )\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public cleanup(): void {\n this.batchedEvents = []\n this.shouldBatchEvents = false\n }\n}\n"],"names":["subscription","CollectionSubscription","NegativeActiveSubscribersError"],"mappings":";;;;AASO,MAAM,yBAKX;AAAA;AAAA;AAAA;AAAA,EAcA,cAAc;AARd,SAAO,yBAAyB;AAChC,SAAO,0CAA0B,IAAA;AACjC,SAAO,gBAAqD,CAAA;AAC5D,SAAO,oBAAoB;AAAA,EAKZ;AAAA,EAER,QAAQ,MAKZ;AACD,SAAK,YAAY,KAAK;AACtB,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AACnB,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,sBAA4B;AAEjC,eAAWA,iBAAgB,KAAK,qBAAqB;AACnD,MAAAA,cAAa,WAAW,EAAE;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,WACL,SACA,YAAY,OACN;AAEN,QAAI,KAAK,qBAAqB,CAAC,WAAW;AAExC,WAAK,cAAc,KAAK,GAAG,OAAO;AAClC;AAAA,IACF;AAGA,QAAI,eAAe;AAEnB,QAAI,WAAW;AAIb,UAAI,KAAK,cAAc,SAAS,GAAG;AACjC,uBAAe,CAAC,GAAG,KAAK,eAAe,GAAG,OAAO;AAAA,MACnD;AACA,WAAK,gBAAgB,CAAA;AACrB,WAAK,oBAAoB;AAAA,IAC3B;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B;AAAA,IACF;AAGA,eAAWA,iBAAgB,KAAK,qBAAqB;AACnD,MAAAA,cAAa,WAAW,YAAY;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,iBACL,UACA,UAAmC,IACX;AAExB,SAAK,cAAA;AAEL,UAAMA,iBAAe,IAAIC,aAAAA,uBAAuB,KAAK,YAAY,UAAU;AAAA,MACzE,GAAG;AAAA,MACH,eAAe,MAAM;AACnB,aAAK,iBAAA;AACL,aAAK,oBAAoB,OAAOD,cAAY;AAAA,MAC9C;AAAA,IAAA,CACD;AAED,QAAI,QAAQ,qBAAqB;AAC/BA,qBAAa,gBAAgB,EAAE,wBAAwB,MAAA,CAAO;AAAA,IAChE;AAGA,SAAK,oBAAoB,IAAIA,cAAY;AAEzC,WAAOA;AAAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,UAAM,0BAA0B,KAAK;AACrC,SAAK;AACL,SAAK,UAAU,cAAA;AAGf,QACE,KAAK,UAAU,WAAW,gBAC1B,KAAK,UAAU,WAAW,QAC1B;AACA,WAAK,KAAK,UAAA;AAAA,IACZ;AAEA,SAAK,OAAO;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,UAAM,0BAA0B,KAAK;AACrC,SAAK;AAEL,QAAI,KAAK,2BAA2B,GAAG;AACrC,WAAK,UAAU,aAAA;AAAA,IACjB,WAAW,KAAK,yBAAyB,GAAG;AAC1C,YAAM,IAAIE,OAAAA,+BAAA;AAAA,IACZ;AAEA,SAAK,OAAO;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UAAgB;AACrB,SAAK,gBAAgB,CAAA;AACrB,SAAK,oBAAoB;AAAA,EAC3B;AACF;;"}
@@ -116,7 +116,10 @@ class CollectionSubscription extends eventEmitter.EventEmitter {
116
116
  where: stateOpts.where,
117
117
  subscription: this
118
118
  });
119
- this.trackLoadSubsetPromise(syncResult);
119
+ const trackLoadSubsetPromise = opts?.trackLoadSubsetPromise ?? true;
120
+ if (trackLoadSubsetPromise) {
121
+ this.trackLoadSubsetPromise(syncResult);
122
+ }
120
123
  const snapshot = this.collection.currentStateAsChanges(stateOpts);
121
124
  if (snapshot === void 0) {
122
125
  return false;
@@ -129,10 +132,12 @@ class CollectionSubscription extends eventEmitter.EventEmitter {
129
132
  return true;
130
133
  }
131
134
  /**
132
- * Sends a snapshot that is limited to the first `limit` rows that fulfill the `where` clause and are bigger than `minValue`.
135
+ * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to `minValue`.
133
136
  * Requires a range index to be set with `setOrderByIndex` prior to calling this method.
134
137
  * It uses that range index to load the items in the order of the index.
135
- * Note: it does not send keys that have already been sent before.
138
+ * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to `minValue` + limit values greater than `minValue`.
139
+ * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.
140
+ * Note 2: it does not send keys that have already been sent before.
136
141
  */
137
142
  requestLimitedSnapshot({
138
143
  orderBy,
@@ -160,10 +165,31 @@ class CollectionSubscription extends eventEmitter.EventEmitter {
160
165
  };
161
166
  let biggestObservedValue = minValue;
162
167
  const changes = [];
163
- let keys = index.take(limit, minValue, filterFn);
168
+ let keys = [];
169
+ if (minValue !== void 0) {
170
+ const { expression } = orderBy[0];
171
+ const allRowsWithMinValue = this.collection.currentStateAsChanges({
172
+ where: functions.eq(expression, new ir.Value(minValue))
173
+ });
174
+ if (allRowsWithMinValue) {
175
+ const keysWithMinValue = allRowsWithMinValue.map((change) => change.key).filter((key) => !this.sentKeys.has(key) && filterFn(key));
176
+ keys.push(...keysWithMinValue);
177
+ const keysGreaterThanMin = index.take(
178
+ limit - keys.length,
179
+ minValue,
180
+ filterFn
181
+ );
182
+ keys.push(...keysGreaterThanMin);
183
+ } else {
184
+ keys = index.take(limit, minValue, filterFn);
185
+ }
186
+ } else {
187
+ keys = index.take(limit, minValue, filterFn);
188
+ }
164
189
  const valuesNeeded = () => Math.max(limit - changes.length, 0);
165
190
  const collectionExhausted = () => keys.length === 0;
166
191
  while (valuesNeeded() > 0 && !collectionExhausted()) {
192
+ const insertedKeys = /* @__PURE__ */ new Set();
167
193
  for (const key of keys) {
168
194
  const value = this.collection.get(key);
169
195
  changes.push({
@@ -172,6 +198,7 @@ class CollectionSubscription extends eventEmitter.EventEmitter {
172
198
  value
173
199
  });
174
200
  biggestObservedValue = value;
201
+ insertedKeys.add(key);
175
202
  }
176
203
  keys = index.take(valuesNeeded(), biggestObservedValue, filterFn);
177
204
  }
@@ -189,8 +216,31 @@ class CollectionSubscription extends eventEmitter.EventEmitter {
189
216
  orderBy,
190
217
  subscription: this
191
218
  });
192
- this.trackLoadSubsetPromise(syncResult);
219
+ const promises = [];
220
+ if (typeof minValue !== `undefined`) {
221
+ const { expression } = orderBy[0];
222
+ const exactValueFilter = functions.eq(expression, new ir.Value(minValue));
223
+ const equalValueResult = this.collection._sync.loadSubset({
224
+ where: exactValueFilter,
225
+ subscription: this
226
+ });
227
+ if (equalValueResult instanceof Promise) {
228
+ promises.push(equalValueResult);
229
+ }
230
+ }
231
+ if (syncResult instanceof Promise) {
232
+ promises.push(syncResult);
233
+ }
234
+ if (promises.length > 0) {
235
+ const combinedPromise = Promise.all(promises).then(() => {
236
+ });
237
+ this.trackLoadSubsetPromise(combinedPromise);
238
+ } else {
239
+ this.trackLoadSubsetPromise(syncResult);
240
+ }
193
241
  }
242
+ // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function
243
+ // and that that also works properly (i.e. does not skip duplicate values)
194
244
  /**
195
245
  * Filters and flips changes for keys that have not been sent yet.
196
246
  * Deletes are filtered out for keys that have not been sent yet.
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.cjs","sources":["../../../src/collection/subscription.ts"],"sourcesContent":["import { ensureIndexForExpression } from \"../indexes/auto-index.js\"\nimport { and, gt, lt } from \"../query/builder/functions.js\"\nimport { Value } from \"../query/ir.js\"\nimport { EventEmitter } from \"../event-emitter.js\"\nimport {\n createFilterFunctionFromExpression,\n createFilteredCallback,\n} from \"./change-events.js\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\nimport type { IndexInterface } from \"../indexes/base-index.js\"\nimport type {\n ChangeMessage,\n Subscription,\n SubscriptionEvents,\n SubscriptionStatus,\n SubscriptionUnsubscribedEvent,\n} from \"../types.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\ntype RequestSnapshotOptions = {\n where?: BasicExpression<boolean>\n optimizedOnly?: boolean\n}\n\ntype RequestLimitedSnapshotOptions = {\n orderBy: OrderBy\n limit: number\n minValue?: any\n}\n\ntype CollectionSubscriptionOptions = {\n includeInitialState?: boolean\n /** Pre-compiled expression for filtering changes */\n whereExpression?: BasicExpression<boolean>\n /** Callback to call when the subscription is unsubscribed */\n onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void\n}\n\nexport class CollectionSubscription\n extends EventEmitter<SubscriptionEvents>\n implements Subscription\n{\n private loadedInitialState = false\n\n // Flag to indicate that we have sent at least 1 snapshot.\n // While `snapshotSent` is false we filter out all changes from subscription to the collection.\n private snapshotSent = false\n\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void\n\n private orderByIndex: IndexInterface<string | number> | undefined\n\n // Status tracking\n private _status: SubscriptionStatus = `ready`\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n public get status(): SubscriptionStatus {\n return this._status\n }\n\n constructor(\n private collection: CollectionImpl<any, any, any, any, any>,\n private callback: (changes: Array<ChangeMessage<any, any>>) => void,\n private options: CollectionSubscriptionOptions\n ) {\n super()\n if (options.onUnsubscribe) {\n this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))\n }\n\n // Auto-index for where expressions if enabled\n if (options.whereExpression) {\n ensureIndexForExpression(options.whereExpression, this.collection)\n }\n\n const callbackWithSentKeysTracking = (\n changes: Array<ChangeMessage<any, any>>\n ) => {\n callback(changes)\n this.trackSentKeys(changes)\n }\n\n this.callback = callbackWithSentKeysTracking\n\n // Create a filtered callback if where clause is provided\n this.filteredCallback = options.whereExpression\n ? createFilteredCallback(this.callback, options)\n : this.callback\n }\n\n setOrderByIndex(index: IndexInterface<any>) {\n this.orderByIndex = index\n }\n\n /**\n * Set subscription status and emit events if changed\n */\n private setStatus(newStatus: SubscriptionStatus) {\n if (this._status === newStatus) {\n return // No change\n }\n\n const previousStatus = this._status\n this._status = newStatus\n\n // Emit status:change event\n this.emitInner(`status:change`, {\n type: `status:change`,\n subscription: this,\n previousStatus,\n status: newStatus,\n })\n\n // Emit specific status event\n const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`\n this.emitInner(eventKey, {\n type: eventKey,\n subscription: this,\n previousStatus,\n status: newStatus,\n } as SubscriptionEvents[typeof eventKey])\n }\n\n /**\n * Track a loadSubset promise and manage loading status\n */\n private trackLoadSubsetPromise(syncResult: Promise<void> | true) {\n // Track the promise if it's actually a promise (async work)\n if (syncResult instanceof Promise) {\n this.pendingLoadSubsetPromises.add(syncResult)\n this.setStatus(`loadingSubset`)\n\n syncResult.finally(() => {\n this.pendingLoadSubsetPromises.delete(syncResult)\n if (this.pendingLoadSubsetPromises.size === 0) {\n this.setStatus(`ready`)\n }\n })\n }\n }\n\n hasLoadedInitialState() {\n return this.loadedInitialState\n }\n\n hasSentAtLeastOneSnapshot() {\n return this.snapshotSent\n }\n\n emitEvents(changes: Array<ChangeMessage<any, any>>) {\n const newChanges = this.filterAndFlipChanges(changes)\n this.filteredCallback(newChanges)\n }\n\n /**\n * Sends the snapshot to the callback.\n * Returns a boolean indicating if it succeeded.\n * It can only fail if there is no index to fulfill the request\n * and the optimizedOnly option is set to true,\n * or, the entire state was already loaded.\n */\n requestSnapshot(opts?: RequestSnapshotOptions): boolean {\n if (this.loadedInitialState) {\n // Subscription was deoptimized so we already sent the entire initial state\n return false\n }\n\n const stateOpts: RequestSnapshotOptions = {\n where: this.options.whereExpression,\n optimizedOnly: opts?.optimizedOnly ?? false,\n }\n\n if (opts) {\n if (`where` in opts) {\n const snapshotWhereExp = opts.where\n if (stateOpts.where) {\n // Combine the two where expressions\n const subWhereExp = stateOpts.where\n const combinedWhereExp = and(subWhereExp, snapshotWhereExp)\n stateOpts.where = combinedWhereExp\n } else {\n stateOpts.where = snapshotWhereExp\n }\n }\n } else {\n // No options provided so it's loading the entire initial state\n this.loadedInitialState = true\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const syncResult = this.collection._sync.loadSubset({\n where: stateOpts.where,\n subscription: this,\n })\n\n this.trackLoadSubsetPromise(syncResult)\n\n // Also load data immediately from the collection\n const snapshot = this.collection.currentStateAsChanges(stateOpts)\n\n if (snapshot === undefined) {\n // Couldn't load from indexes\n return false\n }\n\n // Only send changes that have not been sent yet\n const filteredSnapshot = snapshot.filter(\n (change) => !this.sentKeys.has(change.key)\n )\n\n this.snapshotSent = true\n this.callback(filteredSnapshot)\n return true\n }\n\n /**\n * Sends a snapshot that is limited to the first `limit` rows that fulfill the `where` clause and are bigger than `minValue`.\n * Requires a range index to be set with `setOrderByIndex` prior to calling this method.\n * It uses that range index to load the items in the order of the index.\n * Note: it does not send keys that have already been sent before.\n */\n requestLimitedSnapshot({\n orderBy,\n limit,\n minValue,\n }: RequestLimitedSnapshotOptions) {\n if (!limit) throw new Error(`limit is required`)\n\n if (!this.orderByIndex) {\n throw new Error(\n `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`\n )\n }\n\n const index = this.orderByIndex\n const where = this.options.whereExpression\n const whereFilterFn = where\n ? createFilterFunctionFromExpression(where)\n : undefined\n\n const filterFn = (key: string | number): boolean => {\n if (this.sentKeys.has(key)) {\n return false\n }\n\n const value = this.collection.get(key)\n if (value === undefined) {\n return false\n }\n\n return whereFilterFn?.(value) ?? true\n }\n\n let biggestObservedValue = minValue\n const changes: Array<ChangeMessage<any, string | number>> = []\n let keys: Array<string | number> = index.take(limit, minValue, filterFn)\n\n const valuesNeeded = () => Math.max(limit - changes.length, 0)\n const collectionExhausted = () => keys.length === 0\n\n while (valuesNeeded() > 0 && !collectionExhausted()) {\n for (const key of keys) {\n const value = this.collection.get(key)!\n changes.push({\n type: `insert`,\n key,\n value,\n })\n biggestObservedValue = value\n }\n\n keys = index.take(valuesNeeded(), biggestObservedValue, filterFn)\n }\n\n this.callback(changes)\n\n let whereWithValueFilter = where\n if (typeof minValue !== `undefined`) {\n // Only request data that we haven't seen yet (i.e. is bigger than the minValue)\n const { expression, compareOptions } = orderBy[0]!\n const operator = compareOptions.direction === `asc` ? gt : lt\n const valueFilter = operator(expression, new Value(minValue))\n whereWithValueFilter = where ? and(where, valueFilter) : valueFilter\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const syncResult = this.collection._sync.loadSubset({\n where: whereWithValueFilter,\n limit,\n orderBy,\n subscription: this,\n })\n\n this.trackLoadSubsetPromise(syncResult)\n }\n\n /**\n * Filters and flips changes for keys that have not been sent yet.\n * Deletes are filtered out for keys that have not been sent yet.\n * Updates are flipped into inserts for keys that have not been sent yet.\n */\n private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {\n if (this.loadedInitialState) {\n // We loaded the entire initial state\n // so no need to filter or flip changes\n return changes\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert`, previousValue: undefined }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n return newChanges\n }\n\n private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {\n if (this.loadedInitialState) {\n // No need to track sent keys if we loaded the entire state.\n // Since we sent everything, all keys must have been observed.\n return\n }\n\n for (const change of changes) {\n this.sentKeys.add(change.key)\n }\n }\n\n unsubscribe() {\n this.emitInner(`unsubscribed`, {\n type: `unsubscribed`,\n subscription: this,\n })\n // Clear all event listeners to prevent memory leaks\n this.clearListeners()\n }\n}\n"],"names":["EventEmitter","ensureIndexForExpression","createFilteredCallback","and","createFilterFunctionFromExpression","gt","lt","Value"],"mappings":"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"}
1
+ {"version":3,"file":"subscription.cjs","sources":["../../../src/collection/subscription.ts"],"sourcesContent":["import { ensureIndexForExpression } from \"../indexes/auto-index.js\"\nimport { and, eq, gt, lt } from \"../query/builder/functions.js\"\nimport { Value } from \"../query/ir.js\"\nimport { EventEmitter } from \"../event-emitter.js\"\nimport {\n createFilterFunctionFromExpression,\n createFilteredCallback,\n} from \"./change-events.js\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\nimport type { IndexInterface } from \"../indexes/base-index.js\"\nimport type {\n ChangeMessage,\n Subscription,\n SubscriptionEvents,\n SubscriptionStatus,\n SubscriptionUnsubscribedEvent,\n} from \"../types.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\ntype RequestSnapshotOptions = {\n where?: BasicExpression<boolean>\n optimizedOnly?: boolean\n trackLoadSubsetPromise?: boolean\n}\n\ntype RequestLimitedSnapshotOptions = {\n orderBy: OrderBy\n limit: number\n minValue?: any\n}\n\ntype CollectionSubscriptionOptions = {\n includeInitialState?: boolean\n /** Pre-compiled expression for filtering changes */\n whereExpression?: BasicExpression<boolean>\n /** Callback to call when the subscription is unsubscribed */\n onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void\n}\n\nexport class CollectionSubscription\n extends EventEmitter<SubscriptionEvents>\n implements Subscription\n{\n private loadedInitialState = false\n\n // Flag to indicate that we have sent at least 1 snapshot.\n // While `snapshotSent` is false we filter out all changes from subscription to the collection.\n private snapshotSent = false\n\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void\n\n private orderByIndex: IndexInterface<string | number> | undefined\n\n // Status tracking\n private _status: SubscriptionStatus = `ready`\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n public get status(): SubscriptionStatus {\n return this._status\n }\n\n constructor(\n private collection: CollectionImpl<any, any, any, any, any>,\n private callback: (changes: Array<ChangeMessage<any, any>>) => void,\n private options: CollectionSubscriptionOptions\n ) {\n super()\n if (options.onUnsubscribe) {\n this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))\n }\n\n // Auto-index for where expressions if enabled\n if (options.whereExpression) {\n ensureIndexForExpression(options.whereExpression, this.collection)\n }\n\n const callbackWithSentKeysTracking = (\n changes: Array<ChangeMessage<any, any>>\n ) => {\n callback(changes)\n this.trackSentKeys(changes)\n }\n\n this.callback = callbackWithSentKeysTracking\n\n // Create a filtered callback if where clause is provided\n this.filteredCallback = options.whereExpression\n ? createFilteredCallback(this.callback, options)\n : this.callback\n }\n\n setOrderByIndex(index: IndexInterface<any>) {\n this.orderByIndex = index\n }\n\n /**\n * Set subscription status and emit events if changed\n */\n private setStatus(newStatus: SubscriptionStatus) {\n if (this._status === newStatus) {\n return // No change\n }\n\n const previousStatus = this._status\n this._status = newStatus\n\n // Emit status:change event\n this.emitInner(`status:change`, {\n type: `status:change`,\n subscription: this,\n previousStatus,\n status: newStatus,\n })\n\n // Emit specific status event\n const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`\n this.emitInner(eventKey, {\n type: eventKey,\n subscription: this,\n previousStatus,\n status: newStatus,\n } as SubscriptionEvents[typeof eventKey])\n }\n\n /**\n * Track a loadSubset promise and manage loading status\n */\n private trackLoadSubsetPromise(syncResult: Promise<void> | true) {\n // Track the promise if it's actually a promise (async work)\n if (syncResult instanceof Promise) {\n this.pendingLoadSubsetPromises.add(syncResult)\n this.setStatus(`loadingSubset`)\n\n syncResult.finally(() => {\n this.pendingLoadSubsetPromises.delete(syncResult)\n if (this.pendingLoadSubsetPromises.size === 0) {\n this.setStatus(`ready`)\n }\n })\n }\n }\n\n hasLoadedInitialState() {\n return this.loadedInitialState\n }\n\n hasSentAtLeastOneSnapshot() {\n return this.snapshotSent\n }\n\n emitEvents(changes: Array<ChangeMessage<any, any>>) {\n const newChanges = this.filterAndFlipChanges(changes)\n this.filteredCallback(newChanges)\n }\n\n /**\n * Sends the snapshot to the callback.\n * Returns a boolean indicating if it succeeded.\n * It can only fail if there is no index to fulfill the request\n * and the optimizedOnly option is set to true,\n * or, the entire state was already loaded.\n */\n requestSnapshot(opts?: RequestSnapshotOptions): boolean {\n if (this.loadedInitialState) {\n // Subscription was deoptimized so we already sent the entire initial state\n return false\n }\n\n const stateOpts: RequestSnapshotOptions = {\n where: this.options.whereExpression,\n optimizedOnly: opts?.optimizedOnly ?? false,\n }\n\n if (opts) {\n if (`where` in opts) {\n const snapshotWhereExp = opts.where\n if (stateOpts.where) {\n // Combine the two where expressions\n const subWhereExp = stateOpts.where\n const combinedWhereExp = and(subWhereExp, snapshotWhereExp)\n stateOpts.where = combinedWhereExp\n } else {\n stateOpts.where = snapshotWhereExp\n }\n }\n } else {\n // No options provided so it's loading the entire initial state\n this.loadedInitialState = true\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const syncResult = this.collection._sync.loadSubset({\n where: stateOpts.where,\n subscription: this,\n })\n\n const trackLoadSubsetPromise = opts?.trackLoadSubsetPromise ?? true\n if (trackLoadSubsetPromise) {\n this.trackLoadSubsetPromise(syncResult)\n }\n\n // Also load data immediately from the collection\n const snapshot = this.collection.currentStateAsChanges(stateOpts)\n\n if (snapshot === undefined) {\n // Couldn't load from indexes\n return false\n }\n\n // Only send changes that have not been sent yet\n const filteredSnapshot = snapshot.filter(\n (change) => !this.sentKeys.has(change.key)\n )\n\n this.snapshotSent = true\n this.callback(filteredSnapshot)\n return true\n }\n\n /**\n * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to `minValue`.\n * Requires a range index to be set with `setOrderByIndex` prior to calling this method.\n * It uses that range index to load the items in the order of the index.\n * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to `minValue` + limit values greater than `minValue`.\n * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.\n * Note 2: it does not send keys that have already been sent before.\n */\n requestLimitedSnapshot({\n orderBy,\n limit,\n minValue,\n }: RequestLimitedSnapshotOptions) {\n if (!limit) throw new Error(`limit is required`)\n\n if (!this.orderByIndex) {\n throw new Error(\n `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`\n )\n }\n\n const index = this.orderByIndex\n const where = this.options.whereExpression\n const whereFilterFn = where\n ? createFilterFunctionFromExpression(where)\n : undefined\n\n const filterFn = (key: string | number): boolean => {\n if (this.sentKeys.has(key)) {\n return false\n }\n\n const value = this.collection.get(key)\n if (value === undefined) {\n return false\n }\n\n return whereFilterFn?.(value) ?? true\n }\n\n let biggestObservedValue = minValue\n const changes: Array<ChangeMessage<any, string | number>> = []\n\n // If we have a minValue we need to handle the case\n // where there might be duplicate values equal to minValue that we need to include\n // because we can have data like this: [1, 2, 3, 3, 3, 4, 5]\n // so if minValue is 3 then the previous snapshot may not have included all 3s\n // e.g. if it was offset 0 and limit 3 it would only have loaded the first 3\n // so we load all rows equal to minValue first, to be sure we don't skip any duplicate values\n let keys: Array<string | number> = []\n if (minValue !== undefined) {\n // First, get all items with the same value as minValue\n const { expression } = orderBy[0]!\n const allRowsWithMinValue = this.collection.currentStateAsChanges({\n where: eq(expression, new Value(minValue)),\n })\n\n if (allRowsWithMinValue) {\n const keysWithMinValue = allRowsWithMinValue\n .map((change) => change.key)\n .filter((key) => !this.sentKeys.has(key) && filterFn(key))\n\n // Add items with the minValue first\n keys.push(...keysWithMinValue)\n\n // Then get items greater than minValue\n const keysGreaterThanMin = index.take(\n limit - keys.length,\n minValue,\n filterFn\n )\n keys.push(...keysGreaterThanMin)\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n\n const valuesNeeded = () => Math.max(limit - changes.length, 0)\n const collectionExhausted = () => keys.length === 0\n\n while (valuesNeeded() > 0 && !collectionExhausted()) {\n const insertedKeys = new Set<string | number>() // Track keys we add to `changes` in this iteration\n\n for (const key of keys) {\n const value = this.collection.get(key)!\n changes.push({\n type: `insert`,\n key,\n value,\n })\n biggestObservedValue = value\n insertedKeys.add(key) // Track this key\n }\n\n keys = index.take(valuesNeeded(), biggestObservedValue, filterFn)\n }\n\n this.callback(changes)\n\n let whereWithValueFilter = where\n if (typeof minValue !== `undefined`) {\n // Only request data that we haven't seen yet (i.e. is bigger than the minValue)\n const { expression, compareOptions } = orderBy[0]!\n const operator = compareOptions.direction === `asc` ? gt : lt\n const valueFilter = operator(expression, new Value(minValue))\n whereWithValueFilter = where ? and(where, valueFilter) : valueFilter\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const syncResult = this.collection._sync.loadSubset({\n where: whereWithValueFilter,\n limit,\n orderBy,\n subscription: this,\n })\n\n // Make parallel loadSubset calls for values equal to minValue and values greater than minValue\n const promises: Array<Promise<void>> = []\n\n // First promise: load all values equal to minValue\n if (typeof minValue !== `undefined`) {\n const { expression } = orderBy[0]!\n const exactValueFilter = eq(expression, new Value(minValue))\n\n const equalValueResult = this.collection._sync.loadSubset({\n where: exactValueFilter,\n subscription: this,\n })\n\n if (equalValueResult instanceof Promise) {\n promises.push(equalValueResult)\n }\n }\n\n // Second promise: load values greater than minValue\n if (syncResult instanceof Promise) {\n promises.push(syncResult)\n }\n\n // Track the combined promise\n if (promises.length > 0) {\n const combinedPromise = Promise.all(promises).then(() => {})\n this.trackLoadSubsetPromise(combinedPromise)\n } else {\n this.trackLoadSubsetPromise(syncResult)\n }\n }\n\n // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function\n // and that that also works properly (i.e. does not skip duplicate values)\n\n /**\n * Filters and flips changes for keys that have not been sent yet.\n * Deletes are filtered out for keys that have not been sent yet.\n * Updates are flipped into inserts for keys that have not been sent yet.\n */\n private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {\n if (this.loadedInitialState) {\n // We loaded the entire initial state\n // so no need to filter or flip changes\n return changes\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert`, previousValue: undefined }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n return newChanges\n }\n\n private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {\n if (this.loadedInitialState) {\n // No need to track sent keys if we loaded the entire state.\n // Since we sent everything, all keys must have been observed.\n return\n }\n\n for (const change of changes) {\n this.sentKeys.add(change.key)\n }\n }\n\n unsubscribe() {\n this.emitInner(`unsubscribed`, {\n type: `unsubscribed`,\n subscription: this,\n })\n // Clear all event listeners to prevent memory leaks\n this.clearListeners()\n }\n}\n"],"names":["EventEmitter","ensureIndexForExpression","createFilteredCallback","and","createFilterFunctionFromExpression","eq","Value","gt","lt"],"mappings":"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"}
@@ -6,6 +6,7 @@ import { CollectionImpl } from './index.js';
6
6
  type RequestSnapshotOptions = {
7
7
  where?: BasicExpression<boolean>;
8
8
  optimizedOnly?: boolean;
9
+ trackLoadSubsetPromise?: boolean;
9
10
  };
10
11
  type RequestLimitedSnapshotOptions = {
11
12
  orderBy: OrderBy;
@@ -53,10 +54,12 @@ export declare class CollectionSubscription extends EventEmitter<SubscriptionEve
53
54
  */
54
55
  requestSnapshot(opts?: RequestSnapshotOptions): boolean;
55
56
  /**
56
- * Sends a snapshot that is limited to the first `limit` rows that fulfill the `where` clause and are bigger than `minValue`.
57
+ * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to `minValue`.
57
58
  * Requires a range index to be set with `setOrderByIndex` prior to calling this method.
58
59
  * It uses that range index to load the items in the order of the index.
59
- * Note: it does not send keys that have already been sent before.
60
+ * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to `minValue` + limit values greater than `minValue`.
61
+ * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.
62
+ * Note 2: it does not send keys that have already been sent before.
60
63
  */
61
64
  requestLimitedSnapshot({ orderBy, limit, minValue, }: RequestLimitedSnapshotOptions): void;
62
65
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -22,6 +22,34 @@ class SchemaValidationError extends TanStackDBError {
22
22
  this.issues = issues;
23
23
  }
24
24
  }
25
+ class DuplicateDbInstanceError extends TanStackDBError {
26
+ constructor() {
27
+ super(
28
+ `Multiple instances of @tanstack/db detected!
29
+
30
+ This causes transaction context to be lost because each instance maintains its own transaction stack.
31
+
32
+ Common causes:
33
+ 1. Different versions of @tanstack/db installed
34
+ 2. Incompatible peer dependency versions in packages
35
+ 3. Module resolution issues in bundler configuration
36
+
37
+ To fix:
38
+ 1. Check installed versions: npm list @tanstack/db (or pnpm/yarn list)
39
+ 2. Force a single version using package manager overrides:
40
+ - npm: "overrides" in package.json
41
+ - pnpm: "pnpm.overrides" in package.json
42
+ - yarn: "resolutions" in package.json
43
+ 3. Clear node_modules and lockfile, then reinstall
44
+
45
+ To temporarily disable this check (not recommended):
46
+ Set environment variable: TANSTACK_DB_DISABLE_DUP_CHECK=1
47
+
48
+ See: https://tanstack.com/db/latest/docs/troubleshooting#duplicate-instances`
49
+ );
50
+ this.name = `DuplicateDbInstanceError`;
51
+ }
52
+ }
25
53
  class CollectionConfigurationError extends TanStackDBError {
26
54
  constructor(message) {
27
55
  super(message);
@@ -179,6 +207,14 @@ class MissingMutationFunctionError extends TransactionError {
179
207
  super(`mutationFn is required when creating a transaction`);
180
208
  }
181
209
  }
210
+ class OnMutateMustBeSynchronousError extends TransactionError {
211
+ constructor() {
212
+ super(
213
+ `onMutate must be synchronous and cannot return a promise. Remove async/await or returned promises from onMutate.`
214
+ );
215
+ this.name = `OnMutateMustBeSynchronousError`;
216
+ }
217
+ }
182
218
  class TransactionNotPendingMutateError extends TransactionError {
183
219
  constructor() {
184
220
  super(
@@ -287,6 +323,13 @@ class CollectionInputNotFoundError extends QueryCompilationError {
287
323
  super(`Input for ${details} not found in inputs map${availableKeysMsg}`);
288
324
  }
289
325
  }
326
+ class DuplicateAliasInSubqueryError extends QueryCompilationError {
327
+ constructor(alias, parentAliases) {
328
+ super(
329
+ `Subquery uses alias "${alias}" which is already used in the parent query. Each alias must be unique across parent and subquery contexts. Parent query aliases: ${parentAliases.join(`, `)}. Please rename "${alias}" in either the parent query or subquery to avoid conflicts.`
330
+ );
331
+ }
332
+ }
290
333
  class UnsupportedFromTypeError extends QueryCompilationError {
291
334
  constructor(type) {
292
335
  super(`Unsupported FROM type: ${type}`);
@@ -495,6 +538,8 @@ exports.CollectionRequiresSyncConfigError = CollectionRequiresSyncConfigError;
495
538
  exports.CollectionStateError = CollectionStateError;
496
539
  exports.DeleteKeyNotFoundError = DeleteKeyNotFoundError;
497
540
  exports.DistinctRequiresSelectError = DistinctRequiresSelectError;
541
+ exports.DuplicateAliasInSubqueryError = DuplicateAliasInSubqueryError;
542
+ exports.DuplicateDbInstanceError = DuplicateDbInstanceError;
498
543
  exports.DuplicateKeyError = DuplicateKeyError;
499
544
  exports.DuplicateKeySyncError = DuplicateKeySyncError;
500
545
  exports.EmptyReferencePathError = EmptyReferencePathError;
@@ -530,6 +575,7 @@ exports.NoPendingSyncTransactionCommitError = NoPendingSyncTransactionCommitErro
530
575
  exports.NoPendingSyncTransactionWriteError = NoPendingSyncTransactionWriteError;
531
576
  exports.NonAggregateExpressionNotInGroupByError = NonAggregateExpressionNotInGroupByError;
532
577
  exports.NonRetriableError = NonRetriableError;
578
+ exports.OnMutateMustBeSynchronousError = OnMutateMustBeSynchronousError;
533
579
  exports.OnlyOneSourceAllowedError = OnlyOneSourceAllowedError;
534
580
  exports.QueryBuilderError = QueryBuilderError;
535
581
  exports.QueryCompilationError = QueryCompilationError;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","sources":["../../src/errors.ts"],"sourcesContent":["// Root error class for all TanStack DB errors\nexport class TanStackDBError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `TanStackDBError`\n }\n}\n\n// Base error classes\nexport class NonRetriableError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n\n// Schema validation error (exported from index for backward compatibility)\nexport class SchemaValidationError extends TanStackDBError {\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) => `\\n- ${issue.message} - path: ${issue.path}`)\n .join(``)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\n// Collection Configuration Errors\nexport class CollectionConfigurationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionConfigurationError`\n }\n}\n\nexport class CollectionRequiresConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a config`)\n }\n}\n\nexport class CollectionRequiresSyncConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a sync config`)\n }\n}\n\nexport class InvalidSchemaError extends CollectionConfigurationError {\n constructor() {\n super(`Schema must implement the standard-schema interface`)\n }\n}\n\nexport class SchemaMustBeSynchronousError extends CollectionConfigurationError {\n constructor() {\n super(`Schema validation must be synchronous`)\n }\n}\n\n// Collection State Errors\nexport class CollectionStateError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionStateError`\n }\n}\n\nexport class CollectionInErrorStateError extends CollectionStateError {\n constructor(operation: string, collectionId: string) {\n super(\n `Cannot perform ${operation} on collection \"${collectionId}\" - collection is in error state. Try calling cleanup() and restarting the collection.`\n )\n }\n}\n\nexport class InvalidCollectionStatusTransitionError extends CollectionStateError {\n constructor(from: string, to: string, collectionId: string) {\n super(\n `Invalid collection status transition from \"${from}\" to \"${to}\" for collection \"${collectionId}\"`\n )\n }\n}\n\nexport class CollectionIsInErrorStateError extends CollectionStateError {\n constructor() {\n super(`Collection is in error state`)\n }\n}\n\nexport class NegativeActiveSubscribersError extends CollectionStateError {\n constructor() {\n super(`Active subscribers count is negative - this should never happen`)\n }\n}\n\n// Collection Operation Errors\nexport class CollectionOperationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionOperationError`\n }\n}\n\nexport class UndefinedKeyError extends CollectionOperationError {\n constructor(item: any) {\n super(\n `An object was created without a defined key: ${JSON.stringify(item)}`\n )\n }\n}\n\nexport class DuplicateKeyError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Cannot insert document with ID \"${key}\" because it already exists in the collection`\n )\n }\n}\n\nexport class DuplicateKeySyncError extends CollectionOperationError {\n constructor(key: string | number, collectionId: string) {\n super(\n `Cannot insert document with key \"${key}\" from sync because it already exists in the collection \"${collectionId}\"`\n )\n }\n}\n\nexport class MissingUpdateArgumentError extends CollectionOperationError {\n constructor() {\n super(`The first argument to update is missing`)\n }\n}\n\nexport class NoKeysPassedToUpdateError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to update`)\n }\n}\n\nexport class UpdateKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `The key \"${key}\" was passed to update but an object for this key was not found in the collection`\n )\n }\n}\n\nexport class KeyUpdateNotAllowedError extends CollectionOperationError {\n constructor(originalKey: string | number, newKey: string | number) {\n super(\n `Updating the key of an item is not allowed. Original key: \"${originalKey}\", Attempted new key: \"${newKey}\". Please delete the old item and create a new one if a key change is necessary.`\n )\n }\n}\n\nexport class NoKeysPassedToDeleteError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to delete`)\n }\n}\n\nexport class DeleteKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Collection.delete was called with key '${key}' but there is no item in the collection with this key`\n )\n }\n}\n\n// Collection Handler Errors\nexport class MissingHandlerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `MissingHandlerError`\n }\n}\n\nexport class MissingInsertHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n}\n\nexport class MissingUpdateHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n}\n\nexport class MissingDeleteHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n}\n\n// Transaction Errors\nexport class TransactionError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `TransactionError`\n }\n}\n\nexport class MissingMutationFunctionError extends TransactionError {\n constructor() {\n super(`mutationFn is required when creating a transaction`)\n }\n}\n\nexport class TransactionNotPendingMutateError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .mutate() as the transaction is no longer pending`\n )\n }\n}\n\nexport class TransactionAlreadyCompletedRollbackError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .rollback() as the transaction is already completed`\n )\n }\n}\n\nexport class TransactionNotPendingCommitError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .commit() as the transaction is no longer pending`\n )\n }\n}\n\nexport class NoPendingSyncTransactionWriteError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to write to`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedWriteError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n}\n\nexport class NoPendingSyncTransactionCommitError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to commit`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n}\n\n// Query Builder Errors\nexport class QueryBuilderError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryBuilderError`\n }\n}\n\nexport class OnlyOneSourceAllowedError extends QueryBuilderError {\n constructor(context: string) {\n super(`Only one source is allowed in the ${context}`)\n }\n}\n\nexport class SubQueryMustHaveFromClauseError extends QueryBuilderError {\n constructor(context: string) {\n super(`A sub query passed to a ${context} must have a from clause itself`)\n }\n}\n\nexport class InvalidSourceError extends QueryBuilderError {\n constructor(alias: string) {\n super(\n `Invalid source for live query: The value provided for alias \"${alias}\" is not a Collection or subquery. Live queries only accept Collection instances or subqueries. Please ensure you're passing a valid Collection or QueryBuilder, not a plain array or other data type.`\n )\n }\n}\n\nexport class JoinConditionMustBeEqualityError extends QueryBuilderError {\n constructor() {\n super(`Join condition must be an equality expression`)\n }\n}\n\nexport class QueryMustHaveFromClauseError extends QueryBuilderError {\n constructor() {\n super(`Query must have a from clause`)\n }\n}\n\n// Query Compilation Errors\nexport class QueryCompilationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryCompilationError`\n }\n}\n\nexport class DistinctRequiresSelectError extends QueryCompilationError {\n constructor() {\n super(`DISTINCT requires a SELECT clause.`)\n }\n}\n\nexport class HavingRequiresGroupByError extends QueryCompilationError {\n constructor() {\n super(`HAVING clause requires GROUP BY clause`)\n }\n}\n\nexport class LimitOffsetRequireOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n}\n\n/**\n * Error thrown when a collection input stream is not found during query compilation.\n * In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream.\n */\nexport class CollectionInputNotFoundError extends QueryCompilationError {\n constructor(\n alias: string,\n collectionId?: string,\n availableKeys?: Array<string>\n ) {\n const details = collectionId\n ? `alias \"${alias}\" (collection \"${collectionId}\")`\n : `collection \"${alias}\"`\n const availableKeysMsg = availableKeys?.length\n ? `. Available keys: ${availableKeys.join(`, `)}`\n : ``\n super(`Input for ${details} not found in inputs map${availableKeysMsg}`)\n }\n}\n\nexport class UnsupportedFromTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unsupported FROM type: ${type}`)\n }\n}\n\nexport class UnknownExpressionTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unknown expression type: ${type}`)\n }\n}\n\nexport class EmptyReferencePathError extends QueryCompilationError {\n constructor() {\n super(`Reference path cannot be empty`)\n }\n}\n\nexport class UnknownFunctionError extends QueryCompilationError {\n constructor(functionName: string) {\n super(`Unknown function: ${functionName}`)\n }\n}\n\nexport class JoinCollectionNotFoundError extends QueryCompilationError {\n constructor(collectionId: string) {\n super(`Collection \"${collectionId}\" not found during compilation of join`)\n }\n}\n\n// JOIN Operation Errors\nexport class JoinError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `JoinError`\n }\n}\n\nexport class UnsupportedJoinTypeError extends JoinError {\n constructor(joinType: string) {\n super(`Unsupported join type: ${joinType}`)\n }\n}\n\nexport class InvalidJoinConditionSameSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: both expressions refer to the same source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinConditionSourceMismatchError extends JoinError {\n constructor() {\n super(`Invalid join condition: expressions must reference source aliases`)\n }\n}\n\nexport class InvalidJoinConditionLeftSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: left expression refers to an unavailable source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinConditionRightSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: right expression does not refer to the joined source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinCondition extends JoinError {\n constructor() {\n super(`Invalid join condition`)\n }\n}\n\nexport class UnsupportedJoinSourceTypeError extends JoinError {\n constructor(type: string) {\n super(`Unsupported join source type: ${type}`)\n }\n}\n\n// GROUP BY and Aggregation Errors\nexport class GroupByError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `GroupByError`\n }\n}\n\nexport class NonAggregateExpressionNotInGroupByError extends GroupByError {\n constructor(alias: string) {\n super(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`\n )\n }\n}\n\nexport class UnsupportedAggregateFunctionError extends GroupByError {\n constructor(functionName: string) {\n super(`Unsupported aggregate function: ${functionName}`)\n }\n}\n\nexport class AggregateFunctionNotInSelectError extends GroupByError {\n constructor(functionName: string) {\n super(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${functionName}`\n )\n }\n}\n\nexport class UnknownHavingExpressionTypeError extends GroupByError {\n constructor(type: string) {\n super(`Unknown expression type in HAVING clause: ${type}`)\n }\n}\n\n// Storage Errors\nexport class StorageError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `StorageError`\n }\n}\n\nexport class SerializationError extends StorageError {\n constructor(operation: string, originalError: string) {\n super(\n `Cannot ${operation} item because it cannot be JSON serialized: ${originalError}`\n )\n }\n}\n\n// LocalStorage Collection Errors\nexport class LocalStorageCollectionError extends StorageError {\n constructor(message: string) {\n super(message)\n this.name = `LocalStorageCollectionError`\n }\n}\n\nexport class StorageKeyRequiredError extends LocalStorageCollectionError {\n constructor() {\n super(`[LocalStorageCollection] storageKey must be provided.`)\n }\n}\n\nexport class InvalidStorageDataFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string, key: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\" for key \"${key}\".`\n )\n }\n}\n\nexport class InvalidStorageObjectFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\". Expected object format.`\n )\n }\n}\n\n// Sync Cleanup Errors\nexport class SyncCleanupError extends TanStackDBError {\n constructor(collectionId: string, error: Error | string) {\n const message = error instanceof Error ? error.message : String(error)\n super(\n `Collection \"${collectionId}\" sync cleanup function threw an error: ${message}`\n )\n this.name = `SyncCleanupError`\n }\n}\n\n// Query Optimizer Errors\nexport class QueryOptimizerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryOptimizerError`\n }\n}\n\nexport class CannotCombineEmptyExpressionListError extends QueryOptimizerError {\n constructor() {\n super(`Cannot combine empty expression list`)\n }\n}\n\n/**\n * Internal error when the query optimizer fails to convert a WHERE clause to a collection filter.\n */\nexport class WhereClauseConversionError extends QueryOptimizerError {\n constructor(collectionId: string, alias: string) {\n super(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}' alias '${alias}'. This indicates a bug in the query optimization logic.`\n )\n }\n}\n\n/**\n * Error when a subscription cannot be found during lazy join processing.\n * For subqueries, aliases may be remapped (e.g., 'activeUser' → 'user').\n */\nexport class SubscriptionNotFoundError extends QueryCompilationError {\n constructor(\n resolvedAlias: string,\n originalAlias: string,\n collectionId: string,\n availableAliases: Array<string>\n ) {\n super(\n `Internal error: subscription for alias '${resolvedAlias}' (remapped from '${originalAlias}', collection '${collectionId}') is missing in join pipeline. Available aliases: ${availableAliases.join(`, `)}. This indicates a bug in alias tracking.`\n )\n }\n}\n\n/**\n * Error thrown when aggregate expressions are used outside of a GROUP BY context.\n */\nexport class AggregateNotSupportedError extends QueryCompilationError {\n constructor() {\n super(\n `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`\n )\n }\n}\n\n/**\n * Internal error when the compiler returns aliases that don't have corresponding input streams.\n * This should never happen since all aliases come from user declarations.\n */\nexport class MissingAliasInputsError extends QueryCompilationError {\n constructor(missingAliases: Array<string>) {\n super(\n `Internal error: compiler returned aliases without inputs: ${missingAliases.join(`, `)}. ` +\n `This indicates a bug in query compilation. Please report this issue.`\n )\n }\n}\n\n/**\n * Error thrown when setWindow is called on a collection without an ORDER BY clause.\n */\nexport class SetWindowRequiresOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `setWindow() can only be called on collections with an ORDER BY clause. ` +\n `Add .orderBy() to your query to enable window movement.`\n )\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.cjs","sources":["../../src/errors.ts"],"sourcesContent":["// Root error class for all TanStack DB errors\nexport class TanStackDBError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `TanStackDBError`\n }\n}\n\n// Base error classes\nexport class NonRetriableError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n\n// Schema validation error (exported from index for backward compatibility)\nexport class SchemaValidationError extends TanStackDBError {\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) => `\\n- ${issue.message} - path: ${issue.path}`)\n .join(``)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\n// Module Instance Errors\nexport class DuplicateDbInstanceError extends TanStackDBError {\n constructor() {\n super(\n `Multiple instances of @tanstack/db detected!\\n\\n` +\n `This causes transaction context to be lost because each instance maintains ` +\n `its own transaction stack.\\n\\n` +\n `Common causes:\\n` +\n `1. Different versions of @tanstack/db installed\\n` +\n `2. Incompatible peer dependency versions in packages\\n` +\n `3. Module resolution issues in bundler configuration\\n\\n` +\n `To fix:\\n` +\n `1. Check installed versions: npm list @tanstack/db (or pnpm/yarn list)\\n` +\n `2. Force a single version using package manager overrides:\\n` +\n ` - npm: \"overrides\" in package.json\\n` +\n ` - pnpm: \"pnpm.overrides\" in package.json\\n` +\n ` - yarn: \"resolutions\" in package.json\\n` +\n `3. Clear node_modules and lockfile, then reinstall\\n\\n` +\n `To temporarily disable this check (not recommended):\\n` +\n `Set environment variable: TANSTACK_DB_DISABLE_DUP_CHECK=1\\n\\n` +\n `See: https://tanstack.com/db/latest/docs/troubleshooting#duplicate-instances`\n )\n this.name = `DuplicateDbInstanceError`\n }\n}\n\n// Collection Configuration Errors\nexport class CollectionConfigurationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionConfigurationError`\n }\n}\n\nexport class CollectionRequiresConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a config`)\n }\n}\n\nexport class CollectionRequiresSyncConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a sync config`)\n }\n}\n\nexport class InvalidSchemaError extends CollectionConfigurationError {\n constructor() {\n super(`Schema must implement the standard-schema interface`)\n }\n}\n\nexport class SchemaMustBeSynchronousError extends CollectionConfigurationError {\n constructor() {\n super(`Schema validation must be synchronous`)\n }\n}\n\n// Collection State Errors\nexport class CollectionStateError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionStateError`\n }\n}\n\nexport class CollectionInErrorStateError extends CollectionStateError {\n constructor(operation: string, collectionId: string) {\n super(\n `Cannot perform ${operation} on collection \"${collectionId}\" - collection is in error state. Try calling cleanup() and restarting the collection.`\n )\n }\n}\n\nexport class InvalidCollectionStatusTransitionError extends CollectionStateError {\n constructor(from: string, to: string, collectionId: string) {\n super(\n `Invalid collection status transition from \"${from}\" to \"${to}\" for collection \"${collectionId}\"`\n )\n }\n}\n\nexport class CollectionIsInErrorStateError extends CollectionStateError {\n constructor() {\n super(`Collection is in error state`)\n }\n}\n\nexport class NegativeActiveSubscribersError extends CollectionStateError {\n constructor() {\n super(`Active subscribers count is negative - this should never happen`)\n }\n}\n\n// Collection Operation Errors\nexport class CollectionOperationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionOperationError`\n }\n}\n\nexport class UndefinedKeyError extends CollectionOperationError {\n constructor(item: any) {\n super(\n `An object was created without a defined key: ${JSON.stringify(item)}`\n )\n }\n}\n\nexport class DuplicateKeyError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Cannot insert document with ID \"${key}\" because it already exists in the collection`\n )\n }\n}\n\nexport class DuplicateKeySyncError extends CollectionOperationError {\n constructor(key: string | number, collectionId: string) {\n super(\n `Cannot insert document with key \"${key}\" from sync because it already exists in the collection \"${collectionId}\"`\n )\n }\n}\n\nexport class MissingUpdateArgumentError extends CollectionOperationError {\n constructor() {\n super(`The first argument to update is missing`)\n }\n}\n\nexport class NoKeysPassedToUpdateError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to update`)\n }\n}\n\nexport class UpdateKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `The key \"${key}\" was passed to update but an object for this key was not found in the collection`\n )\n }\n}\n\nexport class KeyUpdateNotAllowedError extends CollectionOperationError {\n constructor(originalKey: string | number, newKey: string | number) {\n super(\n `Updating the key of an item is not allowed. Original key: \"${originalKey}\", Attempted new key: \"${newKey}\". Please delete the old item and create a new one if a key change is necessary.`\n )\n }\n}\n\nexport class NoKeysPassedToDeleteError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to delete`)\n }\n}\n\nexport class DeleteKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Collection.delete was called with key '${key}' but there is no item in the collection with this key`\n )\n }\n}\n\n// Collection Handler Errors\nexport class MissingHandlerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `MissingHandlerError`\n }\n}\n\nexport class MissingInsertHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n}\n\nexport class MissingUpdateHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n}\n\nexport class MissingDeleteHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n}\n\n// Transaction Errors\nexport class TransactionError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `TransactionError`\n }\n}\n\nexport class MissingMutationFunctionError extends TransactionError {\n constructor() {\n super(`mutationFn is required when creating a transaction`)\n }\n}\n\nexport class OnMutateMustBeSynchronousError extends TransactionError {\n constructor() {\n super(\n `onMutate must be synchronous and cannot return a promise. Remove async/await or returned promises from onMutate.`\n )\n this.name = `OnMutateMustBeSynchronousError`\n }\n}\n\nexport class TransactionNotPendingMutateError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .mutate() as the transaction is no longer pending`\n )\n }\n}\n\nexport class TransactionAlreadyCompletedRollbackError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .rollback() as the transaction is already completed`\n )\n }\n}\n\nexport class TransactionNotPendingCommitError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .commit() as the transaction is no longer pending`\n )\n }\n}\n\nexport class NoPendingSyncTransactionWriteError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to write to`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedWriteError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n}\n\nexport class NoPendingSyncTransactionCommitError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to commit`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n}\n\n// Query Builder Errors\nexport class QueryBuilderError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryBuilderError`\n }\n}\n\nexport class OnlyOneSourceAllowedError extends QueryBuilderError {\n constructor(context: string) {\n super(`Only one source is allowed in the ${context}`)\n }\n}\n\nexport class SubQueryMustHaveFromClauseError extends QueryBuilderError {\n constructor(context: string) {\n super(`A sub query passed to a ${context} must have a from clause itself`)\n }\n}\n\nexport class InvalidSourceError extends QueryBuilderError {\n constructor(alias: string) {\n super(\n `Invalid source for live query: The value provided for alias \"${alias}\" is not a Collection or subquery. Live queries only accept Collection instances or subqueries. Please ensure you're passing a valid Collection or QueryBuilder, not a plain array or other data type.`\n )\n }\n}\n\nexport class JoinConditionMustBeEqualityError extends QueryBuilderError {\n constructor() {\n super(`Join condition must be an equality expression`)\n }\n}\n\nexport class QueryMustHaveFromClauseError extends QueryBuilderError {\n constructor() {\n super(`Query must have a from clause`)\n }\n}\n\n// Query Compilation Errors\nexport class QueryCompilationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryCompilationError`\n }\n}\n\nexport class DistinctRequiresSelectError extends QueryCompilationError {\n constructor() {\n super(`DISTINCT requires a SELECT clause.`)\n }\n}\n\nexport class HavingRequiresGroupByError extends QueryCompilationError {\n constructor() {\n super(`HAVING clause requires GROUP BY clause`)\n }\n}\n\nexport class LimitOffsetRequireOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n}\n\n/**\n * Error thrown when a collection input stream is not found during query compilation.\n * In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream.\n */\nexport class CollectionInputNotFoundError extends QueryCompilationError {\n constructor(\n alias: string,\n collectionId?: string,\n availableKeys?: Array<string>\n ) {\n const details = collectionId\n ? `alias \"${alias}\" (collection \"${collectionId}\")`\n : `collection \"${alias}\"`\n const availableKeysMsg = availableKeys?.length\n ? `. Available keys: ${availableKeys.join(`, `)}`\n : ``\n super(`Input for ${details} not found in inputs map${availableKeysMsg}`)\n }\n}\n\n/**\n * Error thrown when a subquery uses the same alias as its parent query.\n * This causes issues because parent and subquery would share the same input streams,\n * leading to empty results or incorrect data (aggregation cross-leaking).\n */\nexport class DuplicateAliasInSubqueryError extends QueryCompilationError {\n constructor(alias: string, parentAliases: Array<string>) {\n super(\n `Subquery uses alias \"${alias}\" which is already used in the parent query. ` +\n `Each alias must be unique across parent and subquery contexts. ` +\n `Parent query aliases: ${parentAliases.join(`, `)}. ` +\n `Please rename \"${alias}\" in either the parent query or subquery to avoid conflicts.`\n )\n }\n}\n\nexport class UnsupportedFromTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unsupported FROM type: ${type}`)\n }\n}\n\nexport class UnknownExpressionTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unknown expression type: ${type}`)\n }\n}\n\nexport class EmptyReferencePathError extends QueryCompilationError {\n constructor() {\n super(`Reference path cannot be empty`)\n }\n}\n\nexport class UnknownFunctionError extends QueryCompilationError {\n constructor(functionName: string) {\n super(`Unknown function: ${functionName}`)\n }\n}\n\nexport class JoinCollectionNotFoundError extends QueryCompilationError {\n constructor(collectionId: string) {\n super(`Collection \"${collectionId}\" not found during compilation of join`)\n }\n}\n\n// JOIN Operation Errors\nexport class JoinError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `JoinError`\n }\n}\n\nexport class UnsupportedJoinTypeError extends JoinError {\n constructor(joinType: string) {\n super(`Unsupported join type: ${joinType}`)\n }\n}\n\nexport class InvalidJoinConditionSameSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: both expressions refer to the same source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinConditionSourceMismatchError extends JoinError {\n constructor() {\n super(`Invalid join condition: expressions must reference source aliases`)\n }\n}\n\nexport class InvalidJoinConditionLeftSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: left expression refers to an unavailable source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinConditionRightSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: right expression does not refer to the joined source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinCondition extends JoinError {\n constructor() {\n super(`Invalid join condition`)\n }\n}\n\nexport class UnsupportedJoinSourceTypeError extends JoinError {\n constructor(type: string) {\n super(`Unsupported join source type: ${type}`)\n }\n}\n\n// GROUP BY and Aggregation Errors\nexport class GroupByError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `GroupByError`\n }\n}\n\nexport class NonAggregateExpressionNotInGroupByError extends GroupByError {\n constructor(alias: string) {\n super(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`\n )\n }\n}\n\nexport class UnsupportedAggregateFunctionError extends GroupByError {\n constructor(functionName: string) {\n super(`Unsupported aggregate function: ${functionName}`)\n }\n}\n\nexport class AggregateFunctionNotInSelectError extends GroupByError {\n constructor(functionName: string) {\n super(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${functionName}`\n )\n }\n}\n\nexport class UnknownHavingExpressionTypeError extends GroupByError {\n constructor(type: string) {\n super(`Unknown expression type in HAVING clause: ${type}`)\n }\n}\n\n// Storage Errors\nexport class StorageError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `StorageError`\n }\n}\n\nexport class SerializationError extends StorageError {\n constructor(operation: string, originalError: string) {\n super(\n `Cannot ${operation} item because it cannot be JSON serialized: ${originalError}`\n )\n }\n}\n\n// LocalStorage Collection Errors\nexport class LocalStorageCollectionError extends StorageError {\n constructor(message: string) {\n super(message)\n this.name = `LocalStorageCollectionError`\n }\n}\n\nexport class StorageKeyRequiredError extends LocalStorageCollectionError {\n constructor() {\n super(`[LocalStorageCollection] storageKey must be provided.`)\n }\n}\n\nexport class InvalidStorageDataFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string, key: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\" for key \"${key}\".`\n )\n }\n}\n\nexport class InvalidStorageObjectFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\". Expected object format.`\n )\n }\n}\n\n// Sync Cleanup Errors\nexport class SyncCleanupError extends TanStackDBError {\n constructor(collectionId: string, error: Error | string) {\n const message = error instanceof Error ? error.message : String(error)\n super(\n `Collection \"${collectionId}\" sync cleanup function threw an error: ${message}`\n )\n this.name = `SyncCleanupError`\n }\n}\n\n// Query Optimizer Errors\nexport class QueryOptimizerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryOptimizerError`\n }\n}\n\nexport class CannotCombineEmptyExpressionListError extends QueryOptimizerError {\n constructor() {\n super(`Cannot combine empty expression list`)\n }\n}\n\n/**\n * Internal error when the query optimizer fails to convert a WHERE clause to a collection filter.\n */\nexport class WhereClauseConversionError extends QueryOptimizerError {\n constructor(collectionId: string, alias: string) {\n super(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}' alias '${alias}'. This indicates a bug in the query optimization logic.`\n )\n }\n}\n\n/**\n * Error when a subscription cannot be found during lazy join processing.\n * For subqueries, aliases may be remapped (e.g., 'activeUser' → 'user').\n */\nexport class SubscriptionNotFoundError extends QueryCompilationError {\n constructor(\n resolvedAlias: string,\n originalAlias: string,\n collectionId: string,\n availableAliases: Array<string>\n ) {\n super(\n `Internal error: subscription for alias '${resolvedAlias}' (remapped from '${originalAlias}', collection '${collectionId}') is missing in join pipeline. Available aliases: ${availableAliases.join(`, `)}. This indicates a bug in alias tracking.`\n )\n }\n}\n\n/**\n * Error thrown when aggregate expressions are used outside of a GROUP BY context.\n */\nexport class AggregateNotSupportedError extends QueryCompilationError {\n constructor() {\n super(\n `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`\n )\n }\n}\n\n/**\n * Internal error when the compiler returns aliases that don't have corresponding input streams.\n * This should never happen since all aliases come from user declarations.\n */\nexport class MissingAliasInputsError extends QueryCompilationError {\n constructor(missingAliases: Array<string>) {\n super(\n `Internal error: compiler returned aliases without inputs: ${missingAliases.join(`, `)}. ` +\n `This indicates a bug in query compilation. Please report this issue.`\n )\n }\n}\n\n/**\n * Error thrown when setWindow is called on a collection without an ORDER BY clause.\n */\nexport class SetWindowRequiresOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `setWindow() can only be called on collections with an ORDER BY clause. ` +\n `Add .orderBy() to your query to enable window movement.`\n )\n }\n}\n"],"names":[],"mappings":"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"}
@@ -15,6 +15,9 @@ export declare class SchemaValidationError extends TanStackDBError {
15
15
  path?: ReadonlyArray<string | number | symbol>;
16
16
  }>, message?: string);
17
17
  }
18
+ export declare class DuplicateDbInstanceError extends TanStackDBError {
19
+ constructor();
20
+ }
18
21
  export declare class CollectionConfigurationError extends TanStackDBError {
19
22
  constructor(message: string);
20
23
  }
@@ -93,6 +96,9 @@ export declare class TransactionError extends TanStackDBError {
93
96
  export declare class MissingMutationFunctionError extends TransactionError {
94
97
  constructor();
95
98
  }
99
+ export declare class OnMutateMustBeSynchronousError extends TransactionError {
100
+ constructor();
101
+ }
96
102
  export declare class TransactionNotPendingMutateError extends TransactionError {
97
103
  constructor();
98
104
  }
@@ -151,6 +157,14 @@ export declare class LimitOffsetRequireOrderByError extends QueryCompilationErro
151
157
  export declare class CollectionInputNotFoundError extends QueryCompilationError {
152
158
  constructor(alias: string, collectionId?: string, availableKeys?: Array<string>);
153
159
  }
160
+ /**
161
+ * Error thrown when a subquery uses the same alias as its parent query.
162
+ * This causes issues because parent and subquery would share the same input streams,
163
+ * leading to empty results or incorrect data (aggregation cross-leaking).
164
+ */
165
+ export declare class DuplicateAliasInSubqueryError extends QueryCompilationError {
166
+ constructor(alias: string, parentAliases: Array<string>);
167
+ }
154
168
  export declare class UnsupportedFromTypeError extends QueryCompilationError {
155
169
  constructor(type: string);
156
170
  }
@@ -46,6 +46,8 @@ exports.CollectionRequiresSyncConfigError = errors.CollectionRequiresSyncConfigE
46
46
  exports.CollectionStateError = errors.CollectionStateError;
47
47
  exports.DeleteKeyNotFoundError = errors.DeleteKeyNotFoundError;
48
48
  exports.DistinctRequiresSelectError = errors.DistinctRequiresSelectError;
49
+ exports.DuplicateAliasInSubqueryError = errors.DuplicateAliasInSubqueryError;
50
+ exports.DuplicateDbInstanceError = errors.DuplicateDbInstanceError;
49
51
  exports.DuplicateKeyError = errors.DuplicateKeyError;
50
52
  exports.DuplicateKeySyncError = errors.DuplicateKeySyncError;
51
53
  exports.EmptyReferencePathError = errors.EmptyReferencePathError;
@@ -81,6 +83,7 @@ exports.NoPendingSyncTransactionCommitError = errors.NoPendingSyncTransactionCom
81
83
  exports.NoPendingSyncTransactionWriteError = errors.NoPendingSyncTransactionWriteError;
82
84
  exports.NonAggregateExpressionNotInGroupByError = errors.NonAggregateExpressionNotInGroupByError;
83
85
  exports.NonRetriableError = errors.NonRetriableError;
86
+ exports.OnMutateMustBeSynchronousError = errors.OnMutateMustBeSynchronousError;
84
87
  exports.OnlyOneSourceAllowedError = errors.OnlyOneSourceAllowedError;
85
88
  exports.QueryBuilderError = errors.QueryBuilderError;
86
89
  exports.QueryCompilationError = errors.QueryCompilationError;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const transactions = require("./transactions.cjs");
4
+ const errors = require("./errors.cjs");
5
+ const typeGuards = require("./utils/type-guards.cjs");
4
6
  function createOptimisticAction(options) {
5
7
  const { mutationFn, onMutate, ...config } = options;
6
8
  return (variables) => {
@@ -12,7 +14,10 @@ function createOptimisticAction(options) {
12
14
  }
13
15
  });
14
16
  transaction.mutate(() => {
15
- onMutate(variables);
17
+ const maybePromise = onMutate(variables);
18
+ if (typeGuards.isPromiseLike(maybePromise)) {
19
+ throw new errors.OnMutateMustBeSynchronousError();
20
+ }
16
21
  });
17
22
  return transaction;
18
23
  };
@@ -1 +1 @@
1
- {"version":3,"file":"optimistic-action.cjs","sources":["../../src/optimistic-action.ts"],"sourcesContent":["import { createTransaction } from \"./transactions\"\nimport type { CreateOptimisticActionsOptions, Transaction } from \"./types\"\n\n/**\n * Creates an optimistic action function that applies local optimistic updates immediately\n * before executing the actual mutation on the server.\n *\n * This pattern allows for responsive UI updates while the actual mutation is in progress.\n * The optimistic update is applied via the `onMutate` callback, and the server mutation\n * is executed via the `mutationFn`.\n *\n * **Important:** Inside your `mutationFn`, you must ensure that your server writes have synced back\n * before you return, as the optimistic state is dropped when you return from the mutation function.\n * You generally use collection-specific helpers to do this, such as Query's `utils.refetch()`,\n * direct write APIs, or Electric's `utils.awaitTxId()`.\n *\n * @example\n * ```ts\n * const addTodo = createOptimisticAction<string>({\n * onMutate: (text) => {\n * // Instantly applies local optimistic state\n * todoCollection.insert({\n * id: uuid(),\n * text,\n * completed: false\n * })\n * },\n * mutationFn: async (text, params) => {\n * // Persist the todo to your backend\n * const response = await fetch('/api/todos', {\n * method: 'POST',\n * body: JSON.stringify({ text, completed: false }),\n * })\n * const result = await response.json()\n *\n * // IMPORTANT: Ensure server writes have synced back before returning\n * // This ensures the optimistic state can be safely discarded\n * await todoCollection.utils.refetch()\n *\n * return result\n * }\n * })\n *\n * // Usage\n * const transaction = addTodo('New Todo Item')\n * ```\n *\n * @template TVariables - The type of variables that will be passed to the action function\n * @param options - Configuration options for the optimistic action\n * @returns A function that accepts variables of type TVariables and returns a Transaction\n */\nexport function createOptimisticAction<TVariables = unknown>(\n options: CreateOptimisticActionsOptions<TVariables>\n) {\n const { mutationFn, onMutate, ...config } = options\n\n return (variables: TVariables): Transaction => {\n // Create transaction with the original config\n const transaction = createTransaction({\n ...config,\n // Wire the mutationFn to use the provided variables\n mutationFn: async (params) => {\n return await mutationFn(variables, params)\n },\n })\n\n // Execute the transaction. The mutationFn is called once mutate()\n // is finished.\n transaction.mutate(() => {\n // Call onMutate with variables to apply optimistic updates\n onMutate(variables)\n })\n\n return transaction\n }\n}\n"],"names":["createTransaction"],"mappings":";;;AAmDO,SAAS,uBACd,SACA;AACA,QAAM,EAAE,YAAY,UAAU,GAAG,WAAW;AAE5C,SAAO,CAAC,cAAuC;AAE7C,UAAM,cAAcA,aAAAA,kBAAkB;AAAA,MACpC,GAAG;AAAA;AAAA,MAEH,YAAY,OAAO,WAAW;AAC5B,eAAO,MAAM,WAAW,WAAW,MAAM;AAAA,MAC3C;AAAA,IAAA,CACD;AAID,gBAAY,OAAO,MAAM;AAEvB,eAAS,SAAS;AAAA,IACpB,CAAC;AAED,WAAO;AAAA,EACT;AACF;;"}
1
+ {"version":3,"file":"optimistic-action.cjs","sources":["../../src/optimistic-action.ts"],"sourcesContent":["import { createTransaction } from \"./transactions\"\nimport { OnMutateMustBeSynchronousError } from \"./errors\"\nimport { isPromiseLike } from \"./utils/type-guards\"\nimport type { CreateOptimisticActionsOptions, Transaction } from \"./types\"\n\n/**\n * Creates an optimistic action function that applies local optimistic updates immediately\n * before executing the actual mutation on the server.\n *\n * This pattern allows for responsive UI updates while the actual mutation is in progress.\n * The optimistic update is applied via the `onMutate` callback, and the server mutation\n * is executed via the `mutationFn`.\n *\n * **Important:** Inside your `mutationFn`, you must ensure that your server writes have synced back\n * before you return, as the optimistic state is dropped when you return from the mutation function.\n * You generally use collection-specific helpers to do this, such as Query's `utils.refetch()`,\n * direct write APIs, or Electric's `utils.awaitTxId()`.\n *\n * @example\n * ```ts\n * const addTodo = createOptimisticAction<string>({\n * onMutate: (text) => {\n * // Instantly applies local optimistic state\n * todoCollection.insert({\n * id: uuid(),\n * text,\n * completed: false\n * })\n * },\n * mutationFn: async (text, params) => {\n * // Persist the todo to your backend\n * const response = await fetch('/api/todos', {\n * method: 'POST',\n * body: JSON.stringify({ text, completed: false }),\n * })\n * const result = await response.json()\n *\n * // IMPORTANT: Ensure server writes have synced back before returning\n * // This ensures the optimistic state can be safely discarded\n * await todoCollection.utils.refetch()\n *\n * return result\n * }\n * })\n *\n * // Usage\n * const transaction = addTodo('New Todo Item')\n * ```\n *\n * @template TVariables - The type of variables that will be passed to the action function\n * @param options - Configuration options for the optimistic action\n * @returns A function that accepts variables of type TVariables and returns a Transaction\n */\nexport function createOptimisticAction<TVariables = unknown>(\n options: CreateOptimisticActionsOptions<TVariables>\n) {\n const { mutationFn, onMutate, ...config } = options\n\n return (variables: TVariables): Transaction => {\n // Create transaction with the original config\n const transaction = createTransaction({\n ...config,\n // Wire the mutationFn to use the provided variables\n mutationFn: async (params) => {\n return await mutationFn(variables, params)\n },\n })\n\n // Execute the transaction. The mutationFn is called once mutate()\n // is finished.\n transaction.mutate(() => {\n const maybePromise = onMutate(variables) as unknown\n\n if (isPromiseLike(maybePromise)) {\n throw new OnMutateMustBeSynchronousError()\n }\n })\n\n return transaction\n }\n}\n"],"names":["createTransaction","isPromiseLike","OnMutateMustBeSynchronousError"],"mappings":";;;;;AAqDO,SAAS,uBACd,SACA;AACA,QAAM,EAAE,YAAY,UAAU,GAAG,WAAW;AAE5C,SAAO,CAAC,cAAuC;AAE7C,UAAM,cAAcA,aAAAA,kBAAkB;AAAA,MACpC,GAAG;AAAA;AAAA,MAEH,YAAY,OAAO,WAAW;AAC5B,eAAO,MAAM,WAAW,WAAW,MAAM;AAAA,MAC3C;AAAA,IAAA,CACD;AAID,gBAAY,OAAO,MAAM;AACvB,YAAM,eAAe,SAAS,SAAS;AAEvC,UAAIC,WAAAA,cAAc,YAAY,GAAG;AAC/B,cAAM,IAAIC,OAAAA,+BAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AACF;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"paced-mutations.cjs","sources":["../../src/paced-mutations.ts"],"sourcesContent":["import { createTransaction } from \"./transactions\"\nimport type { MutationFn, Transaction } from \"./types\"\nimport type { Strategy } from \"./strategies/types\"\n\n/**\n * Configuration for creating a paced mutations manager\n */\nexport interface PacedMutationsConfig<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n> {\n /**\n * Callback to apply optimistic updates immediately.\n * Receives the variables passed to the mutate function.\n */\n onMutate: (variables: TVariables) => void\n /**\n * Function to execute the mutation on the server.\n * Receives the transaction parameters containing all merged mutations.\n */\n mutationFn: MutationFn<T>\n /**\n * Strategy for controlling mutation execution timing\n * Examples: debounceStrategy, queueStrategy, throttleStrategy\n */\n strategy: Strategy\n /**\n * Custom metadata to associate with transactions\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Creates a paced mutations manager with pluggable timing strategies.\n *\n * This function provides a way to control when and how optimistic mutations\n * are persisted to the backend, using strategies like debouncing, queuing,\n * or throttling. The optimistic updates are applied immediately via `onMutate`,\n * and the actual persistence is controlled by the strategy.\n *\n * The returned function accepts variables of type TVariables and returns a\n * Transaction object that can be awaited to know when persistence completes\n * or to handle errors.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A function that accepts variables and returns a Transaction\n *\n * @example\n * ```ts\n * // Debounced mutations for auto-save\n * const updateTodo = createPacedMutations<string>({\n * onMutate: (text) => {\n * // Apply optimistic update immediately\n * collection.update(id, draft => { draft.text = text })\n * },\n * mutationFn: async (text, { transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * // Call with variables, returns a transaction\n * const tx = updateTodo('New text')\n *\n * // Await persistence or handle errors\n * await tx.isPersisted.promise\n * ```\n *\n * @example\n * ```ts\n * // Queue strategy for sequential processing\n * const addTodo = createPacedMutations<{ text: string }>({\n * onMutate: ({ text }) => {\n * collection.insert({ id: uuid(), text, completed: false })\n * },\n * mutationFn: async ({ text }, { transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: queueStrategy({\n * wait: 200,\n * addItemsTo: 'back',\n * getItemsFrom: 'front'\n * })\n * })\n * ```\n */\nexport function createPacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>\n): (variables: TVariables) => Transaction<T> {\n const { onMutate, mutationFn, strategy, ...transactionConfig } = config\n\n // The currently active transaction (pending, not yet persisting)\n let activeTransaction: Transaction<T> | null = null\n\n // Commit callback that the strategy will call when it's time to persist\n const commitCallback = () => {\n if (!activeTransaction) {\n throw new Error(\n `Strategy callback called but no active transaction exists. This indicates a bug in the strategy implementation.`\n )\n }\n\n if (activeTransaction.state !== `pending`) {\n throw new Error(\n `Strategy callback called but active transaction is in state \"${activeTransaction.state}\". Expected \"pending\".`\n )\n }\n\n const txToCommit = activeTransaction\n\n // Clear active transaction reference before committing\n activeTransaction = null\n\n // Commit the transaction\n txToCommit.commit().catch(() => {\n // Errors are handled via transaction.isPersisted.promise\n // This catch prevents unhandled promise rejections\n })\n\n return txToCommit\n }\n\n /**\n * Executes a mutation with the given variables. Creates a new transaction if none is active,\n * or adds to the existing active transaction. The strategy controls when\n * the transaction is actually committed.\n */\n function mutate(variables: TVariables): Transaction<T> {\n // Create a new transaction if we don't have an active one\n if (!activeTransaction || activeTransaction.state !== `pending`) {\n activeTransaction = createTransaction<T>({\n ...transactionConfig,\n mutationFn,\n autoCommit: false,\n })\n }\n\n // Execute onMutate with variables to apply optimistic updates\n activeTransaction.mutate(() => {\n onMutate(variables)\n })\n\n // Save reference before calling strategy.execute\n const txToReturn = activeTransaction\n\n // For queue strategy, pass a function that commits the captured transaction\n // This prevents the error when commitCallback tries to access the cleared activeTransaction\n if (strategy._type === `queue`) {\n const capturedTx = activeTransaction\n activeTransaction = null // Clear so next mutation creates a new transaction\n strategy.execute(() => {\n capturedTx.commit().catch(() => {\n // Errors are handled via transaction.isPersisted.promise\n })\n return capturedTx\n })\n } else {\n // For debounce/throttle, use commitCallback which manages activeTransaction\n strategy.execute(commitCallback)\n }\n\n return txToReturn\n }\n\n return mutate\n}\n"],"names":["createTransaction"],"mappings":";;;AAsFO,SAAS,qBAId,QAC2C;AAC3C,QAAM,EAAE,UAAU,YAAY,UAAU,GAAG,sBAAsB;AAGjE,MAAI,oBAA2C;AAG/C,QAAM,iBAAiB,MAAM;AAC3B,QAAI,CAAC,mBAAmB;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,kBAAkB,UAAU,WAAW;AACzC,YAAM,IAAI;AAAA,QACR,gEAAgE,kBAAkB,KAAK;AAAA,MAAA;AAAA,IAE3F;AAEA,UAAM,aAAa;AAGnB,wBAAoB;AAGpB,eAAW,SAAS,MAAM,MAAM;AAAA,IAGhC,CAAC;AAED,WAAO;AAAA,EACT;AAOA,WAAS,OAAO,WAAuC;AAErD,QAAI,CAAC,qBAAqB,kBAAkB,UAAU,WAAW;AAC/D,0BAAoBA,aAAAA,kBAAqB;AAAA,QACvC,GAAG;AAAA,QACH;AAAA,QACA,YAAY;AAAA,MAAA,CACb;AAAA,IACH;AAGA,sBAAkB,OAAO,MAAM;AAC7B,eAAS,SAAS;AAAA,IACpB,CAAC;AAGD,UAAM,aAAa;AAInB,QAAI,SAAS,UAAU,SAAS;AAC9B,YAAM,aAAa;AACnB,0BAAoB;AACpB,eAAS,QAAQ,MAAM;AACrB,mBAAW,SAAS,MAAM,MAAM;AAAA,QAEhC,CAAC;AACD,eAAO;AAAA,MACT,CAAC;AAAA,IACH,OAAO;AAEL,eAAS,QAAQ,cAAc;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;"}
1
+ {"version":3,"file":"paced-mutations.cjs","sources":["../../src/paced-mutations.ts"],"sourcesContent":["import { createTransaction } from \"./transactions\"\nimport type { MutationFn, Transaction } from \"./types\"\nimport type { Strategy } from \"./strategies/types\"\n\n/**\n * Configuration for creating a paced mutations manager\n */\nexport interface PacedMutationsConfig<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n> {\n /**\n * Callback to apply optimistic updates immediately.\n * Receives the variables passed to the mutate function.\n */\n onMutate: (variables: TVariables) => void\n /**\n * Function to execute the mutation on the server.\n * Receives the transaction parameters containing all merged mutations.\n */\n mutationFn: MutationFn<T>\n /**\n * Strategy for controlling mutation execution timing\n * Examples: debounceStrategy, queueStrategy, throttleStrategy\n */\n strategy: Strategy\n /**\n * Custom metadata to associate with transactions\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Creates a paced mutations manager with pluggable timing strategies.\n *\n * This function provides a way to control when and how optimistic mutations\n * are persisted to the backend, using strategies like debouncing, queuing,\n * or throttling. The optimistic updates are applied immediately via `onMutate`,\n * and the actual persistence is controlled by the strategy.\n *\n * The returned function accepts variables of type TVariables and returns a\n * Transaction object that can be awaited to know when persistence completes\n * or to handle errors.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A function that accepts variables and returns a Transaction\n *\n * @example\n * ```ts\n * // Debounced mutations for auto-save\n * const updateTodo = createPacedMutations<string>({\n * onMutate: (text) => {\n * // Apply optimistic update immediately\n * collection.update(id, draft => { draft.text = text })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * // Call with variables, returns a transaction\n * const tx = updateTodo('New text')\n *\n * // Await persistence or handle errors\n * await tx.isPersisted.promise\n * ```\n *\n * @example\n * ```ts\n * // Queue strategy for sequential processing\n * const addTodo = createPacedMutations<{ text: string }>({\n * onMutate: ({ text }) => {\n * collection.insert({ id: uuid(), text, completed: false })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: queueStrategy({\n * wait: 200,\n * addItemsTo: 'back',\n * getItemsFrom: 'front'\n * })\n * })\n * ```\n */\nexport function createPacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>\n): (variables: TVariables) => Transaction<T> {\n const { onMutate, mutationFn, strategy, ...transactionConfig } = config\n\n // The currently active transaction (pending, not yet persisting)\n let activeTransaction: Transaction<T> | null = null\n\n // Commit callback that the strategy will call when it's time to persist\n const commitCallback = () => {\n if (!activeTransaction) {\n throw new Error(\n `Strategy callback called but no active transaction exists. This indicates a bug in the strategy implementation.`\n )\n }\n\n if (activeTransaction.state !== `pending`) {\n throw new Error(\n `Strategy callback called but active transaction is in state \"${activeTransaction.state}\". Expected \"pending\".`\n )\n }\n\n const txToCommit = activeTransaction\n\n // Clear active transaction reference before committing\n activeTransaction = null\n\n // Commit the transaction\n txToCommit.commit().catch(() => {\n // Errors are handled via transaction.isPersisted.promise\n // This catch prevents unhandled promise rejections\n })\n\n return txToCommit\n }\n\n /**\n * Executes a mutation with the given variables. Creates a new transaction if none is active,\n * or adds to the existing active transaction. The strategy controls when\n * the transaction is actually committed.\n */\n function mutate(variables: TVariables): Transaction<T> {\n // Create a new transaction if we don't have an active one\n if (!activeTransaction || activeTransaction.state !== `pending`) {\n activeTransaction = createTransaction<T>({\n ...transactionConfig,\n mutationFn,\n autoCommit: false,\n })\n }\n\n // Execute onMutate with variables to apply optimistic updates\n activeTransaction.mutate(() => {\n onMutate(variables)\n })\n\n // Save reference before calling strategy.execute\n const txToReturn = activeTransaction\n\n // For queue strategy, pass a function that commits the captured transaction\n // This prevents the error when commitCallback tries to access the cleared activeTransaction\n if (strategy._type === `queue`) {\n const capturedTx = activeTransaction\n activeTransaction = null // Clear so next mutation creates a new transaction\n strategy.execute(() => {\n capturedTx.commit().catch(() => {\n // Errors are handled via transaction.isPersisted.promise\n })\n return capturedTx\n })\n } else {\n // For debounce/throttle, use commitCallback which manages activeTransaction\n strategy.execute(commitCallback)\n }\n\n return txToReturn\n }\n\n return mutate\n}\n"],"names":["createTransaction"],"mappings":";;;AAsFO,SAAS,qBAId,QAC2C;AAC3C,QAAM,EAAE,UAAU,YAAY,UAAU,GAAG,sBAAsB;AAGjE,MAAI,oBAA2C;AAG/C,QAAM,iBAAiB,MAAM;AAC3B,QAAI,CAAC,mBAAmB;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,kBAAkB,UAAU,WAAW;AACzC,YAAM,IAAI;AAAA,QACR,gEAAgE,kBAAkB,KAAK;AAAA,MAAA;AAAA,IAE3F;AAEA,UAAM,aAAa;AAGnB,wBAAoB;AAGpB,eAAW,SAAS,MAAM,MAAM;AAAA,IAGhC,CAAC;AAED,WAAO;AAAA,EACT;AAOA,WAAS,OAAO,WAAuC;AAErD,QAAI,CAAC,qBAAqB,kBAAkB,UAAU,WAAW;AAC/D,0BAAoBA,aAAAA,kBAAqB;AAAA,QACvC,GAAG;AAAA,QACH;AAAA,QACA,YAAY;AAAA,MAAA,CACb;AAAA,IACH;AAGA,sBAAkB,OAAO,MAAM;AAC7B,eAAS,SAAS;AAAA,IACpB,CAAC;AAGD,UAAM,aAAa;AAInB,QAAI,SAAS,UAAU,SAAS;AAC9B,YAAM,aAAa;AACnB,0BAAoB;AACpB,eAAS,QAAQ,MAAM;AACrB,mBAAW,SAAS,MAAM,MAAM;AAAA,QAEhC,CAAC;AACD,eAAO;AAAA,MACT,CAAC;AAAA,IACH,OAAO;AAEL,eAAS,QAAQ,cAAc;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;"}