@tanstack/db 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class CollectionEvents {
4
+ constructor(collection) {
5
+ this.listeners = /* @__PURE__ */ new Map();
6
+ this.collection = collection;
7
+ }
8
+ on(event, callback) {
9
+ if (!this.listeners.has(event)) {
10
+ this.listeners.set(event, /* @__PURE__ */ new Set());
11
+ }
12
+ this.listeners.get(event).add(callback);
13
+ return () => {
14
+ this.listeners.get(event).delete(callback);
15
+ };
16
+ }
17
+ once(event, callback) {
18
+ const unsubscribe = this.on(event, (eventPayload) => {
19
+ callback(eventPayload);
20
+ unsubscribe();
21
+ });
22
+ return unsubscribe;
23
+ }
24
+ off(event, callback) {
25
+ var _a;
26
+ (_a = this.listeners.get(event)) == null ? void 0 : _a.delete(callback);
27
+ }
28
+ waitFor(event, timeout) {
29
+ return new Promise((resolve, reject) => {
30
+ let timeoutId;
31
+ const unsubscribe = this.on(event, (eventPayload) => {
32
+ if (timeoutId) {
33
+ clearTimeout(timeoutId);
34
+ timeoutId = void 0;
35
+ }
36
+ resolve(eventPayload);
37
+ unsubscribe();
38
+ });
39
+ if (timeout) {
40
+ timeoutId = setTimeout(() => {
41
+ timeoutId = void 0;
42
+ unsubscribe();
43
+ reject(new Error(`Timeout waiting for event ${event}`));
44
+ }, timeout);
45
+ }
46
+ });
47
+ }
48
+ emit(event, eventPayload) {
49
+ var _a;
50
+ (_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((listener) => {
51
+ try {
52
+ listener(eventPayload);
53
+ } catch (error) {
54
+ queueMicrotask(() => {
55
+ throw error;
56
+ });
57
+ }
58
+ });
59
+ }
60
+ emitStatusChange(status, previousStatus) {
61
+ this.emit(`status:change`, {
62
+ type: `status:change`,
63
+ collection: this.collection,
64
+ previousStatus,
65
+ status
66
+ });
67
+ const eventKey = `status:${status}`;
68
+ this.emit(eventKey, {
69
+ type: eventKey,
70
+ collection: this.collection,
71
+ previousStatus,
72
+ status
73
+ });
74
+ }
75
+ emitSubscribersChange(subscriberCount, previousSubscriberCount) {
76
+ this.emit(`subscribers:change`, {
77
+ type: `subscribers:change`,
78
+ collection: this.collection,
79
+ previousSubscriberCount,
80
+ subscriberCount
81
+ });
82
+ }
83
+ cleanup() {
84
+ this.listeners.clear();
85
+ }
86
+ }
87
+ exports.CollectionEvents = CollectionEvents;
88
+ //# sourceMappingURL=collection-events.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection-events.cjs","sources":["../../src/collection-events.ts"],"sourcesContent":["import type { Collection } from \"./collection\"\nimport type { CollectionStatus } from \"./types\"\n\n/**\n * Event emitted when the collection status changes\n */\nexport interface CollectionStatusChangeEvent {\n type: `status:change`\n collection: Collection\n previousStatus: CollectionStatus\n status: CollectionStatus\n}\n\n/**\n * Event emitted when the collection status changes to a specific status\n */\nexport interface CollectionStatusEvent<T extends CollectionStatus> {\n type: `status:${T}`\n collection: Collection\n previousStatus: CollectionStatus\n status: T\n}\n\n/**\n * Event emitted when the number of subscribers to the collection changes\n */\nexport interface CollectionSubscribersChangeEvent {\n type: `subscribers:change`\n collection: Collection\n previousSubscriberCount: number\n subscriberCount: number\n}\n\nexport type AllCollectionEvents = {\n \"status:change\": CollectionStatusChangeEvent\n \"subscribers:change\": CollectionSubscribersChangeEvent\n} & {\n [K in CollectionStatus as `status:${K}`]: CollectionStatusEvent<K>\n}\n\nexport type CollectionEvent =\n | AllCollectionEvents[keyof AllCollectionEvents]\n | CollectionStatusChangeEvent\n | CollectionSubscribersChangeEvent\n\nexport type CollectionEventHandler<T extends keyof AllCollectionEvents> = (\n event: AllCollectionEvents[T]\n) => void\n\nexport class CollectionEvents {\n private collection: Collection<any, any, any, any, any>\n private listeners = new Map<\n keyof AllCollectionEvents,\n Set<CollectionEventHandler<any>>\n >()\n\n constructor(collection: Collection<any, any, any, any, any>) {\n this.collection = collection\n }\n\n on<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(callback)\n\n return () => {\n this.listeners.get(event)!.delete(callback)\n }\n }\n\n once<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n const unsubscribe = this.on(event, (eventPayload) => {\n callback(eventPayload)\n unsubscribe()\n })\n return unsubscribe\n }\n\n off<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n this.listeners.get(event)?.delete(callback)\n }\n\n waitFor<T extends keyof AllCollectionEvents>(\n event: T,\n timeout?: number\n ): Promise<AllCollectionEvents[T]> {\n return new Promise((resolve, reject) => {\n let timeoutId: NodeJS.Timeout | undefined\n const unsubscribe = this.on(event, (eventPayload) => {\n if (timeoutId) {\n clearTimeout(timeoutId)\n timeoutId = undefined\n }\n resolve(eventPayload)\n unsubscribe()\n })\n if (timeout) {\n timeoutId = setTimeout(() => {\n timeoutId = undefined\n unsubscribe()\n reject(new Error(`Timeout waiting for event ${event}`))\n }, timeout)\n }\n })\n }\n\n emit<T extends keyof AllCollectionEvents>(\n event: T,\n eventPayload: AllCollectionEvents[T]\n ) {\n this.listeners.get(event)?.forEach((listener) => {\n try {\n listener(eventPayload)\n } catch (error) {\n // Re-throw in a microtask to surface the error\n queueMicrotask(() => {\n throw error\n })\n }\n })\n }\n\n emitStatusChange<T extends CollectionStatus>(\n status: T,\n previousStatus: CollectionStatus\n ) {\n this.emit(`status:change`, {\n type: `status:change`,\n collection: this.collection,\n previousStatus,\n status,\n })\n\n // Emit specific status event using type assertion\n const eventKey: `status:${T}` = `status:${status}`\n this.emit(eventKey, {\n type: eventKey,\n collection: this.collection,\n previousStatus,\n status,\n } as AllCollectionEvents[`status:${T}`])\n }\n\n emitSubscribersChange(\n subscriberCount: number,\n previousSubscriberCount: number\n ) {\n this.emit(`subscribers:change`, {\n type: `subscribers:change`,\n collection: this.collection,\n previousSubscriberCount,\n subscriberCount,\n })\n }\n\n cleanup() {\n this.listeners.clear()\n }\n}\n"],"names":[],"mappings":";;AAiDO,MAAM,iBAAiB;AAAA,EAO5B,YAAY,YAAiD;AAL7D,SAAQ,gCAAgB,IAAA;AAMtB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,GACE,OACA,UACA;AACA,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,KAAK;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;AAEvC,WAAO,MAAM;AACX,WAAK,UAAU,IAAI,KAAK,EAAG,OAAO,QAAQ;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,KACE,OACA,UACA;AACA,UAAM,cAAc,KAAK,GAAG,OAAO,CAAC,iBAAiB;AACnD,eAAS,YAAY;AACrB,kBAAA;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,IACE,OACA,UACA;;AACA,eAAK,UAAU,IAAI,KAAK,MAAxB,mBAA2B,OAAO;AAAA,EACpC;AAAA,EAEA,QACE,OACA,SACiC;AACjC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACJ,YAAM,cAAc,KAAK,GAAG,OAAO,CAAC,iBAAiB;AACnD,YAAI,WAAW;AACb,uBAAa,SAAS;AACtB,sBAAY;AAAA,QACd;AACA,gBAAQ,YAAY;AACpB,oBAAA;AAAA,MACF,CAAC;AACD,UAAI,SAAS;AACX,oBAAY,WAAW,MAAM;AAC3B,sBAAY;AACZ,sBAAA;AACA,iBAAO,IAAI,MAAM,6BAA6B,KAAK,EAAE,CAAC;AAAA,QACxD,GAAG,OAAO;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,KACE,OACA,cACA;;AACA,eAAK,UAAU,IAAI,KAAK,MAAxB,mBAA2B,QAAQ,CAAC,aAAa;AAC/C,UAAI;AACF,iBAAS,YAAY;AAAA,MACvB,SAAS,OAAO;AAEd,uBAAe,MAAM;AACnB,gBAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,iBACE,QACA,gBACA;AACA,SAAK,KAAK,iBAAiB;AAAA,MACzB,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IAAA,CACD;AAGD,UAAM,WAA0B,UAAU,MAAM;AAChD,SAAK,KAAK,UAAU;AAAA,MAClB,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IAAA,CACqC;AAAA,EACzC;AAAA,EAEA,sBACE,iBACA,yBACA;AACA,SAAK,KAAK,sBAAsB;AAAA,MAC9B,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEA,UAAU;AACR,SAAK,UAAU,MAAA;AAAA,EACjB;AACF;;"}
@@ -0,0 +1,50 @@
1
+ import { Collection } from './collection.cjs';
2
+ import { CollectionStatus } from './types.cjs';
3
+ /**
4
+ * Event emitted when the collection status changes
5
+ */
6
+ export interface CollectionStatusChangeEvent {
7
+ type: `status:change`;
8
+ collection: Collection;
9
+ previousStatus: CollectionStatus;
10
+ status: CollectionStatus;
11
+ }
12
+ /**
13
+ * Event emitted when the collection status changes to a specific status
14
+ */
15
+ export interface CollectionStatusEvent<T extends CollectionStatus> {
16
+ type: `status:${T}`;
17
+ collection: Collection;
18
+ previousStatus: CollectionStatus;
19
+ status: T;
20
+ }
21
+ /**
22
+ * Event emitted when the number of subscribers to the collection changes
23
+ */
24
+ export interface CollectionSubscribersChangeEvent {
25
+ type: `subscribers:change`;
26
+ collection: Collection;
27
+ previousSubscriberCount: number;
28
+ subscriberCount: number;
29
+ }
30
+ export type AllCollectionEvents = {
31
+ "status:change": CollectionStatusChangeEvent;
32
+ "subscribers:change": CollectionSubscribersChangeEvent;
33
+ } & {
34
+ [K in CollectionStatus as `status:${K}`]: CollectionStatusEvent<K>;
35
+ };
36
+ export type CollectionEvent = AllCollectionEvents[keyof AllCollectionEvents] | CollectionStatusChangeEvent | CollectionSubscribersChangeEvent;
37
+ export type CollectionEventHandler<T extends keyof AllCollectionEvents> = (event: AllCollectionEvents[T]) => void;
38
+ export declare class CollectionEvents {
39
+ private collection;
40
+ private listeners;
41
+ constructor(collection: Collection<any, any, any, any, any>);
42
+ on<T extends keyof AllCollectionEvents>(event: T, callback: CollectionEventHandler<T>): () => void;
43
+ once<T extends keyof AllCollectionEvents>(event: T, callback: CollectionEventHandler<T>): () => void;
44
+ off<T extends keyof AllCollectionEvents>(event: T, callback: CollectionEventHandler<T>): void;
45
+ waitFor<T extends keyof AllCollectionEvents>(event: T, timeout?: number): Promise<AllCollectionEvents[T]>;
46
+ emit<T extends keyof AllCollectionEvents>(event: T, eventPayload: AllCollectionEvents[T]): void;
47
+ emitStatusChange<T extends CollectionStatus>(status: T, previousStatus: CollectionStatus): void;
48
+ emitSubscribersChange(subscriberCount: number, previousSubscriberCount: number): void;
49
+ cleanup(): void;
50
+ }
@@ -10,6 +10,7 @@ const autoIndex = require("./indexes/auto-index.cjs");
10
10
  const transactions = require("./transactions.cjs");
11
11
  const errors = require("./errors.cjs");
12
12
  const changeEvents = require("./change-events.cjs");
13
+ const collectionEvents = require("./collection-events.cjs");
13
14
  function createCollection(options) {
14
15
  const collection = new CollectionImpl(
15
16
  options
@@ -462,6 +463,7 @@ class CollectionImpl {
462
463
  } else {
463
464
  this.syncedData = /* @__PURE__ */ new Map();
464
465
  }
466
+ this.events = new collectionEvents.CollectionEvents(this);
465
467
  if (config.startSync === true) {
466
468
  this.startSync();
467
469
  }
@@ -527,6 +529,12 @@ class CollectionImpl {
527
529
  get status() {
528
530
  return this._status;
529
531
  }
532
+ /**
533
+ * Get the number of subscribers to the collection
534
+ */
535
+ get subscriberCount() {
536
+ return this.activeSubscribersCount;
537
+ }
530
538
  /**
531
539
  * Validates that the collection is in a usable state for data operations
532
540
  * @private
@@ -566,12 +574,14 @@ class CollectionImpl {
566
574
  */
567
575
  setStatus(newStatus) {
568
576
  this.validateStatusTransition(this._status, newStatus);
577
+ const previousStatus = this._status;
569
578
  this._status = newStatus;
570
579
  if (newStatus === `ready` && !this.isIndexesResolved) {
571
580
  this.resolveAllIndexes().catch((error) => {
572
581
  console.warn(`Failed to resolve indexes:`, error);
573
582
  });
574
583
  }
584
+ this.events.emitStatusChange(newStatus, previousStatus);
575
585
  }
576
586
  /**
577
587
  * Start sync immediately - internal method for compiled queries
@@ -731,6 +741,7 @@ class CollectionImpl {
731
741
  this.preloadPromise = null;
732
742
  this.batchedEvents = [];
733
743
  this.shouldBatchEvents = false;
744
+ this.events.cleanup();
734
745
  this.setStatus(`cleaned-up`);
735
746
  return Promise.resolve();
736
747
  }
@@ -766,22 +777,32 @@ class CollectionImpl {
766
777
  * Increment the active subscribers count and start sync if needed
767
778
  */
768
779
  addSubscriber() {
780
+ const previousSubscriberCount = this.activeSubscribersCount;
769
781
  this.activeSubscribersCount++;
770
782
  this.cancelGCTimer();
771
783
  if (this._status === `cleaned-up` || this._status === `idle`) {
772
784
  this.startSync();
773
785
  }
786
+ this.events.emitSubscribersChange(
787
+ this.activeSubscribersCount,
788
+ previousSubscriberCount
789
+ );
774
790
  }
775
791
  /**
776
792
  * Decrement the active subscribers count and start GC timer if needed
777
793
  */
778
794
  removeSubscriber() {
795
+ const previousSubscriberCount = this.activeSubscribersCount;
779
796
  this.activeSubscribersCount--;
780
797
  if (this.activeSubscribersCount === 0) {
781
798
  this.startGCTimer();
782
799
  } else if (this.activeSubscribersCount < 0) {
783
800
  throw new errors.NegativeActiveSubscribersError();
784
801
  }
802
+ this.events.emitSubscribersChange(
803
+ this.activeSubscribersCount,
804
+ previousSubscriberCount
805
+ );
785
806
  }
786
807
  /**
787
808
  * Recompute optimistic state from active transactions
@@ -1412,11 +1433,7 @@ class CollectionImpl {
1412
1433
  if (this.size > 0 || this.isReady()) {
1413
1434
  return Promise.resolve(this.state);
1414
1435
  }
1415
- return new Promise((resolve) => {
1416
- this.onFirstReady(() => {
1417
- resolve(this.state);
1418
- });
1419
- });
1436
+ return this.preload().then(() => this.state);
1420
1437
  }
1421
1438
  /**
1422
1439
  * Gets the current state of the collection as an Array
@@ -1436,11 +1453,7 @@ class CollectionImpl {
1436
1453
  if (this.size > 0 || this.isReady()) {
1437
1454
  return Promise.resolve(this.toArray);
1438
1455
  }
1439
- return new Promise((resolve) => {
1440
- this.onFirstReady(() => {
1441
- resolve(this.toArray);
1442
- });
1443
- });
1456
+ return this.preload().then(() => this.toArray);
1444
1457
  }
1445
1458
  /**
1446
1459
  * Returns the current state of the collection as an array of changes
@@ -1582,6 +1595,30 @@ class CollectionImpl {
1582
1595
  this.capturePreSyncVisibleState();
1583
1596
  this.recomputeOptimisticState(false);
1584
1597
  }
1598
+ /**
1599
+ * Subscribe to a collection event
1600
+ */
1601
+ on(event, callback) {
1602
+ return this.events.on(event, callback);
1603
+ }
1604
+ /**
1605
+ * Subscribe to a collection event once
1606
+ */
1607
+ once(event, callback) {
1608
+ return this.events.once(event, callback);
1609
+ }
1610
+ /**
1611
+ * Unsubscribe from a collection event
1612
+ */
1613
+ off(event, callback) {
1614
+ this.events.off(event, callback);
1615
+ }
1616
+ /**
1617
+ * Wait for a collection event
1618
+ */
1619
+ waitFor(event, timeout) {
1620
+ return this.events.waitFor(event, timeout);
1621
+ }
1585
1622
  }
1586
1623
  exports.CollectionImpl = CollectionImpl;
1587
1624
  exports.createCollection = createCollection;