@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
786 lines (785 loc) • 31.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 EventEmitter = require("./index.cjs12.js");
const match = require("./index.cjs14.js");
const modify = require("./index.cjs10.js");
const isEqual = require("./index.cjs9.js");
const randomId = require("./index.cjs11.js");
const deepClone = require("./index.cjs15.js");
const serializeValue = require("./index.cjs16.js");
const createSignal = require("./index.cjs17.js");
const Cursor = require("./index.cjs18.js");
const getIndexInfo = require("./index.cjs19.js");
const createIndex = require("./index.cjs13.js");
function hasPendingUpdates(pendingUpdates) {
return pendingUpdates.added.length > 0 || pendingUpdates.modified.length > 0 || pendingUpdates.removed.length > 0;
}
function applyUpdates(currentItems, { added, modified, removed }) {
const items = [...currentItems];
added.forEach((item) => {
items.push(item);
});
modified.forEach((item) => {
const index = items.findIndex(({ id }) => id === item.id);
if (index === -1)
return;
items[index] = item;
});
removed.forEach((item) => {
const index = items.findIndex(({ id }) => id === item.id);
if (index === -1)
return;
items.splice(index, 1);
});
return items;
}
const _Collection = class _Collection extends EventEmitter {
/**
* Initializes a new instance of the `Collection` class with optional configuration.
* Sets up memory, persistence, reactivity, and indices as specified in the options.
* @template T - The type of the items stored in the collection.
* @template I - The type of the unique identifier for the items.
* @template U - The transformed item type after applying transformations (default is T).
* @param options - Optional configuration for the collection.
* @param options.name - An optional name for the collection.
* @param options.memory - The in-memory adapter for storing items.
* @param options.reactivity - The reactivity adapter for observing changes in the collection.
* @param options.transform - A transformation function to apply to items when retrieving them.
* @param options.persistence - The persistence adapter for saving and loading items.
* @param options.indices - An array of index providers for optimized querying.
* @param options.enableDebugMode - A boolean to enable or disable debug mode.
* @param options.fieldTracking - A boolean to enable or disable field tracking by default.
*/
constructor(options) {
super();
__publicField(this, "name");
__publicField(this, "options");
__publicField(this, "persistenceAdapter", null);
__publicField(this, "isPullingSignal");
__publicField(this, "isPushingSignal");
__publicField(this, "indexProviders", []);
__publicField(this, "indicesOutdated", false);
__publicField(this, "idIndex", /* @__PURE__ */ new Map());
__publicField(this, "debugMode");
__publicField(this, "batchOperationInProgress", false);
__publicField(this, "isDisposed", false);
__publicField(this, "postBatchCallbacks", /* @__PURE__ */ new Set());
__publicField(this, "fieldTracking", false);
__publicField(this, "persistenceReadyPromise");
_Collection.collections.push(this);
this.name = (options == null ? void 0 : options.name) ?? `${this.constructor.name}-${randomId()}`;
this.options = {
memory: [],
...options
};
this.fieldTracking = this.options.fieldTracking ?? _Collection.fieldTracking;
this.debugMode = this.options.enableDebugMode ?? _Collection.debugMode;
this.indexProviders = [
createIndex.createExternalIndex("id", this.idIndex),
...this.options.indices || []
];
this.rebuildIndices();
this.isPullingSignal = createSignal(this.options.reactivity, !!(options == null ? void 0 : options.persistence));
this.isPushingSignal = createSignal(this.options.reactivity, false);
this.on("persistence.pullStarted", () => {
this.isPullingSignal.set(true);
});
this.on("persistence.pullCompleted", () => {
this.isPullingSignal.set(false);
});
this.on("persistence.pushStarted", () => {
this.isPushingSignal.set(true);
});
this.on("persistence.pushCompleted", () => {
this.isPushingSignal.set(false);
});
this.persistenceAdapter = this.options.persistence ?? null;
if (this.persistenceAdapter) {
let ongoingSaves = 0;
let isInitialized = false;
const pendingUpdates = { added: [], modified: [], removed: [] };
const loadPersistentData = async (data) => {
if (!this.persistenceAdapter)
throw new Error("Persistence adapter not found");
this.emit("persistence.pullStarted");
const { items, changes } = data ?? await this.persistenceAdapter.load();
if (items) {
if (ongoingSaves > 0)
return;
this.memory().splice(0, this.memoryArray().length, ...items);
this.idIndex.clear();
this.memory().map((item, index) => {
this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([index]));
});
} else if (changes) {
changes.added.forEach((item) => {
const index = this.memory().findIndex((document) => document.id === item.id);
if (index !== -1) {
this.memory().splice(index, 1, item);
return;
}
this.memory().push(item);
const itemIndex = this.memory().findIndex((document) => document === item);
this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([itemIndex]));
});
changes.modified.forEach((item) => {
const index = this.memory().findIndex((document) => document.id === item.id);
if (index === -1)
throw new Error("Cannot resolve index for item");
this.memory().splice(index, 1, item);
});
changes.removed.forEach((item) => {
const index = this.memory().findIndex((document) => document.id === item.id);
if (index === -1)
throw new Error("Cannot resolve index for item");
this.memory().splice(index, 1);
});
}
this.rebuildIndices();
this.emit("persistence.received");
setTimeout(() => this.emit("persistence.pullCompleted"), 0);
};
const saveQueue = {
added: [],
modified: [],
removed: []
};
let isFlushing = false;
const flushQueue = () => {
if (!this.persistenceAdapter)
throw new Error("Persistence adapter not found");
if (ongoingSaves <= 0)
this.emit("persistence.pushStarted");
if (isFlushing)
return;
if (!hasPendingUpdates(saveQueue))
return;
isFlushing = true;
ongoingSaves += 1;
const currentItems = this.memoryArray();
const changes = { ...saveQueue };
saveQueue.added = [];
saveQueue.modified = [];
saveQueue.removed = [];
this.persistenceAdapter.save(currentItems, changes).then(() => {
this.emit("persistence.transmitted");
}).catch((error) => {
this.emit("persistence.error", error instanceof Error ? error : new Error(error));
}).finally(() => {
ongoingSaves -= 1;
isFlushing = false;
flushQueue();
if (ongoingSaves <= 0)
this.emit("persistence.pushCompleted");
});
};
this.on("added", (item) => {
if (!isInitialized) {
pendingUpdates.added.push(item);
return;
}
saveQueue.added.push(item);
flushQueue();
});
this.on("changed", (item) => {
if (!isInitialized) {
pendingUpdates.modified.push(item);
return;
}
saveQueue.modified.push(item);
flushQueue();
});
this.on("removed", (item) => {
if (!isInitialized) {
pendingUpdates.removed.push(item);
return;
}
saveQueue.removed.push(item);
flushQueue();
});
this.persistenceAdapter.register((data) => loadPersistentData(data)).then(async () => {
if (!this.persistenceAdapter)
throw new Error("Persistence adapter not found");
let currentItems = this.memoryArray();
await loadPersistentData();
while (hasPendingUpdates(pendingUpdates)) {
const added = pendingUpdates.added.splice(0);
const modified = pendingUpdates.modified.splice(0);
const removed = pendingUpdates.removed.splice(0);
currentItems = applyUpdates(this.memoryArray(), { added, modified, removed });
await this.persistenceAdapter.save(currentItems, { added, modified, removed }).then(() => {
this.emit("persistence.transmitted");
});
}
await loadPersistentData();
isInitialized = true;
setTimeout(() => this.emit("persistence.init"), 0);
}).catch((error) => {
this.emit("persistence.error", error instanceof Error ? error : new Error(error));
});
}
this.persistenceReadyPromise = new Promise((resolve, reject) => {
if (!this.persistenceAdapter)
return resolve();
this.once("persistence.init", resolve);
this.once("persistence.error", reject);
});
_Collection.onCreationCallbacks.forEach((callback) => callback(this));
}
static getCollections() {
return _Collection.collections;
}
static onCreation(callback) {
_Collection.onCreationCallbacks.push(callback);
}
static onDispose(callback) {
_Collection.onDisposeCallbacks.push(callback);
}
/**
* Executes a batch operation, allowing multiple modifications to the collection
* while deferring index rebuilding until all operations in the batch are completed.
* This improves performance by avoiding repetitive index recalculations and
* provides atomicity for the batch of operations.
* @param callback - The batch operation to execute.
*/
static batch(callback) {
_Collection.batchOperationInProgress = true;
_Collection.collections.reduce((memo, collection) => () => collection.batch(() => memo()), callback)();
_Collection.batchOperationInProgress = false;
}
/**
* Checks whether the collection is currently performing a pull operation
* ⚡️ this function is reactive!
* (loading data from the persistence adapter).
* @returns A boolean indicating if the collection is in the process of pulling data.
*/
isPulling() {
return this.isPullingSignal.get() ?? false;
}
/**
* Checks whether the collection is currently performing a push operation
* ⚡️ this function is reactive!
* (saving data to the persistence adapter).
* @returns A boolean indicating if the collection is in the process of pushing data.
*/
isPushing() {
return this.isPushingSignal.get() ?? false;
}
/**
* Checks whether the collection is currently performing either a pull or push operation,
* ⚡️ this function is reactive!
* indicating that it is loading or saving data.
* @returns A boolean indicating if the collection is in the process of loading or saving data.
*/
isLoading() {
const isPulling = this.isPulling();
const isPushing = this.isPushing();
return isPulling || isPushing;
}
/**
* Retrieves the current debug mode status of the collection.
* @returns A boolean indicating whether debug mode is enabled for the collection.
*/
getDebugMode() {
return this.debugMode;
}
/**
* Enables or disables debug mode for the collection.
* When debug mode is enabled, additional debugging information and events are emitted.
* @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) debug mode.
*/
setDebugMode(enable) {
this.debugMode = enable;
}
/**
* Enables or disables field tracking for the collection.
* @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) field tracking.
*/
setFieldTracking(enable) {
this.fieldTracking = enable;
}
/**
* Resolves when the persistence adapter finished initializing
* and the collection is ready to be used.
* @returns A promise that resolves when the collection is ready.
* @example
* ```ts
* const collection = new Collection({
* persistence: // ...
* })
* await collection.isReady()
*
* collection.insert({ name: 'Item 1' })
*/
async isReady() {
return this.persistenceReadyPromise;
}
profile(fn, measureFunction) {
if (!this.debugMode)
return fn();
const startTime = performance.now();
const result = fn();
const endTime = performance.now();
measureFunction(endTime - startTime);
return result;
}
executeInDebugMode(fn) {
if (!this.debugMode)
return;
const callstack = new Error().stack || "";
fn(callstack);
}
rebuildIndices() {
this.indicesOutdated = true;
if (this.batchOperationInProgress)
return;
this.rebuildAllIndices();
}
rebuildAllIndices() {
this.idIndex.clear();
this.memory().map((item, index) => {
this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([index]));
});
this.indexProviders.forEach((index) => index.rebuild(this.memoryArray()));
this.indicesOutdated = false;
}
getIndexInfo(selector) {
if (selector != null && Object.keys(selector).length === 1 && "id" in selector && typeof selector.id !== "object") {
return {
matched: true,
positions: [...this.idIndex.get(serializeValue(selector.id)) || []],
optimizedSelector: {}
};
}
if (selector == null || this.indicesOutdated) {
return {
matched: false,
positions: [],
optimizedSelector: {}
};
}
return getIndexInfo.default(this.indexProviders, selector);
}
getItemAndIndex(selector) {
const memory = this.memoryArray();
const indexInfo = this.getIndexInfo(selector);
const items = indexInfo.matched ? indexInfo.positions.map((index2) => memory[index2]) : memory;
const item = items.find((document) => match(document, selector));
const foundInIndex = indexInfo.matched && indexInfo.positions.find((itemIndex) => memory[itemIndex] === item);
const index = foundInIndex || memory.findIndex((document) => document === item);
if (item == null)
return { item: null, index: -1 };
if (index === -1)
throw new Error("Cannot resolve index for item");
return { item, index };
}
deleteFromIdIndex(id, index) {
this.idIndex.delete(serializeValue(id));
if (!this.batchOperationInProgress)
return;
this.idIndex.forEach(([currenIndex], key) => {
if (currenIndex > index) {
this.idIndex.set(key, /* @__PURE__ */ new Set([currenIndex - 1]));
}
});
}
memory() {
return this.options.memory;
}
memoryArray() {
return this.memory().map((item) => item);
}
transform(item) {
if (!this.options.transform)
return item;
return this.options.transform(item);
}
getItems(selector) {
return this.profile(() => {
const indexInfo = this.getIndexInfo(selector);
const matchItems = (item) => {
if (indexInfo.optimizedSelector == null)
return true;
if (Object.keys(indexInfo.optimizedSelector).length <= 0)
return true;
const matches = match(item, indexInfo.optimizedSelector);
return matches;
};
this.emit("getItems", selector);
const memory = this.memoryArray();
if (!indexInfo.matched) {
if (isEqual(selector, {}))
return memory;
return memory.filter(matchItems);
}
const items = indexInfo.positions.map((index) => memory[index]);
if (isEqual(indexInfo.optimizedSelector, {}))
return items;
return items.filter(matchItems);
}, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
}
/**
* Disposes the collection, unregisters persistence adapters, clears memory, and
* cleans up all resources used by the collection.
* @returns A promise that resolves when the collection is disposed.
*/
async dispose() {
var _a;
if ((_a = this.persistenceAdapter) == null ? void 0 : _a.unregister)
await this.persistenceAdapter.unregister();
this.persistenceAdapter = null;
this.memory().map(() => this.memory().pop());
this.idIndex.clear();
this.indexProviders = [];
this.isDisposed = true;
this.removeAllListeners();
_Collection.collections = _Collection.collections.filter((collection) => collection !== this);
_Collection.onDisposeCallbacks.forEach((callback) => callback(this));
}
/**
* Finds multiple items in the collection based on a selector and optional options.
* Returns a cursor for reactive data queries.
* @template O - The options type for the find operation.
* @param [selector] - The criteria to select items.
* @param [options] - Options for the find operation, such as limit and sort.
* @returns A cursor to fetch and observe the matching items.
*/
find(selector, options) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (selector !== void 0 && (!selector || typeof selector !== "object"))
throw new Error("Invalid selector");
const cursor = new Cursor.default(() => this.getItems(selector), {
reactive: this.options.reactivity,
fieldTracking: this.fieldTracking,
...options,
transform: this.transform.bind(this),
bindEvents: (requery) => {
const handleRequery = () => {
if (this.batchOperationInProgress) {
this.postBatchCallbacks.add(requery);
return;
}
requery();
};
this.addListener("persistence.received", handleRequery);
this.addListener("added", handleRequery);
this.addListener("changed", handleRequery);
this.addListener("removed", handleRequery);
this.emit("observer.created", selector, options);
return () => {
this.removeListener("persistence.received", handleRequery);
this.removeListener("added", handleRequery);
this.removeListener("changed", handleRequery);
this.removeListener("removed", handleRequery);
this.emit("observer.disposed", selector, options);
};
}
});
this.emit("find", selector, options, cursor);
this.executeInDebugMode((callstack) => this.emit("_debug.find", callstack, selector, options, cursor));
return cursor;
}
/**
* Finds a single item in the collection based on a selector and optional options.
* ⚡️ this function is reactive!
* Returns the found item or undefined if no item matches.
* @template O - The options type for the find operation.
* @param selector - The criteria to select the item.
* @param [options] - Options for the find operation, such as projection.
* @returns The found item or `undefined`.
*/
findOne(selector, options) {
if (this.isDisposed)
throw new Error("Collection is disposed");
const cursor = this.find(selector, {
limit: 1,
...options
});
const returnValue = cursor.fetch()[0] || void 0;
this.emit("findOne", selector, options, returnValue);
this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
return returnValue;
}
/**
* Performs a batch operation, deferring index rebuilds and allowing multiple
* modifications to be made atomically. Executes any post-batch callbacks afterwards.
* @param callback - The batch operation to execute.
*/
batch(callback) {
this.batchOperationInProgress = true;
callback();
this.batchOperationInProgress = false;
this.rebuildAllIndices();
this.postBatchCallbacks.forEach((callback_) => callback_());
this.postBatchCallbacks.clear();
}
/**
* Inserts a single item into the collection. Generates a unique ID if not provided.
* @param item - The item to insert.
* @returns The ID of the inserted item.
* @throws {Error} If the collection is disposed or the item has an invalid ID.
*/
insert(item) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!item)
throw new Error("Invalid item");
const newItem = { id: randomId(), ...item };
this.emit("validate", newItem);
if (this.idIndex.has(serializeValue(newItem.id)))
throw new Error("Item with same id already exists");
this.memory().push(newItem);
const itemIndex = this.memory().findIndex((document) => document === newItem);
this.idIndex.set(serializeValue(newItem.id), /* @__PURE__ */ new Set([itemIndex]));
this.rebuildIndices();
this.emit("added", newItem);
this.emit("insert", newItem);
this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
return newItem.id;
}
/**
* Inserts multiple items into the collection. Generates unique IDs for items if not provided.
* @param items - The items to insert.
* @returns An array of IDs of the inserted items.
* @throws {Error} If the collection is disposed or the items are invalid.
*/
insertMany(items) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!items)
throw new Error("Invalid items");
if (items.length === 0) {
return [];
}
const ids = [];
this.batch(() => {
items.forEach((item) => {
ids.push(this.insert(item));
});
});
return ids;
}
/**
* Updates a single item in the collection that matches the given selector.
* @param selector - The criteria to select the item to update.
* @param modifier - The modifications to apply to the item.
* @param [options] - Optional settings for the update operation.
* @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
* @returns The number of items updated (0 or 1).
* @throws {Error} If the collection is disposed or invalid arguments are provided.
*/
updateOne(selector, modifier, options) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!selector)
throw new Error("Invalid selector");
if (!modifier)
throw new Error("Invalid modifier");
const { $setOnInsert, ...restModifier } = modifier;
const { item, index } = this.getItemAndIndex(selector);
if (item == null) {
if (options == null ? void 0 : options.upsert) {
const newItem = modify({}, {
...restModifier,
$set: {
...$setOnInsert,
...restModifier.$set
}
});
if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
throw new Error("Item with same id already exists");
}
this.insert(newItem);
}
} else {
const modifiedItem = modify(deepClone.default(item), restModifier);
if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
throw new Error("Item with same id already exists");
}
this.emit("validate", modifiedItem);
this.memory().splice(index, 1, modifiedItem);
this.rebuildIndices();
this.emit("changed", modifiedItem, restModifier);
}
this.emit("updateOne", selector, modifier);
this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
if (item == null && !(options == null ? void 0 : options.upsert))
return 0;
return 1;
}
/**
* Updates multiple items in the collection that match the given selector.
* @param selector - The criteria to select the items to update.
* @param modifier - The modifications to apply to the items.
* @param [options] - Optional settings for the update operation.
* @param [options.upsert] - If `true`, creates new items if no items match the selector.
* @returns The number of items updated.
* @throws {Error} If the collection is disposed or invalid arguments are provided.
*/
updateMany(selector, modifier, options) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!selector)
throw new Error("Invalid selector");
if (!modifier)
throw new Error("Invalid modifier");
const { $setOnInsert, ...restModifier } = modifier;
const items = this.getItems(selector);
if (items.length === 0 && (options == null ? void 0 : options.upsert)) {
const newItem = modify({}, {
...restModifier,
$set: {
...$setOnInsert,
...restModifier.$set
}
});
if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
throw new Error("Item with same id already exists");
}
this.insert(newItem);
}
const changes = items.map((item) => {
const { index } = this.getItemAndIndex({ id: item.id });
if (index === -1)
throw new Error(`Cannot resolve index for item with id '${item.id}'`);
const modifiedItem = modify(deepClone.default(item), restModifier);
if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
throw new Error(`Item with same id '${modifiedItem.id}' already exists`);
}
this.emit("validate", modifiedItem);
return {
item: modifiedItem,
index
};
});
changes.forEach(({ item, index }) => {
this.memory().splice(index, 1, item);
});
this.rebuildIndices();
changes.forEach(({ item }) => {
this.emit("changed", item, restModifier);
});
this.emit("updateMany", selector, modifier);
this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
return changes.length === 0 && (options == null ? void 0 : options.upsert) ? 1 : changes.length;
}
/**
* Replaces a single item in the collection that matches the given selector.
* @param selector - The criteria to select the item to replace.
* @param replacement - The item to replace the selected item with.
* @param [options] - Optional settings for the replace operation.
* @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
* @returns The number of items replaced (0 or 1).
* @throws {Error} If the collection is disposed or invalid arguments are provided.
*/
replaceOne(selector, replacement, options) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!selector)
throw new Error("Invalid selector");
const { item, index } = this.getItemAndIndex(selector);
if (item == null) {
if (options == null ? void 0 : options.upsert) {
if (replacement.id != null && this.getItemAndIndex({ id: replacement.id }).item != null) {
throw new Error("Item with same id already exists");
}
this.insert(replacement);
}
} else {
if (item.id !== replacement.id && this.getItemAndIndex({ id: replacement.id }).item != null) {
throw new Error("Item with same id already exists");
}
const modifiedItem = { id: item.id, ...replacement };
this.emit("validate", modifiedItem);
this.memory().splice(index, 1, modifiedItem);
this.rebuildIndices();
this.emit("changed", modifiedItem, replacement);
}
this.emit("replaceOne", selector, replacement);
this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
if (item == null && !(options == null ? void 0 : options.upsert))
return 0;
return 1;
}
/**
* Removes a single item from the collection that matches the given selector.
* @param selector - The criteria to select the item to remove.
* @returns The number of items removed (0 or 1).
* @throws {Error} If the collection is disposed or invalid arguments are provided.
*/
removeOne(selector) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!selector)
throw new Error("Invalid selector");
const { item, index } = this.getItemAndIndex(selector);
if (item != null) {
this.memory().splice(index, 1);
this.deleteFromIdIndex(item.id, index);
this.rebuildIndices();
this.emit("removed", item);
}
this.emit("removeOne", selector);
this.executeInDebugMode((callstack) => this.emit("_debug.removeOne", callstack, selector));
return item == null ? 0 : 1;
}
/**
* Removes multiple items from the collection that match the given selector.
* @param selector - The criteria to select the items to remove.
* @returns The number of items removed.
* @throws {Error} If the collection is disposed or invalid arguments are provided.
*/
removeMany(selector) {
if (this.isDisposed)
throw new Error("Collection is disposed");
if (!selector)
throw new Error("Invalid selector");
const items = this.getItems(selector);
items.forEach((item) => {
const index = this.memory().findIndex((document) => document === item);
if (index === -1)
throw new Error("Cannot resolve index for item");
this.memory().splice(index, 1);
this.deleteFromIdIndex(item.id, index);
this.rebuildIndices();
});
items.forEach((item) => {
this.emit("removed", item);
});
this.emit("removeMany", selector);
this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
return items.length;
}
};
__publicField(_Collection, "collections", []);
__publicField(_Collection, "debugMode", false);
__publicField(_Collection, "batchOperationInProgress", false);
__publicField(_Collection, "fieldTracking", false);
__publicField(_Collection, "onCreationCallbacks", []);
__publicField(_Collection, "onDisposeCallbacks", []);
/**
* Enables debug mode for all collections.
*/
__publicField(_Collection, "enableDebugMode", () => {
_Collection.debugMode = true;
_Collection.collections.forEach((collection) => {
collection.setDebugMode(true);
});
});
/**
* Enables field tracking for all collections.
* @param enable - A boolean indicating whether to enable field tracking.
*/
__publicField(_Collection, "setFieldTracking", (enable) => {
_Collection.fieldTracking = enable;
_Collection.collections.forEach((collection) => {
collection.setFieldTracking(enable);
});
});
let Collection = _Collection;
exports.createIndex = createIndex.default;
exports.default = Collection;