@trenskow/app 0.9.3 → 0.9.4

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.
@@ -17,7 +17,7 @@ import Request from './request.js';
17
17
  import Response from './response.js';
18
18
  import Endpoint from './endpoint.js';
19
19
 
20
- import { isObject } from './util.js';
20
+ import { isObject } from './util/index.js';
21
21
 
22
22
  export default class Application extends EventEmitter {
23
23
 
package/lib/endpoint.js CHANGED
@@ -10,36 +10,47 @@ import ApiError from '@trenskow/api-error';
10
10
 
11
11
  import Router from './router.js';
12
12
 
13
- import { isObject, matchPath, resolveInlineImport, methods } from './util.js';
13
+ import { isObject, matchPath, resolveInlineImport, Methods } from './util/index.js';
14
14
 
15
15
  export default class Endpoint extends Router {
16
16
 
17
17
  constructor() {
18
18
  super();
19
19
 
20
- methods.concat(['all']).forEach((method) => {
20
+ Methods.all.forEach((method) => {
21
21
 
22
- this[method] = (...handlers) => {
23
- handlers = [].concat(...handlers);
24
- return this._on(method, handlers);
25
- };
22
+ Object.defineProperty(this, method, {
23
+ get: () => new Methods({
24
+ existing: [method],
25
+ additional: ['catchAll'],
26
+ todo: (methods, ...handlers) => {
26
27
 
27
- this[method].catchAll = (...handlers) => {
28
- handlers = [].concat(...handlers);
29
- return this._on(method, handlers, 'indirect');
30
- };
28
+ handlers = [].concat(...handlers);
29
+
30
+ let match = 'direct';
31
+
32
+ if (methods.includes('catchAll')) {
33
+ match = 'indirect';
34
+ methods = methods.filter((method) => method !== 'catchAll');
35
+ }
36
+
37
+ return this._on(methods, handlers, match);
38
+
39
+ }
40
+ })
41
+ });
31
42
 
32
43
  });
33
44
 
34
45
  }
35
46
 
36
- _on(method, handlers, match) {
37
-
38
- method = method.toLowerCase();
47
+ _on(methods, handlers, match) {
39
48
 
40
- if (!methods.concat(['all']).includes(method)) {
41
- throw new Error(`Method ${method} is unknown.`);
42
- }
49
+ methods.forEach((method) => {
50
+ if (!Methods.all.includes(method)) {
51
+ throw new Error(`Method ${method} is unknown.`);
52
+ }
53
+ });
43
54
 
44
55
  match = match || 'direct';
45
56
 
@@ -56,16 +67,16 @@ export default class Endpoint extends Router {
56
67
  }
57
68
 
58
69
  const existing = this._layers.findIndex((layer) => {
59
- return layer.method === method && layer.handler === this._handleMethod;
70
+ return (layer.handler === this._handleMethod && methods.some((method) => layer.methods.includes(method)));
60
71
  });
61
72
 
62
73
  if (existing !== -1) {
63
- throw new Error(`Endpoint already has a \`${method}\` handler.`);
74
+ throw new Error('Endpoint already has a handler.');
64
75
  }
65
76
 
66
77
  this._layers.push({
67
78
  handler: this._handleMethod,
68
- method,
79
+ methods,
69
80
  handlers,
70
81
  match
71
82
  });
@@ -74,6 +85,10 @@ export default class Endpoint extends Router {
74
85
 
75
86
  }
76
87
 
88
+ all(...handlers) {
89
+ return this._on(Methods.all, handlers);
90
+ }
91
+
77
92
  mount(path, endpoint) {
78
93
 
79
94
  if (isObject(path)) {
@@ -100,10 +115,10 @@ export default class Endpoint extends Router {
100
115
  }
101
116
 
102
117
  get mounts() {
103
- return new Proxy({}, {
104
- get: (_, path) => {
118
+ return new Proxy(this, {
119
+ get: (target, path) => {
105
120
  return (endpoint) => {
106
- return this.mount(path, endpoint);
121
+ return target.mount(path, endpoint);
107
122
  };
108
123
  }
109
124
  });
@@ -183,11 +198,11 @@ export default class Endpoint extends Router {
183
198
 
184
199
  const methods = this._layers
185
200
  .filter((layer) => layer.handler === this._handleMethod)
186
- .map((layer) => layer.method.toUpperCase())
201
+ .reduce((methods, layer) => methods.concat(layer.methods), [])
187
202
  .filter((value, index, array) => array.indexOf(value) === index);
188
203
 
189
204
  if (methods.length) {
190
- context.response.headers.allow = methods.join(', ');
205
+ context.response.headers.allow = methods.map((method) => method.toUpperCase()).join(', ');
191
206
  throw new ApiError.MethodNotAllowed();
192
207
  }
193
208
 
@@ -221,12 +236,12 @@ export default class Endpoint extends Router {
221
236
 
222
237
  let underlyingMethod = context.request.method.toLowerCase();
223
238
 
224
- if (underlyingMethod === 'head' && !this._layers.some((layer) => layer.method === 'head')) {
239
+ if (underlyingMethod === 'head' && !this._layers.some((layer) => layer.methods.includes('head'))) {
225
240
  underlyingMethod = 'get';
226
241
  }
227
242
 
228
243
  if (layer.match === 'direct' && !path.isLast) return await next();
229
- if (layer.method !== 'all' && layer.method !== underlyingMethod) return await next();
244
+ if (!layer.methods.includes(underlyingMethod)) return await next();
230
245
 
231
246
  for (let handler of layer.handlers) {
232
247
  const result = await handler(context);
package/lib/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  //
2
2
  // index.js
3
3
  // @trenskow/app
4
- //
4
+ //
5
5
  // Created by Kristian Trenskow on 2021/11/07
6
6
  // For license see LICENSE.
7
- //
7
+ //
8
8
 
9
9
  import Router from './router.js';
10
10
  import Endpoint from './endpoint.js';
@@ -13,7 +13,7 @@ import Request from './request.js';
13
13
  import Response from './response.js';
14
14
  import ApiError from '@trenskow/api-error';
15
15
 
16
- import { isObject, matchPath, resolveInlineImport } from './util.js';
16
+ import { isObject, matchPath, resolveInlineImport } from './util/index.js';
17
17
 
18
18
  export default Application;
19
19
 
package/lib/router.js CHANGED
@@ -9,10 +9,8 @@
9
9
  import {
10
10
  isObject,
11
11
  resolveInlineImport,
12
- methods,
13
- methodsRead,
14
- methodsWrite
15
- } from './util.js';
12
+ Methods
13
+ } from './util/index.js';
16
14
 
17
15
  export default class Router {
18
16
 
@@ -20,27 +18,9 @@ export default class Router {
20
18
 
21
19
  this._layers = [];
22
20
 
23
- methods.forEach((method) => {
24
- return this.use[method] = (...handlers) => {
25
- return this.#_use(handlers, method);
26
- };
27
- });
28
-
29
- this.use.read = (...handlers) => {
30
- methodsRead.forEach((method) => {
31
- return this.#_use(handlers, method);
32
- });
33
- };
34
-
35
- this.use.write = (...handlers) => {
36
- methodsWrite.forEach((method) => {
37
- return this.#_use(handlers, method);
38
- });
39
- };
40
-
41
21
  }
42
22
 
43
- #_use(handlers, method) {
23
+ #_use(handlers, methods) {
44
24
 
45
25
  handlers = [].concat(...handlers);
46
26
 
@@ -55,15 +35,19 @@ export default class Router {
55
35
  this._layers.push({
56
36
  handler: this._handleUse,
57
37
  handlers,
58
- method
38
+ methods
59
39
  });
60
40
 
61
41
  return this;
62
42
 
63
43
  }
64
44
 
65
- use(...handlers) {
66
- return this.#_use(handlers, 'all');
45
+ get use() {
46
+ return new Methods({
47
+ todo: (methods, ...handlers) => {
48
+ return this.#_use(handlers, methods);
49
+ }
50
+ });
67
51
  }
68
52
 
69
53
  mixin(router) {
@@ -127,7 +111,7 @@ export default class Router {
127
111
 
128
112
  const { request } = context;
129
113
 
130
- if (layer.method === 'all' || request.method.toLowerCase() === layer.method) {
114
+ if (layer.methods.length === 0 || layer.methods.includes(request.method.toLowerCase())) {
131
115
 
132
116
  for (let handler of layer.handlers) {
133
117
  await handler(context);
@@ -6,13 +6,9 @@
6
6
  // For license see LICENSE.
7
7
  //
8
8
 
9
- import { METHODS } from 'http';
10
-
11
9
  import caseit from '@trenskow/caseit';
12
10
 
13
- const methods = METHODS.map((method) => method.toLowerCase());
14
- const methodsWrite = ['post', 'put', 'patch', 'delete'];
15
- const methodsRead = methods.filter((method) => !methodsWrite.includes(method));
11
+ import Methods from './methods.js';
16
12
 
17
13
  const isObject = (value) => value?.constructor === Object;
18
14
 
@@ -27,9 +23,7 @@ const resolveInlineImport = (value) => {
27
23
  };
28
24
 
29
25
  export {
30
- methods,
31
- methodsWrite,
32
- methodsRead,
26
+ Methods,
33
27
  isObject,
34
28
  matchPath,
35
29
  resolveInlineImport
@@ -0,0 +1,49 @@
1
+ //
2
+ // is-object.js
3
+ // @trenskow/app
4
+ //
5
+ // Created by Kristian Trenskow on 2025/01/02
6
+ // For license see LICENSE.
7
+ //
8
+
9
+ import { METHODS } from 'http';
10
+
11
+ const methods = METHODS.map((method) => method.toLowerCase());
12
+
13
+ export default class Methods extends Function {
14
+
15
+ static get all() {
16
+ return methods;
17
+ }
18
+
19
+ constructor({ existing = [], additional = [], todo }) {
20
+ super();
21
+
22
+ if (!Array.isArray(existing)) {
23
+ throw new Error('Existing must be an array.');
24
+ }
25
+
26
+ let methods = existing;
27
+
28
+ return new Proxy(this, {
29
+ get: (_ /* target */, property, receiver) => {
30
+
31
+ if (property === 'all') {
32
+ methods = Methods.all;
33
+ } else if (Methods.all.concat(additional).includes(property)) {
34
+ if (!methods.includes(property)) methods.push(property);
35
+ } else {
36
+ throw new Error(`Method ${property} is unknown.`);
37
+ }
38
+
39
+ return receiver;
40
+
41
+ },
42
+ apply: (_ /* target */, __ /* this */, args) => {
43
+ return todo(methods, ...args);
44
+ }
45
+ });
46
+
47
+ }
48
+
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/app",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "A small HTTP router.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/test/index.js CHANGED
@@ -50,9 +50,7 @@ describe('Application', () => {
50
50
  it ('should respond with 405 when a route is configured, but request method is not specified,', async () => {
51
51
  app.root(
52
52
  new Endpoint()
53
- .post(() => {})
54
- .put(() => {})
55
- .delete(() => {})
53
+ .post.put.delete(() => {})
56
54
  );
57
55
  await request
58
56
  .get('/')
@@ -109,7 +107,7 @@ describe('Application', () => {
109
107
  new Endpoint()
110
108
  .get(() => 'Hello, World!')
111
109
  .get(() => 'Hello, World! (2)');
112
- }).to.throw('Endpoint already has a `get` handler.');
110
+ }).to.throw('Endpoint already has a handler.');
113
111
  });
114
112
 
115
113
  it ('should ignore GET method handler when path has been rewritten and respond with 200 and `Hello, World!`.', async () => {
@@ -283,7 +281,7 @@ describe('Application', () => {
283
281
 
284
282
  });
285
283
 
286
- it ('should respond with a value when using PUT on a catch-all method.', async () => {
284
+ it ('should respond with a value when using PUT on a all handler.', async () => {
287
285
 
288
286
  app.root(
289
287
  new Endpoint()