@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
JavaScript
"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;