UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

170 lines (151 loc) 4.19 kB
import type { Collection } from "./collection" import type { CollectionStatus } from "./types" /** * Event emitted when the collection status changes */ export interface CollectionStatusChangeEvent { type: `status:change` collection: Collection previousStatus: CollectionStatus status: CollectionStatus } /** * Event emitted when the collection status changes to a specific status */ export interface CollectionStatusEvent<T extends CollectionStatus> { type: `status:${T}` collection: Collection previousStatus: CollectionStatus status: T } /** * Event emitted when the number of subscribers to the collection changes */ export interface CollectionSubscribersChangeEvent { type: `subscribers:change` collection: Collection previousSubscriberCount: number subscriberCount: number } export type AllCollectionEvents = { "status:change": CollectionStatusChangeEvent "subscribers:change": CollectionSubscribersChangeEvent } & { [K in CollectionStatus as `status:${K}`]: CollectionStatusEvent<K> } export type CollectionEvent = | AllCollectionEvents[keyof AllCollectionEvents] | CollectionStatusChangeEvent | CollectionSubscribersChangeEvent export type CollectionEventHandler<T extends keyof AllCollectionEvents> = ( event: AllCollectionEvents[T] ) => void export class CollectionEvents { private collection: Collection<any, any, any, any, any> private listeners = new Map< keyof AllCollectionEvents, Set<CollectionEventHandler<any>> >() constructor(collection: Collection<any, any, any, any, any>) { this.collection = collection } on<T extends keyof AllCollectionEvents>( event: T, callback: CollectionEventHandler<T> ) { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()) } this.listeners.get(event)!.add(callback) return () => { this.listeners.get(event)!.delete(callback) } } once<T extends keyof AllCollectionEvents>( event: T, callback: CollectionEventHandler<T> ) { const unsubscribe = this.on(event, (eventPayload) => { callback(eventPayload) unsubscribe() }) return unsubscribe } off<T extends keyof AllCollectionEvents>( event: T, callback: CollectionEventHandler<T> ) { this.listeners.get(event)?.delete(callback) } waitFor<T extends keyof AllCollectionEvents>( event: T, timeout?: number ): Promise<AllCollectionEvents[T]> { return new Promise((resolve, reject) => { let timeoutId: NodeJS.Timeout | undefined const unsubscribe = this.on(event, (eventPayload) => { if (timeoutId) { clearTimeout(timeoutId) timeoutId = undefined } resolve(eventPayload) unsubscribe() }) if (timeout) { timeoutId = setTimeout(() => { timeoutId = undefined unsubscribe() reject(new Error(`Timeout waiting for event ${event}`)) }, timeout) } }) } emit<T extends keyof AllCollectionEvents>( event: T, eventPayload: AllCollectionEvents[T] ) { this.listeners.get(event)?.forEach((listener) => { try { listener(eventPayload) } catch (error) { // Re-throw in a microtask to surface the error queueMicrotask(() => { throw error }) } }) } emitStatusChange<T extends CollectionStatus>( status: T, previousStatus: CollectionStatus ) { this.emit(`status:change`, { type: `status:change`, collection: this.collection, previousStatus, status, }) // Emit specific status event using type assertion const eventKey: `status:${T}` = `status:${status}` this.emit(eventKey, { type: eventKey, collection: this.collection, previousStatus, status, } as AllCollectionEvents[`status:${T}`]) } emitSubscribersChange( subscriberCount: number, previousSubscriberCount: number ) { this.emit(`subscribers:change`, { type: `subscribers:change`, collection: this.collection, previousSubscriberCount, subscriberCount, }) } cleanup() { this.listeners.clear() } }