UNPKG

@feathersjs/memory

Version:
329 lines (264 loc) 9.94 kB
import { BadRequest, MethodNotAllowed, NotFound } from '@feathersjs/errors' import { _ } from '@feathersjs/commons' import { sorter, select, AdapterBase, AdapterServiceOptions, PaginationOptions, AdapterParams } from '@feathersjs/adapter-commons' import sift from 'sift' import { NullableId, Id, Params, Paginated } from '@feathersjs/feathers' export interface MemoryServiceStore<T> { [key: string]: T } export interface MemoryServiceOptions<T = any> extends AdapterServiceOptions { store?: MemoryServiceStore<T> startId?: number matcher?: (query: any) => any sorter?: (sort: any) => any } const _select = (data: any, params: any, ...args: string[]) => { const base = select(params, ...args) return base(JSON.parse(JSON.stringify(data))) } export class MemoryAdapter< Result = any, Data = Partial<Result>, ServiceParams extends Params = Params, PatchData = Partial<Data> > extends AdapterBase<Result, Data, PatchData, ServiceParams, MemoryServiceOptions<Result>> { store: MemoryServiceStore<Result> _uId: number constructor(options: MemoryServiceOptions<Result> = {}) { super({ id: 'id', matcher: sift, sorter, store: {}, startId: 0, ...options }) this._uId = this.options.startId this.store = { ...this.options.store } } async getEntries(_params?: ServiceParams) { const params = _params || ({} as ServiceParams) return this._find({ ...params, paginate: false }) } getQuery(params: ServiceParams) { const { $skip, $sort, $limit, $select, ...query } = params.query || {} return { query, filters: { $skip, $sort, $limit, $select } } } async _find(_params?: ServiceParams & { paginate?: PaginationOptions }): Promise<Paginated<Result>> async _find(_params?: ServiceParams & { paginate: false }): Promise<Result[]> async _find(_params?: ServiceParams): Promise<Paginated<Result> | Result[]> async _find(params: ServiceParams = {} as ServiceParams): Promise<Paginated<Result> | Result[]> { const { paginate } = this.getOptions(params) const { query, filters } = this.getQuery(params) let values = _.values(this.store) const hasSkip = filters.$skip !== undefined const hasSort = filters.$sort !== undefined const hasLimit = filters.$limit !== undefined const hasQuery = _.keys(query).length > 0 if (hasSort) { values.sort(this.options.sorter(filters.$sort)) } if (paginate) { if (hasQuery) { values = values.filter(this.options.matcher(query)) } const total = values.length if (hasSkip) { values = values.slice(filters.$skip) } if (hasLimit) { values = values.slice(0, filters.$limit) } const result: Paginated<Result> = { total, limit: filters.$limit, skip: filters.$skip || 0, data: values.map((value) => _select(value, params, this.id)) } return result } /* Without pagination, we don't have to match every result and gain considerable performance improvements with a breaking for loop. */ if (hasQuery || hasLimit || hasSkip) { let skipped = 0 const matcher = this.options.matcher(query) const matched = [] if (hasLimit && filters.$limit === 0) { return [] } for (let index = 0, length = values.length; index < length; index++) { const value = values[index] if (hasQuery && !matcher(value, index, values)) { continue } if (hasSkip && filters.$skip > skipped) { skipped++ continue } matched.push(_select(value, params, this.id)) if (hasLimit && filters.$limit === matched.length) { break } } return matched } return values.map((value) => _select(value, params, this.id)) } async _get(id: Id, params: ServiceParams = {} as ServiceParams): Promise<Result> { const { query } = this.getQuery(params) if (id in this.store) { const value = this.store[id] if (this.options.matcher(query)(value)) { return _select(value, params, this.id) } } throw new NotFound(`No record found for id '${id}'`) } async _create(data: Partial<Data>, params?: ServiceParams): Promise<Result> async _create(data: Partial<Data>[], params?: ServiceParams): Promise<Result[]> async _create(data: Partial<Data> | Partial<Data>[], _params?: ServiceParams): Promise<Result | Result[]> async _create( data: Partial<Data> | Partial<Data>[], params: ServiceParams = {} as ServiceParams ): Promise<Result | Result[]> { if (Array.isArray(data)) { return Promise.all(data.map((current) => this._create(current, params))) } const id = (data as any)[this.id] || this._uId++ const current = _.extend({}, data, { [this.id]: id }) const result = (this.store[id] = current) return _select(result, params, this.id) as Result } async _update(id: Id, data: Data, params: ServiceParams = {} as ServiceParams): Promise<Result> { if (id === null || Array.isArray(data)) { throw new BadRequest("You can not replace multiple instances. Did you mean 'patch'?") } const oldEntry = await this._get(id) // We don't want our id to change type if it can be coerced const oldId = (oldEntry as any)[this.id] // eslint-disable-next-line eqeqeq id = oldId == id ? oldId : id this.store[id] = _.extend({}, data, { [this.id]: id }) return this._get(id, params) } async _patch(id: null, data: PatchData | Partial<Result>, params?: ServiceParams): Promise<Result[]> async _patch(id: Id, data: PatchData | Partial<Result>, params?: ServiceParams): Promise<Result> async _patch( id: NullableId, data: PatchData | Partial<Result>, _params?: ServiceParams ): Promise<Result | Result[]> async _patch( id: NullableId, data: PatchData | Partial<Result>, params: ServiceParams = {} as ServiceParams ): Promise<Result | Result[]> { if (id === null && !this.allowsMulti('patch', params)) { throw new MethodNotAllowed('Can not patch multiple entries') } const { query } = this.getQuery(params) const patchEntry = (entry: Result) => { const currentId = (entry as any)[this.id] this.store[currentId] = _.extend(this.store[currentId], _.omit(data, this.id)) return _select(this.store[currentId], params, this.id) } if (id === null) { const entries = await this.getEntries({ ...params, query }) return entries.map(patchEntry) } return patchEntry(await this._get(id, params)) // Will throw an error if not found } async _remove(id: null, params?: ServiceParams): Promise<Result[]> async _remove(id: Id, params?: ServiceParams): Promise<Result> async _remove(id: NullableId, _params?: ServiceParams): Promise<Result | Result[]> async _remove(id: NullableId, params: ServiceParams = {} as ServiceParams): Promise<Result | Result[]> { if (id === null && !this.allowsMulti('remove', params)) { throw new MethodNotAllowed('Can not remove multiple entries') } const { query } = this.getQuery(params) if (id === null) { const entries = await this.getEntries({ ...params, query }) return Promise.all(entries.map((current: any) => this._remove(current[this.id] as Id, params))) } const entry = await this._get(id, params) delete this.store[id] return entry } } export class MemoryService< Result = any, Data = Partial<Result>, ServiceParams extends AdapterParams = AdapterParams, PatchData = Partial<Data> > extends MemoryAdapter<Result, Data, ServiceParams, PatchData> { async find(params?: ServiceParams & { paginate?: PaginationOptions }): Promise<Paginated<Result>> async find(params?: ServiceParams & { paginate: false }): Promise<Result[]> async find(params?: ServiceParams): Promise<Paginated<Result> | Result[]> async find(params?: ServiceParams): Promise<Paginated<Result> | Result[]> { return this._find({ ...params, query: await this.sanitizeQuery(params) }) } async get(id: Id, params?: ServiceParams): Promise<Result> { return this._get(id, { ...params, query: await this.sanitizeQuery(params) }) } async create(data: Data, params?: ServiceParams): Promise<Result> async create(data: Data[], params?: ServiceParams): Promise<Result[]> async create(data: Data | Data[], params?: ServiceParams): Promise<Result | Result[]> { if (Array.isArray(data) && !this.allowsMulti('create', params)) { throw new MethodNotAllowed('Can not create multiple entries') } return this._create(data, params) } async update(id: Id, data: Data, params?: ServiceParams): Promise<Result> { return this._update(id, data, { ...params, query: await this.sanitizeQuery(params) }) } async patch(id: Id, data: PatchData, params?: ServiceParams): Promise<Result> async patch(id: null, data: PatchData, params?: ServiceParams): Promise<Result[]> async patch(id: NullableId, data: PatchData, params?: ServiceParams): Promise<Result | Result[]> { const { $limit, ...query } = await this.sanitizeQuery(params) return this._patch(id, data, { ...params, query }) } async remove(id: Id, params?: ServiceParams): Promise<Result> async remove(id: null, params?: ServiceParams): Promise<Result[]> async remove(id: NullableId, params?: ServiceParams): Promise<Result | Result[]> { const { $limit, ...query } = await this.sanitizeQuery(params) return this._remove(id, { ...params, query }) } } export function memory<T = any, D = Partial<T>, P extends Params = Params>( options: Partial<MemoryServiceOptions<T>> = {} ) { return new MemoryService<T, D, P>(options) }