@feathersjs/feathers 5.0.0-pre.14 → 5.0.0-pre.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const _default: "5.0.0-pre.14";
1
+ declare const _default: "5.0.0-pre.15";
2
2
  export default _default;
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = '5.0.0-pre.14';
3
+ exports.default = '5.0.0-pre.15';
4
4
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@feathersjs/feathers",
3
3
  "description": "A framework for real-time applications and REST API with JavaScript and TypeScript",
4
- "version": "5.0.0-pre.14",
4
+ "version": "5.0.0-pre.15",
5
5
  "homepage": "http://feathersjs.com",
6
6
  "repository": {
7
7
  "type": "git",
@@ -57,17 +57,17 @@
57
57
  "access": "public"
58
58
  },
59
59
  "dependencies": {
60
- "@feathersjs/commons": "^5.0.0-pre.14",
60
+ "@feathersjs/commons": "^5.0.0-pre.15",
61
61
  "@feathersjs/hooks": "^0.6.5",
62
62
  "events": "^3.3.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/mocha": "^9.0.0",
66
- "@types/node": "^16.10.4",
67
- "mocha": "^9.1.2",
66
+ "@types/node": "^16.11.6",
67
+ "mocha": "^9.1.3",
68
68
  "shx": "^0.3.3",
69
- "ts-node": "^10.3.0",
69
+ "ts-node": "^10.4.0",
70
70
  "typescript": "^4.4.4"
71
71
  },
72
- "gitHead": "9bb26276938aead1f3f582be5517a49a8ce00f09"
72
+ "gitHead": "8008bf4f8529a2a40b6a2f976c1f43ae13675693"
73
73
  }
@@ -15,34 +15,34 @@ import {
15
15
  HookOptions,
16
16
  FeathersService,
17
17
  HookMap,
18
- LegacyHookMap
18
+ RegularHookMap
19
19
  } from './declarations';
20
- import { enableLegacyHooks } from './hooks/legacy';
20
+ import { enableRegularHooks } from './hooks/regular';
21
21
 
22
22
  const debug = createDebug('@feathersjs/feathers');
23
23
 
24
- export class Feathers<ServiceTypes, AppSettings> extends EventEmitter implements FeathersApplication<ServiceTypes, AppSettings> {
25
- services: ServiceTypes = ({} as ServiceTypes);
26
- settings: AppSettings = ({} as AppSettings);
27
- mixins: ServiceMixin<Application<ServiceTypes, AppSettings>>[] = [ hookMixin, eventMixin ];
24
+ export class Feathers<Services, Settings> extends EventEmitter implements FeathersApplication<Services, Settings> {
25
+ services: Services = ({} as Services);
26
+ settings: Settings = ({} as Settings);
27
+ mixins: ServiceMixin<Application<Services, Settings>>[] = [ hookMixin, eventMixin ];
28
28
  version: string = version;
29
29
  _isSetup = false;
30
- appHooks: HookMap<Application<ServiceTypes, AppSettings>, any> = {
30
+ appHooks: HookMap<Application<Services, Settings>, any> = {
31
31
  [HOOKS]: [ (eventHook as any) ]
32
32
  };
33
33
 
34
- private legacyHooks: (this: any, allHooks: any) => any;
34
+ private regularHooks: (this: any, allHooks: any) => any;
35
35
 
36
36
  constructor () {
37
37
  super();
38
- this.legacyHooks = enableLegacyHooks(this);
38
+ this.regularHooks = enableRegularHooks(this);
39
39
  }
40
40
 
41
- get<L extends keyof AppSettings & string> (name: L): AppSettings[L] {
41
+ get<L extends keyof Settings & string> (name: L): Settings[L] {
42
42
  return this.settings[name];
43
43
  }
44
44
 
45
- set<L extends keyof AppSettings & string> (name: L, value: AppSettings[L]) {
45
+ set<L extends keyof Settings & string> (name: L, value: Settings[L]) {
46
46
  this.settings[name] = value;
47
47
  return this;
48
48
  }
@@ -53,13 +53,13 @@ export class Feathers<ServiceTypes, AppSettings> extends EventEmitter implements
53
53
  return this;
54
54
  }
55
55
 
56
- defaultService (location: string): ServiceInterface<any> {
56
+ defaultService (location: string): ServiceInterface {
57
57
  throw new Error(`Can not find service '${location}'`);
58
58
  }
59
59
 
60
- service<L extends keyof ServiceTypes & string> (
60
+ service<L extends keyof Services & string> (
61
61
  location: L
62
- ): FeathersService<this, keyof any extends keyof ServiceTypes ? Service<any> : ServiceTypes[L]> {
62
+ ): FeathersService<this, keyof any extends keyof Services ? Service : Services[L]> {
63
63
  const path = (stripSlashes(location) || '/') as L;
64
64
  const current = this.services[path];
65
65
 
@@ -71,9 +71,9 @@ export class Feathers<ServiceTypes, AppSettings> extends EventEmitter implements
71
71
  return current as any;
72
72
  }
73
73
 
74
- use<L extends keyof ServiceTypes & string> (
74
+ use<L extends keyof Services & string> (
75
75
  path: L,
76
- service: keyof any extends keyof ServiceTypes ? ServiceInterface<any> | Application : ServiceTypes[L],
76
+ service: keyof any extends keyof Services ? ServiceInterface | Application : Services[L],
77
77
  options?: ServiceOptions
78
78
  ): this {
79
79
  if (typeof path !== 'string') {
@@ -118,16 +118,16 @@ export class Feathers<ServiceTypes, AppSettings> extends EventEmitter implements
118
118
  }
119
119
 
120
120
  hooks (hookMap: HookOptions<this, any>) {
121
- const legacyMap = hookMap as LegacyHookMap<this, any>;
121
+ const regularMap = hookMap as RegularHookMap<this, any>;
122
122
 
123
- if (legacyMap.before || legacyMap.after || legacyMap.error) {
124
- return this.legacyHooks(legacyMap);
123
+ if (regularMap.before || regularMap.after || regularMap.error) {
124
+ return this.regularHooks(regularMap);
125
125
  }
126
126
 
127
127
  if (Array.isArray(hookMap)) {
128
128
  this.appHooks[HOOKS].push(...hookMap as any);
129
129
  } else {
130
- const methodHookMap = hookMap as HookMap<Application<ServiceTypes, AppSettings>, any>;
130
+ const methodHookMap = hookMap as HookMap<Application<Services, Settings>, any>;
131
131
 
132
132
  Object.keys(methodHookMap).forEach(key => {
133
133
  const methodHooks = this.appHooks[key] || [];
@@ -146,10 +146,10 @@ export class Feathers<ServiceTypes, AppSettings> extends EventEmitter implements
146
146
  for (const path of Object.keys(this.services)) {
147
147
  promise = promise.then(() => {
148
148
  const service: any = this.service(path as any);
149
-
149
+
150
150
  if (typeof service.setup === 'function') {
151
151
  debug(`Setting up service for \`${path}\``);
152
-
152
+
153
153
  return service.setup(this, path);
154
154
  }
155
155
  });
@@ -18,9 +18,10 @@ export interface ServiceOptions {
18
18
  events?: string[];
19
19
  methods?: string[];
20
20
  serviceEvents?: string[];
21
+ routeParams?: { [key: string]: any };
21
22
  }
22
23
 
23
- export interface ServiceMethods<T, D = Partial<T>> {
24
+ export interface ServiceMethods<T = any, D = Partial<T>> {
24
25
  find (params?: Params): Promise<T | T[]>;
25
26
 
26
27
  get (id: Id, params?: Params): Promise<T>;
@@ -36,7 +37,7 @@ export interface ServiceMethods<T, D = Partial<T>> {
36
37
  setup (app: Application, path: string): Promise<void>;
37
38
  }
38
39
 
39
- export interface ServiceOverloads<T, D> {
40
+ export interface ServiceOverloads<T = any, D = Partial<T>> {
40
41
  create? (data: D[], params?: Params): Promise<T[]>;
41
42
 
42
43
  update? (id: Id, data: D, params?: Params): Promise<T>;
@@ -52,14 +53,14 @@ export interface ServiceOverloads<T, D> {
52
53
  remove? (id: null, params?: Params): Promise<T[]>;
53
54
  }
54
55
 
55
- export type Service<T, D = Partial<T>> =
56
+ export type Service<T = any, D = Partial<T>> =
56
57
  ServiceMethods<T, D> &
57
58
  ServiceOverloads<T, D>;
58
59
 
59
- export type ServiceInterface<T, D = Partial<T>> =
60
+ export type ServiceInterface<T = any, D = Partial<T>> =
60
61
  Partial<ServiceMethods<T, D>>;
61
62
 
62
- export interface ServiceAddons<A = Application, S = Service<any, any>> extends EventEmitter {
63
+ export interface ServiceAddons<A = Application, S = Service> extends EventEmitter {
63
64
  id?: string;
64
65
  hooks (options: HookOptions<A, S>): this;
65
66
  }
@@ -103,19 +104,19 @@ export interface ServiceHookOverloads<S> {
103
104
  ): Promise<HookContext>;
104
105
  }
105
106
 
106
- export type FeathersService<A = FeathersApplication, S = Service<any>> =
107
+ export type FeathersService<A = FeathersApplication, S = Service> =
107
108
  S & ServiceAddons<A, S> & OptionalPick<ServiceHookOverloads<S>, keyof S>;
108
109
 
109
- export type CustomMethod<Methods extends string> = {
110
- [k in Methods]: <X = any> (data: any, params?: Params) => Promise<X>;
110
+ export type CustomMethods<T extends {[key: string]: [any, any]}> = {
111
+ [K in keyof T]: (data: T[K][0], params?: Params) => Promise<T[K][1]>;
111
112
  }
112
113
 
113
- export type ServiceMixin<A> = (service: FeathersService<A>, path: string, options?: ServiceOptions) => void;
114
+ export type ServiceMixin<A> = (service: FeathersService<A>, path: string, options: ServiceOptions) => void;
114
115
 
115
116
  export type ServiceGenericType<S> = S extends ServiceInterface<infer T> ? T : any;
116
117
  export type ServiceGenericData<S> = S extends ServiceInterface<infer _T, infer D> ? D : any;
117
118
 
118
- export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
119
+ export interface FeathersApplication<Services = any, Settings = any> {
119
120
  /**
120
121
  * The Feathers application version
121
122
  */
@@ -124,7 +125,7 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
124
125
  /**
125
126
  * A list of callbacks that run when a new service is registered
126
127
  */
127
- mixins: ServiceMixin<Application<ServiceTypes, AppSettings>>[];
128
+ mixins: ServiceMixin<Application<Services, Settings>>[];
128
129
 
129
130
  /**
130
131
  * The index of all services keyed by their path.
@@ -132,13 +133,13 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
132
133
  * __Important:__ Services should always be retrieved via `app.service('name')`
133
134
  * not via `app.services`.
134
135
  */
135
- services: ServiceTypes;
136
+ services: Services;
136
137
 
137
138
  /**
138
139
  * The application settings that can be used via
139
140
  * `app.get` and `app.set`
140
141
  */
141
- settings: AppSettings;
142
+ settings: Settings;
142
143
 
143
144
  /**
144
145
  * A private-ish indicator if `app.setup()` has been called already
@@ -148,14 +149,14 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
148
149
  /**
149
150
  * Contains all registered application level hooks.
150
151
  */
151
- appHooks: HookMap<Application<ServiceTypes, AppSettings>, any>;
152
+ appHooks: HookMap<Application<Services, Settings>, any>;
152
153
 
153
154
  /**
154
155
  * Retrieve an application setting by name
155
156
  *
156
157
  * @param name The setting name
157
158
  */
158
- get<L extends keyof AppSettings & string> (name: L): AppSettings[L];
159
+ get<L extends keyof Settings & string> (name: L): Settings[L];
159
160
 
160
161
  /**
161
162
  * Set an application setting
@@ -163,7 +164,7 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
163
164
  * @param name The setting name
164
165
  * @param value The setting value
165
166
  */
166
- set<L extends keyof AppSettings & string> (name: L, value: AppSettings[L]): this;
167
+ set<L extends keyof Settings & string> (name: L, value: Settings[L]): this;
167
168
 
168
169
  /**
169
170
  * Runs a callback configure function with the current application instance.
@@ -179,7 +180,7 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
179
180
  *
180
181
  * @param location The path of the service
181
182
  */
182
- defaultService (location: string): ServiceInterface<any>;
183
+ defaultService (location: string): ServiceInterface;
183
184
 
184
185
  /**
185
186
  * Register a new service or a sub-app. When passed another
@@ -191,9 +192,9 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
191
192
  * Feathers application to use a sub-app under the `path` prefix.
192
193
  * @param options The options for this service
193
194
  */
194
- use<L extends keyof ServiceTypes & string> (
195
+ use<L extends keyof Services & string> (
195
196
  path: L,
196
- service: keyof any extends keyof ServiceTypes ? ServiceInterface<any> | Application : ServiceTypes[L],
197
+ service: keyof any extends keyof Services ? ServiceInterface | Application : Services[L],
197
198
  options?: ServiceOptions
198
199
  ): this;
199
200
 
@@ -204,9 +205,9 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
204
205
  *
205
206
  * @param path The name of the service.
206
207
  */
207
- service<L extends keyof ServiceTypes & string> (
208
+ service<L extends keyof Services & string> (
208
209
  path: L
209
- ): FeathersService<this, keyof any extends keyof ServiceTypes ? Service<any> : ServiceTypes[L]>;
210
+ ): FeathersService<this, keyof any extends keyof Services ? Service : Services[L]>;
210
211
 
211
212
  setup (server?: any): Promise<this>;
212
213
 
@@ -220,7 +221,7 @@ export interface FeathersApplication<ServiceTypes = any, AppSettings = any> {
220
221
 
221
222
  // This needs to be an interface instead of a type
222
223
  // so that the declaration can be extended by other modules
223
- export interface Application<ServiceTypes = any, AppSettings = any> extends FeathersApplication<ServiceTypes, AppSettings>, EventEmitter {
224
+ export interface Application<Services = any, Settings = any> extends FeathersApplication<Services, Settings>, EventEmitter {
224
225
 
225
226
  }
226
227
 
@@ -234,11 +235,19 @@ export interface Query {
234
235
  export interface Params {
235
236
  query?: Query;
236
237
  provider?: string;
237
- route?: { [key: string]: string };
238
+ route?: { [key: string]: any };
238
239
  headers?: { [key: string]: any };
239
240
  [key: string]: any; // (JL) not sure if we want this
240
241
  }
241
242
 
243
+ export interface Http {
244
+ /**
245
+ * A writeable, optional property that allows to override the standard HTTP status
246
+ * code that should be returned.
247
+ */
248
+ statusCode?: number;
249
+ }
250
+
242
251
  export interface HookContext<A = Application, S = any> extends BaseHookContext<ServiceGenericType<S>> {
243
252
  /**
244
253
  * A read only property that contains the Feathers application object. This can be used to
@@ -309,35 +318,41 @@ export interface HookContext<A = Application, S = any> extends BaseHookContext<S
309
318
  /**
310
319
  * A writeable, optional property that allows to override the standard HTTP status
311
320
  * code that should be returned.
321
+ *
322
+ * @deprecated Use `http.statusCode` instead.
312
323
  */
313
324
  statusCode?: number;
325
+ /**
326
+ * A writeable, optional property that contains options specific to HTTP transports.
327
+ */
328
+ http?: Http;
314
329
  /**
315
330
  * The event emitted by this method. Can be set to `null` to skip event emitting.
316
331
  */
317
332
  event: string|null;
318
333
  }
319
334
 
320
- // Legacy hook typings
321
- export type LegacyHookFunction<A = Application, S = Service<any, any>> =
335
+ // Regular hook typings
336
+ export type RegularHookFunction<A = Application, S = Service> =
322
337
  (this: S, context: HookContext<A, S>) => (Promise<HookContext<Application, S> | void> | HookContext<Application, S> | void);
323
338
 
324
- export type Hook<A = Application, S = Service<any, any>> = LegacyHookFunction<A, S>;
339
+ export type Hook<A = Application, S = Service> = RegularHookFunction<A, S>;
325
340
 
326
- type LegacyHookMethodMap<A, S> =
327
- { [L in keyof S]?: SelfOrArray<LegacyHookFunction<A, S>>; } &
328
- { all?: SelfOrArray<LegacyHookFunction<A, S>> };
341
+ type RegularHookMethodMap<A, S> =
342
+ { [L in keyof S]?: SelfOrArray<RegularHookFunction<A, S>>; } &
343
+ { all?: SelfOrArray<RegularHookFunction<A, S>> };
329
344
 
330
- type LegacyHookTypeMap<A, S> =
331
- SelfOrArray<LegacyHookFunction<A, S>> | LegacyHookMethodMap<A, S>;
345
+ type RegularHookTypeMap<A, S> =
346
+ SelfOrArray<RegularHookFunction<A, S>> | RegularHookMethodMap<A, S>;
332
347
 
333
- export type LegacyHookMap<A, S> = {
334
- before?: LegacyHookTypeMap<A, S>,
335
- after?: LegacyHookTypeMap<A, S>,
336
- error?: LegacyHookTypeMap<A, S>
348
+ export type RegularHookMap<A, S> = {
349
+ before?: RegularHookTypeMap<A, S>,
350
+ after?: RegularHookTypeMap<A, S>,
351
+ error?: RegularHookTypeMap<A, S>
337
352
  }
338
353
 
339
354
  // New @feathersjs/hook typings
340
- export type HookFunction<A = Application, S = Service<any, any>> =
355
+ export type HookFunction<A = Application, S = Service> =
341
356
  (context: HookContext<A, S>, next: NextFunction) => Promise<void>;
342
357
 
343
358
  export type HookMap<A, S> = {
@@ -345,4 +360,4 @@ export type HookMap<A, S> = {
345
360
  };
346
361
 
347
362
  export type HookOptions<A, S> =
348
- HookMap<A, S> | HookFunction<A, S>[] | LegacyHookMap<A, S>;
363
+ HookMap<A, S> | HookFunction<A, S>[] | RegularHookMap<A, S>;
@@ -6,16 +6,20 @@ import {
6
6
  } from '../declarations';
7
7
  import { defaultServiceArguments, getHookMethods } from '../service';
8
8
  import {
9
- collectLegacyHooks,
10
- enableLegacyHooks,
11
- fromAfterHook,
9
+ collectRegularHooks,
10
+ enableRegularHooks
11
+ } from './regular';
12
+
13
+ export {
12
14
  fromBeforeHook,
15
+ fromBeforeHooks,
16
+ fromAfterHook,
17
+ fromAfterHooks,
18
+ fromErrorHook,
13
19
  fromErrorHooks
14
- } from './legacy';
20
+ } from './regular';
15
21
 
16
- export { fromAfterHook, fromBeforeHook, fromErrorHooks };
17
-
18
- export function createContext (service: Service<any>, method: string, data: HookContextData = {}) {
22
+ export function createContext (service: Service, method: string, data: HookContextData = {}) {
19
23
  const createContext = (service as any)[method].createContext;
20
24
 
21
25
  if (typeof createContext !== 'function') {
@@ -34,11 +38,11 @@ export class FeathersHookManager<A> extends HookManager {
34
38
  collectMiddleware (self: any, args: any[]): Middleware[] {
35
39
  const app = this.app as any as Application;
36
40
  const appHooks = app.appHooks[HOOKS].concat(app.appHooks[this.method] || []);
37
- const legacyAppHooks = collectLegacyHooks(this.app, this.method);
41
+ const regularAppHooks = collectRegularHooks(this.app, this.method);
38
42
  const middleware = super.collectMiddleware(self, args);
39
- const legacyHooks = collectLegacyHooks(self, this.method);
43
+ const regularHooks = collectRegularHooks(self, this.method);
40
44
 
41
- return [...appHooks, ...legacyAppHooks, ...middleware, ...legacyHooks];
45
+ return [...appHooks, ...regularAppHooks, ...middleware, ...regularHooks];
42
46
  }
43
47
 
44
48
  initializeContext (self: any, args: any[], context: HookContext) {
@@ -63,7 +67,9 @@ export function hookMixin<A> (
63
67
  }
64
68
 
65
69
  const app = this;
66
- const serviceMethodHooks = getHookMethods(service, options).reduce((res, method) => {
70
+ const hookMethods = getHookMethods(service, options);
71
+
72
+ const serviceMethodHooks = hookMethods.reduce((res, method) => {
67
73
  const params = (defaultServiceArguments as any)[method] || [ 'data', 'params' ];
68
74
 
69
75
  res[method] = new FeathersHookManager<A>(app, method)
@@ -74,18 +80,25 @@ export function hookMixin<A> (
74
80
  method,
75
81
  service,
76
82
  event: null,
77
- type: null
83
+ type: null,
84
+ get statusCode() {
85
+ return this.http?.statusCode;
86
+ },
87
+ set statusCode(value: number) {
88
+ (this.http ||= {}).statusCode = value;
89
+ }
78
90
  });
79
91
 
80
92
  return res;
81
93
  }, {} as HookMap);
82
- const handleLegacyHooks = enableLegacyHooks(service);
94
+
95
+ const handleRegularHooks = enableRegularHooks(service, hookMethods);
83
96
 
84
97
  hooks(service, serviceMethodHooks);
85
98
 
86
99
  service.hooks = function (this: any, hookOptions: any) {
87
100
  if (hookOptions.before || hookOptions.after || hookOptions.error) {
88
- return handleLegacyHooks.call(this, hookOptions);
101
+ return handleRegularHooks.call(this, hookOptions);
89
102
  }
90
103
 
91
104
  if (Array.isArray(hookOptions)) {
@@ -0,0 +1,207 @@
1
+ import { HookFunction, RegularHookFunction, RegularHookMap } from '../declarations';
2
+ import { defaultServiceMethods } from '../service';
3
+
4
+ const runHook = <A, S> (hook: RegularHookFunction<A, S>, context: any, type?: string) => {
5
+ if (type) context.type = type;
6
+ return Promise.resolve(hook.call(context.self, context))
7
+ .then((res: any) => {
8
+ if (type) context.type = null;
9
+ if (res && res !== context) {
10
+ Object.assign(context, res);
11
+ }
12
+ });
13
+ };
14
+
15
+ export function fromBeforeHook<A, S> (hook: RegularHookFunction<A, S>): HookFunction<A, S> {
16
+ return (context, next) => {
17
+ return runHook(hook, context, 'before').then(next);
18
+ };
19
+ }
20
+
21
+ export function fromAfterHook<A, S> (hook: RegularHookFunction<A, S>): HookFunction<A, S> {
22
+ return (context, next) => {
23
+ return next().then(() => runHook(hook, context, 'after'));
24
+ }
25
+ }
26
+
27
+ export function fromErrorHook<A, S> (hook: RegularHookFunction<A, S>): HookFunction<A, S> {
28
+ return (context, next) => {
29
+ return next().catch((error: any) => {
30
+ if (context.error !== error || context.result !== undefined) {
31
+ (context as any).original = { ...context };
32
+ context.error = error;
33
+ delete context.result;
34
+ }
35
+
36
+ return runHook(hook, context, 'error').then(() => {
37
+ if (context.result === undefined && context.error !== undefined) {
38
+ throw context.error;
39
+ }
40
+ });
41
+ });
42
+ }
43
+ }
44
+
45
+ const RunHooks = <A, S> (hooks: RegularHookFunction<A, S>[]) => (context: any) => {
46
+ return hooks.reduce((promise, hook) => {
47
+ return promise.then(() => runHook(hook, context))
48
+ }, Promise.resolve(undefined));
49
+ };
50
+
51
+ export function fromBeforeHooks<A, S> (hooks: RegularHookFunction<A, S>[]) {
52
+ return fromBeforeHook(RunHooks(hooks));
53
+ }
54
+
55
+ export function fromAfterHooks<A, S> (hooks: RegularHookFunction<A, S>[]) {
56
+ return fromAfterHook(RunHooks(hooks));
57
+ }
58
+
59
+ export function fromErrorHooks<A, S> (hooks: RegularHookFunction<A, S>[]) {
60
+ return fromErrorHook(RunHooks(hooks));
61
+ }
62
+
63
+ export function collectRegularHooks (target: any, method: string) {
64
+ return target.__hooks.hooks[method] || [];
65
+ }
66
+
67
+ // Converts different hook registration formats into the
68
+ // same internal format
69
+ export function convertHookData (input: any) {
70
+ const result: { [ method: string ]: RegularHookFunction[] } = {};
71
+
72
+ if (Array.isArray(input)) {
73
+ result.all = input;
74
+ } else if (typeof input !== 'object') {
75
+ result.all = [ input ];
76
+ } else {
77
+ for (const key of Object.keys(input)) {
78
+ const value = input[key];
79
+ result[key] = Array.isArray(value) ? value : [ value ];
80
+ }
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ type RegularType = 'before' | 'after' | 'error';
87
+
88
+ type RegularMap = { [ type in RegularType ]: ReturnType< typeof convertHookData > };
89
+
90
+ type RegularAdapter = HookFunction & { hooks: RegularHookFunction[] };
91
+
92
+ type RegularStore = {
93
+ before: { [ method: string ]: RegularAdapter },
94
+ after: { [ method: string ]: RegularAdapter },
95
+ error: { [ method: string ]: RegularAdapter },
96
+ hooks: { [ method: string ]: HookFunction[] }
97
+ };
98
+
99
+ const types: RegularType[] = ['before', 'after', 'error'];
100
+
101
+ const isType = (value: any): value is RegularType => types.includes(value);
102
+
103
+ const wrappers = {
104
+ before: fromBeforeHooks,
105
+ after: fromAfterHooks,
106
+ error: fromErrorHooks
107
+ };
108
+
109
+ const createStore = (methods: string[]) => {
110
+ const store: RegularStore = {
111
+ before: {},
112
+ after: {},
113
+ error: {},
114
+ hooks: {}
115
+ };
116
+
117
+ for (const method of methods) {
118
+ store.hooks[method] = [];
119
+ }
120
+
121
+ return store;
122
+ };
123
+
124
+ const setStore = (object: any, store: RegularStore) => {
125
+ Object.defineProperty(object, '__hooks', {
126
+ configurable: true,
127
+ value: store,
128
+ writable: true
129
+ });
130
+ };
131
+
132
+ const getStore = (object: any): RegularStore => object.__hooks;
133
+
134
+ const createMap = (input: RegularHookMap<any, any>, methods: string[]) => {
135
+ const map = {} as RegularMap;
136
+
137
+ Object.keys(input).forEach((type) => {
138
+ if (!isType(type)) {
139
+ throw new Error(`'${type}' is not a valid hook type`);
140
+ }
141
+
142
+ const data = convertHookData(input[type]);
143
+
144
+ Object.keys(data).forEach((method) => {
145
+ if (method !== 'all' && !methods.includes(method)) {
146
+ throw new Error(`'${method}' is not a valid hook method`);
147
+ }
148
+ });
149
+
150
+ map[type] = data;
151
+ });
152
+
153
+ return map;
154
+ };
155
+
156
+ const createAdapter = (type: RegularType) => {
157
+ const hooks: RegularHookFunction[] = [];
158
+ const hook = wrappers[type](hooks);
159
+ const adapter = Object.assign(hook, { hooks });
160
+
161
+ return adapter;
162
+ };
163
+
164
+ const updateStore = (store: RegularStore, map: RegularMap) => {
165
+ Object.keys(store.hooks).forEach((method) => {
166
+ let adapted = false;
167
+
168
+ Object.keys(map).forEach((key) => {
169
+ const type = key as RegularType;
170
+ const allHooks = map[type].all || [];
171
+ const methodHooks = map[type][method] || [];
172
+
173
+ if (allHooks.length || methodHooks.length) {
174
+ const adapter = store[type][method] ||= (adapted = true, createAdapter(type));
175
+
176
+ adapter.hooks.push(...allHooks, ...methodHooks);
177
+ }
178
+ });
179
+
180
+ if (adapted) {
181
+ store.hooks[method] = [
182
+ store.error[method],
183
+ store.before[method],
184
+ store.after[method]
185
+ ].filter(hook => hook);
186
+ }
187
+ });
188
+ };
189
+
190
+ // Add `.hooks` functionality to an object
191
+ export function enableRegularHooks (
192
+ object: any,
193
+ methods: string[] = defaultServiceMethods
194
+ ) {
195
+ const store = createStore(methods);
196
+
197
+ setStore(object, store);
198
+
199
+ return function regularHooks (this: any, input: RegularHookMap<any, any>) {
200
+ const store = getStore(this);
201
+ const map = createMap(input, methods);
202
+
203
+ updateStore(store, map);
204
+
205
+ return this;
206
+ }
207
+ }