@trenskow/app 0.6.13 → 0.7.1

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.
@@ -5,7 +5,7 @@
5
5
  "version": "0.2.0",
6
6
  "configurations": [
7
7
  {
8
- "type": "pwa-node",
8
+ "type": "node",
9
9
  "request": "launch",
10
10
  "name": "Test",
11
11
  "skipFiles": [
package/README.md CHANGED
@@ -123,21 +123,26 @@ import { Application, Endpoint } from '@trenskow/app';
123
123
 
124
124
  const app = new Application({ port: 8080 });
125
125
 
126
- (async () => {
127
-
128
- return (await app
129
- .root(
130
- new Endpoint()
131
- .mount('iam', await import('./iam.js')))
132
- .renderer(async ({ result, response }) => {
126
+ try {
127
+
128
+ const root = new Endpoint()
129
+ .mount('iam', await import('./iam.js'));
130
+
131
+ const renderer = async ({ result, response }) => {
133
132
  response.headers.contentType = 'text/plain';
134
133
  response.end(result);
135
- })
136
- .start())
137
- .port;
138
-
139
- })().then((port) => console.info(`Application is running on port ${app.port}`))
140
- .catch(console.error);
134
+ };
135
+
136
+ await app
137
+ .root(root)
138
+ .renderer(renderer)
139
+ .start();
140
+
141
+ console.info(`Application is running on port ${app.port}`)
142
+
143
+ } catch (error) {
144
+ console.error(error);
145
+ }
141
146
  ````
142
147
 
143
148
  ````javascript
@@ -216,7 +221,7 @@ Endpoints has the [`mount`](#mount) method, which "mounts" a router to the speci
216
221
 
217
222
  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
223
 
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.
224
+ 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
225
 
221
226
  > `Endpoint` extends `Router`.
222
227
 
@@ -228,9 +233,9 @@ All handlers and routers support async functions (and non-async). No need to cal
228
233
 
229
234
  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
235
 
231
- Middleware can also use the context to provide data and services, which is then available for subsequent endpoints, routers and handlers.
236
+ Middleware can assign values to the context to provide data and services, which is then available for subsequent endpoints, routers and handlers.
232
237
 
233
- When a request is incoming, the `context`object looks like this.
238
+ When a request is incoming, the `context` object looks like this.
234
239
 
235
240
  | Name | Description | Type |
236
241
  | ---------------- | ------------------------------------------------------------ | :-------------------------: |
@@ -241,7 +246,7 @@ When a request is incoming, the `context`object looks like this.
241
246
  | `path` | An object that has properties representing different paths. | Object |
242
247
  | `path.full` | An array of strings that joined represent the path of the fully requested path. | Array of String |
243
248
  | `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 |
249
+ | `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
250
  | `query` | An object holding the URL query parameters as an object ([keys has been converted to camel case](#query-parameters)). | Object |
246
251
  | `state` | A string indicating the current state of the request – possible values are `'routing'`, `'rendering'`, `'completed'` or `'aborted'`. | String |
247
252
  | `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 +272,15 @@ JavaScript is a camel cased language. HTTP is a mixture of different case types.
267
272
 
268
273
  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
274
 
270
- The same goes for request headers like `Accept-Language: en` that is accessible through `context.request.headers.acceptLanguage`.
275
+ The same goes for request headers like `Accept-Language: en` which is accessible through `context.request.headers.acceptLanguage`.
271
276
 
272
277
  ##### Query parameters
273
278
 
274
- Request with `?my-parameter=value` is accessible through `context.query.myParameter` .
279
+ Request with quuries like `?my-parameter=value` is accessible through `context.query.myParameter` .
275
280
 
276
281
  ##### Mount paths
277
282
 
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`.
283
+ 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
284
 
280
285
  #### Endpoints, routers and handlers
281
286
 
@@ -287,22 +292,13 @@ Endpoints takes care of a path component. As example the `/this/is/my/path/` pat
287
292
 
288
293
  Endpoints can have a couple of things mounted / attached to it – those are.
289
294
 
290
- * Other endpoints
291
- * using the [`.mount`](#mount) method of [`Endpoint`](#endpoint-2).
292
-
293
- * Parameters
295
+ * Other endpoints (using the [`.mount`](#mount) method of [`Endpoint`](#endpoint-2))
296
+ * Parameters (using the [`.parameter`](#parameter) method of [`Endpoint`](#endpoint-2))
294
297
  * 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
298
+ * Handlers (using the [`.use`](#use) method of [`Router`](#router-2) or [`Endpoint`](#endpoint-2) )
299
+ * Middleware (using the [`.middleware`](#middleware) method of [`Endpoint`](#endpoint-2) )
301
300
  * 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)
301
+ * Methods (using the [`.get`, `.post`, `.put`, `.delete`, etc.](#get-post-put-delete-etc) method of [`Endpoint`](#endpoint-2))
306
302
  * 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
303
 
308
304
 
@@ -311,7 +307,7 @@ Endpoints can have a couple of things mounted / attached to it – those are.
311
307
  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
308
 
313
309
  * 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).
310
+ * 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
311
 
316
312
  ##### Routers
317
313
 
@@ -322,7 +318,7 @@ A router is the same as above, except it only supports [`.use`](#use).
322
318
  As above, whenever a function takes a router as a parameter, it can be provided in any of the following ways.
323
319
 
324
320
  * 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).
321
+ * 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
322
 
327
323
  ##### Handlers
328
324
 
@@ -340,7 +336,7 @@ Whenever a function takes a handler as a parameter, it can be provided in any of
340
336
 
341
337
  ### `Application`
342
338
 
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.
339
+ 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
340
 
345
341
  > If no root route has been set, all requests will be responded with `404 Not Found`.
346
342
 
@@ -352,17 +348,17 @@ The `Application` class takes an "options" object as it's parameter.
352
348
 
353
349
  ##### Parameters
354
350
 
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) |
351
+ | Name | Description | Type | Required | Default value |
352
+ | ------------------------ | ------------------------------------------------------------ | :-----------------------: | :------: | :--------------------------: |
353
+ | options | An object representing the options. | Object | | {} |
354
+ | `options.port` | The port at which to listen for incoming connections. | Number | | `0` (automatically assigned) |
359
355
  | `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 | | `{}` |
356
+ | `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) |
357
+ | `path` | An object that represents path related options. | Object | | `{}` |
358
+ | `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'` |
359
+ | `options.server` | An object that represents how to instantiate the HTTP server. | Object | | `{}` |
360
+ | `options.server.create` | A function that is able to create a server. | Function | | `http.createServer` |
361
+ | `options.server.options` | An object to be passed as options when creating a server. | Object | | `{}` |
366
362
 
367
363
  #### Events
368
364
 
@@ -499,7 +495,7 @@ You can only call these methods once per method per endpoint – calling it mult
499
495
 
500
496
  These also ends routing. After a method route has been called, the routing will go strait to the renderer.
501
497
 
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.
498
+ > 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
499
 
504
500
  > Returns the endpoint.
505
501
 
@@ -518,11 +514,13 @@ Below is an example on how to use the method.
518
514
  ````javascript
519
515
  default export ({ endpoint }) => {
520
516
  endpoint
521
- .get(async (context) => 'Hello, world!');
517
+ .get(
518
+ async (context) => 'Hello, world!',
519
+ () => console.info("Said hello."));
522
520
  };
523
521
  ````
524
522
 
525
- > In the above example `'Hello, world!'` is immediately send to the [renderer](#renderer) and the request ends.
523
+ > 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
524
 
527
525
  ###### Catch all
528
526
 
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.13",
3
+ "version": "0.7.1",
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
  }