1 | import version from './version';
|
2 | import {
|
3 | EventEmitter, stripSlashes, createDebug, HOOKS, hooks, middleware
|
4 | } from './dependencies';
|
5 | import { eventHook, eventMixin } from './events';
|
6 | import { hookMixin } from './hooks/index';
|
7 | import { wrapService, getServiceOptions, protectedMethods } from './service';
|
8 | import {
|
9 | FeathersApplication,
|
10 | ServiceMixin,
|
11 | Service,
|
12 | ServiceOptions,
|
13 | ServiceInterface,
|
14 | Application,
|
15 | FeathersService,
|
16 | HookMap,
|
17 | ApplicationHookOptions
|
18 | } from './declarations';
|
19 | import { enableRegularHooks } from './hooks/regular';
|
20 |
|
21 | const debug = createDebug('@feathersjs/feathers');
|
22 |
|
23 | export class Feathers<Services, Settings> extends EventEmitter implements FeathersApplication<Services, Settings> {
|
24 | services: Services = ({} as Services);
|
25 | settings: Settings = ({} as Settings);
|
26 | mixins: ServiceMixin<Application<Services, Settings>>[] = [ hookMixin, eventMixin ];
|
27 | version: string = version;
|
28 | _isSetup = false;
|
29 | appHooks: HookMap<Application<Services, Settings>, any> = {
|
30 | [HOOKS]: [ (eventHook as any) ]
|
31 | };
|
32 |
|
33 | private regularHooks: (this: any, allHooks: any) => any;
|
34 |
|
35 | constructor () {
|
36 | super();
|
37 | this.regularHooks = enableRegularHooks(this);
|
38 | hooks(this, {
|
39 | setup: middleware().params('server').props({
|
40 | app: this
|
41 | }),
|
42 | teardown: middleware().params('server').props({
|
43 | app: this
|
44 | })
|
45 | });
|
46 | }
|
47 |
|
48 | get<L extends keyof Settings & string> (name: L): Settings[L] {
|
49 | return this.settings[name];
|
50 | }
|
51 |
|
52 | set<L extends keyof Settings & string> (name: L, value: Settings[L]) {
|
53 | this.settings[name] = value;
|
54 | return this;
|
55 | }
|
56 |
|
57 | configure (callback: (this: this, app: this) => void) {
|
58 | callback.call(this, this);
|
59 |
|
60 | return this;
|
61 | }
|
62 |
|
63 | defaultService (location: string): ServiceInterface {
|
64 | throw new Error(`Can not find service '${location}'`);
|
65 | }
|
66 |
|
67 | service<L extends keyof Services & string> (
|
68 | location: L
|
69 | ): FeathersService<this, keyof any extends keyof Services ? Service : Services[L]> {
|
70 | const path = (stripSlashes(location) || '/') as L;
|
71 | const current = this.services[path];
|
72 |
|
73 | if (typeof current === 'undefined') {
|
74 | this.use(path, this.defaultService(path) as any);
|
75 | return this.service(path);
|
76 | }
|
77 |
|
78 | return current as any;
|
79 | }
|
80 |
|
81 | use<L extends keyof Services & string> (
|
82 | path: L,
|
83 | service: keyof any extends keyof Services ? ServiceInterface | Application : Services[L],
|
84 | options?: ServiceOptions
|
85 | ): this {
|
86 | if (typeof path !== 'string') {
|
87 | throw new Error(`'${path}' is not a valid service path.`);
|
88 | }
|
89 |
|
90 | const location = (stripSlashes(path) || '/') as L;
|
91 | const subApp = service as Application;
|
92 | const isSubApp = typeof subApp.service === 'function' && subApp.services;
|
93 |
|
94 | if (isSubApp) {
|
95 | Object.keys(subApp.services).forEach(subPath =>
|
96 | this.use(`${location}/${subPath}` as any, subApp.service(subPath) as any)
|
97 | );
|
98 |
|
99 | return this;
|
100 | }
|
101 |
|
102 | const protoService = wrapService(location, service, options);
|
103 | const serviceOptions = getServiceOptions(protoService);
|
104 |
|
105 | for (const name of protectedMethods) {
|
106 | if (serviceOptions.methods.includes(name)) {
|
107 | throw new Error(`'${name}' on service '${location}' is not allowed as a custom method name`);
|
108 | }
|
109 | }
|
110 |
|
111 | debug(`Registering new service at \`${location}\``);
|
112 |
|
113 |
|
114 | this.mixins.forEach(fn => fn.call(this, protoService, location, serviceOptions));
|
115 |
|
116 | this.services[location] = protoService;
|
117 |
|
118 |
|
119 | if (this._isSetup && typeof protoService.setup === 'function') {
|
120 | debug(`Setting up service for \`${location}\``);
|
121 | protoService.setup(this, location);
|
122 | }
|
123 |
|
124 | return this;
|
125 | }
|
126 |
|
127 | hooks (hookMap: ApplicationHookOptions<this>) {
|
128 | const untypedMap = hookMap as any;
|
129 |
|
130 | if (untypedMap.before || untypedMap.after || untypedMap.error) {
|
131 | this.regularHooks(untypedMap);
|
132 | } else if (untypedMap.setup || untypedMap.teardown) {
|
133 | hooks(this, untypedMap);
|
134 | } else if (Array.isArray(hookMap)) {
|
135 | this.appHooks[HOOKS].push(...hookMap as any);
|
136 | } else {
|
137 | const methodHookMap = hookMap as HookMap<Application<Services, Settings>, any>;
|
138 |
|
139 | Object.keys(methodHookMap).forEach(key => {
|
140 | const methodHooks = this.appHooks[key] || [];
|
141 |
|
142 | this.appHooks[key] = methodHooks.concat(methodHookMap[key]);
|
143 | });
|
144 | }
|
145 |
|
146 | return this;
|
147 | }
|
148 |
|
149 | setup () {
|
150 | this._isSetup = true;
|
151 |
|
152 | return Object.keys(this.services).reduce((current, path) => current
|
153 | .then(() => {
|
154 | const service: any = this.service(path as any);
|
155 |
|
156 | if (typeof service.setup === 'function') {
|
157 | debug(`Setting up service for \`${path}\``);
|
158 |
|
159 | return service.setup(this, path);
|
160 | }
|
161 | }), Promise.resolve()).then(() => this);
|
162 | }
|
163 |
|
164 | teardown () {
|
165 | this._isSetup = false;
|
166 |
|
167 | return Object.keys(this.services).reduce((current, path) => current
|
168 | .then(() => {
|
169 | const service: any = this.service(path as any);
|
170 |
|
171 | if (typeof service.teardown === 'function') {
|
172 | debug(`Tearing down service for \`${path}\``);
|
173 |
|
174 | return service.teardown(this, path);
|
175 | }
|
176 | }), Promise.resolve()).then(() => this)
|
177 | }
|
178 | }
|