1 | import version from './version'
|
2 | import { EventEmitter } from 'events'
|
3 | import { stripSlashes, createDebug } from '@feathersjs/commons'
|
4 | import { HOOKS, hooks, middleware } from '@feathersjs/hooks'
|
5 | import { eventHook, eventMixin } from './events'
|
6 | import { hookMixin } from './hooks'
|
7 | import { wrapService, getServiceOptions, protectedMethods } from './service'
|
8 | import {
|
9 | FeathersApplication,
|
10 | ServiceMixin,
|
11 | Service,
|
12 | ServiceOptions,
|
13 | ServiceInterface,
|
14 | Application,
|
15 | FeathersService,
|
16 | ApplicationHookOptions
|
17 | } from './declarations'
|
18 | import { enableHooks } from './hooks'
|
19 |
|
20 | const debug = createDebug('@feathersjs/feathers')
|
21 |
|
22 | export 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 |
|
178 | this.mixins.forEach((fn) => fn.call(this, protoService, location, serviceOptions))
|
179 |
|
180 | this.services[location] = protoService
|
181 |
|
182 |
|
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 |
|
211 | this.registerHooks(untypedMap)
|
212 | } else if (untypedMap.setup || untypedMap.teardown) {
|
213 |
|
214 | hooks(this, untypedMap)
|
215 | } else {
|
216 |
|
217 | this.registerHooks({
|
218 | around: untypedMap
|
219 | })
|
220 | }
|
221 |
|
222 | return this
|
223 | }
|
224 | }
|