@feathersjs/express 5.0.0-pre.2 → 5.0.0-pre.22

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/src/index.ts CHANGED
@@ -1,25 +1,18 @@
1
- import Debug from 'debug';
2
- import express, {
3
- Express, static as _static, json, raw, text, urlencoded, query
4
- } from 'express';
5
- import {
6
- Application as FeathersApplication, defaultServiceMethods
7
- } from '@feathersjs/feathers';
1
+ import express, { Express } from 'express';
2
+ import { Application as FeathersApplication, defaultServiceMethods } from '@feathersjs/feathers';
3
+ import { routing } from '@feathersjs/transport-commons';
4
+ import { createDebug } from '@feathersjs/commons';
8
5
 
9
6
  import { Application } from './declarations';
10
- import { errorHandler, notFound } from './handlers';
11
- import { parseAuthentication, authenticate } from './authentication';
12
7
 
13
- export {
14
- _static as static, json, raw, text, urlencoded, query,
15
- errorHandler, notFound, express as original,
16
- authenticate, parseAuthentication
17
- };
8
+ export { default as original, static, static as serveStatic, json, raw, text, urlencoded, query } from 'express';
18
9
 
19
- export * from './rest';
10
+ export * from './authentication';
20
11
  export * from './declarations';
12
+ export * from './handlers';
13
+ export * from './rest';
21
14
 
22
- const debug = Debug('@feathersjs/express');
15
+ const debug = createDebug('@feathersjs/express');
23
16
 
24
17
  export default function feathersExpress<S = any, C = any> (feathersApp?: FeathersApplication<S, C>, expressApp: Express = express()): Application<S, C> {
25
18
  if (!feathersApp) {
@@ -30,10 +23,12 @@ export default function feathersExpress<S = any, C = any> (feathersApp?: Feather
30
23
  throw new Error('@feathersjs/express requires a valid Feathers application instance');
31
24
  }
32
25
 
33
- const { use, listen } = expressApp as any;
34
- // A mixin that provides the extended functionality
35
- const mixin: any = {
36
- use (location: string, ...rest: any[]) {
26
+ const app = expressApp as any as Application<S, C>;
27
+ const { use: expressUse, listen: expressListen } = expressApp as any;
28
+ const { use: feathersUse, teardown: feathersTeardown } = feathersApp;
29
+
30
+ Object.assign(app, {
31
+ use (location: string & keyof S, ...rest: any[]) {
37
32
  let service: any;
38
33
  let options = {};
39
34
 
@@ -60,46 +55,63 @@ export default function feathersExpress<S = any, C = any> (feathersApp?: Feather
60
55
  // Check for service (any object with at least one service method)
61
56
  if (hasMethod(['handle', 'set']) || !hasMethod(defaultServiceMethods)) {
62
57
  debug('Passing app.use call to Express app');
63
- return use.call(this, location, ...rest);
58
+ return expressUse.call(this, location, ...rest);
64
59
  }
65
60
 
66
61
  debug('Registering service with middleware', middleware);
67
62
  // Since this is a service, call Feathers `.use`
68
- (feathersApp as FeathersApplication).use.call(this, location, service, {
63
+ feathersUse.call(this, location, service, {
69
64
  ...options,
70
- middleware
65
+ express: middleware
71
66
  });
72
67
 
73
68
  return this;
74
69
  },
75
70
 
76
71
  async listen (...args: any[]) {
77
- const server = listen.call(this, ...args);
72
+ const server = expressListen.call(this, ...args);
78
73
 
74
+ this.server = server;
79
75
  await this.setup(server);
80
76
  debug('Feathers application listening');
81
77
 
82
78
  return server;
79
+ },
80
+
81
+ async teardown (server?: any) {
82
+ return feathersTeardown.call(this, server).then(() =>
83
+ new Promise((resolve, reject) => this.server.close(e => e ? reject(e) : resolve(this)))
84
+ );
83
85
  }
84
- };
86
+ } as Application<S, C>);
85
87
 
86
- const feathersDescriptors = {
88
+ const appDescriptors = {
89
+ ...Object.getOwnPropertyDescriptors(Object.getPrototypeOf(app)),
90
+ ...Object.getOwnPropertyDescriptors(app)
91
+ };
92
+ const newDescriptors = {
87
93
  ...Object.getOwnPropertyDescriptors(Object.getPrototypeOf(feathersApp)),
88
94
  ...Object.getOwnPropertyDescriptors(feathersApp)
89
95
  };
90
96
 
91
97
  // Copy all non-existing properties (including non-enumerables)
92
98
  // that don't already exist on the Express app
93
- Object.keys(feathersDescriptors).forEach(prop => {
94
- const feathersProp = feathersDescriptors[prop];
95
- const expressProp = Object.getOwnPropertyDescriptor(expressApp, prop);
99
+ Object.keys(newDescriptors).forEach(prop => {
100
+ const appProp = appDescriptors[prop];
101
+ const newProp = newDescriptors[prop];
96
102
 
97
- if (expressProp === undefined && feathersProp !== undefined) {
98
- Object.defineProperty(expressApp, prop, feathersProp);
103
+ if (appProp === undefined && newProp !== undefined) {
104
+ Object.defineProperty(expressApp, prop, newProp);
99
105
  }
100
106
  });
101
107
 
102
- return Object.assign(expressApp, mixin);
108
+ app.configure(routing() as any);
109
+ app.use((req, _res, next) => {
110
+ req.feathers = { ...req.feathers, provider: 'rest' };
111
+ return next();
112
+ });
113
+
114
+ return app;
103
115
  }
104
116
 
105
117
  if (typeof module !== 'undefined') {
package/src/rest.ts CHANGED
@@ -1,160 +1,110 @@
1
- import Debug from 'debug';
1
+ import { Request, Response, RequestHandler, Router } from 'express';
2
2
  import { MethodNotAllowed } from '@feathersjs/errors';
3
- import { HookContext } from '@feathersjs/hooks';
4
- import { createContext, defaultServiceMethods, getServiceOptions, NullableId, Params } from '@feathersjs/feathers';
5
- import { Request, Response, NextFunction, RequestHandler, Router } from 'express';
3
+ import { createDebug } from '@feathersjs/commons';
4
+ import { http } from '@feathersjs/transport-commons';
5
+ import { createContext, defaultServiceMethods, getServiceOptions } from '@feathersjs/feathers';
6
6
 
7
- import { parseAuthentication } from './authentication';
7
+ import { AuthenticationSettings, parseAuthentication } from './authentication';
8
+ import { Application } from './declarations';
8
9
 
9
- const debug = Debug('@feathersjs/express/rest');
10
+ const debug = createDebug('@feathersjs/express/rest');
10
11
 
11
- export const METHOD_HEADER = 'x-service-method';
12
-
13
- export interface ServiceParams {
14
- id: NullableId,
15
- data: any,
16
- params: Params
17
- }
18
-
19
- export type ServiceCallback = (req: Request, res: Response, options: ServiceParams) => Promise<HookContext|any>;
20
-
21
- export const statusCodes = {
22
- created: 201,
23
- noContent: 204,
24
- methodNotAllowed: 405,
25
- success: 200
12
+ const toHandler = (func: (req: Request, res: Response, next: () => void) => Promise<void>): RequestHandler => {
13
+ return (req, res, next) => func(req, res, next).catch(error => next(error));
26
14
  };
27
15
 
28
- export const feathersParams = (req: Request, _res: Response, next: NextFunction) => {
29
- req.feathers = {
30
- ...req.feathers,
31
- provider: 'rest',
32
- headers: req.headers
33
- };
34
- next();
35
- }
36
-
37
- export const formatter = (_req: Request, res: Response, next: NextFunction) => {
38
- if (res.data === undefined) {
39
- return next();
40
- }
41
-
42
- res.format({
43
- 'application/json' () {
44
- res.json(res.data);
45
- }
46
- });
47
- }
16
+ const serviceMiddleware = (): RequestHandler => {
17
+ return toHandler(async (req, res, next) => {
18
+ const { query, headers, path, body: data, method: httpMethod } = req;
19
+ const methodOverride = req.headers[http.METHOD_HEADER] as string | undefined;
48
20
 
49
- const getData = (context: HookContext) => {
50
- if (!(context instanceof HookContext)) {
51
- return context;
52
- }
21
+ const { service, params: { __id: id = null, ...route } = {} } = req.lookup!;
22
+ const method = http.getServiceMethod(httpMethod, id, methodOverride);
23
+ const { methods } = getServiceOptions(service);
53
24
 
54
- return context.dispatch !== undefined
55
- ? context.dispatch
56
- : context.result;
57
- }
25
+ debug(`Found service for path ${path}, attempting to run '${method}' service method`);
58
26
 
59
- const getStatusCode = (context: HookContext, res: Response) => {
60
- if (context instanceof HookContext) {
61
- if (context.statusCode) {
62
- return context.statusCode;
27
+ if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) {
28
+ const error = new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`);
29
+ res.statusCode = error.code;
30
+ throw error;
63
31
  }
64
32
 
65
- if (context.method === 'create') {
66
- return statusCodes.created;
67
- }
68
- }
69
-
70
- if (!res.data) {
71
- return statusCodes.noContent;
72
- }
33
+ const createArguments = http.argumentsFor[method as 'get'] || http.argumentsFor.default;
34
+ const params = { query, headers, route, ...req.feathers };
35
+ const args = createArguments({ id, data, params });
36
+ const contextBase = createContext(service, method, { http: {} });
37
+ res.hook = contextBase;
73
38
 
74
- return statusCodes.success;
75
- }
39
+ const context = await (service as any)[method](...args, contextBase);
40
+ res.hook = context;
76
41
 
77
- export const serviceMiddleware = (callback: ServiceCallback) =>
78
- async (req: Request, res: Response, next: NextFunction) => {
79
- debug(`Running service middleware for '${req.url}'`);
42
+ const response = http.getResponse(context);
43
+ res.statusCode = response.status;
44
+ res.set(response.headers);
45
+ res.data = response.body;
80
46
 
81
- try {
82
- const { query, body: data } = req;
83
- const { __feathersId: id = null, ...route } = req.params;
84
- const params = { query, route, ...req.feathers };
85
- const context = await callback(req, res, { id, data, params });
47
+ return next();
48
+ });
49
+ };
86
50
 
87
- res.data = getData(context);
88
- res.status(getStatusCode(context, res));
51
+ const servicesMiddleware = (): RequestHandler => {
52
+ return toHandler(async (req, res, next) => {
53
+ const app = req.app as any as Application;
54
+ const lookup = app.lookup(req.path);
89
55
 
90
- next();
91
- } catch (error) {
92
- next(error);
56
+ if (!lookup) {
57
+ return next();
93
58
  }
94
- }
95
59
 
96
- export const serviceMethodHandler = (
97
- service: any, methodName: string, getArgs: (opts: ServiceParams) => any[], headerOverride?: string
98
- ) => serviceMiddleware(async (req, res, options) => {
99
- const methodOverride = typeof headerOverride === 'string' && (req.headers[headerOverride] as string);
100
- const method = methodOverride ? methodOverride : methodName
101
- const { methods } = getServiceOptions(service);
60
+ req.lookup = lookup;
102
61
 
103
- if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) {
104
- res.status(statusCodes.methodNotAllowed);
62
+ const options = getServiceOptions(lookup.service);
63
+ const middleware = options.express!.composed!;
105
64
 
106
- throw new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`);
65
+ return middleware(req, res, next);
66
+ });
67
+ };
68
+
69
+ export const formatter: RequestHandler = (_req, res, next) => {
70
+ if (res.data === undefined) {
71
+ return next();
107
72
  }
108
73
 
109
- const args = getArgs(options);
110
- const context = createContext(service, method);
74
+ res.format({
75
+ 'application/json' () {
76
+ res.json(res.data);
77
+ }
78
+ });
79
+ };
111
80
 
112
- res.hook = context as any;
81
+ export type RestOptions = {
82
+ formatter?: RequestHandler;
83
+ authentication?: AuthenticationSettings;
84
+ };
85
+
86
+ export const rest = (options?: RestOptions | RequestHandler) => {
87
+ options = typeof options === 'function' ? { formatter: options } : options || {};
113
88
 
114
- return service[method](...args, context);
115
- });
89
+ const formatterMiddleware = options.formatter || formatter;
90
+ const authenticationOptions = options.authentication;
116
91
 
117
- export function rest (handler: RequestHandler = formatter) {
118
- return function (this: any, app: any) {
92
+ return (app: Application) => {
119
93
  if (typeof app.route !== 'function') {
120
94
  throw new Error('@feathersjs/express/rest needs an Express compatible app.');
121
95
  }
122
96
 
123
- app.use(feathersParams);
124
- app.use(parseAuthentication());
125
-
126
- // Register the REST provider
127
- app.mixins.push(function (service: any, path: string, options: any) {
128
- const { middleware: { before = [] } } = options;
129
- let { middleware: { after = [] } } = options;
130
-
131
- if (typeof handler === 'function') {
132
- after = after.concat(handler);
133
- }
134
-
135
- const baseUri = `/${path}`;
136
- const find = serviceMethodHandler(service, 'find', ({ params }) => [ params ]);
137
- const get = serviceMethodHandler(service, 'get', ({ id, params }) => [ id, params ]);
138
- const create = serviceMethodHandler(service, 'create', ({ data, params }) => [ data, params ], METHOD_HEADER);
139
- const update = serviceMethodHandler(service, 'update', ({ id, data, params }) => [ id, data, params ]);
140
- const patch = serviceMethodHandler(service, 'patch', ({ id, data, params }) => [ id, data, params ]);
141
- const remove = serviceMethodHandler(service, 'remove', ({ id, params }) => [ id, params ]);
142
-
143
- debug(`Adding REST provider for service \`${path}\` at base route \`${baseUri}\``);
144
-
145
- const idRoute = '/:__feathersId';
146
- const serviceRouter = Router({ mergeParams: true })
147
- .get('/', find)
148
- .post('/', create)
149
- .get(idRoute, get)
150
- .put('/', update)
151
- .put(idRoute, update)
152
- .patch('/', patch)
153
- .patch(idRoute, patch)
154
- .delete('/', remove)
155
- .delete(idRoute, remove);
156
-
157
- app.use(baseUri, ...before, serviceRouter, ...after);
97
+ app.use(parseAuthentication(authenticationOptions));
98
+ app.use(servicesMiddleware());
99
+
100
+ app.mixins.push((_service, _path, options) => {
101
+ const { express: { before = [], after = [] } = {} } = options;
102
+
103
+ const middlewares = [].concat(before, serviceMiddleware(), after, formatterMiddleware);
104
+ const middleware = Router().use(middlewares);
105
+
106
+ options.express ||= {};
107
+ options.express.composed = middleware;
158
108
  });
159
109
  };
160
110
  }