UNPKG

@signaldb/sync

Version:

This is the sync implementation of [SignalDB](https://github.com/maxnowack/signaldb). SignalDB is a local-first JavaScript database with real-time sync, enabling optimistic UI with signal-based reactivity across multiple frameworks.

181 lines (180 loc) 8.07 kB
import type { BaseItem, PersistenceAdapter, ReactivityAdapter, Changeset, LoadResponse } from '@signaldb/core'; import { Collection } from '@signaldb/core'; import PromiseQueue from './utils/PromiseQueue'; import type { Change, Snapshot, SyncOperation } from './types'; type SyncOptions<T extends Record<string, any>> = { name: string; } & T; type CleanupFunction = (() => void | Promise<void>) | void; interface Options<CollectionOptions extends Record<string, any>, ItemType extends BaseItem<IdType> = BaseItem, IdType = any> { pull: (collectionOptions: SyncOptions<CollectionOptions>, pullParameters: { lastFinishedSyncStart?: number; lastFinishedSyncEnd?: number; }) => Promise<LoadResponse<ItemType>>; push: (collectionOptions: SyncOptions<CollectionOptions>, pushParameters: { changes: Changeset<ItemType>; }) => Promise<void>; registerRemoteChange?: (collectionOptions: SyncOptions<CollectionOptions>, onChange: (data?: LoadResponse<ItemType>) => Promise<void>) => CleanupFunction | Promise<CleanupFunction>; id?: string; persistenceAdapter?: (id: string, registerErrorHandler: (handler: (error: Error) => void) => void) => PersistenceAdapter<any, any>; reactivity?: ReactivityAdapter; onError?: (collectionOptions: SyncOptions<CollectionOptions>, error: Error) => void; autostart?: boolean; debounceTime?: number; } /** * Class to manage syncing of collections. * @template CollectionOptions * @template ItemType * @template IdType * @example * const syncManager = new SyncManager({ * pull: async (collectionOptions) => { * const response = await fetch(`/api/collections/${collectionOptions.name}`) * return await response.json() * }, * push: async (collectionOptions, { changes }) => { * await fetch(`/api/collections/${collectionOptions.name}`, { * method: 'POST', * body: JSON.stringify(changes), * }) * }, * }) * * const collection = new Collection() * syncManager.addCollection(collection, { * name: 'todos', * }) * * syncManager.sync('todos') */ export default class SyncManager<CollectionOptions extends Record<string, any>, ItemType extends BaseItem<IdType> = BaseItem, IdType = any> { protected options: Options<CollectionOptions, ItemType, IdType>; protected collections: Map<string, { collection: Collection<ItemType, IdType, any>; options: SyncOptions<CollectionOptions>; readyPromise: Promise<void>; syncPaused: boolean; cleanupFunction?: CleanupFunction; }>; protected changes: Collection<Change<ItemType>, string>; protected snapshots: Collection<Snapshot<ItemType>, string>; protected syncOperations: Collection<SyncOperation, string>; protected scheduledPushes: Set<string>; protected remoteChanges: Omit<Change, 'id' | 'time'>[]; protected syncQueues: Map<string, PromiseQueue>; protected persistenceReady: Promise<void>; protected isDisposed: boolean; protected instanceId: string; protected id: string; protected debouncedFlush: () => void; /** * @param options Collection options * @param options.pull Function to pull data from remote source. * @param options.push Function to push data to remote source. * @param [options.registerRemoteChange] Function to register a callback for remote changes. * @param [options.id] Unique identifier for this sync manager. Only nessesary if you have multiple sync managers. * @param [options.persistenceAdapter] Persistence adapter to use for storing changes, snapshots and sync operations. * @param [options.reactivity] Reactivity adapter to use for reactivity. * @param [options.onError] Function to handle errors that occur async during syncing. * @param [options.autostart] Whether to automatically start syncing new collections. * @param [options.debounceTime] The time in milliseconds to debounce push operations. */ constructor(options: Options<CollectionOptions, ItemType, IdType>); protected createPersistenceAdapter(name: string): { adapter: PersistenceAdapter<any, any>; handler: (error: Error) => void; } | undefined; protected getSyncQueue(name: string): PromiseQueue; /** * Clears all internal data structures */ dispose(): Promise<void>; /** * Gets a collection with it's options by name * @deprecated Use getCollectionProperties instead. * @param name Name of the collection * @throws Will throw an error if the name wasn't found * @returns Tuple of collection and options */ getCollection(name: string): (Collection<ItemType, IdType, any> | SyncOptions<CollectionOptions>)[]; /** * Gets collection options by name * @param name Name of the collection * @throws Will throw an error if the name wasn't found * @returns An object of all properties of the collection */ getCollectionProperties(name: string): { collection: Collection<ItemType, IdType, any>; options: SyncOptions<CollectionOptions>; readyPromise: Promise<void>; syncPaused: boolean; cleanupFunction?: CleanupFunction | undefined; }; /** * Adds a collection to the sync manager. * @param collection Collection to add * @param options Options for the collection. The object needs at least a `name` property. * @param options.name Unique name of the collection */ addCollection(collection: Collection<ItemType, IdType, any>, options: SyncOptions<CollectionOptions>): void; protected flushScheduledPushes(): void; protected schedulePush(name: string): void; /** * Setup all collections to be synced with remote changes * and enable automatic pushing changes to the remote source. */ startAll(): Promise<void>; /** * Setup a collection to be synced with remote changes * and enable automatic pushing changes to the remote source. * @param name Name of the collection */ startSync(name: string): Promise<void>; /** * Pauses the sync process for all collections. * This means that the collections will not be synced with remote changes * and changes will not automatically be pushed to the remote source. */ pauseAll(): Promise<void>; /** * Pauses the sync process for a collection. * This means that the collection will not be synced with remote changes * and changes will not automatically be pushed to the remote source. * @param name Name of the collection */ pauseSync(name: string): Promise<void>; /** * Starts the sync process for all collections */ syncAll(): Promise<void>; /** * Checks if a collection is currently beeing synced * @param [name] Name of the collection. If not provided, it will check if any collection is currently beeing synced. * @returns True if the collection is currently beeing synced, false otherwise. */ isSyncing(name?: string): boolean; /** * Checks if the sync manager is ready to sync. * @returns A promise that resolves when the sync manager is ready to sync. */ isReady(): Promise<void>; /** * Starts the sync process for a collection * @param name Name of the collection * @param options Options for the sync process. * @param options.force If true, the sync process will be started even if there are no changes and onlyWithChanges is true. * @param options.onlyWithChanges If true, the sync process will only be started if there are changes. */ sync(name: string, options?: { force?: boolean; onlyWithChanges?: boolean; }): Promise<void>; /** * Starts the push process for a collection (sync process but only if there are changes) * @param name Name of the collection */ pushChanges(name: string): Promise<void>; protected syncWithData(name: string, data: LoadResponse<ItemType>): Promise<void>; } export {};