@trenskow/app 0.9.2 → 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.
- package/lib/application.js +4 -1
- package/lib/endpoint.js +41 -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 +3 -5
package/lib/application.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import http from 'http';
|
|
10
10
|
import EventEmitter from 'events';
|
|
11
|
+
import { Stream } from 'stream';
|
|
11
12
|
|
|
12
13
|
import ApiError from '@trenskow/api-error';
|
|
13
14
|
import caseit from '@trenskow/caseit';
|
|
@@ -16,7 +17,7 @@ import Request from './request.js';
|
|
|
16
17
|
import Response from './response.js';
|
|
17
18
|
import Endpoint from './endpoint.js';
|
|
18
19
|
|
|
19
|
-
import { isObject } from './util.js';
|
|
20
|
+
import { isObject } from './util/index.js';
|
|
20
21
|
|
|
21
22
|
export default class Application extends EventEmitter {
|
|
22
23
|
|
|
@@ -67,6 +68,8 @@ export default class Application extends EventEmitter {
|
|
|
67
68
|
this.#_renderer = async ({ result, request, response }) => {
|
|
68
69
|
if (request.method.toLowerCase() === 'head') {
|
|
69
70
|
response.end();
|
|
71
|
+
} else if (result instanceof Stream) {
|
|
72
|
+
result.pipe(response);
|
|
70
73
|
} else if (result instanceof ApiError) {
|
|
71
74
|
response.end();
|
|
72
75
|
} else if (typeof result === 'string') {
|
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,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
|
-
.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 () => {
|
|
@@ -283,7 +281,7 @@ describe('Application', () => {
|
|
|
283
281
|
|
|
284
282
|
});
|
|
285
283
|
|
|
286
|
-
it ('should respond with a value when using PUT on a
|
|
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()
|