UNPKG

6.31 kBPlain TextView Raw
1import {
2 getManager,
3 HookContextData,
4 HookManager,
5 HookMap as BaseHookMap,
6 hooks,
7 Middleware,
8 collect
9} from '@feathersjs/hooks'
10import {
11 Service,
12 ServiceOptions,
13 HookContext,
14 FeathersService,
15 HookMap,
16 AroundHookFunction,
17 HookFunction,
18 HookType
19} from './declarations'
20import { defaultServiceArguments, getHookMethods } from './service'
21
22type ConvertedMap = { [type in HookType]: ReturnType<typeof convertHookData> }
23
24type HookStore = {
25 around: { [method: string]: AroundHookFunction[] }
26 before: { [method: string]: HookFunction[] }
27 after: { [method: string]: HookFunction[] }
28 error: { [method: string]: HookFunction[] }
29 collected: { [method: string]: AroundHookFunction[] }
30 collectedAll: { before?: AroundHookFunction[]; after?: AroundHookFunction[] }
31}
32
33type HookEnabled = { __hooks: HookStore }
34
35const types: HookType[] = ['before', 'after', 'error', 'around']
36
37const isType = (value: any): value is HookType => types.includes(value)
38
39// Converts different hook registration formats into the
40// same internal format
41export function convertHookData(input: any) {
42 const result: { [method: string]: HookFunction[] | AroundHookFunction[] } = {}
43
44 if (Array.isArray(input)) {
45 result.all = input
46 } else if (typeof input !== 'object') {
47 result.all = [input]
48 } else {
49 for (const key of Object.keys(input)) {
50 const value = input[key]
51 result[key] = Array.isArray(value) ? value : [value]
52 }
53 }
54
55 return result
56}
57
58export function collectHooks(target: HookEnabled, method: string) {
59 const { collected, collectedAll, around } = target.__hooks
60
61 return [
62 ...(around.all || []),
63 ...(around[method] || []),
64 ...(collectedAll.before || []),
65 ...(collected[method] || []),
66 ...(collectedAll.after || [])
67 ] as AroundHookFunction[]
68}
69
70// Add `.hooks` functionality to an object
71export function enableHooks(object: any) {
72 const store: HookStore = {
73 around: {},
74 before: {},
75 after: {},
76 error: {},
77 collected: {},
78 collectedAll: {}
79 }
80
81 Object.defineProperty(object, '__hooks', {
82 configurable: true,
83 value: store,
84 writable: true
85 })
86
87 return function registerHooks(this: HookEnabled, input: HookMap<any, any>) {
88 const store = this.__hooks
89 const map = Object.keys(input).reduce((map, type) => {
90 if (!isType(type)) {
91 throw new Error(`'${type}' is not a valid hook type`)
92 }
93
94 map[type] = convertHookData(input[type])
95
96 return map
97 }, {} as ConvertedMap)
98 const types = Object.keys(map) as HookType[]
99
100 types.forEach((type) =>
101 Object.keys(map[type]).forEach((method) => {
102 const mapHooks = map[type][method]
103 const storeHooks: any[] = (store[type][method] ||= [])
104
105 storeHooks.push(...mapHooks)
106
107 if (method === 'all') {
108 if (store.before[method] || store.error[method]) {
109 const beforeAll = collect({
110 before: store.before[method] || [],
111 error: store.error[method] || []
112 })
113 store.collectedAll.before = [beforeAll]
114 }
115
116 if (store.after[method]) {
117 const afterAll = collect({
118 after: store.after[method] || []
119 })
120 store.collectedAll.after = [afterAll]
121 }
122 } else {
123 if (store.before[method] || store.after[method] || store.error[method]) {
124 const collected = collect({
125 before: store.before[method] || [],
126 after: store.after[method] || [],
127 error: store.error[method] || []
128 })
129
130 store.collected[method] = [collected]
131 }
132 }
133 })
134 )
135
136 return this
137 }
138}
139
140export function createContext(service: Service, method: string, data: HookContextData = {}) {
141 const createContext = (service as any)[method].createContext
142
143 if (typeof createContext !== 'function') {
144 throw new Error(`Can not create context for method ${method}`)
145 }
146
147 return createContext(data) as HookContext
148}
149
150export class FeathersHookManager<A> extends HookManager {
151 constructor(
152 public app: A,
153 public method: string
154 ) {
155 super()
156 this._middleware = []
157 }
158
159 collectMiddleware(self: any, args: any[]): Middleware[] {
160 const appHooks = collectHooks(this.app as any as HookEnabled, this.method)
161 const middleware = super.collectMiddleware(self, args)
162 const methodHooks = collectHooks(self, this.method)
163
164 return [...appHooks, ...middleware, ...methodHooks]
165 }
166
167 initializeContext(self: any, args: any[], context: HookContext) {
168 const ctx = super.initializeContext(self, args, context)
169
170 ctx.params = ctx.params || {}
171
172 return ctx
173 }
174
175 middleware(mw: Middleware[]) {
176 this._middleware.push(...mw)
177 return this
178 }
179}
180
181export function hookMixin<A>(this: A, service: FeathersService<A>, path: string, options: ServiceOptions) {
182 if (typeof service.hooks === 'function') {
183 return service
184 }
185
186 const hookMethods = getHookMethods(service, options)
187
188 const serviceMethodHooks = hookMethods.reduce((res, method) => {
189 const params = (defaultServiceArguments as any)[method] || ['data', 'params']
190
191 res[method] = new FeathersHookManager<A>(this, method).params(...params).props({
192 app: this,
193 path,
194 method,
195 service,
196 event: null,
197 type: 'around',
198 get statusCode() {
199 return this.http?.status
200 },
201 set statusCode(value: number) {
202 this.http = this.http || {}
203 this.http.status = value
204 }
205 })
206
207 return res
208 }, {} as BaseHookMap)
209
210 const registerHooks = enableHooks(service)
211
212 hooks(service, serviceMethodHooks)
213
214 service.hooks = function (this: any, hookOptions: any) {
215 if (hookOptions.before || hookOptions.after || hookOptions.error || hookOptions.around) {
216 return registerHooks.call(this, hookOptions)
217 }
218
219 if (Array.isArray(hookOptions)) {
220 return hooks(this, hookOptions)
221 }
222
223 Object.keys(hookOptions).forEach((method) => {
224 const manager = getManager(this[method])
225
226 if (!(manager instanceof FeathersHookManager)) {
227 throw new Error(`Method ${method} is not a Feathers hooks enabled service method`)
228 }
229
230 manager.middleware(hookOptions[method])
231 })
232
233 return this
234 }
235
236 return service
237}