@furystack/core
Version:
Core FuryStack package
195 lines • 8.27 kB
JavaScript
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