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

786 lines (785 loc) 31.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 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;