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

147 lines (146 loc) 5.45 kB
"use strict"; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); const isEqual = require("./index.cjs9.js"); const uniqueBy = require("./index.cjs29.js"); class Observer { /** * 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) { __publicField(this, "previousItems", []); __publicField(this, "callbacks"); __publicField(this, "unbindEvents"); 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 }) => { var _a; const newItem = newItemsMap.get(oldItem.id); if (newItem) { if (this.hasCallbacks(["changed", "changedField"]) && !isEqual(newItem.item, oldItem)) { this.call("changed", newItem.item); if (this.hasCallbacks(["changedField"])) { const keys = uniqueBy([ ...Object.keys(newItem.item), ...Object.keys(oldItem) ], (value) => value); keys.forEach((key) => { if (isEqual(newItem.item[key], oldItem[key])) return; this.call("changedField", newItem.item, key, oldItem[key], newItem.item[key]); }); } } if (newItem.index !== index && ((_a = newItem.beforeItem) == null ? void 0 : _a.id) !== (oldBeforeItem == null ? void 0 : oldBeforeItem.id)) { this.call("movedBefore", newItem.item, newItem.beforeItem); } } else { this.call("removed", oldItem); } }); } if (this.hasCallbacks(["added", "addedBefore"])) { newItems.forEach((newItem, index) => { const oldItem = oldItemsMap.get(newItem.id); if (oldItem) 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); }); } } module.exports = Observer;