UNPKG

@furystack/core

Version:
195 lines 8.27 kB
import { EventHub } from '@furystack/utils'; import { isLogicalOperator, isOperator, selectFields } from './models/physical-store.js'; export class InMemoryStore extends EventHub { /** * * @param keys The keys to remove from the store */ async remove(...keys) { keys.forEach((key) => { this.cache.delete(key); this.emit('onEntityRemoved', { key }); }); } async add(...entries) { const created = entries.map((e) => { const entry = { ...e }; if (this.cache.has(entry[this.primaryKey])) { throw new Error('Item with the primary key already exists.'); } this.cache.set(entry[this.primaryKey], entry); this.emit('onEntityAdded', { entity: entry }); return entry; }); return { created }; } cache = new Map(); get = (key, select) => { const item = this.cache.get(key); return Promise.resolve(item && select ? selectFields(item, ...select) : item); }; evaluateLike = (value, likeString) => { const likeRegex = `^${likeString.replace(/%/g, '.*')}$`; return value.match(new RegExp(likeRegex, 'i')); }; filterInternal(values, filter) { if (!filter) { return values; } return values.filter((item) => { for (const key in filter) { if (isLogicalOperator(key)) { const filterValue = filter[key]; switch (key) { case '$and': if (filterValue.some((v) => !this.filterInternal([item], v).length)) { return false; } break; case '$or': if (filterValue.some((v) => this.filterInternal([item], v).length)) { break; } return false; default: throw new Error(`The logical operation '${key}' is not a valid operation`); } } else if (typeof filter[key] === 'object') { for (const filterKey in filter[key]) { if (isOperator(filterKey)) { const itemValue = item[key]; const filterValue = filter[key][filterKey]; switch (filterKey) { case '$eq': if (filterValue !== itemValue) { return false; } break; case '$ne': if (filterValue === itemValue) { return false; } break; case '$in': if (!filterValue.includes(itemValue)) { return false; } break; case '$nin': if (filterValue.includes(itemValue)) { return false; } break; case '$lt': if (itemValue < filterValue) { break; } return false; case '$lte': if (itemValue <= filterValue) { break; } return false; case '$gt': if (itemValue > filterValue) { break; } return false; case '$gte': if (itemValue >= filterValue) { break; } return false; case '$regex': if (!new RegExp(filterValue).test(itemValue.toString())) { return false; } break; case '$startsWith': if (!itemValue.startsWith(filterValue)) { return false; } break; case '$endsWith': if (!itemValue.endsWith(filterValue)) { return false; } break; case '$like': if (!this.evaluateLike(itemValue, filterValue)) { return false; } break; default: throw new Error(`The expression (${filterKey}) is not supported by '${this.constructor.name}'!`); } } else { throw new Error(`The filter key '${filterKey}' is not a valid operation`); } } } else { throw new Error(`The filter has to be an object, got ${typeof filter[key]} for field '${key}'`); } } return true; }); } async find(searchOptions) { let value = this.filterInternal([...this.cache.values()], searchOptions.filter); if (searchOptions.order) { for (const fieldName of Object.keys(searchOptions.order)) { value = value.sort((a, b) => { const order = searchOptions.order[fieldName]; if (a[fieldName] < b[fieldName]) return order === 'ASC' ? -1 : 1; if (a[fieldName] > b[fieldName]) return order === 'ASC' ? 1 : -1; return 0; }); } } if (searchOptions.top || searchOptions.skip) { value = value.slice(searchOptions.skip, (searchOptions.skip || 0) + (searchOptions.top || this.cache.size)); } if (searchOptions.select) { value = value.map((item) => { return selectFields(item, ...searchOptions.select); }); } return value; } async count(filter) { return this.filterInternal([...this.cache.values()], filter).length; } async update(id, data) { if (!this.cache.has(id)) { throw Error(`Entity not found with id '${id}', cannot update!`); } this.cache.set(id, { ...this.cache.get(id), ...data, }); this.emit('onEntityUpdated', { id, change: data }); } [Symbol.dispose]() { this.cache.clear(); super[Symbol.dispose](); } primaryKey; model; /** * Creates an InMemoryStore that can be used for testing purposes. * @param options Options for the In Memory Store * @param options.primaryKey The name of the primary key field * @param options.model The Entity Model */ constructor(options) { super(); this.primaryKey = options.primaryKey; this.model = options.model; } } //# sourceMappingURL=in-memory-store.js.map