diff --git a/README.md b/README.md index 6b9e3fb..f5f82f9 100755 --- a/README.md +++ b/README.md @@ -19,15 +19,15 @@ Please refer to the documentation for more information. ```js Promise.all([ app.service('posts').get(1), - app.service('posts').get(2), - app.service('posts').get(3) + app.service('posts').get(1), + app.service('posts').get(2) ]); ``` is slower than ```js -app.service('posts').find({ query: { id: { $in: [1, 2, 3] } } }); +app.service('posts').find({ query: { id: { $in: [1, 2] } } }); ``` Feathers Dataloader makes it easy and fast to write these kinds of queries. The loader handles coalescing all of the IDs into one request and mapping them back to the proper caller. @@ -37,15 +37,15 @@ const loader = new AppLoader({ app: context.app }); Promise.all([ loader.service('posts').load(1), - loader.service('posts').load(2), - loader.service('posts').load(3) + loader.service('posts').load(1), + loader.service('posts').load(2) ]); ``` is automatically converted to ```js -app.service('posts').find({ query: { id: { $in: [1, 2, 3] } } }); +app.service('posts').find({ query: { id: { $in: [1, 2] } } }); ``` @@ -54,8 +54,6 @@ app.service('posts').find({ query: { id: { $in: [1, 2, 3] } } }); ```js const { AppLoader } = require('feathers-dataloader'); -// See Guide for more information about how to better pass -// loaders from service to service. const initializeLoader = context => { if (context.params.loader) { return context; @@ -71,33 +69,37 @@ app.hooks({ all: [initializeLoader] } }) +``` +Loaders are most commonly used in resolvers like @feathersjs/schema, withResults, or fastJoin. See the Guide section for more information and common usecases. Pass the loader to any and all service/loader calls. This maximizes performance by allowing the loader to reuse its cache and batching mechanism as much as possible. -// Loaders are most commonly used in resolvers like @feathersjs/schema, -// withResults, or fastJoin. See the Guide section for more -// information and common usecases. -// Pass the loader to any and all service/loader calls. This maximizes -// performance by allowing the loader to reuse its cache and -// batching mechanism as much as possible. +```js const { resolveResult, resolve } = require('@feathersjs/schema'); const postResultsResolver = resolve({ properties: { user: async (value, post, context) => { - const { loader } = context.params; - return await loader.service('users').load(post.userId, { loader }); + return context.params.loader + .service('users') + .load(post.userId, { loader }); }, category: async (value, post, context) => { - const { loader } = context.params; - return await loader.service('categories').key('name').load(post.categoryName, { loader }); + return context.params.loader + .service('categories') + .key('name') + .load(post.categoryName, { loader }); }, tags: async (value, post, context) => { const { loader } = context.params; - return await loader.service('tags').load(post.tagIds, { loader }); + return context.params.loader + .service('tags') + .load(post.tagIds, { loader }); }, comments: async (value, post, context) => { - const { loader } = context.params; - return await loader.service('comments').multi('postId').load(post.id, { loader }); + return context.params.loader + .service('comments') + .multi('postId') + .load(post.id, { loader }); } } }); diff --git a/docs/guide.md b/docs/guide.md index 07b1934..8364b95 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -125,7 +125,7 @@ app.service('posts').hooks({ The ServiceLoader's underlying DataLoader takes a `maxBatchSize` option. This option limits the number of ids in the `$in` query. Huge arrays of ids can lead to performance issues and even database lockup. By using the `maxBatchSize` you can break those queries into smaller ones. You should monitor your own application to determine the best number to use for your batch size, but setting some maximum is recommended. ```js -const { AppLoader, ServiceLoader } = require('feathers-dataloader'); +const { AppLoader } = require('feathers-dataloader'); const loader = new AppLoader({ app, maxBatchSize: 100 }); diff --git a/docs/index.md b/docs/index.md index e3557ca..d2b5105 100644 --- a/docs/index.md +++ b/docs/index.md @@ -83,13 +83,13 @@ app.service('posts').hooks({ }); ``` -The `AppLoader` lazily configures a new `ServiceLoader` per service as you use them. This means that you do not have to configure the lower level `ServiceLoader` classes. But, you can use these classes individually, although it is generally not needed. +The `AppLoader` lazily configures a new `ServiceLoader` per service as you use them. This means that you do not have to configure the lower level `ServiceLoader` classes. You can use these classes individually, although it is generally not needed. ```js const { ServiceLoader } = require('feathers-dataloader'); -const serviceLoader = new ServiceLoader({ app, serviceName: 'users' }); +const serviceLoader = new ServiceLoader({ app, path: 'users' }); const user = await serviceLoader.load(1, params); const user = await serviceLoader.get(1, params); const users = await serviceLoader.find(params); @@ -131,7 +131,7 @@ Create a new app-loader. This is the most commonly used class. | Argument | Type | Default | Description | | --------------- | :--------: | ---------- | ------------------------------------------------ | | `app` | `Object` | | A Feathers app | -| `services` | `Object` | `{}`| An object where each property is a service name and the value is loader options for that service. These options override the `globalLoaderOptions` | +| `services` | `Object` | `{}`| An object where each property is a service name and the value is `loaderOptions` for that service. These options override the `globalLoaderOptions` | | `ServiceLoader` | `Class` | `ServiceLoader`| A base class that will be used to create each ServiceLoader instance | | `globalLoaderOptions` | `Object` | {} | Options that will be assigned to every new `ServiceLoader` | @@ -164,21 +164,23 @@ Create a new app-loader. This is the most commonly used class.

class ServiceLoader( [, options] )

-Create a new service-loader. This class lazily configures underlying `DataLoader` and `FindLoader` for a given service +Create a new service-loader. This class lazily configures underlying `DataLoader` and takes many `DataLoader` options. - **Arguments:** - `{Object} [ options ]` - - `{Object} service` + - `{Object} path` - `{Map} cacheMap` - - `{Map} cacheParamsFn` + - `{Function} cacheParamsFn` + - `{Function} selectFn` - ...loaderOptions | Argument | Type | Default | Description | | --------------- | :--------: | ---------- | ------------------------------------------------ | -| `app` | `Object` | | A Feathers app` | -| `serviceName` | `String` | | The name of the service like "users"` | -| `cacheMap` | `Map` | | A Map like object with methods get, set, and clear to serve as the cache of results.` | +| `app` | `Object` | | A Feathers app | +| `path` | `String` | | The name of the service like "users" | +| `cacheMap` | `Map` | | A Map like object with methods get, set, and clear to serve as the cache of results. | | `cacheParamsFn` | `Function` | defaultCacheParamsFn | A function that returns a JSON stringifiable set or params to be used in the cacheKey. The default function traverses the params and removes any functions` | +| `selectFn` | `Function` | defaultSelectFn | A function that selects or resolves data after it is returned from the cache | | `loaderOptions` | `Object` | {} | See `DataLoader` | @@ -187,7 +189,7 @@ Create a new service-loader. This class lazily configures underlying `DataLoader const loader = new ServiceLoader({ app, - serviceName: 'users' + path: 'users' cacheParamsFn: (params) => { return { userId: params.user.id, @@ -217,7 +219,7 @@ Create a new service-loader. This class lazily configures underlying `DataLoader

class DataLoader( batchLoadFunc [, options] )

-This library re-exports [Dataloader](https://www.npmjs.com/package/dataloader) from its original package. Please see its documentation for more information. `loaderOptions` given to `BatchLoader` will be used to configure Dataloaders. You can also import `Dataloader` along with some helpful utility functions to build custom loaders. +This library re-exports [Dataloader](https://www.npmjs.com/package/dataloader) from its original package. Please see its documentation for more information. `loaderOptions` given to `ServiceLoader` will be used to configure Dataloaders. You can also import `Dataloader` along with some helpful utility functions to build custom loaders. ```js const { DataLoader uniqueResults, uniqueKeys } = require("feathers-dataloader"); diff --git a/docs/loader/service-loader.md b/docs/loader/service-loader.md index 654cbd2..743f7bc 100644 --- a/docs/loader/service-loader.md +++ b/docs/loader/service-loader.md @@ -30,10 +30,9 @@ TODO: Provide Links to the DataLoader and FindLoader options. - **options** `{Object}` - **app** `{Object}` - A Feathers app, - - **serviceName** `{String}` - The name of the service + - **service** `{String}` - The name of the service - **cacheMap** `{Object}` - Instance of Map (or an object with a similar API) to be used as cache. Defaults to `new Map()` - **cacheParamsFn** `{Function}` - A function that returns JSON.strinify-able params of a query to be used in the `cacheMap`. This function should return a set of params that will be used to identify this unique query and removes any non-serializable items. The default function returns traverses params and removes any functions. Defaults to `defaultCacheParamsFn` - - **cacheKeyFn** `{Function}` - Normalize keys. `(key) => key && key.toString ? key.toString() : String(key)` Defaults to `defaultCacheKeyFn` There are two ways to create `ServiceLoader` instances. @@ -58,7 +57,7 @@ You can also directly create an instance using the `ServiceLoader` class. const { ServiceLoader } = require('@feathersjs/loader') // This is our ServiceLoader instance -const userLoader = new ServiceLoader({ app, serviceName: 'users' }) +const userLoader = new ServiceLoader({ app, service: 'users' }) ``` ## Example @@ -71,7 +70,7 @@ const loaderOptions = {} const loader = new ServiceLoader({ app - serviceName: 'users', + service: 'users', ...loaderOptions }) diff --git a/src/appLoader.js b/src/appLoader.js index b367850..f502f07 100644 --- a/src/appLoader.js +++ b/src/appLoader.js @@ -2,18 +2,22 @@ const BaseServiceLoader = require('./serviceLoader') module.exports = class AppLoader { constructor({ app, services = {}, ...loaderOptions }) { - this.options = { app, services, loaderOptions } - this.loaders = new Map() + this.options = { + app, + services, + loaderOptions, + loaders: new Map() + } } - service(serviceName) { + service(path) { const { app } = this.options const { ServiceLoader, ...loaderOptions } = { ServiceLoader: BaseServiceLoader, ...this.options.loaderOptions, - ...(this.options.services[serviceName] || {}) + ...(this.options.services[path] || {}) } - const cachedLoader = this.loaders.get(serviceName) + const cachedLoader = this.options.loaders.get(path) if (cachedLoader) { return cachedLoader @@ -21,22 +25,23 @@ module.exports = class AppLoader { const loader = new ServiceLoader({ ...loaderOptions, - serviceName, + path, app }) - this.loaders.set(serviceName, loader) + this.options.loaders.set(path, loader) return loader } async clear() { + const { loaders } = this.options const promises = [] - this.loaders.forEach((loader) => { - promises.push(loader.cacheMap.clear()) + loaders.forEach((loader) => { + promises.push(loader.clear()) }) await Promise.all(promises) - this.loaders.clear() + loaders.clear() return this } } diff --git a/src/serviceLoader.js b/src/serviceLoader.js index 4b68680..7219766 100644 --- a/src/serviceLoader.js +++ b/src/serviceLoader.js @@ -6,10 +6,13 @@ const { defaultCacheKeyFn, uniqueKeys, uniqueResults, - uniqueResultsMulti + uniqueResultsMulti, + _ } = require('./utils') -const createDataLoader = ({ service, key, loaderOptions, multi, method, params = {} }) => { +const filters = ['$limit', '$skip', '$sort'] + +const createDataLoader = ({ service, key, loaderOptions, multi, method, params }) => { const serviceMethod = method === '_load' ? '_find' : 'find' if (!service[serviceMethod]) { @@ -30,199 +33,159 @@ const createDataLoader = ({ service, key, loaderOptions, multi, method, params = [key]: { $in: uniqueKeys(keys) } } } - delete loaderParams.query.$limit return service[serviceMethod](loaderParams).then((result) => getResults(keys, result, key)) }, loaderOptions) } +const stringifyKey = (options, cacheParamsFn) => { + return stableStringify({ + ...options, + params: cacheParamsFn(options.params) + }) +} + module.exports = class ServiceLoader { - constructor({ app, serviceName, cacheParamsFn, cacheMap, ...loaderOptions }) { - this.cacheMap = cacheMap || new Map() - this.loaders = new Map() - const service = app.service(serviceName) + constructor({ app, path, cacheParamsFn, cacheMap, ...loaderOptions }, state) { + const service = app.service(path) this.options = { app, - serviceName, + path, service, key: service.options.id, cacheParamsFn: cacheParamsFn || defaultCacheParamsFn, - loaderOptions: { - cacheKeyFn: defaultCacheKeyFn, - ...loaderOptions - } + loaderOptions: _.assign({ cacheKeyFn: defaultCacheKeyFn }, loaderOptions), + loaders: new Map(), + cacheMap: cacheMap || new Map() } + this.state = state || {} } async exec({ cacheParamsFn, ...options }) { - const { service, loaderOptions } = this.options - - options = { - id: null, - key: this.options.key, - params: null, - multi: false, - method: 'load', - ...options - } + const { path, service, loaderOptions, cacheMap, loaders } = this.options + cacheParamsFn = cacheParamsFn || this.options.cacheParamsFn + + options = _.assign( + { + id: null, + key: this.options.key, + params: {}, + multi: false, + method: 'load' + }, + { + ...options, + path + } + ) if (['get', '_get', 'find', '_find'].includes(options.method)) { - const cacheKey = this.stringifyKey(options, cacheParamsFn) + const cacheKey = stringifyKey(options, cacheParamsFn) - const cachedResult = await this.cacheMap.get(cacheKey) + const cachedPromise = await cacheMap.get(cacheKey) - if (cachedResult) { - return cachedResult + if (cachedPromise) { + return cachedPromise } - const result = ['get', '_get'].includes(options.method) - ? await service[options.method](options.id, options.params) - : await service[options.method](options.params) + const promise = ['get', '_get'].includes(options.method) + ? service[options.method](options.id, options.params) + : service[options.method](options.params) - await this.cacheMap.set(cacheKey, result) + await cacheMap.set(cacheKey, promise) - return result + return promise } - // stableStringify does not sort arrays on purpose because - // array order matters in most cases. In this case, the - // order of ids does not matter to the load function but - // does to the cache key, thats why these are sorted. - const sortedId = Array.isArray(options.id) ? [...options.id].sort() : options.id + if (options.params.query && _.has(options.params.query, filters)) { + throw new GeneralError('Loader `load()` method cannot contain ${filters} in the query') + } - const cacheKey = this.stringifyKey( - { - ...options, - id: sortedId - }, - cacheParamsFn - ) + const cacheKey = stringifyKey(options, cacheParamsFn) - const cachedResult = await this.cacheMap.get(cacheKey) + const cachedPromise = await cacheMap.get(cacheKey) - if (cachedResult) { - return cachedResult + if (cachedPromise) { + return cachedPromise } - const loaderKey = this.stringifyKey( - { - key: options.key, - multi: options.multi, - method: options.method, - params: options.params - }, - cacheParamsFn - ) + const loaderConfig = { + key: options.key, + multi: options.multi, + method: options.method, + params: options.params + } + + const loaderKey = stringifyKey(loaderConfig, cacheParamsFn) - const dataLoader = - this.loaders.get(loaderKey) || - createDataLoader({ - key: options.key, - multi: options.multi, - method: options.method, - params: options.params, + let dataLoader = loaders.get(loaderKey) + + if (!dataLoader) { + dataLoader = createDataLoader({ service, - loaderOptions + loaderOptions, + ...loaderConfig }) - this.loaders.set(loaderKey, dataLoader) + loaders.set(loaderKey, dataLoader) + } - const result = Array.isArray(sortedId) - ? await dataLoader.loadMany(sortedId) - : await dataLoader.load(sortedId) + const promise = Array.isArray(options.id) ? dataLoader.loadMany(options.id) : dataLoader.load(options.id) - await this.cacheMap.set(cacheKey, result) + await cacheMap.set(cacheKey, promise) - return result + return promise } - get(id, params, cacheParamsFn) { - return this.exec({ method: 'get', id, params, cacheParamsFn }) + get(id, params) { + return this.exec({ ...this.state, method: 'get', id, params }) } - _get(id, params, cacheParamsFn) { - return this.exec({ method: '_get', id, params, cacheParamsFn }) + _get(id, params) { + return this.exec({ ...this.state, method: '_get', id, params }) } - find(params, cacheParamsFn) { - return this.exec({ method: 'find', params, cacheParamsFn }) + find(params) { + return this.exec({ ...this.state, method: 'find', params }) } - _find(params, cacheParamsFn) { - return this.exec({ method: '_find', params, cacheParamsFn }) + _find(params) { + return this.exec({ ...this.state, method: '_find', params }) } - load(id, params, cacheParamsFn) { - return this.exec({ method: 'load', id, params, cacheParamsFn }) + load(id, params) { + return this.exec({ ...this.state, method: 'load', id, params }) } - _load(id, params, cacheParamsFn) { - return this.exec({ method: '_load', id, params, cacheParamsFn }) + _load(id, params) { + return this.exec({ ...this.state, method: '_load', id, params }) } key(key) { - return { - load: (id, params, cacheParamsFn) => { - return this.exec({ - method: 'load', - id, - key, - params, - cacheParamsFn - }) - }, - _load: (id, params, cacheParamsFn) => { - return this.exec({ - method: '_load', - id, - key, - params, - cacheParamsFn - }) - } - } + return new ServiceLoader(this.options, { key }) } multi(key) { - return { - load: (id, params, cacheParamsFn) => { - return this.exec({ - method: 'load', - id, - key, - params, - cacheParamsFn, - multi: true - }) - }, - _load: (id, params, cacheParamsFn) => { - return this.exec({ - method: '_load', - id, - key, - params, - cacheParamsFn, - multi: true - }) - } - } - } - - stringifyKey(options, cacheParamsFn = this.options.cacheParamsFn) { - return stableStringify({ - ...options, - serviceName: this.options.serviceName, - params: cacheParamsFn(options.params) - }) + return new ServiceLoader(this.options, { key, multi: true }) } async clear() { - const { serviceName } = this.options - this.loaders.clear() + const { path, loaders, cacheMap } = this.options + loaders.clear() const promises = [] - for await (const cacheKey of this.cacheMap.keys()) { + // TODO: This could be a redis store or some other + // async storage. That's why there is this for/await + // iterator used to step over all the keys in a collection. + // Is it a good idea to just be pushing all the deletes into + // one Promise.all() after? Instead, we may want to call + // the delete in the iteration. But that would be slower. + // Also note that we don't just clear the whole cache + // and only delete the keys that match the path because + // the user may have shared the cacheMap with other + // services. + for await (const cacheKey of cacheMap.keys()) { const parsedKey = JSON.parse(cacheKey) - if (parsedKey.serviceName === serviceName) { - promises.push(this.cacheMap.delete(cacheKey)) + if (parsedKey.path === path) { + promises.push(cacheMap.delete(cacheKey)) } } await Promise.all(promises) diff --git a/src/utils.js b/src/utils.js index 1c89abc..5766611 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,12 +1,50 @@ const { GeneralError } = require('@feathersjs/errors') -const isObject = (obj) => { - if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) { +const _ = { + isObject: (obj) => { + if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) { + return false + } + return Object.getPrototypeOf(obj) === Object.prototype + }, + assign: (target, source) => { + const result = { ...target } + const keys = Object.keys(source) + for (let index = 0; index < keys.length; index++) { + const key = keys[index] + if (source[key] !== undefined) { + result[key] = source[key] + } + } + return result + }, + omit: (source, keys) => { + const result = { ...source } + for (let index = 0; index < keys.length; index++) { + delete result[keys[index]] + } + return result + }, + pick: (source, keys) => { + return keys.reduce((result, key) => { + if (source[key] !== undefined) { + result[key] = source[key] + } + return result + }, {}) + }, + has: (source, keys) => { + for (let index = 0; index < keys.length; index++) { + if (Object.prototype.hasOwnProperty.call(source, keys[index])) { + return true + } + } return false } - return Object.getPrototypeOf(obj) === Object.prototype } +module.exports._ = _ + module.exports.stableStringify = (object) => { return JSON.stringify(object, (key, value) => { if (typeof value === 'function') { @@ -15,7 +53,7 @@ module.exports.stableStringify = (object) => { ) } - if (isObject(value)) { + if (_.isObject(value)) { const keys = Object.keys(value).sort() const result = {} for (let index = 0, length = keys.length; index < length; ++index) { diff --git a/tests/appLoader.test.js b/tests/appLoader.test.js index 8538153..b3d389a 100644 --- a/tests/appLoader.test.js +++ b/tests/appLoader.test.js @@ -25,11 +25,10 @@ describe('appLoader.test', () => { assert.isFunction(serviceLoader._find) assert.isFunction(serviceLoader.load) assert.isFunction(serviceLoader._load) - assert.isFunction(serviceLoader.multi) assert.isFunction(serviceLoader.key) + assert.isFunction(serviceLoader.multi) assert.isFunction(serviceLoader.exec) assert.isFunction(serviceLoader.clear) - assert.isFunction(serviceLoader.stringifyKey) }) it('returns a cached ServiceLoader', () => { @@ -73,13 +72,13 @@ describe('appLoader.test', () => { const commentsLoader = appLoader.service('comments') await postsLoader.load(1) await commentsLoader.load(1) - assert.deepEqual(appLoader.loaders.size, 2) - assert.deepEqual(postsLoader.cacheMap.size, 1) - assert.deepEqual(commentsLoader.cacheMap.size, 1) + assert.deepEqual(appLoader.options.loaders.size, 2) + assert.deepEqual(postsLoader.options.cacheMap.size, 1) + assert.deepEqual(commentsLoader.options.cacheMap.size, 1) await appLoader.clear() - assert.deepEqual(appLoader.loaders.size, 0) - assert.deepEqual(postsLoader.cacheMap.size, 0) - assert.deepEqual(commentsLoader.cacheMap.size, 0) + assert.deepEqual(appLoader.options.loaders.size, 0) + assert.deepEqual(postsLoader.options.cacheMap.size, 0) + assert.deepEqual(commentsLoader.options.cacheMap.size, 0) }) it('takes a base class in global config', () => { diff --git a/tests/serviceLoader.test.js b/tests/serviceLoader.test.js index 87e3e29..520525f 100644 --- a/tests/serviceLoader.test.js +++ b/tests/serviceLoader.test.js @@ -1,6 +1,5 @@ const { assert } = require('chai') const { ServiceLoader } = require('../src') -const { stableStringify } = require('../src/utils') const { makeApp } = require('./utils') const testFunc = () => {} @@ -10,7 +9,7 @@ describe('serviceLoader.test', () => { it('creates a serviceLoader', () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) assert.isFunction(serviceLoader.get) assert.isFunction(serviceLoader._get) @@ -18,17 +17,16 @@ describe('serviceLoader.test', () => { assert.isFunction(serviceLoader._find) assert.isFunction(serviceLoader.load) assert.isFunction(serviceLoader._load) - assert.isFunction(serviceLoader.multi) assert.isFunction(serviceLoader.key) + assert.isFunction(serviceLoader.multi) assert.isFunction(serviceLoader.exec) assert.isFunction(serviceLoader.clear) - assert.isFunction(serviceLoader.stringifyKey) }) it('takes a cacheParamsFn option', () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts', + path: 'posts', cacheParamsFn: testFunc }) assert.deepEqual(serviceLoader.options.cacheParamsFn, testFunc) @@ -37,18 +35,18 @@ describe('serviceLoader.test', () => { it('passes loader options', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts', + path: 'posts', cacheKeyFn: testFunc }) await serviceLoader.load(1) - const [dataLoader] = serviceLoader.loaders.values() + const [dataLoader] = serviceLoader.options.loaders.values() assert.deepEqual(dataLoader._cacheKeyFn, testFunc) }) it('works with load(id)', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const defaultResult = await app.service('posts').get(1) const result = await serviceLoader.load(1) @@ -58,7 +56,7 @@ describe('serviceLoader.test', () => { it('works with load([id1, id2])', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const defaultResult = await Promise.all([app.service('posts').get(1), app.service('posts').get(2)]) const result = await serviceLoader.load([1, 2]) @@ -68,7 +66,7 @@ describe('serviceLoader.test', () => { it('works with key("key").load(id)', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const defaultResult = await app.service('posts').get(1) const result = await serviceLoader.key('body').load('John post') @@ -78,7 +76,7 @@ describe('serviceLoader.test', () => { it('works with key("key")._load(id)', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const defaultResult = await app.service('posts').get(1) const result = await serviceLoader.key('body')._load('John post') @@ -88,7 +86,7 @@ describe('serviceLoader.test', () => { it('works with multi("key").load(id)', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'comments' + path: 'comments' }) const result = await serviceLoader.multi('postId').load(1) assert.deepEqual(result.length, 3) @@ -97,7 +95,7 @@ describe('serviceLoader.test', () => { it('works with multi("key")._load(id)', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'comments' + path: 'comments' }) const result = await serviceLoader.multi('postId')._load(1) assert.deepEqual(result.length, 3) @@ -106,7 +104,7 @@ describe('serviceLoader.test', () => { it('works with multi("key").load([id1, id2])', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'comments' + path: 'comments' }) const defaultResult = await Promise.all([ app.service('comments').find({ paginate: false, query: { postId: 1 } }), @@ -119,7 +117,7 @@ describe('serviceLoader.test', () => { it('works with get', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const defaultResult = await app.service('posts').get(1) const result = await serviceLoader.get(1) @@ -129,7 +127,7 @@ describe('serviceLoader.test', () => { it('works with find', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const defaultResult = await app.service('posts').find() const result = await serviceLoader.find() @@ -139,12 +137,11 @@ describe('serviceLoader.test', () => { it('works with underscored methods', async () => { const serviceLoader = new ServiceLoader({ app, - serviceName: 'posts' + path: 'posts' }) const methods = ['_get', '_find', '_load'] let hookCalled = false const hookCallback = (context) => { - console.log('hookCallback called') hookCalled = true return context } @@ -159,30 +156,16 @@ describe('serviceLoader.test', () => { assert.deepEqual(hookCalled, false) }) - it('works with stringifyKey', async () => { - const serviceLoader = new ServiceLoader({ - app, - serviceName: 'posts' - }) - const cacheKey = serviceLoader.stringifyKey({ id: 1, key: 'id' }) - const stableKey = stableStringify({ - serviceName: 'posts', - id: 1, - key: 'id' - }) - assert.deepEqual(cacheKey, stableKey) - }) - it('clears', async () => { const cacheMap = new Map() const postsLoader = new ServiceLoader({ app, - serviceName: 'posts', + path: 'posts', cacheMap: cacheMap }) const commentsLoader = new ServiceLoader({ app, - serviceName: 'comments', + path: 'comments', cacheMap: cacheMap }) @@ -195,6 +178,6 @@ describe('serviceLoader.test', () => { await postsLoader.clear() assert.deepEqual(cacheMap.size, 1) - assert.deepEqual(postsLoader.loaders.size, 0) + assert.deepEqual(postsLoader.options.loaders.size, 0) }) })