UNPKG

5.26 kBPlain TextView Raw
1import version from './version';
2import {
3 EventEmitter, stripSlashes, createDebug, HOOKS, hooks, middleware
4} from './dependencies';
5import { eventHook, eventMixin } from './events';
6import { hookMixin } from './hooks/index';
7import { wrapService, getServiceOptions, protectedMethods } from './service';
8import {
9 FeathersApplication,
10 ServiceMixin,
11 Service,
12 ServiceOptions,
13 ServiceInterface,
14 Application,
15 FeathersService,
16 HookMap,
17 ApplicationHookOptions
18} from './declarations';
19import { enableRegularHooks } from './hooks/regular';
20
21const debug = createDebug('@feathersjs/feathers');
22
23export 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 // Add all the mixins
114 this.mixins.forEach(fn => fn.call(this, protoService, location, serviceOptions));
115
116 this.services[location] = protoService;
117
118 // If we ran setup already, set this service up explicitly, this will not `await`
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}