@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
TypeScript
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 {};