UNPKG

6.11 kBPlain TextView Raw
1import version from './version'
2import { EventEmitter } from 'events'
3import { stripSlashes, createDebug } from '@feathersjs/commons'
4import { HOOKS, hooks, middleware } from '@feathersjs/hooks'
5import { eventHook, eventMixin } from './events'
6import { hookMixin } from './hooks'
7import { wrapService, getServiceOptions, protectedMethods } from './service'
8import {
9 FeathersApplication,
10 ServiceMixin,
11 Service,
12 ServiceOptions,
13 ServiceInterface,
14 Application,
15 FeathersService,
16 ApplicationHookOptions
17} from './declarations'
18import { enableHooks } from './hooks'
19
20const debug = createDebug('@feathersjs/feathers')
21
22export class Feathers<Services, Settings>
23 extends EventEmitter
24 implements FeathersApplication<Services, Settings>
25{
26 services: Services = {} as Services
27 settings: Settings = {} as Settings
28 mixins: ServiceMixin<Application<Services, Settings>>[] = [hookMixin, eventMixin]
29 version: string = version
30 _isSetup = false
31
32 protected registerHooks: (this: any, allHooks: any) => any
33
34 constructor() {
35 super()
36 this.registerHooks = enableHooks(this)
37 this.registerHooks({
38 around: [eventHook]
39 })
40 }
41
42 get<L extends keyof Settings & string>(name: L): Settings[L] {
43 return this.settings[name]
44 }
45
46 set<L extends keyof Settings & string>(name: L, value: Settings[L]) {
47 this.settings[name] = value
48 return this
49 }
50
51 configure(callback: (this: this, app: this) => void) {
52 callback.call(this, this)
53
54 return this
55 }
56
57 defaultService(location: string): ServiceInterface {
58 throw new Error(`Can not find service '${location}'`)
59 }
60
61 service<L extends keyof Services & string>(
62 location: L
63 ): FeathersService<this, keyof any extends keyof Services ? Service : Services[L]> {
64 const path = (stripSlashes(location) || '/') as L
65 const current = this.services.hasOwnProperty(path) ? this.services[path] : undefined
66
67 if (typeof current === 'undefined') {
68 this.use(path, this.defaultService(path) as any)
69 return this.service(path)
70 }
71
72 return current as any
73 }
74
75 protected _setup() {
76 this._isSetup = true
77
78 return Object.keys(this.services)
79 .reduce(
80 (current, path) =>
81 current.then(() => {
82 const service: any = this.service(path as any)
83
84 if (typeof service.setup === 'function') {
85 debug(`Setting up service for \`${path}\``)
86
87 return service.setup(this, path)
88 }
89 }),
90 Promise.resolve()
91 )
92 .then(() => this)
93 }
94
95 get setup() {
96 return this._setup
97 }
98
99 set setup(value) {
100 this._setup = (value as any)[HOOKS]
101 ? value
102 : hooks(
103 value,
104 middleware().params('server').props({
105 app: this
106 })
107 )
108 }
109
110 protected _teardown() {
111 this._isSetup = false
112
113 return Object.keys(this.services)
114 .reduce(
115 (current, path) =>
116 current.then(() => {
117 const service: any = this.service(path as any)
118
119 if (typeof service.teardown === 'function') {
120 debug(`Tearing down service for \`${path}\``)
121
122 return service.teardown(this, path)
123 }
124 }),
125 Promise.resolve()
126 )
127 .then(() => this)
128 }
129
130 get teardown() {
131 return this._teardown
132 }
133
134 set teardown(value) {
135 this._teardown = (value as any)[HOOKS]
136 ? value
137 : hooks(
138 value,
139 middleware().params('server').props({
140 app: this
141 })
142 )
143 }
144
145 use<L extends keyof Services & string>(
146 path: L,
147 service: keyof any extends keyof Services ? ServiceInterface | Application : Services[L],
148 options?: ServiceOptions<keyof any extends keyof Services ? string : keyof Services[L]>
149 ): this {
150 if (typeof path !== 'string') {
151 throw new Error(`'${path}' is not a valid service path.`)
152 }
153
154 const location = (stripSlashes(path) || '/') as L
155 const subApp = service as Application
156 const isSubApp = typeof subApp.service === 'function' && subApp.services
157
158 if (isSubApp) {
159 Object.keys(subApp.services).forEach((subPath) =>
160 this.use(`${location}/${subPath}` as any, subApp.service(subPath) as any)
161 )
162
163 return this
164 }
165
166 const protoService = wrapService(location, service, options as ServiceOptions)
167 const serviceOptions = getServiceOptions(protoService)
168
169 for (const name of protectedMethods) {
170 if (serviceOptions.methods.includes(name)) {
171 throw new Error(`'${name}' on service '${location}' is not allowed as a custom method name`)
172 }
173 }
174
175 debug(`Registering new service at \`${location}\``)
176
177 // Add all the mixins
178 this.mixins.forEach((fn) => fn.call(this, protoService, location, serviceOptions))
179
180 this.services[location] = protoService
181
182 // If we ran setup already, set this service up explicitly, this will not `await`
183 if (this._isSetup && typeof protoService.setup === 'function') {
184 debug(`Setting up service for \`${location}\``)
185 protoService.setup(this, location)
186 }
187
188 return this
189 }
190
191 async unuse<L extends keyof Services & string>(
192 location: L
193 ): Promise<FeathersService<this, keyof any extends keyof Services ? Service : Services[L]>> {
194 const path = (stripSlashes(location) || '/') as L
195 const service = this.services[path] as Service
196
197 if (service && typeof service.teardown === 'function') {
198 await service.teardown(this as any, path)
199 }
200
201 delete this.services[path]
202
203 return service as any
204 }
205
206 hooks(hookMap: ApplicationHookOptions<this>) {
207 const untypedMap = hookMap as any
208
209 if (untypedMap.before || untypedMap.after || untypedMap.error || untypedMap.around) {
210 // regular hooks for all service methods
211 this.registerHooks(untypedMap)
212 } else if (untypedMap.setup || untypedMap.teardown) {
213 // .setup and .teardown application hooks
214 hooks(this, untypedMap)
215 } else {
216 // Other registration formats are just `around` hooks
217 this.registerHooks({
218 around: untypedMap
219 })
220 }
221
222 return this
223 }
224}