'use strict'; const { cloneDeep } = require('lodash/fp'); const { createAsyncSeriesHook, createAsyncParallelHook } = require('./hooks'); /** * @typedef ProviderHooksMap * @property willRegister * @property didRegister * @property willDelete * @property didDelete */ /** * Creates a new object containing various hooks used by the providers * @return {ProviderHooksMap} */ const createProviderHooksMap = () => ({ // Register events willRegister: createAsyncSeriesHook(), didRegister: createAsyncParallelHook(), // Delete events willDelete: createAsyncParallelHook(), didDelete: createAsyncParallelHook(), }); /** * A customizable item provider enhanced with register/delete hooks * @typedef {Object} Provider * @property hooks * @property register * @property delete * @property get * @property getWhere * @property values * @property keys * @property has * @property size * @property clear */ /** * A {@link Provider} factory * @param {Object} [options] - The factory options * @param {boolean = true} options.throwOnDuplicates - Specify the wanted behaviour when encountering a duplicate key on register * @return {Provider} */ const providerFactory = (options = {}) => { const { throwOnDuplicates = true } = options; const state = { hooks: createProviderHooksMap(), registry: new Map(), }; return { hooks: state.hooks, async register(key, item) { if (throwOnDuplicates && this.has(key)) { throw new Error(`Duplicated item key: ${key}`); } await state.hooks.willRegister.call({ key, value: item }); state.registry.set(key, item); await state.hooks.didRegister.call({ key, value: cloneDeep(item) }); return this; }, async delete(key) { if (this.has(key)) { const item = this.get(key); await state.hooks.willDelete.call({ key, value: cloneDeep(item) }); state.registry.delete(key); await state.hooks.didDelete.call({ key, value: cloneDeep(item) }); } return this; }, get(key) { return state.registry.get(key); }, getWhere(filters = {}) { const items = this.values(); const filtersEntries = Object.entries(filters); if (filtersEntries.length === 0) { return items; } return items.filter(item => { return filtersEntries.every(([key, value]) => item[key] === value); }); }, values() { return Array.from(state.registry.values()); }, keys() { return Array.from(state.registry.keys()); }, has(key) { return state.registry.has(key); }, size() { return state.registry.size; }, async clear() { const keys = this.keys(); for (const key of keys) { await this.delete(key); } return this; }, }; }; module.exports = providerFactory;