UNPKG

6.3 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(public app: A, public method: string) {
152 super()
153 this._middleware = []
154 }
155
156 collectMiddleware(self: any, args: any[]): Middleware[] {
157 const appHooks = collectHooks(this.app as any as HookEnabled, this.method)
158 const middleware = super.collectMiddleware(self, args)
159 const methodHooks = collectHooks(self, this.method)
160
161 return [...appHooks, ...middleware, ...methodHooks]
162 }
163
164 initializeContext(self: any, args: any[], context: HookContext) {
165 const ctx = super.initializeContext(self, args, context)
166
167 ctx.params = ctx.params || {}
168
169 return ctx
170 }
171
172 middleware(mw: Middleware[]) {
173 this._middleware.push(...mw)
174 return this
175 }
176}
177
178export function hookMixin<A>(this: A, service: FeathersService<A>, path: string, options: ServiceOptions) {
179 if (typeof service.hooks === 'function') {
180 return service
181 }
182
183 const hookMethods = getHookMethods(service, options)
184
185 const serviceMethodHooks = hookMethods.reduce((res, method) => {
186 const params = (defaultServiceArguments as any)[method] || ['data', 'params']
187
188 res[method] = new FeathersHookManager<A>(this, method).params(...params).props({
189 app: this,
190 path,
191 method,
192 service,
193 event: null,
194 type: 'around',
195 get statusCode() {
196 return this.http?.status
197 },
198 set statusCode(value: number) {
199 this.http = this.http || {}
200 this.http.status = value
201 }
202 })
203
204 return res
205 }, {} as BaseHookMap)
206
207 const registerHooks = enableHooks(service)
208
209 hooks(service, serviceMethodHooks)
210
211 service.hooks = function (this: any, hookOptions: any) {
212 if (hookOptions.before || hookOptions.after || hookOptions.error || hookOptions.around) {
213 return registerHooks.call(this, hookOptions)
214 }
215
216 if (Array.isArray(hookOptions)) {
217 return hooks(this, hookOptions)
218 }
219
220 Object.keys(hookOptions).forEach((method) => {
221 const manager = getManager(this[method])
222
223 if (!(manager instanceof FeathersHookManager)) {
224 throw new Error(`Method ${method} is not a Feathers hooks enabled service method`)
225 }
226
227 manager.middleware(hookOptions[method])
228 })
229
230 return this
231 }
232
233 return service
234}