@trenskow/app 0.9.5 → 0.9.6
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/application.js +1 -1
- package/lib/endpoint.js +43 -26
- package/lib/index.js +3 -3
- package/lib/router.js +11 -27
- package/lib/{util.js → util/index.js} +2 -8
- package/lib/util/methods.js +49 -0
- package/package.json +1 -1
- package/test/index.js +22 -5
package/lib/application.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
20
|
+
Methods.all.forEach((method) => {
|
|
21
21
|
|
|
22
|
-
this
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
Object.defineProperty(this, method, {
|
|
23
|
+
get: () => new Methods({
|
|
24
|
+
existing: [method],
|
|
25
|
+
additional: ['catchAll'],
|
|
26
|
+
todo: (methods, ...handlers) => {
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
37
|
-
|
|
38
|
-
method = method.toLowerCase();
|
|
47
|
+
_on(methods, handlers, match) {
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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(
|
|
74
|
+
throw new Error('Endpoint already has a handler.');
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
this._layers.push({
|
|
67
78
|
handler: this._handleMethod,
|
|
68
|
-
|
|
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: (
|
|
118
|
+
return new Proxy(this, {
|
|
119
|
+
get: (target, path) => {
|
|
105
120
|
return (endpoint) => {
|
|
106
|
-
return
|
|
121
|
+
return target.mount(path, endpoint);
|
|
107
122
|
};
|
|
108
123
|
}
|
|
109
124
|
});
|
|
@@ -183,12 +198,14 @@ export default class Endpoint extends Router {
|
|
|
183
198
|
|
|
184
199
|
const methods = this._layers
|
|
185
200
|
.filter((layer) => layer.handler === this._handleMethod)
|
|
186
|
-
.
|
|
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();
|
|
207
|
+
} else {
|
|
208
|
+
throw new ApiError.NotFound();
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
} else {
|
|
@@ -221,12 +238,12 @@ export default class Endpoint extends Router {
|
|
|
221
238
|
|
|
222
239
|
let underlyingMethod = context.request.method.toLowerCase();
|
|
223
240
|
|
|
224
|
-
if (underlyingMethod === 'head' && !this._layers.some((layer) => layer.
|
|
241
|
+
if (underlyingMethod === 'head' && !this._layers.some((layer) => layer.methods.includes('head'))) {
|
|
225
242
|
underlyingMethod = 'get';
|
|
226
243
|
}
|
|
227
244
|
|
|
228
245
|
if (layer.match === 'direct' && !path.isLast) return await next();
|
|
229
|
-
if (layer.
|
|
246
|
+
if (!layer.methods.includes(underlyingMethod)) return await next();
|
|
230
247
|
|
|
231
248
|
for (let handler of layer.handlers) {
|
|
232
249
|
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
|
-
|
|
13
|
-
|
|
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,
|
|
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
|
-
|
|
38
|
+
methods
|
|
59
39
|
});
|
|
60
40
|
|
|
61
41
|
return this;
|
|
62
42
|
|
|
63
43
|
}
|
|
64
44
|
|
|
65
|
-
use(
|
|
66
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
|
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 () => {
|
|
@@ -197,6 +195,25 @@ describe('Application', () => {
|
|
|
197
195
|
|
|
198
196
|
});
|
|
199
197
|
|
|
198
|
+
it ('should respond with 404 when endpoint has no methods.', async () => {
|
|
199
|
+
|
|
200
|
+
app.root(
|
|
201
|
+
new Endpoint()
|
|
202
|
+
.mounts.hello(
|
|
203
|
+
new Endpoint()
|
|
204
|
+
.mounts.world(
|
|
205
|
+
new Endpoint()
|
|
206
|
+
.get(() => 'Hello, World!')
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
await request
|
|
212
|
+
.get('/hello')
|
|
213
|
+
.expect(404);
|
|
214
|
+
|
|
215
|
+
});
|
|
216
|
+
|
|
200
217
|
it ('should respond with parameter value.', async () => {
|
|
201
218
|
|
|
202
219
|
app.root(
|
|
@@ -283,7 +300,7 @@ describe('Application', () => {
|
|
|
283
300
|
|
|
284
301
|
});
|
|
285
302
|
|
|
286
|
-
it ('should respond with a value when using PUT on a
|
|
303
|
+
it ('should respond with a value when using PUT on a all handler.', async () => {
|
|
287
304
|
|
|
288
305
|
app.root(
|
|
289
306
|
new Endpoint()
|