@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.
- package/dist/cjs/collection-events.cjs +88 -0
- package/dist/cjs/collection-events.cjs.map +1 -0
- package/dist/cjs/collection-events.d.cts +50 -0
- package/dist/cjs/collection.cjs +47 -10
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +22 -0
- package/dist/esm/collection-events.d.ts +50 -0
- package/dist/esm/collection-events.js +88 -0
- package/dist/esm/collection-events.js.map +1 -0
- package/dist/esm/collection.d.ts +22 -0
- package/dist/esm/collection.js +47 -10
- package/dist/esm/collection.js.map +1 -1
- package/package.json +1 -1
- package/src/collection-events.ts +169 -0
- package/src/collection.ts +80 -12
|
@@ -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
|
+
}
|
package/dist/cjs/collection.cjs
CHANGED
|
@@ -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
|
|
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
|
|
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;
|