UNPKG

@signaldb/core

Version:

SignalDB is a client-side database that provides a simple MongoDB-like interface to the data with first-class typescript support to achieve an optimistic UI. Data persistence can be achieved by using storage providers that store the data through a JSON in

140 lines (139 loc) 4.8 kB
const require_isEqual = require("./index7.cjs.js"); const require_uniqueBy = require("./index8.cjs.js"); //#region src/Collection/Observer.ts /** * Represents an observer that tracks changes in a collection of items and triggers * callbacks for various events such as addition, removal, and modification of items. * @template T - The type of the items being observed, which must include an `id` field. */ var Observer = class { previousItems = []; callbacks; unbindEvents; /** * Creates a new instance of the `Observer` class. * Sets up event bindings and initializes the callbacks for tracking changes in a collection. * @param bindEvents - A function to bind external events to the observer. Must return a cleanup function to unbind those events. */ constructor(bindEvents) { this.callbacks = { added: [], addedBefore: [], changed: [], changedField: [], movedBefore: [], removed: [] }; this.unbindEvents = bindEvents(); } call(event, ...args) { this.callbacks[event].forEach(({ callback, options }) => { if (!options.skipInitial || !options.isInitial) callback(...args); }); } hasCallbacks(events) { return events.some((event) => this.callbacks[event].length > 0); } /** * Determines if the observer has no active callbacks registered for any events. * @returns A boolean indicating whether the observer is empty (i.e., no callbacks are registered). */ isEmpty() { return !this.hasCallbacks([ "added", "addedBefore", "changed", "changedField", "movedBefore", "removed" ]); } /** * Compares the previous state of items with the new state and triggers the appropriate callbacks * for events such as added, removed, changed, or moved items. * @param newItems - The new list of items to compare against the previous state. */ runChecks(newItems) { const oldItemsMap = new Map(this.previousItems.map((item, index) => [item.id, { item, index, beforeItem: this.previousItems[index + 1] || null }])); const newItemsMap = new Map(newItems.map((item, index) => [item.id, { item, index, beforeItem: newItems[index + 1] || null }])); if (this.hasCallbacks([ "changed", "changedField", "movedBefore", "removed" ])) oldItemsMap.forEach(({ item: oldItem, index, beforeItem: oldBeforeItem }) => { const newItem = newItemsMap.get(oldItem.id); if (newItem) { if (this.hasCallbacks(["changed", "changedField"]) && !require_isEqual.default(newItem.item, oldItem)) { this.call("changed", newItem.item); if (this.hasCallbacks(["changedField"])) require_uniqueBy.default([...Object.keys(newItem.item), ...Object.keys(oldItem)], (value) => value).forEach((key) => { if (require_isEqual.default(newItem.item[key], oldItem[key])) return; this.call("changedField", newItem.item, key, oldItem[key], newItem.item[key]); }); } if (newItem.index !== index && newItem.beforeItem?.id !== oldBeforeItem?.id) this.call("movedBefore", newItem.item, newItem.beforeItem); } else this.call("removed", oldItem); }); if (this.hasCallbacks(["added", "addedBefore"])) newItems.forEach((newItem, index) => { if (oldItemsMap.get(newItem.id)) return; this.call("added", newItem); this.call("addedBefore", newItem, newItems[index + 1] || null); }); this.previousItems = newItems; Object.keys(this.callbacks).forEach((key) => { const event = key; const callbacks = this.callbacks[event]; this.callbacks[event] = callbacks.map((callback) => ({ ...callback, options: { ...callback.options, isInitial: false } })); }); } /** * Stops the observer by unbinding all events and cleaning up resources. */ stop() { this.unbindEvents(); } /** * Registers callbacks for specific events to observe changes in the collection. * @param callbacks - An object containing the callbacks for various events (e.g., 'added', 'removed'). * @param skipInitial - A boolean indicating whether to skip invoking the callbacks for the initial state of the collection. */ addCallbacks(callbacks, skipInitial = false) { Object.keys(callbacks).forEach((key) => { const typedKey = key; this.callbacks[typedKey].push({ callback: callbacks[typedKey], options: { skipInitial, isInitial: true } }); }); } /** * Removes the specified callbacks for specific events, unregistering them from the observer. * @param callbacks - An object containing the callbacks to be removed for various events. */ removeCallbacks(callbacks) { Object.keys(callbacks).forEach((key) => { const typedKey = key; const index = this.callbacks[typedKey].findIndex(({ callback }) => callback === callbacks[typedKey]); this.callbacks[typedKey].splice(index, 1); }); } }; //#endregion exports.default = Observer;