@oimdb/core
Version:
Core in-memory data library with type-safe indices, reactive subscriptions, and event processing
1,616 lines (1,574 loc) • 44 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
EOIMCollectionEventType: () => EOIMCollectionEventType,
EOIMEventQueueEventType: () => EOIMEventQueueEventType,
EOIMEventQueueSchedulerEventType: () => EOIMEventQueueSchedulerEventType,
EOIMIndexEventType: () => EOIMIndexEventType,
EOIMUpdateEventCoalescerEventType: () => EOIMUpdateEventCoalescerEventType,
OIMCollection: () => OIMCollection,
OIMCollectionStore: () => OIMCollectionStore,
OIMCollectionStoreMapDriven: () => OIMCollectionStoreMapDriven,
OIMComparatorFactory: () => OIMComparatorFactory,
OIMDBSettings: () => OIMDBSettings,
OIMEntityUpdaterFactory: () => OIMEntityUpdaterFactory,
OIMEventEmitter: () => OIMEventEmitter,
OIMEventQueue: () => OIMEventQueue,
OIMEventQueueScheduler: () => OIMEventQueueScheduler,
OIMEventQueueSchedulerAnimationFrame: () => OIMEventQueueSchedulerAnimationFrame,
OIMEventQueueSchedulerFactory: () => OIMEventQueueSchedulerFactory,
OIMEventQueueSchedulerImmediate: () => OIMEventQueueSchedulerImmediate,
OIMEventQueueSchedulerMicrotask: () => OIMEventQueueSchedulerMicrotask,
OIMEventQueueSchedulerTimeout: () => OIMEventQueueSchedulerTimeout,
OIMIndexArrayBased: () => OIMIndexArrayBased,
OIMIndexComparatorFactory: () => OIMIndexComparatorFactory,
OIMIndexManualArrayBased: () => OIMIndexManualArrayBased,
OIMIndexManualSetBased: () => OIMIndexManualSetBased,
OIMIndexSetBased: () => OIMIndexSetBased,
OIMIndexStoreArrayBased: () => OIMIndexStoreArrayBased,
OIMIndexStoreMapDrivenArrayBased: () => OIMIndexStoreMapDrivenArrayBased,
OIMIndexStoreMapDrivenSetBased: () => OIMIndexStoreMapDrivenSetBased,
OIMIndexStoreSetBased: () => OIMIndexStoreSetBased,
OIMMap2Keys: () => OIMMap2Keys,
OIMPkSelectorFactory: () => OIMPkSelectorFactory,
OIMRICollection: () => OIMRICollection,
OIMReactiveCollection: () => OIMReactiveCollection,
OIMReactiveIndexArrayBased: () => OIMReactiveIndexArrayBased,
OIMReactiveIndexManualArrayBased: () => OIMReactiveIndexManualArrayBased,
OIMReactiveIndexManualSetBased: () => OIMReactiveIndexManualSetBased,
OIMReactiveIndexSetBased: () => OIMReactiveIndexSetBased,
OIMUpdateEventCoalescer: () => OIMUpdateEventCoalescer,
OIMUpdateEventCoalescerCollection: () => OIMUpdateEventCoalescerCollection,
OIMUpdateEventCoalescerIndex: () => OIMUpdateEventCoalescerIndex,
OIMUpdateEventEmitter: () => OIMUpdateEventEmitter
});
module.exports = __toCommonJS(index_exports);
// src/abstract/OIMCollectionStore.ts
var OIMCollectionStore = class {
};
// src/abstract/OIMIndexStoreSetBased.ts
var OIMIndexStoreSetBased = class {
};
// src/abstract/OIMIndexStoreArrayBased.ts
var OIMIndexStoreArrayBased = class {
};
// src/core/OIMEventEmitter.ts
var OIMEventEmitter = class {
constructor() {
this.buckets = {};
}
getOrCreateBucket(event) {
let bucket = this.buckets[event];
if (!bucket) {
bucket = {
handlers: [],
indexByHandler: /* @__PURE__ */ new Map(),
tombstoneCount: 0,
isEmitting: 0
};
this.buckets[event] = bucket;
}
return bucket;
}
compactBucket(bucket) {
const handlers = bucket.handlers;
const length = handlers.length;
const newHandlers = [];
bucket.indexByHandler.clear();
if (bucket.tombstoneCount < length >> 2) {
newHandlers.length = length - bucket.tombstoneCount;
}
let writeIndex = 0;
for (let i = 0; i < length; i++) {
const handler = handlers[i];
if (handler) {
newHandlers[writeIndex] = handler;
bucket.indexByHandler.set(handler, writeIndex);
writeIndex++;
}
}
if (newHandlers.length !== writeIndex) {
newHandlers.length = writeIndex;
}
bucket.handlers = newHandlers;
bucket.tombstoneCount = 0;
}
on(event, handler) {
const bucket = this.getOrCreateBucket(event);
if (bucket.indexByHandler.has(handler))
return;
const index = bucket.handlers.length;
bucket.handlers.push(handler);
bucket.indexByHandler.set(
handler,
index
);
}
emit(event, payload) {
const bucket = this.buckets[event];
if (!bucket) return;
const handlers = bucket.handlers;
const length = handlers.length;
if (length === 0) return;
bucket.isEmitting++;
for (let i = 0; i < length; i++) {
const handler = handlers[i];
if (handler) handler(payload);
}
bucket.isEmitting--;
if (bucket.isEmitting === 0 && bucket.tombstoneCount > 0) {
if (bucket.tombstoneCount >= length >> 1) {
this.compactBucket(bucket);
} else if (length === bucket.tombstoneCount) {
delete this.buckets[event];
}
}
}
off(event, handler) {
const bucket = this.buckets[event];
if (!bucket) return;
const index = bucket.indexByHandler.get(handler);
if (index === void 0) return;
bucket.handlers[index] = null;
bucket.tombstoneCount++;
bucket.indexByHandler.delete(handler);
if (bucket.isEmitting === 0) {
const length = bucket.handlers.length;
if (bucket.tombstoneCount >= length >> 1) {
this.compactBucket(bucket);
} else if (length === bucket.tombstoneCount) {
delete this.buckets[event];
}
}
}
offAll(event) {
if (event !== void 0) {
delete this.buckets[event];
} else {
this.buckets = {};
}
}
};
// src/enum/EOIMEventQueueSchedulerEventType.ts
var EOIMEventQueueSchedulerEventType = /* @__PURE__ */ ((EOIMEventQueueSchedulerEventType2) => {
EOIMEventQueueSchedulerEventType2[EOIMEventQueueSchedulerEventType2["FLUSH"] = 0] = "FLUSH";
return EOIMEventQueueSchedulerEventType2;
})(EOIMEventQueueSchedulerEventType || {});
// src/abstract/OIMEventQueueScheduler.ts
var OIMEventQueueScheduler = class {
constructor() {
this.emitter = new OIMEventEmitter();
}
on(event, handler) {
this.emitter.on(event, handler);
}
off(event, handler) {
this.emitter.off(event, handler);
}
/**
* Trigger a flush event. Should be called by subclasses when they execute the scheduled flush.
*/
flush() {
this.emitter.emit(0 /* FLUSH */, void 0);
}
};
// src/enum/EOIMIndexEventType.ts
var EOIMIndexEventType = /* @__PURE__ */ ((EOIMIndexEventType2) => {
EOIMIndexEventType2[EOIMIndexEventType2["UPDATE"] = 0] = "UPDATE";
return EOIMIndexEventType2;
})(EOIMIndexEventType || {});
// src/core/OIMIndexStoreMapDrivenSetBased.ts
var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
constructor() {
super(...arguments);
this.pks = /* @__PURE__ */ new Map();
}
setOneByKey(key, pks) {
this.pks.set(key, pks);
}
removeOneByKey(key) {
this.pks.delete(key);
}
removeManyByKeys(keys) {
for (const key of keys) {
this.pks.delete(key);
}
}
getOneByKey(key) {
return this.pks.get(key);
}
getManyByKeys(keys) {
const result = /* @__PURE__ */ new Map();
for (const key of keys) {
const pks = this.getOneByKey(key);
if (pks !== void 0) {
result.set(key, pks);
}
}
return result;
}
getAllKeys() {
return Array.from(this.pks.keys());
}
getAll() {
return new Map(this.pks);
}
countAll() {
return this.pks.size;
}
clear() {
this.pks.clear();
}
};
// src/abstract/OIMIndexSetBased.ts
var OIMIndexSetBased = class {
constructor(options = {}) {
this.emitter = new OIMEventEmitter();
this.comparePks = options.comparePks;
this.store = options.store ?? new OIMIndexStoreMapDrivenSetBased();
}
/**
* Get primary keys for multiple index keys
*/
getPksByKeys(keys) {
return this.store.getManyByKeys(keys);
}
/**
* @deprecated Use getPksByKey instead
* Get primary keys for a specific index key
*/
getPks(key) {
return this.getPksByKey(key);
}
/**
* Get primary keys for a specific index key
*/
getPksByKey(key) {
const pksSet = this.store.getOneByKey(key);
return pksSet ? pksSet : /* @__PURE__ */ new Set();
}
/**
* Check if an index key exists
*/
hasKey(key) {
return this.store.getOneByKey(key) !== void 0;
}
/**
* Get all index keys
*/
getKeys() {
return this.store.getAllKeys();
}
/**
* Get the number of primary keys for a specific index key
*/
getKeySize(key) {
const pksSet = this.store.getOneByKey(key);
return pksSet ? pksSet.size : 0;
}
/**
* Get total number of index keys
*/
get size() {
return this.store.countAll();
}
/**
* Check if the index is empty
*/
get isEmpty() {
return this.store.countAll() === 0;
}
/**
* Get performance metrics for monitoring and debugging
*/
getMetrics() {
let totalPks = 0;
let maxBucketSize = 0;
let minBucketSize = Infinity;
const allPks = this.store.getAll();
for (const pksSet of allPks.values()) {
totalPks += pksSet.size;
maxBucketSize = Math.max(maxBucketSize, pksSet.size);
minBucketSize = Math.min(minBucketSize, pksSet.size);
}
return {
totalKeys: allPks.size,
totalPks,
averagePksPerKey: allPks.size > 0 ? totalPks / allPks.size : 0,
maxBucketSize: maxBucketSize === -Infinity ? 0 : maxBucketSize,
minBucketSize: minBucketSize === Infinity ? 0 : minBucketSize
};
}
/**
* Clean up event listeners when index is no longer needed
*/
destroy() {
this.emitter.offAll();
this.store.clear();
}
/**
* Set primary keys for a specific index key with optional comparison.
* If comparator is provided and returns true (no changes), skip the update.
*/
setPksWithComparison(key, newPks) {
if (this.comparePks) {
const existingPksSet = this.store.getOneByKey(key);
if (existingPksSet && existingPksSet.size === newPks.size) {
const existingPksArray = existingPksSet.size > 0 ? [...existingPksSet] : [];
const newPksArray = newPks.size > 0 ? [...newPks] : [];
if (this.comparePks(existingPksArray, newPksArray)) {
return false;
}
} else if (!existingPksSet && newPks.size === 0) {
return false;
}
}
this.store.setOneByKey(key, newPks);
return true;
}
/**
* Emit update event with changed keys
*/
emitUpdate(keys) {
this.emitter.emit(0 /* UPDATE */, { keys });
}
};
// src/core/OIMIndexStoreMapDrivenArrayBased.ts
var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
constructor() {
super(...arguments);
this.pks = /* @__PURE__ */ new Map();
}
setOneByKey(key, pks) {
this.pks.set(key, pks);
}
removeOneByKey(key) {
this.pks.delete(key);
}
removeManyByKeys(keys) {
for (const key of keys) {
this.pks.delete(key);
}
}
getOneByKey(key) {
return this.pks.get(key);
}
getManyByKeys(keys) {
const result = /* @__PURE__ */ new Map();
for (const key of keys) {
const pks = this.getOneByKey(key);
if (pks !== void 0) {
result.set(key, pks);
}
}
return result;
}
getAllKeys() {
return Array.from(this.pks.keys());
}
getAll() {
return new Map(this.pks);
}
countAll() {
return this.pks.size;
}
clear() {
this.pks.clear();
}
};
// src/abstract/OIMIndexArrayBased.ts
var OIMIndexArrayBased = class {
constructor(options = {}) {
this.emitter = new OIMEventEmitter();
this.comparePks = options.comparePks;
this.store = options.store ?? new OIMIndexStoreMapDrivenArrayBased();
}
/**
* Get primary keys for multiple index keys
*/
getPksByKeys(keys) {
return this.store.getManyByKeys(keys);
}
/**
* Get primary keys for a specific index key
*/
getPksByKey(key) {
const pksArray = this.store.getOneByKey(key);
return pksArray ? pksArray : [];
}
/**
* Check if an index key exists
*/
hasKey(key) {
return this.store.getOneByKey(key) !== void 0;
}
/**
* Get all index keys
*/
getKeys() {
return this.store.getAllKeys();
}
/**
* Get the number of primary keys for a specific index key
*/
getKeySize(key) {
const pksArray = this.store.getOneByKey(key);
return pksArray ? pksArray.length : 0;
}
/**
* Get total number of index keys
*/
get size() {
return this.store.countAll();
}
/**
* Check if the index is empty
*/
get isEmpty() {
return this.store.countAll() === 0;
}
/**
* Get performance metrics for monitoring and debugging
*/
getMetrics() {
let totalPks = 0;
let maxBucketSize = 0;
let minBucketSize = Infinity;
const allPks = this.store.getAll();
for (const pksArray of allPks.values()) {
totalPks += pksArray.length;
maxBucketSize = Math.max(maxBucketSize, pksArray.length);
minBucketSize = Math.min(minBucketSize, pksArray.length);
}
return {
totalKeys: allPks.size,
totalPks,
averagePksPerKey: allPks.size > 0 ? totalPks / allPks.size : 0,
maxBucketSize: maxBucketSize === -Infinity ? 0 : maxBucketSize,
minBucketSize: minBucketSize === Infinity ? 0 : minBucketSize
};
}
/**
* Clean up event listeners when index is no longer needed
*/
destroy() {
this.emitter.offAll();
this.store.clear();
}
/**
* Set primary keys for a specific index key with optional comparison.
* If comparator is provided and returns true (no changes), skip the update.
*/
setPksWithComparison(key, newPks) {
if (this.comparePks) {
const existingPksArray = this.store.getOneByKey(key);
if (existingPksArray && existingPksArray.length === newPks.length) {
if (this.comparePks(existingPksArray, newPks)) {
return false;
}
} else if (!existingPksArray && newPks.length === 0) {
return false;
}
}
this.store.setOneByKey(key, newPks);
return true;
}
/**
* Emit update event with changed keys
*/
emitUpdate(keys) {
this.emitter.emit(0 /* UPDATE */, { keys });
}
};
// src/enum/EOIMUpdateEventCoalescerEventType.ts
var EOIMUpdateEventCoalescerEventType = /* @__PURE__ */ ((EOIMUpdateEventCoalescerEventType2) => {
EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["HAS_CHANGES"] = 0] = "HAS_CHANGES";
EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["AFTER_FLUSH"] = 2] = "AFTER_FLUSH";
return EOIMUpdateEventCoalescerEventType2;
})(EOIMUpdateEventCoalescerEventType || {});
// src/core/OIMUpdateEventEmitter.ts
var OIMUpdateEventEmitter = class {
constructor(opts) {
this.keyHandlers = /* @__PURE__ */ new Map();
this.handleHasChanges = () => {
this.queue.enqueue(() => {
const updatedKeys = this.coalescer.getUpdatedKeys();
if (this.keyHandlers.size === 0) {
this.coalescer.clearUpdatedKeys();
return;
}
if (updatedKeys.size * 2 < this.keyHandlers.size) {
for (const key of updatedKeys.values()) {
const handlers = this.keyHandlers.get(key);
if (handlers) {
for (const handler of handlers) {
handler();
}
}
}
} else {
for (const [key, handlers] of this.keyHandlers) {
if (updatedKeys.has(key)) {
for (const handler of handlers) {
handler();
}
}
}
}
this.coalescer.clearUpdatedKeys();
});
};
this.coalescer = opts.coalescer;
this.coalescer.emitter.on(
0 /* HAS_CHANGES */,
this.handleHasChanges
);
this.queue = opts.queue;
}
destroy() {
this.coalescer.emitter.off(
0 /* HAS_CHANGES */,
this.handleHasChanges
);
this.keyHandlers.clear();
}
/**
* Get performance metrics for monitoring and debugging
*/
getMetrics() {
let totalHandlers = 0;
for (const handlers of this.keyHandlers.values()) {
totalHandlers += handlers.size;
}
return {
totalKeys: this.keyHandlers.size,
totalHandlers,
averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
queueLength: this.queue.length
};
}
/**
* Check if there are any active subscriptions
*/
hasSubscriptions() {
return this.keyHandlers.size > 0;
}
/**
* Get the number of handlers for a specific key
*/
getHandlerCount(key) {
return this.keyHandlers.get(key)?.size ?? 0;
}
subscribeOnKey(key, handler) {
let handlers = this.keyHandlers.get(key);
if (!handlers) {
handlers = /* @__PURE__ */ new Set();
this.keyHandlers.set(key, handlers);
}
handlers.add(handler);
return () => {
this.unsubscribeFromKey(key, handler);
};
}
unsubscribeFromKey(key, handler) {
const handlers = this.keyHandlers.get(key);
if (!handlers) return;
handlers.delete(handler);
if (handlers.size === 0) {
this.keyHandlers.delete(key);
}
}
subscribeOnKeys(keys, handler) {
const subscribedKeys = [];
for (const key of keys) {
let handlers = this.keyHandlers.get(key);
if (!handlers) {
handlers = /* @__PURE__ */ new Set();
this.keyHandlers.set(key, handlers);
}
if (!handlers.has(handler)) {
handlers.add(handler);
subscribedKeys.push(key);
}
}
return () => {
for (const key of subscribedKeys) {
this.unsubscribeFromKey(key, handler);
}
};
}
unsubscribeFromKeys(keys, handler) {
for (const key of keys) {
this.unsubscribeFromKey(key, handler);
}
}
};
// src/core/OIMIndexManualSetBased.ts
var OIMIndexManualSetBased = class extends OIMIndexSetBased {
constructor(options = {}) {
super(options);
}
/**
* Set primary keys for a specific index key, replacing any existing values.
* Uses optional comparator to skip updates if PKs haven't actually changed.
*/
setPks(key, pks) {
const hasChanges = this.setPksWithComparison(key, new Set(pks));
if (hasChanges) {
this.emitUpdate([key]);
}
}
/**
* Add primary keys to a specific index key
*/
addPks(key, pks) {
if (pks.length === 0) return;
let pksSet = this.store.getOneByKey(key);
if (!pksSet) {
pksSet = /* @__PURE__ */ new Set();
this.store.setOneByKey(key, pksSet);
}
let hasChanges = false;
for (const pk of pks) {
if (!pksSet.has(pk)) {
pksSet.add(pk);
hasChanges = true;
}
}
if (hasChanges) {
this.store.setOneByKey(key, pksSet);
this.emitUpdate([key]);
}
}
/**
* Remove primary keys from a specific index key
*/
removePks(key, pks) {
if (pks.length === 0) return;
const pksSet = this.store.getOneByKey(key);
if (!pksSet) return;
let hasChanges = false;
for (const pk of pks) {
if (pksSet.delete(pk)) {
hasChanges = true;
}
}
if (pksSet.size === 0) {
this.store.removeOneByKey(key);
hasChanges = true;
} else if (hasChanges) {
this.store.setOneByKey(key, pksSet);
}
if (hasChanges) {
this.emitUpdate([key]);
}
}
/**
* Clear all primary keys for a specific index key, or all keys if no key specified
*/
clear(key) {
if (key === void 0) {
const allKeys = this.store.getAllKeys();
if (allKeys.length > 0) {
this.store.clear();
this.emitUpdate(allKeys);
}
} else {
if (this.store.getOneByKey(key) !== void 0) {
this.store.removeOneByKey(key);
this.emitUpdate([key]);
}
}
}
};
// src/const/OIMDBSettings.ts
var OIMDBSettings = class {
};
/**
* Fixed key used in the manual index to store updated keys set in coalescers.
* This constant is exported so users can create their own indexes and pass them to coalescers.
*/
OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
// src/core/OIMUpdateEventCoalescer.ts
var OIMUpdateEventCoalescer = class {
constructor(index) {
this.emitter = new OIMEventEmitter();
this.hasEmittedChanges = false;
this.updatedKeysIndex = index ?? new OIMIndexManualSetBased({
store: new OIMIndexStoreMapDrivenSetBased()
});
if (!this.updatedKeysIndex.hasKey(OIMDBSettings.UPDATED_KEYS_INDEX_KEY)) {
this.updatedKeysIndex.setPks(
OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
[]
);
}
}
/**
* Get the set of keys that have been updated since last clear
*/
getUpdatedKeys() {
return this.updatedKeysIndex.getPksByKey(
OIMDBSettings.UPDATED_KEYS_INDEX_KEY
);
}
/**
* Clear all updated keys and reset the changed state
*/
clearUpdatedKeys() {
this.emitter.emit(
1 /* BEFORE_FLUSH */,
void 0
);
this.updatedKeysIndex.setPks(OIMDBSettings.UPDATED_KEYS_INDEX_KEY, []);
this.hasEmittedChanges = false;
this.emitter.emit(
2 /* AFTER_FLUSH */,
void 0
);
}
/**
* Add keys to the updated set and emit HAS_CHANGES event if needed
*/
addUpdatedKeys(keys) {
this.updatedKeysIndex.addPks(
OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
keys
);
if (!this.hasEmittedChanges) {
this.hasEmittedChanges = true;
this.emitter.emit(
0 /* HAS_CHANGES */,
void 0
);
}
}
};
// src/core/OIMUpdateEventCoalescerIndex.ts
var OIMUpdateEventCoalescerIndex = class extends OIMUpdateEventCoalescer {
constructor(indexEmitter) {
super();
this.indexEmitter = indexEmitter;
/**
* When the index is updated, we keep track of the updated keys.
*/
this.handleUpdate = (payload) => {
this.addUpdatedKeys(payload.keys);
};
this.indexEmitter.on(0 /* UPDATE */, this.handleUpdate);
}
destroy() {
this.indexEmitter.off(0 /* UPDATE */, this.handleUpdate);
this.emitter.offAll();
this.clearUpdatedKeys();
}
};
// src/abstract/OIMReactiveIndexSetBased.ts
var OIMReactiveIndexSetBased = class {
constructor(queue, opts) {
this.index = opts?.index ?? this.createDefaultIndex();
this.coalescer = new OIMUpdateEventCoalescerIndex(
this.index.emitter
);
this.updateEventEmitter = new OIMUpdateEventEmitter({
coalescer: this.coalescer,
queue
});
}
getPksByKey(key) {
return this.index.getPksByKey(key);
}
getPksByKeys(keys) {
return this.index.getPksByKeys(keys);
}
hasKey(key) {
return this.index.hasKey(key);
}
getKeys() {
return this.index.getKeys();
}
getKeySize(key) {
return this.index.getKeySize(key);
}
get size() {
return this.index.size;
}
get isEmpty() {
return this.index.isEmpty;
}
getMetrics() {
return this.index.getMetrics();
}
destroy() {
this.index.destroy();
}
};
// src/abstract/OIMReactiveIndexArrayBased.ts
var OIMReactiveIndexArrayBased = class {
constructor(queue, opts) {
this.index = opts?.index ?? this.createDefaultIndex();
this.coalescer = new OIMUpdateEventCoalescerIndex(
this.index.emitter
);
this.updateEventEmitter = new OIMUpdateEventEmitter({
coalescer: this.coalescer,
queue
});
}
getPksByKey(key) {
return this.index.getPksByKey(key);
}
getPksByKeys(keys) {
return this.index.getPksByKeys(keys);
}
hasKey(key) {
return this.index.hasKey(key);
}
getKeys() {
return this.index.getKeys();
}
getKeySize(key) {
return this.index.getKeySize(key);
}
get size() {
return this.index.size;
}
get isEmpty() {
return this.index.isEmpty;
}
getMetrics() {
return this.index.getMetrics();
}
destroy() {
this.index.destroy();
}
};
// src/core/OIMPkSelectorFactory.ts
var OIMPkSelectorFactory = class {
createIdSelector() {
return (entity) => entity.id;
}
};
// src/core/OIMCollectionStoreMapDriven.ts
var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
constructor() {
super(...arguments);
this.entities = /* @__PURE__ */ new Map();
}
setOneByPk(pk, entity) {
this.entities.set(pk, entity);
}
setManyByPks(pks, entities) {
for (let i = 0; i < pks.length; i++) {
this.entities.set(pks[i], entities[i]);
}
}
removeOneByPk(pk) {
this.entities.delete(pk);
}
removeManyByPks(pks) {
for (const pk of pks) {
this.entities.delete(pk);
}
}
getOneByPk(pk) {
return this.entities.get(pk);
}
getManyByPks(pks) {
const result = [];
result.length = pks.length;
let writeIndex = 0;
for (let i = 0; i < pks.length; i++) {
const entity = this.getOneByPk(pks[i]);
if (entity !== void 0) {
result[writeIndex++] = entity;
}
}
result.length = writeIndex;
return result;
}
getAll() {
return Array.from(this.entities.values());
}
countAll() {
return this.entities.size;
}
clear() {
this.entities.clear();
}
getAllPks() {
return Array.from(this.entities.keys());
}
};
// src/core/OIMEntityUpdaterFactory.ts
var OIMEntityUpdaterFactory = class {
createMergeEntityUpdater() {
return (draft, prev) => {
return { ...prev, ...draft };
};
}
};
// src/enum/EOIMCollectionEventType.ts
var EOIMCollectionEventType = /* @__PURE__ */ ((EOIMCollectionEventType2) => {
EOIMCollectionEventType2[EOIMCollectionEventType2["UPDATE"] = 0] = "UPDATE";
return EOIMCollectionEventType2;
})(EOIMCollectionEventType || {});
// src/core/OIMCollection.ts
var OIMCollection = class {
constructor(opts) {
this.emitter = new OIMEventEmitter();
this.selectPk = opts?.selectPk ?? new OIMPkSelectorFactory().createIdSelector();
this.store = opts?.store ?? new OIMCollectionStoreMapDriven();
this.updateEntity = opts?.updateEntity ?? new OIMEntityUpdaterFactory().createMergeEntityUpdater();
}
getOneByPk(pk) {
return this.store.getOneByPk(pk);
}
getManyByPks(pks) {
return this.store.getManyByPks(pks);
}
upsertOneByPk(pk, entity) {
this.upsertOneWithoutNotificationsByPk(pk, entity);
this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
}
upsertOne(entity) {
const pk = this.upsertOneWithoutNotifications(entity);
this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
}
upsertMany(entities) {
const pks = entities.map(
(entity) => this.upsertOneWithoutNotifications(entity)
);
this.emitter.emit(0 /* UPDATE */, { pks });
}
removeOne(entity) {
const pk = this.selectPk(entity);
this.store.removeOneByPk(pk);
this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
}
removeMany(entities) {
const pks = entities.map(this.selectPk);
this.store.removeManyByPks(pks);
this.emitter.emit(0 /* UPDATE */, { pks });
}
removeOneByPk(pk) {
this.store.removeOneByPk(pk);
this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
}
removeManyByPks(pks) {
this.store.removeManyByPks(pks);
this.emitter.emit(0 /* UPDATE */, { pks });
}
clear() {
this.store.clear();
this.emitter.emit(0 /* UPDATE */, { pks: [] });
}
countAll() {
return this.store.countAll();
}
getAll() {
return this.store.getAll();
}
getAllPks() {
return this.store.getAllPks();
}
upsertOneWithoutNotifications(entity) {
const pk = this.selectPk(entity);
this.upsertOneWithoutNotificationsByPk(pk, entity);
return pk;
}
upsertOneWithoutNotificationsByPk(pk, entity) {
if (!pk) {
throw new Error(
`[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
);
}
const existingEntity = this.store.getOneByPk(pk);
if (existingEntity) {
this.store.setOneByPk(
pk,
this.updateEntity(entity, existingEntity)
);
} else {
this.store.setOneByPk(pk, entity);
}
}
};
// src/core/OIMUpdateEventCoalescerCollection.ts
var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
constructor(collectionEmitter) {
super();
this.collectionEmitter = collectionEmitter;
/**
* When the collection is updated, we keep track of the updated pks.
*/
this.handleUpdate = (payload) => {
this.addUpdatedKeys(payload.pks);
};
this.collectionEmitter.on(
0 /* UPDATE */,
this.handleUpdate
);
}
destroy() {
this.collectionEmitter.off(
0 /* UPDATE */,
this.handleUpdate
);
this.emitter.offAll();
this.clearUpdatedKeys();
}
};
// src/core/OIMReactiveCollection.ts
var OIMReactiveCollection = class extends OIMCollection {
constructor(queue, opts) {
super(opts);
this.coalescer = new OIMUpdateEventCoalescerCollection(
this.emitter
);
this.updateEventEmitter = new OIMUpdateEventEmitter({
coalescer: this.coalescer,
queue
});
}
};
// src/core/OIMReactiveIndexManualSetBased.ts
var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
constructor(queue, opts) {
super(queue, opts);
}
createDefaultIndex() {
return new OIMIndexManualSetBased();
}
setPks(key, pks) {
this.index.setPks(key, pks);
}
addPks(key, pks) {
this.index.addPks(key, pks);
}
removePks(key, pks) {
this.index.removePks(key, pks);
}
clear(key) {
this.index.clear(key);
}
};
// src/core/OIMIndexManualArrayBased.ts
var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
constructor(options = {}) {
super(options);
}
/**
* Set primary keys for a specific index key, replacing any existing values.
* Uses optional comparator to skip updates if PKs haven't actually changed.
*/
setPks(key, pks) {
const hasChanges = this.setPksWithComparison(key, pks);
if (hasChanges) {
this.emitUpdate([key]);
}
}
/**
* Add primary keys to a specific index key
*/
addPks(key, pks) {
if (pks.length === 0) return;
let pksArray = this.store.getOneByKey(key);
if (!pksArray) {
pksArray = [];
this.store.setOneByKey(key, pksArray);
}
const pksSet = new Set(pksArray);
let hasChanges = false;
for (const pk of pks) {
if (!pksSet.has(pk)) {
pksSet.add(pk);
hasChanges = true;
}
}
if (hasChanges) {
this.store.setOneByKey(key, Array.from(pksSet));
this.emitUpdate([key]);
}
}
/**
* Remove primary keys from a specific index key
*/
removePks(key, pks) {
if (pks.length === 0) return;
const pksArray = this.store.getOneByKey(key);
if (!pksArray) return;
const pksSet = new Set(pks);
const filtered = pksArray.filter((pk) => !pksSet.has(pk));
const hasChanges = filtered.length !== pksArray.length;
if (hasChanges) {
if (filtered.length === 0) {
this.store.removeOneByKey(key);
} else {
this.store.setOneByKey(key, filtered);
}
this.emitUpdate([key]);
}
}
/**
* Clear all primary keys for a specific index key, or all keys if no key specified
*/
clear(key) {
if (key === void 0) {
const allKeys = this.store.getAllKeys();
if (allKeys.length > 0) {
this.store.clear();
this.emitUpdate(allKeys);
}
} else {
if (this.store.getOneByKey(key) !== void 0) {
this.store.removeOneByKey(key);
this.emitUpdate([key]);
}
}
}
};
// src/core/OIMReactiveIndexManualArrayBased.ts
var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
constructor(queue, opts) {
super(queue, opts);
}
createDefaultIndex() {
return new OIMIndexManualArrayBased();
}
setPks(key, pks) {
this.index.setPks(key, pks);
}
addPks(key, pks) {
this.index.addPks(key, pks);
}
removePks(key, pks) {
this.index.removePks(key, pks);
}
clear(key) {
this.index.clear(key);
}
};
// src/core/OIMRICollection.ts
var OIMRICollection = class extends OIMReactiveCollection {
constructor(queue, opts) {
super(queue, opts.collectionOpts);
this.indexes = opts.indexes || {};
}
};
// src/core/OIMComparatorFactory.ts
var OIMComparatorFactory = class {
createShallowComparator() {
return (a, b) => {
if (a === b) return false;
const ka = Object.keys(a);
const kb = Object.keys(b);
if (ka.length !== kb.length) return true;
for (let i = 0; i < ka.length; i++) {
const k = ka[i];
if (b[k] !== a[k])
return true;
}
return false;
};
}
};
// src/enum/EOIMEventQueueEventType.ts
var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
return EOIMEventQueueEventType2;
})(EOIMEventQueueEventType || {});
// src/core/OIMEventQueue.ts
var OIMEventQueue = class {
constructor(options = {}) {
this.emitter = new OIMEventEmitter();
this.queue = [];
this.scheduler = options.scheduler;
if (this.scheduler) {
this.flushBound = () => this.flush();
this.scheduler.on(
0 /* FLUSH */,
this.flushBound
);
}
}
/**
* Add a function to the queue. If scheduler is configured and autoSchedule is enabled,
* automatically schedules a flush operation.
*/
enqueue(fn) {
this.queue.push(fn);
if (this.scheduler && this.queue.length === 1) {
this.scheduler.schedule();
}
}
/**
* Execute all queued functions and clear the queue.
* This method is safe to call multiple times and handles reentrancy.
*/
flush() {
if (this.queue.length === 0) return;
this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
const currentQueue = this.queue.slice();
this.queue.length = 0;
for (let i = 0; i < currentQueue.length; i++) {
currentQueue[i]();
}
this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
}
/**
* Get the current number of queued functions.
*/
get length() {
return this.queue.length;
}
/**
* Check if the queue is empty.
*/
get isEmpty() {
return this.queue.length === 0;
}
/**
* Clear the queue without executing functions and cancel any scheduled flush.
*/
clear() {
this.queue.length = 0;
this.scheduler?.cancel();
}
/**
* Clean up scheduler subscription when queue is no longer needed.
*/
destroy() {
if (this.scheduler && this.flushBound) {
this.scheduler.off(
0 /* FLUSH */,
this.flushBound
);
}
this.clear();
}
};
// src/core/OIMIndexComparatorFactory.ts
var OIMIndexComparatorFactory = class {
/**
* Create an element-wise comparator that checks arrays for strict equality.
* Compares array lengths and each element using strict equality (===).
*/
static createElementWiseComparator() {
return (existingPks, newPks) => {
if (existingPks.length !== newPks.length) {
return false;
}
for (let i = 0; i < existingPks.length; i++) {
if (existingPks[i] !== newPks[i]) {
return false;
}
}
return true;
};
}
/**
* Create a set-based comparator that checks if arrays contain the same elements.
* Order doesn't matter, only the presence of elements.
*/
static createSetBasedComparator() {
return (existingPks, newPks) => {
if (existingPks.length !== newPks.length) {
return false;
}
const existingSet = new Set(existingPks);
const newSet = new Set(newPks);
if (existingSet.size !== newSet.size || existingSet.size !== existingPks.length) {
return false;
}
for (const pk of existingSet) {
if (!newSet.has(pk)) {
return false;
}
}
return true;
};
}
/**
* Create a shallow comparator that only checks array references.
* Fastest but only works if you're reusing the same array instances.
*/
static createShallowComparator() {
return (existingPks, newPks) => {
return existingPks === newPks;
};
}
/**
* Create a no-comparison comparator that always returns false (always updates).
* Useful for disabling comparison while keeping the same interface.
*/
static createAlwaysUpdateComparator() {
return () => false;
}
};
// src/core/OIMMap2Keys.ts
var OIMMap2Keys = class {
constructor() {
this.map = /* @__PURE__ */ new Map();
this._size = 0;
}
get size() {
return this._size;
}
set(k1, k2, v) {
let m = this.map.get(k1);
if (!m) {
m = /* @__PURE__ */ new Map();
this.map.set(k1, m);
}
if (!m.has(k2)) this._size++;
m.set(k2, v);
}
get(k1, k2) {
return this.map.get(k1)?.get(k2);
}
has(k1, k2) {
return this.map.get(k1)?.has(k2);
}
delete(k1, k2) {
const m = this.map.get(k1);
if (!m) return false;
const had = m.delete(k2);
if (had) {
this._size--;
if (m.size === 0) this.map.delete(k1);
}
return had;
}
clear() {
this.map.clear();
this._size = 0;
}
};
// src/core/event-queue-scheduler/OIMEventQueueSchedulerAnimationFrame.ts
var OIMEventQueueSchedulerAnimationFrame = class extends OIMEventQueueScheduler {
constructor() {
super();
this.useRequestAnimationFrame = typeof requestAnimationFrame !== "undefined";
}
schedule() {
if (this.frameId !== void 0) return;
if (this.useRequestAnimationFrame) {
this.frameId = requestAnimationFrame(() => {
this.frameId = void 0;
this.flush();
});
} else {
this.frameId = setTimeout(() => {
this.frameId = void 0;
this.flush();
}, 16);
}
}
cancel() {
if (this.frameId === void 0) return;
if (this.useRequestAnimationFrame) {
cancelAnimationFrame(this.frameId);
} else {
clearTimeout(this.frameId);
}
this.frameId = void 0;
}
};
// src/core/event-queue-scheduler/OIMEventQueueSchedulerMicrotask.ts
var OIMEventQueueSchedulerMicrotask = class extends OIMEventQueueScheduler {
constructor() {
super(...arguments);
this.scheduled = false;
}
schedule() {
if (this.scheduled) return;
this.scheduled = true;
Promise.resolve().then(() => {
if (!this.scheduled) return;
this.scheduled = false;
this.flush();
});
}
cancel() {
if (!this.scheduled) return;
this.scheduled = false;
}
};
// src/core/event-queue-scheduler/OIMEventQueueSchedulerTimeout.ts
var OIMEventQueueSchedulerTimeout = class extends OIMEventQueueScheduler {
/**
* @param delay - Delay in milliseconds before executing the flush (default: 0)
*/
constructor(delay = 0) {
super();
this.delay = Math.max(0, delay);
}
schedule() {
if (this.timeoutId !== void 0) return;
this.timeoutId = setTimeout(() => {
this.timeoutId = void 0;
this.flush();
}, this.delay);
}
cancel() {
if (this.timeoutId === void 0) return;
clearTimeout(this.timeoutId);
this.timeoutId = void 0;
}
};
// src/core/event-queue-scheduler/OIMEventQueueSchedulerImmediate.ts
var OIMEventQueueSchedulerImmediate = class extends OIMEventQueueScheduler {
constructor() {
super();
this.useSetImmediate = typeof setImmediate !== "undefined";
this.useMessageChannel = !this.useSetImmediate && typeof MessageChannel !== "undefined" && typeof window !== "undefined";
if (this.useMessageChannel) {
this.messageChannel = new MessageChannel();
this.messageChannel.port2.onmessage = () => {
if (this.pendingCallback && this.immediateId !== void 0) {
const callback = this.pendingCallback;
this.pendingCallback = void 0;
this.immediateId = void 0;
callback();
}
};
}
}
schedule() {
if (this.immediateId !== void 0) return;
const callback = () => {
this.immediateId = void 0;
this.flush();
};
if (this.useSetImmediate) {
this.immediateId = setImmediate(callback);
} else if (this.useMessageChannel && this.messageChannel) {
this.immediateId = 1;
this.pendingCallback = callback;
this.messageChannel.port1.postMessage(null);
} else {
this.immediateId = setTimeout(callback, 0);
}
}
cancel() {
if (this.immediateId === void 0) return;
if (this.useSetImmediate) {
clearImmediate(this.immediateId);
} else if (this.useMessageChannel) {
this.pendingCallback = void 0;
} else {
clearTimeout(this.immediateId);
}
this.immediateId = void 0;
}
};
// src/core/event-queue-scheduler/OIMEventQueueSchedulerFactory.ts
var OIMEventQueueSchedulerFactory = class {
/**
* Create a scheduler of the specified type with optional configuration.
*/
static create(type, ...args) {
switch (type) {
case "microtask":
return new OIMEventQueueSchedulerMicrotask();
case "animationFrame":
return new OIMEventQueueSchedulerAnimationFrame();
case "timeout": {
const options = args[0];
return new OIMEventQueueSchedulerTimeout(options?.delay);
}
case "immediate":
return new OIMEventQueueSchedulerImmediate();
default:
throw new Error(`Unknown scheduler type: ${type}`);
}
}
/**
* Create a microtask scheduler (most common for general use).
*/
static createMicrotask() {
return new OIMEventQueueSchedulerMicrotask();
}
/**
* Create an animation frame scheduler (ideal for UI updates).
*/
static createAnimationFrame() {
return new OIMEventQueueSchedulerAnimationFrame();
}
/**
* Create a timeout scheduler with optional delay.
*/
static createTimeout(delay) {
return new OIMEventQueueSchedulerTimeout(delay);
}
/**
* Create an immediate scheduler (fastest execution).
*/
static createImmediate() {
return new OIMEventQueueSchedulerImmediate();
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
EOIMCollectionEventType,
EOIMEventQueueEventType,
EOIMEventQueueSchedulerEventType,
EOIMIndexEventType,
EOIMUpdateEventCoalescerEventType,
OIMCollection,
OIMCollectionStore,
OIMCollectionStoreMapDriven,
OIMComparatorFactory,
OIMDBSettings,
OIMEntityUpdaterFactory,
OIMEventEmitter,
OIMEventQueue,
OIMEventQueueScheduler,
OIMEventQueueSchedulerAnimationFrame,
OIMEventQueueSchedulerFactory,
OIMEventQueueSchedulerImmediate,
OIMEventQueueSchedulerMicrotask,
OIMEventQueueSchedulerTimeout,
OIMIndexArrayBased,
OIMIndexComparatorFactory,
OIMIndexManualArrayBased,
OIMIndexManualSetBased,
OIMIndexSetBased,
OIMIndexStoreArrayBased,
OIMIndexStoreMapDrivenArrayBased,
OIMIndexStoreMapDrivenSetBased,
OIMIndexStoreSetBased,
OIMMap2Keys,
OIMPkSelectorFactory,
OIMRICollection,
OIMReactiveCollection,
OIMReactiveIndexArrayBased,
OIMReactiveIndexManualArrayBased,
OIMReactiveIndexManualSetBased,
OIMReactiveIndexSetBased,
OIMUpdateEventCoalescer,
OIMUpdateEventCoalescerCollection,
OIMUpdateEventCoalescerIndex,
OIMUpdateEventEmitter
});