@trenskow/app 0.6.12 → 0.7.0

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/README.md CHANGED
@@ -216,7 +216,7 @@ Endpoints has the [`mount`](#mount) method, which "mounts" a router to the speci
216
216
 
217
217
  There is a variant of the `mount` method called [`parameter`](#parameter) which is used to mount an endpoint with a dynamic path – whereas the path is treated like an input parameter (like express' `.param` method). Parameters also supports a transform function, which is able to transform the parameter into something else (eg. a user identifier into a user object).
218
218
 
219
- Lastly there is the [`.middleware`](#middleware) method, which is used to attach middleware. Middleware is defined as a router, which is of the type [`Router`](#router-2) and therefore cannot act as an endpoint. You can regard them like transforms or service providers for the request.
219
+ Lastly there is the [`.middleware`](#middleware) method, which is used to attach middleware. Middleware is defined as a router, which have the type [`Router`](#router-2) and therefore cannot act as an endpoint. You can regard them like transforms or service providers for the request.
220
220
 
221
221
  > `Endpoint` extends `Router`.
222
222
 
@@ -228,9 +228,9 @@ All handlers and routers support async functions (and non-async). No need to cal
228
228
 
229
229
  Where express gives you the `(req, res, next)` parameters for each handler, this application instead just provides a single parameter, the "`context` object", which contains all the information needed to process the request.
230
230
 
231
- Middleware can also use the context to provide data and services, which is then available for subsequent endpoints, routers and handlers.
231
+ Middleware can assign values to the context to provide data and services, which is then available for subsequent endpoints, routers and handlers.
232
232
 
233
- When a request is incoming, the `context`object looks like this.
233
+ When a request is incoming, the `context` object looks like this.
234
234
 
235
235
  | Name | Description | Type |
236
236
  | ---------------- | ------------------------------------------------------------ | :-------------------------: |
@@ -241,7 +241,7 @@ When a request is incoming, the `context`object looks like this.
241
241
  | `path` | An object that has properties representing different paths. | Object |
242
242
  | `path.full` | An array of strings that joined represent the path of the fully requested path. | Array of String |
243
243
  | `path.current` | An array of strings that joined represents the path currently being processed. | Array of String |
244
- | `path.remaining` | An array of strings that joined represents the path that is above the currently processed path. Setting this will rewrite the remaining path. | Array of String |
244
+ | `path.remaining` | An array of strings that joined represents the path that is above the currently processed path. Setting this will rewrite the remaining path (useful when serving single page applications to a browser). | Array of String |
245
245
  | `query` | An object holding the URL query parameters as an object ([keys has been converted to camel case](#query-parameters)). | Object |
246
246
  | `state` | A string indicating the current state of the request – possible values are `'routing'`, `'rendering'`, `'completed'` or `'aborted'`. | String |
247
247
  | `abort` | A function that aborts the request. It takes the parameters `(error, brutally)`, where `error` is the error that needs to be handled by the [renderer](#renderer) – and `brutally` which indicates if the connection should also be closed. | AsyncFunction |
@@ -267,15 +267,15 @@ JavaScript is a camel cased language. HTTP is a mixture of different case types.
267
267
 
268
268
  Case is automatically converted in both directions, so if you do `context.response.headers.contentType = 'application/json'` it will automatically be converted to `Content-Type: application/json` when the response is sent.
269
269
 
270
- The same goes for request headers like `Accept-Language: en` that is accessible through `context.request.headers.acceptLanguage`.
270
+ The same goes for request headers like `Accept-Language: en` which is accessible through `context.request.headers.acceptLanguage`.
271
271
 
272
272
  ##### Query parameters
273
273
 
274
- Request with `?my-parameter=value` is accessible through `context.query.myParameter` .
274
+ Request with quuries like `?my-parameter=value` is accessible through `context.query.myParameter` .
275
275
 
276
276
  ##### Mount paths
277
277
 
278
- When [match mode](#constructor) is set to `'loosely'` (default) a request withe the path component `my-route` or `my_route` will match an endpoint mounted at `myRoute`.
278
+ When [match mode](#constructor) is set to `'loosely'` (default) a request with the path component `my-route` or `my_route` will match an endpoint mounted at `myRoute`.
279
279
 
280
280
  #### Endpoints, routers and handlers
281
281
 
@@ -287,22 +287,13 @@ Endpoints takes care of a path component. As example the `/this/is/my/path/` pat
287
287
 
288
288
  Endpoints can have a couple of things mounted / attached to it – those are.
289
289
 
290
- * Other endpoints
291
- * using the [`.mount`](#mount) method of [`Endpoint`](#endpoint-2).
292
-
293
- * Parameters
290
+ * Other endpoints (using the [`.mount`](#mount) method of [`Endpoint`](#endpoint-2))
291
+ * Parameters (using the [`.parameter`](#parameter) method of [`Endpoint`](#endpoint-2))
294
292
  * which is also a mounted endpoint – but where the path is dynamic and assigned to the `context.parameters` object.
295
- * using the [`.parameter`](#parameter) method of [`Endpoint`](#endpoint-2)
296
-
297
- * Handlers
298
- * using the [`.use`](#use) method of [`Router`](#router-2) or [`Endpoint`](#endpoint-2)
299
-
300
- * Middleware
293
+ * Handlers (using the [`.use`](#use) method of [`Router`](#router-2) or [`Endpoint`](#endpoint-2) )
294
+ * Middleware (using the [`.middleware`](#middleware) method of [`Endpoint`](#endpoint-2) )
301
295
  * sets a [`Router`](#routers-2) router to that the request will be passed through.
302
- * using the [`.middleware`](#middleware) method of [`Endpoint`](#endpoint-2)
303
-
304
- * Methods
305
- * using the [`.get`, `.post`, `.put`, `.delete`, etc.](#get-post-put-delete-etc) method of [`Endpoint`](#endpoint-2)
296
+ * Methods (using the [`.get`, `.post`, `.put`, `.delete`, etc.](#get-post-put-delete-etc) method of [`Endpoint`](#endpoint-2))
306
297
  * When a method returns the request ends and the returned value is send to the client as a response (through the [`Application#renderer`](#renderer))
307
298
 
308
299
 
@@ -311,7 +302,7 @@ Endpoints can have a couple of things mounted / attached to it – those are.
311
302
  Whenever a function (such as [`.mount`](#mount) or [`.parameter`](#parameters) or [`.root`](#root)) takes an endpoint as a parameter, it can be provided in any of the following ways.
312
303
 
313
304
  * An instance of [`Endpoint`](#endpoint-2).
314
- * An object that has a `default` key that has an instance of `Endpoint` as the value (useful when using inline imports).
305
+ * An object that has a `default` key that has an instance of `Endpoint` as the value (useful when using inline imports as `await import('my-endpoint.js')`).
315
306
 
316
307
  ##### Routers
317
308
 
@@ -322,7 +313,7 @@ A router is the same as above, except it only supports [`.use`](#use).
322
313
  As above, whenever a function takes a router as a parameter, it can be provided in any of the following ways.
323
314
 
324
315
  * An instance of [`Router`](#router-2).
325
- * An object that has a `default` key that has an instance of `Router` as the value (useful when using inline imports).
316
+ * An object that has a `default` key that has an instance of `Router` as the value (useful when using inline imports as `await import('my-route.js')`).).
326
317
 
327
318
  ##### Handlers
328
319
 
@@ -340,7 +331,7 @@ Whenever a function takes a handler as a parameter, it can be provided in any of
340
331
 
341
332
  ### `Application`
342
333
 
343
- The `Application` class holds an application and is responsible for handling and bootstrapping request from the server. It does not provide any routing on it's own, instead it has a [`root`](#root) method, which is used to set the root router.
334
+ The `Application` class holds an application and is responsible for handling and bootstrapping request from the server. It does not provide any routing on its own, instead it has a [`root`](#root) method, which is used to set the root router.
344
335
 
345
336
  > If no root route has been set, all requests will be responded with `404 Not Found`.
346
337
 
@@ -352,17 +343,17 @@ The `Application` class takes an "options" object as it's parameter.
352
343
 
353
344
  ##### Parameters
354
345
 
355
- | Name | Description | Type | Required | Default value |
356
- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------: | :------: | :--------------------------: |
357
- | options | An object representing the options. | Object | | {} |
358
- | `options.port` | The port at which to listen for incoming connections. | Number | | `0` (automatically assigned) |
346
+ | Name | Description | Type | Required | Default value |
347
+ | ------------------------ | ------------------------------------------------------------ | :-----------------------: | :------: | :--------------------------: |
348
+ | options | An object representing the options. | Object | | {} |
349
+ | `options.port` | The port at which to listen for incoming connections. | Number | | `0` (automatically assigned) |
359
350
  | `options.RequestType` | An object that inherits from the [`Request`](#request-2) class (an `http.IncomingMessage` subclass) that is used as the request object in routes. | class | | [`Request`](#Request) |
360
- | `options.ResponseType` | An object that inherits from the [`Response`](#response-2) class (`http.ServerResponse` subclass) that is used as the response object in routes. | class | | [`Response`](#Response) |
361
- | `path` | An object that represents path related options. | Object | | `{}` |
362
- | `path.matchMode` | Indicates [how to match requests to mounted paths](#mount-paths). | `'loosely'` or `'strict'` | | `'loosely'` |
363
- | `options.server` | An object that represents how to instantiate the HTTP server. | Object | | `{}` |
364
- | `options.server.create` | A function that is able to create a server. | Function | | `http.createServer` |
365
- | `options.server.options` | An object to be passed as options when creating a server. | Object | | `{}` |
351
+ | `options.ResponseType` | An object that inherits from the [`Response`](#response-2) class (`http.ServerResponse` subclass) that is used as the response object in routes. | class | | [`Response`](#Response) |
352
+ | `path` | An object that represents path related options. | Object | | `{}` |
353
+ | `path.matchMode` | Indicates [how to match requests to mounted paths](#mount-paths) (eg. should the path be converted to camel case). | `'loosely'` or `'strict'` | | `'loosely'` |
354
+ | `options.server` | An object that represents how to instantiate the HTTP server. | Object | | `{}` |
355
+ | `options.server.create` | A function that is able to create a server. | Function | | `http.createServer` |
356
+ | `options.server.options` | An object to be passed as options when creating a server. | Object | | `{}` |
366
357
 
367
358
  #### Events
368
359
 
@@ -499,7 +490,7 @@ You can only call these methods once per method per endpoint – calling it mult
499
490
 
500
491
  These also ends routing. After a method route has been called, the routing will go strait to the renderer.
501
492
 
502
- > Notice: If no `head` method is implemented on endpoint, `get` will instead be called (if availble). When client requests a `head` the result will be ignored.
493
+ > Notice: If no `head` method is implemented on endpoint, `get` will instead be called (if ). When client requests a `head` the result will be ignored.
503
494
 
504
495
  > Returns the endpoint.
505
496
 
@@ -518,11 +509,13 @@ Below is an example on how to use the method.
518
509
  ````javascript
519
510
  default export ({ endpoint }) => {
520
511
  endpoint
521
- .get(async (context) => 'Hello, world!');
512
+ .get(
513
+ async (context) => 'Hello, world!',
514
+ () => console.info("Said hello."));
522
515
  };
523
516
  ````
524
517
 
525
- > In the above example `'Hello, world!'` is immediately send to the [renderer](#renderer) and the request ends.
518
+ > In the above example `'Hello, world!'` is immediately send to the [renderer](#renderer) and the request ends. The second handler is also executed, but as it returns `undefined` its return value is ignored.
526
519
 
527
520
  ###### Catch all
528
521
 
package/lib/endpoint.js CHANGED
@@ -65,7 +65,7 @@ export default class Endpoint extends Router {
65
65
  }
66
66
 
67
67
  this._layers.push({
68
- type: 'method',
68
+ handler: this._handleMethod,
69
69
  method,
70
70
  handlers,
71
71
  match
@@ -91,7 +91,7 @@ export default class Endpoint extends Router {
91
91
  }
92
92
 
93
93
  this._layers.push({
94
- type: 'mount',
94
+ handler: this._handleMount,
95
95
  path,
96
96
  endpoint
97
97
  });
@@ -130,7 +130,7 @@ export default class Endpoint extends Router {
130
130
  }
131
131
 
132
132
  this._layers.push({
133
- type: 'parameter',
133
+ handler: this._handleParameter,
134
134
  name,
135
135
  transform,
136
136
  endpoint
@@ -168,7 +168,7 @@ export default class Endpoint extends Router {
168
168
  }
169
169
 
170
170
  this._layers.push({
171
- type: 'middleware',
171
+ handler: this._handleMiddleware,
172
172
  router
173
173
  });
174
174
 
@@ -183,7 +183,7 @@ export default class Endpoint extends Router {
183
183
  if (path.isLast) {
184
184
 
185
185
  const methods = this._layers
186
- .filter((layer) => layer.type === 'method')
186
+ .filter((layer) => layer.handler === this._handleMethod)
187
187
  .map((layer) => layer.method.toUpperCase())
188
188
  .filter((value, index, array) => array.indexOf(value) === index);
189
189
 
@@ -202,80 +202,69 @@ export default class Endpoint extends Router {
202
202
 
203
203
  }
204
204
 
205
- async _handle(layer, path, context, next) {
205
+ async _handleMount(layer, path, context, next) {
206
206
 
207
- switch (layer.type) {
207
+ return await path.pushed(async (component) => {
208
208
 
209
- case 'mount': {
210
-
211
- return await path.pushed(async (component) => {
212
-
213
- if (!component) return path.popped(next);
214
-
215
- if (matchPath(component, layer.path, context)) {
216
- return await layer.endpoint._route(path, context, next);
217
- }
218
-
219
- return path.popped(next);
220
-
221
- });
209
+ if (!component) return path.popped(next);
222
210
 
211
+ if (matchPath(component, layer.path, context)) {
212
+ return await layer.endpoint._route(path, context, next);
223
213
  }
224
214
 
225
- case 'method': {
215
+ return path.popped(next);
226
216
 
227
- let underlyingMethod = context.request.method.toLowerCase();
217
+ });
228
218
 
229
- if (underlyingMethod === 'head' && !this._layers.some((layer) => layer.method === 'head')) {
230
- underlyingMethod = 'get';
231
- }
219
+ }
232
220
 
233
- if (layer.match === 'direct' && !path.isLast) return await next();
234
- if (layer.method !== 'all' && layer.method !== underlyingMethod) return await next();
221
+ async _handleMethod(layer, path, context, next) {
235
222
 
236
- for (let handler of layer.handlers) {
237
- const result = await handler(context);
238
- if (layer.underlyingMethod === 'head') return;
239
- if (context.state !== 'routing') return;
240
- if (typeof result !== 'undefined') context.result = result;
241
- }
223
+ let underlyingMethod = context.request.method.toLowerCase();
242
224
 
243
- return context.result;
225
+ if (underlyingMethod === 'head' && !this._layers.some((layer) => layer.method === 'head')) {
226
+ underlyingMethod = 'get';
227
+ }
244
228
 
245
- }
229
+ if (layer.match === 'direct' && !path.isLast) return await next();
230
+ if (layer.method !== 'all' && layer.method !== underlyingMethod) return await next();
246
231
 
247
- case 'parameter': {
232
+ for (let handler of layer.handlers) {
233
+ const result = await handler(context);
234
+ if (layer.underlyingMethod === 'head') return;
235
+ if (context.state !== 'routing') return;
236
+ if (typeof result !== 'undefined') context.result = result;
237
+ }
248
238
 
249
- return await path.pushed(async (component) => {
239
+ return context.result;
250
240
 
251
- if (!component) return await path.popped(next);
241
+ }
252
242
 
253
- context.parameters[layer.name] = component;
243
+ async _handleParameter(layer, path, context, next) {
254
244
 
255
- if (typeof layer.transform === 'function') {
256
- context.parameters[layer.name] = await layer.transform(
257
- Object.fromEntries([
258
- ['context', context],
259
- [layer.name, component]
260
- ])
261
- );
262
- }
245
+ return await path.pushed(async (component) => {
263
246
 
264
- return await layer.endpoint._route(path, context, next);
247
+ if (!component) return await path.popped(next);
265
248
 
266
- });
249
+ context.parameters[layer.name] = component;
267
250
 
251
+ if (typeof layer.transform === 'function') {
252
+ context.parameters[layer.name] = await layer.transform(
253
+ Object.fromEntries([
254
+ ['context', context],
255
+ [layer.name, component]
256
+ ])
257
+ );
268
258
  }
269
259
 
270
- case 'middleware': {
271
- return await layer.router._route(path, context, next);
272
- }
260
+ return await layer.endpoint._route(path, context, next);
273
261
 
274
- default:
275
- return await super._handle(layer, path, context, next);
262
+ });
276
263
 
277
- }
264
+ }
278
265
 
266
+ async _handleMiddleware(layer, path, context, next) {
267
+ return await layer.router._route(path, context, next);
279
268
  }
280
269
 
281
270
  }
package/lib/index.js CHANGED
@@ -13,6 +13,16 @@ 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';
17
+
16
18
  export default Application;
17
19
 
20
+ Application.plugin = (plugin) => {
21
+ plugin({ Router, Endpoint, Application, Request, Response, Error: ApiError, util: {
22
+ isObject,
23
+ matchPath,
24
+ resolveInlineImport
25
+ } });
26
+ };
27
+
18
28
  export { Router, Endpoint, Application, Request, Response, ApiError as Error };
package/lib/router.js CHANGED
@@ -27,7 +27,7 @@ export default class Router {
27
27
  }
28
28
 
29
29
  this._layers.push({
30
- type: 'use',
30
+ handler: this._handleUse,
31
31
  handlers
32
32
  });
33
33
 
@@ -48,7 +48,7 @@ export default class Router {
48
48
  }
49
49
 
50
50
  this._layers.push({
51
- type: 'mixin',
51
+ handler: this._handleMixin,
52
52
  router: router
53
53
  });
54
54
 
@@ -68,27 +68,23 @@ export default class Router {
68
68
 
69
69
  }
70
70
 
71
- async _handle(layer, path, context, next) {
72
-
73
- switch (layer.type) {
74
-
75
- case 'use': {
71
+ async _handleUse(layer, _, context, next) {
76
72
 
77
- for (let handler of layer.handlers) {
78
- await handler(context);
79
- if (context.state !== 'routing') return;
80
- }
81
-
82
- return await next();
73
+ for (let handler of layer.handlers) {
74
+ await handler(context);
75
+ if (context.state !== 'routing') return;
76
+ }
83
77
 
84
- }
78
+ return await next();
85
79
 
86
- case 'mixin': {
87
- return await layer.router._route(path, context, next);
88
- }
80
+ }
89
81
 
90
- }
82
+ async _handleMixin(layer, path, context, next) {
83
+ return await layer.router._route(path, context, next);
84
+ }
91
85
 
86
+ async _handle(layer, path, context, next) {
87
+ return await layer.handler.call(this, layer, path, context, next);
92
88
  }
93
89
 
94
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/app",
3
- "version": "0.6.12",
3
+ "version": "0.7.0",
4
4
  "description": "A small HTTP router.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "homepage": "https://github.com/trenskow/app#readme",
27
27
  "devDependencies": {
28
- "eslint": "^8.13.0",
28
+ "eslint": "^8.20.0",
29
29
  "mocha": "^10.0.0",
30
- "supertest": "^6.2.2"
30
+ "supertest": "^6.2.4"
31
31
  },
32
32
  "dependencies": {
33
- "@trenskow/api-error": "^2.2.6",
33
+ "@trenskow/api-error": "^2.3.2",
34
34
  "@trenskow/caseit": "^1.3.0"
35
35
  }
36
36
  }