@tanstack/db
Version:
A reactive client store for building super fast apps on sync
137 lines (136 loc) • 4.2 kB
JavaScript
import { NegativeActiveSubscribersError } from "../errors.js";
import { createSingleRowRefProxy, toExpression } from "../query/builder/ref-proxy.js";
import { CollectionSubscription } from "./subscription.js";
class CollectionChangesManager {
/**
* Creates a new CollectionChangesManager instance
*/
constructor() {
this.activeSubscribersCount = 0;
this.changeSubscriptions = /* @__PURE__ */ new Set();
this.batchedEvents = [];
this.shouldBatchEvents = false;
}
setDeps(deps) {
this.lifecycle = deps.lifecycle;
this.sync = deps.sync;
this.events = deps.events;
this.collection = deps.collection;
}
/**
* Emit an empty ready event to notify subscribers that the collection is ready
* This bypasses the normal empty array check in emitEvents
*/
emitEmptyReadyEvent() {
for (const subscription of this.changeSubscriptions) {
subscription.emitEvents([]);
}
}
/**
* Emit events either immediately or batch them for later emission
*/
emitEvents(changes, forceEmit = false) {
if (this.shouldBatchEvents && !forceEmit) {
this.batchedEvents.push(...changes);
return;
}
let eventsToEmit = changes;
if (forceEmit) {
if (this.batchedEvents.length > 0) {
eventsToEmit = [...this.batchedEvents, ...changes];
}
this.batchedEvents = [];
this.shouldBatchEvents = false;
}
if (eventsToEmit.length === 0) {
return;
}
for (const subscription of this.changeSubscriptions) {
subscription.emitEvents(eventsToEmit);
}
}
/**
* Subscribe to changes in the collection
*/
subscribeChanges(callback, options = {}) {
this.addSubscriber();
if (options.where && options.whereExpression) {
throw new Error(
`Cannot specify both 'where' and 'whereExpression' options. Use one or the other.`
);
}
const { where, ...opts } = options;
let whereExpression = opts.whereExpression;
if (where) {
const proxy = createSingleRowRefProxy();
const result = where(proxy);
whereExpression = toExpression(result);
}
const subscription = new CollectionSubscription(this.collection, callback, {
...opts,
whereExpression,
onUnsubscribe: () => {
this.removeSubscriber();
this.changeSubscriptions.delete(subscription);
}
});
if (options.onStatusChange) {
subscription.on(`status:change`, options.onStatusChange);
}
if (options.includeInitialState) {
subscription.requestSnapshot({
trackLoadSubsetPromise: false,
orderBy: options.orderBy,
limit: options.limit,
onLoadSubsetResult: options.onLoadSubsetResult
});
} else if (options.includeInitialState === false) {
subscription.markAllStateAsSeen();
}
this.changeSubscriptions.add(subscription);
return subscription;
}
/**
* Increment the active subscribers count and start sync if needed
*/
addSubscriber() {
const previousSubscriberCount = this.activeSubscribersCount;
this.activeSubscribersCount++;
this.lifecycle.cancelGCTimer();
if (this.lifecycle.status === `cleaned-up` || this.lifecycle.status === `idle`) {
this.sync.startSync();
}
this.events.emitSubscribersChange(
this.activeSubscribersCount,
previousSubscriberCount
);
}
/**
* Decrement the active subscribers count and start GC timer if needed
*/
removeSubscriber() {
const previousSubscriberCount = this.activeSubscribersCount;
this.activeSubscribersCount--;
if (this.activeSubscribersCount === 0) {
this.lifecycle.startGCTimer();
} else if (this.activeSubscribersCount < 0) {
throw new NegativeActiveSubscribersError();
}
this.events.emitSubscribersChange(
this.activeSubscribersCount,
previousSubscriberCount
);
}
/**
* Clean up the collection by stopping sync and clearing data
* This can be called manually or automatically by garbage collection
*/
cleanup() {
this.batchedEvents = [];
this.shouldBatchEvents = false;
}
}
export {
CollectionChangesManager
};
//# sourceMappingURL=changes.js.map