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

265 lines (264 loc) 10.2 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); Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const sortItems = require("./index.cjs21.js"); const project = require("./index.cjs22.js"); const Observer = require("./index.cjs23.js"); function isInReactiveScope(reactivity) { if (!reactivity) return false; if (!reactivity.isInScope) return true; return reactivity.isInScope(); } class Cursor { /** * Creates a new instance of the `Cursor` class. * Provides utilities for querying, observing, and transforming items from a collection. * @template T - The type of the items in the collection. * @template U - The transformed item type after applying transformations (default is T). * @param getItems - A function that retrieves the filtered list of items. * @param options - Optional configuration for the cursor. * @param options.transform - A transformation function to apply to each item when retrieving them. * @param options.bindEvents - A function to bind reactivity events for the cursor, which should return a cleanup function. * @param options.fields - A projection object defining which fields of the item should be included or excluded. * @param options.sort - A sort specifier to determine the order of the items. * @param options.skip - The number of items to skip from the beginning of the result set. * @param options.limit - The maximum number of items to return in the result set. * @param options.reactive - A reactivity adapter to enable observing changes in the cursor's result set. * @param options.fieldTracking - A boolean to enable fine-grained field tracking for reactivity. */ constructor(getItems, options) { __publicField(this, "observer"); __publicField(this, "getFilteredItems"); __publicField(this, "options"); __publicField(this, "onCleanupCallbacks", []); this.getFilteredItems = getItems; this.options = options || {}; } addGetters(item) { if (!isInReactiveScope(this.options.reactive)) return item; const depend = this.depend.bind(this); return Object.entries(item).reduce((memo, [key, value]) => { Object.defineProperty(memo, key, { get() { depend({ changedField: (notify) => (changedItem, changedFieldName) => { if (changedFieldName !== key || changedItem.id !== item.id) return; notify(); } }); return value; }, enumerable: true, configurable: true }); return memo; }, {}); } transform(rawItem) { const item = this.options.fieldTracking ? this.addGetters(rawItem) : rawItem; if (!this.options.transform) return item; return this.options.transform(item); } getItems() { const items = this.getFilteredItems(); const { sort, skip, limit } = this.options; const sorted = sort ? sortItems(items, sort) : items; const skipped = skip ? sorted.slice(skip) : sorted; const limited = limit ? skipped.slice(0, limit) : skipped; const idExcluded = this.options.fields && this.options.fields.id === 0; return limited.map((item) => { if (!this.options.fields) return item; return { ...idExcluded ? {} : { id: item.id }, ...project(item, this.options.fields) }; }); } depend(changeEvents) { if (!this.options.reactive) return; if (!isInReactiveScope(this.options.reactive)) return; const signal = this.options.reactive.create(); signal.depend(); const notify = () => signal.notify(); function buildNotifier(event) { const eventHandler = changeEvents[event]; return (...args) => { if (eventHandler === true) { notify(); return; } if (typeof eventHandler !== "function") return; eventHandler(notify)(...args); }; } const stop = this.observeRawChanges({ added: buildNotifier("added"), addedBefore: buildNotifier("addedBefore"), changed: buildNotifier("changed"), changedField: buildNotifier("changedField"), movedBefore: buildNotifier("movedBefore"), removed: buildNotifier("removed") }, true); if (this.options.reactive.onDispose) { this.options.reactive.onDispose(() => stop(), signal); } this.onCleanup(stop); } ensureObserver() { if (!this.observer) { const observer = new Observer(() => { const requery = () => { observer.runChecks(this.getItems()); }; const cleanup = this.options.bindEvents && this.options.bindEvents(requery); return () => { if (cleanup) cleanup(); }; }); this.onCleanup(() => observer.stop()); this.observer = observer; } return this.observer; } observeRawChanges(callbacks, skipInitial = false) { const observer = this.ensureObserver(); observer.addCallbacks(callbacks, skipInitial); observer.runChecks(this.getItems()); return () => { observer.removeCallbacks(callbacks); if (!observer.isEmpty()) return; observer.stop(); this.observer = void 0; }; } /** * Cleans up all resources associated with the cursor, such as reactive bindings * and event listeners. This method should be called when the cursor is no longer needed * to prevent memory leaks. */ cleanup() { this.onCleanupCallbacks.forEach((callback) => { callback(); }); this.onCleanupCallbacks = []; } /** * Registers a cleanup callback to be executed when the `cleanup` method is called. * Useful for managing resources and ensuring proper cleanup of bindings or listeners. * @param callback - A function to be executed during cleanup. */ onCleanup(callback) { this.onCleanupCallbacks.push(callback); } /** * Iterates over each item in the cursor's result set, applying the provided callback * function to each transformed item. * ⚡️ this function is reactive! * @param callback - A function to execute for each item in the result set. * @param callback.item - The transformed item. */ forEach(callback) { const items = this.getItems(); this.depend({ addedBefore: true, removed: true, movedBefore: true, ...this.options.fieldTracking ? {} : { changed: true } }); items.forEach((item) => { callback(this.transform(item)); }); } /** * Creates a new array populated with the results of applying the provided callback * function to each transformed item in the cursor's result set. * ⚡️ this function is reactive! * @template V - The type of the items in the resulting array. * @param callback - A function to execute for each item in the result set. * @param callback.item - The transformed item. * @returns An array of results after applying the callback to each item. */ map(callback) { const results = []; this.forEach((item) => { results.push(callback(item)); }); return results; } /** * Fetches all transformed items from the cursor's result set as an array. * Automatically applies filtering, sorting, and limiting as per the cursor's options. * ⚡️ this function is reactive! * @returns An array of transformed items in the result set. */ fetch() { return this.map((item) => item); } /** * Counts the total number of items in the cursor's result set after applying * filtering and other criteria. * ⚡️ this function is reactive! * @returns The total number of items in the result set. */ count() { const items = this.getItems(); this.depend({ added: true, removed: true }); return items.length; } /** * Observes changes to the cursor's result set and triggers the specified callbacks * when items are added, removed, or updated. Supports reactivity and transformation. * @param callbacks - An object containing the callback functions to handle different change events. * @param callbacks.added - Triggered when an item is added to the result set. * @param callbacks.removed - Triggered when an item is removed from the result set. * @param callbacks.changed - Triggered when an item in the result set is modified. * @param callbacks.addedBefore - Triggered when an item is added before another item in the result set. * @param callbacks.movedBefore - Triggered when an item is moved before another item in the result set. * @param callbacks.changedField - Triggered when a specific field of an item changes. * @param skipInitial - A boolean indicating whether to skip the initial notification of the current result set. * @returns A function to stop observing changes. */ observeChanges(callbacks, skipInitial = false) { return this.observeRawChanges(Object.entries(callbacks).reduce((memo, [callbackName, callback]) => { if (!callback) return memo; return { ...memo, [callbackName]: (item, before) => { const transformedValue = this.transform(item); const hasBeforeParameter = before !== void 0; const transformedBeforeValue = hasBeforeParameter && before ? this.transform(before) : null; return callback(transformedValue, ...hasBeforeParameter ? [transformedBeforeValue] : []); } }; }, {}), skipInitial); } /** * Forces the cursor to re-evaluate its result set by re-fetching items * from the collection. This is useful when the underlying data or query * criteria have changed, and you want to ensure the cursor reflects the latest state. */ requery() { if (!this.observer) return; this.observer.runChecks(this.getItems()); } } exports.default = Cursor; exports.isInReactiveScope = isInReactiveScope;