diff --git a/src/service-module/make-service-module.ts b/src/service-module/make-service-module.ts index 83d1d719..ada55c24 100644 --- a/src/service-module/make-service-module.ts +++ b/src/service-module/make-service-module.ts @@ -3,27 +3,37 @@ eslint @typescript-eslint/explicit-function-return-type: 0, @typescript-eslint/no-explicit-any: 0 */ +import _pick from 'lodash/pick' +import _merge from 'lodash/merge' import makeDefaultState from './service-module.state' import makeGetters from './service-module.getters' import makeMutations from './service-module.mutations' import makeActions from './service-module.actions' import { Service } from '@feathersjs/feathers' import { MakeServicePluginOptions } from './types' +import { Store } from 'vuex' export default function makeServiceModule( service: Service, - options: MakeServicePluginOptions + options: MakeServicePluginOptions, + store: Store ) { - const defaultState = makeDefaultState(options) - const defaultGetters = makeGetters() - const defaultMutations = makeMutations() - const defaultActions = makeActions(service) - - return { + const defaults = { namespaced: true, - state: Object.assign({}, defaultState, options.state), - getters: Object.assign({}, defaultGetters, options.getters), - mutations: Object.assign({}, defaultMutations, options.mutations), - actions: Object.assign({}, defaultActions, options.actions) + state: makeDefaultState(options), + getters: makeGetters(), + mutations: makeMutations(), + actions: makeActions(service) } + const fromOptions = _pick(options, [ + 'state', + 'getters', + 'mutations', + 'actions' + ]) + const merged = _merge({}, defaults, fromOptions) + const extended = options.extend({ store, module: merged }) + const finalModule = _merge({}, merged, extended) + + return finalModule } diff --git a/src/service-module/make-service-plugin.ts b/src/service-module/make-service-plugin.ts index 8ab2a5a6..6028d996 100644 --- a/src/service-module/make-service-plugin.ts +++ b/src/service-module/make-service-plugin.ts @@ -3,7 +3,12 @@ eslint @typescript-eslint/explicit-function-return-type: 0, @typescript-eslint/no-explicit-any: 0 */ -import { FeathersVuexOptions, MakeServicePluginOptions } from './types' +import { + FeathersVuexOptions, + MakeServicePluginOptions, + ServicePluginExtendOptions +} from './types' + import makeServiceModule from './make-service-module' import { globalModels, prepareAddModel } from './global-models' import enableServiceEvents from './service-module.events' @@ -13,6 +18,14 @@ import _get from 'lodash/get' interface ServiceOptionsDefaults { servicePath: string namespace: string + extend: ( + options: ServicePluginExtendOptions + ) => { + state: any + getters: any + mutations: any + actions: any + } state: {} getters: {} mutations: {} @@ -25,6 +38,7 @@ interface ServiceOptionsDefaults { const defaults: ServiceOptionsDefaults = { namespace: '', // The namespace for the Vuex module. Will generally be derived from the service.path, service.name, when available. Otherwise, it must be provided here, explicitly. servicePath: '', + extend: ({ module }) => module, // for custom plugin (replaces state, getters, mutations, and actions) state: {}, // for custom state getters: {}, // for custom getters mutations: {}, // for custom mutations @@ -91,7 +105,7 @@ export default function prepareMakeServicePlugin( return store => { // (1^) Create and register the Vuex module options.namespace = makeNamespace(namespace, servicePath, nameStyle) - const module = makeServiceModule(service, options) + const module = makeServiceModule(service, options, store) // Don't preserve state if reinitialized (prevents state pollution in SSR) store.registerModule(options.namespace, module, { preserveState: false }) diff --git a/src/service-module/service-module.state.ts b/src/service-module/service-module.state.ts index a7b4b480..454a2070 100644 --- a/src/service-module/service-module.state.ts +++ b/src/service-module/service-module.state.ts @@ -107,6 +107,7 @@ export default function makeDefaultState(options: MakeServicePluginOptions) { 'instanceDefaults', 'setupInstance', 'handleEvents', + 'extend', 'state', 'getters', 'mutations', diff --git a/src/service-module/types.ts b/src/service-module/types.ts index 4b45fc60..fd20752b 100644 --- a/src/service-module/types.ts +++ b/src/service-module/types.ts @@ -37,6 +37,11 @@ export interface HandleEvents { removed?: Function } +export interface ServicePluginExtendOptions { + store: Store + module: any +} + export interface MakeServicePluginOptions { Model: any service: Service @@ -64,6 +69,15 @@ export interface MakeServicePluginOptions { instanceDefaults?: () => {} setupInstance?: (data: any, { models, store }) => {} handleEvents?: HandleEvents + + extend?: ( + options: ServicePluginExtendOptions + ) => { + state?: any + getters?: any + mutations?: any + actions?: any + } state?: {} getters?: {} mutations?: {} diff --git a/test/service-module/service-module.test.ts b/test/service-module/service-module.test.ts index e22c7460..a5b2fd77 100644 --- a/test/service-module/service-module.test.ts +++ b/test/service-module/service-module.test.ts @@ -936,114 +936,257 @@ describe('Service Module', function () { }) describe('Customizing Service Stores', function () { - it('allows adding custom state', function () { - const { makeServicePlugin, ServiceTodo } = makeContext() + describe('New "extend" method', () => { + it('allows access to the store and default module', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() - const customState = { - test: true, - test2: { - test: true - } - } - const store = new Vuex.Store({ - plugins: [ - makeServicePlugin({ - Model: ServiceTodo, - service: feathersClient.service('service-todos'), - state: customState - }) - ] + new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + extend: ({ store, module }) => { + assert.ok(store, 'should have received received the store') + assert.ok(module.state, 'should have default state') + assert.ok(module.getters, 'should have default getters') + assert.ok(module.mutations, 'should have default mutations') + assert.ok(module.actions, 'should have default actions') + assert.ok(module.namespaced, 'should have default namespaced') + return {} + } + }) + ] + }) }) - assert(store.state['service-todos'].test === true, 'added custom state') - assert( - store.state['service-todos'].test2.test === true, - 'added custom state' - ) - }) + it('allows adding custom state', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() - it('allows custom mutations', function () { - const { makeServicePlugin, ServiceTodo } = makeContext() - const state = { test: true } - const customMutations = { - setTestToFalse(state) { - state.test = false + const customState = { + test: true, + test2: { + test: true + } } - } - const store = new Vuex.Store({ - plugins: [ - makeServicePlugin({ - Model: ServiceTodo, - service: feathersClient.service('service-todos'), - state, - mutations: customMutations - }) - ] + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + extend: () => { + return { + state: customState + } + } + }) + ] + }) + + assert(store.state['service-todos'].test === true, 'added custom state') + assert( + store.state['service-todos'].test2.test === true, + 'added custom state' + ) }) - store.commit('service-todos/setTestToFalse') - assert( - store.state['service-todos'].test === false, - 'the custom state was modified by the custom mutation' - ) - }) + it('allows custom mutations', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + const state = { test: true } + const customMutations = { + setTestToFalse(state) { + state.test = false + } + } + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + extend: () => ({ + state, + mutations: customMutations + }) + }) + ] + }) - it('allows custom getters', function () { - const { makeServicePlugin, ServiceTodo } = makeContext() - const customGetters = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - oneTwoThree(state) { - return 123 + store.commit('service-todos/setTestToFalse') + assert( + store.state['service-todos'].test === false, + 'the custom state was modified by the custom mutation' + ) + }) + + it('allows custom getters', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + const customGetters = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + oneTwoThree(state) { + return 123 + } } - } - const store = new Vuex.Store({ - plugins: [ - makeServicePlugin({ - Model: ServiceTodo, - service: feathersClient.service('service-todos'), - getters: customGetters - }) - ] + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + extend: () => ({ + getters: customGetters + }) + }) + ] + }) + + assert( + store.getters['service-todos/oneTwoThree'] === 123, + 'the custom getter was available' + ) }) - assert( - store.getters['service-todos/oneTwoThree'] === 123, - 'the custom getter was available' - ) + it('allows adding custom actions', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + extend: () => ({ + state: { + isTrue: false + }, + mutations: { + setToTrue(state) { + state.isTrue = true + } + }, + actions: { + trigger(context) { + context.commit('setToTrue') + } + } + }) + }) + ] + }) + + store.dispatch('service-todos/trigger') + assert( + store.state['service-todos'].isTrue === true, + 'the custom action was run' + ) + }) }) - it('allows adding custom actions', function () { - const { makeServicePlugin, ServiceTodo } = makeContext() - const config = { - state: { - isTrue: false - }, - mutations: { - setToTrue(state) { - state.isTrue = true + describe('Deprecated options', () => { + it('allows adding custom state', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + + const customState = { + test: true, + test2: { + test: true } - }, - actions: { - trigger(context) { - context.commit('setToTrue') + } + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + state: customState + }) + ] + }) + + assert(store.state['service-todos'].test === true, 'added custom state') + assert( + store.state['service-todos'].test2.test === true, + 'added custom state' + ) + }) + + it('allows custom mutations', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + const state = { test: true } + const customMutations = { + setTestToFalse(state) { + state.test = false } } - } - const store = new Vuex.Store({ - plugins: [ - makeServicePlugin({ - Model: ServiceTodo, - service: feathersClient.service('service-todos'), - ...config - }) - ] + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + state, + mutations: customMutations + }) + ] + }) + + store.commit('service-todos/setTestToFalse') + assert( + store.state['service-todos'].test === false, + 'the custom state was modified by the custom mutation' + ) }) - store.dispatch('service-todos/trigger') - assert( - store.state['service-todos'].isTrue === true, - 'the custom action was run' - ) + it('allows custom getters', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + const customGetters = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + oneTwoThree(state) { + return 123 + } + } + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + getters: customGetters + }) + ] + }) + + assert( + store.getters['service-todos/oneTwoThree'] === 123, + 'the custom getter was available' + ) + }) + + it('allows adding custom actions', function () { + const { makeServicePlugin, ServiceTodo } = makeContext() + const config = { + state: { + isTrue: false + }, + mutations: { + setToTrue(state) { + state.isTrue = true + } + }, + actions: { + trigger(context) { + context.commit('setToTrue') + } + } + } + const store = new Vuex.Store({ + plugins: [ + makeServicePlugin({ + Model: ServiceTodo, + service: feathersClient.service('service-todos'), + ...config + }) + ] + }) + + store.dispatch('service-todos/trigger') + assert( + store.state['service-todos'].isTrue === true, + 'the custom action was run' + ) + }) }) })