temba 0.10.13 → 0.12.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
@@ -62,13 +62,13 @@ Alternatively, add Temba to your app manually:
62
62
  2. Example code to create a Temba server:
63
63
 
64
64
  ```js
65
- import temba from ("temba");
66
- const server = temba.create();
65
+ import temba from 'temba'
66
+ const server = temba.create()
67
67
 
68
- const port = process.env.PORT || 3000;
68
+ const port = process.env.PORT || 3000
69
69
  server.listen(port, () => {
70
- console.log(`Temba is running on port ${port}`);
71
- });
70
+ console.log(`Temba is running on port ${port}`)
71
+ })
72
72
  ```
73
73
 
74
74
  ### Configuration
@@ -79,20 +79,23 @@ By passing a config object to the `create` function you can customize Temba's be
79
79
 
80
80
  Out of the box, Temba gives you a CRUD REST API to any resource name you can think of.
81
81
 
82
- Whether you `GET` either `/people`, `/movies`, `/pokemons`, or whatever, it all returns a `200 OK` with a `[]` JSON response. As soon as you `POST` a new resource, followed by a `GET` of that resource, the new resource will be returned. You can also `DELETE`, or `PUT` resources by its ID.
82
+ Whether you `GET` either `/people`, `/movies`, `/pokemons`, or whatever, it all returns a `200 OK` with a `[]` JSON response. As soon as you `POST` a new resource, followed by a `GET` of that resource, the new resource will be returned. You can also `DELETE`, `PATCH`, or `PUT` resources by its ID.
83
83
 
84
84
  For every resource (`movies` is just an example), Temba supports the following requests:
85
85
 
86
86
  - `GET /movies` - Get all movies
87
87
  - `GET /movies/:id` - Get a movie by its ID
88
88
  - `POST /movies` - Create a new movie
89
- - `PUT /movies/:id` - Update (fully replace) a movie by its ID
89
+ - `PATCH /movies/:id` - Partially update a movie by its ID
90
+ - `PUT /movies/:id` - Fully replace a movie by its ID
90
91
  - `DELETE /movies` - Delete all movies
91
92
  - `DELETE /movies/:id` - Delete a movie by its ID
93
+ - `HEAD /movies` - Get all movies, but without the response body
94
+ - `HEAD /movies/:id` - Get a movie by its ID, but without the response body
92
95
 
93
96
  ### Supported HTTP methods
94
97
 
95
- The HTTP methods that are supported are `GET`, `POST`, `PUT` and `DELETE`. For any other HTTP method a `405 Method Not Allowed` response will be returned.
98
+ The HTTP methods that are supported are `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `HEAD`.
96
99
 
97
100
  On the root URI (e.g. http://localhost:8080/) only a `GET` request is supported, which shows you a message indicating the API is working. All other HTTP methods on the root URI return a `405 Method Not Allowed` response.
98
101
 
@@ -100,7 +103,7 @@ On the root URI (e.g. http://localhost:8080/) only a `GET` request is supported,
100
103
 
101
104
  Temba supports JSON only.
102
105
 
103
- Request bodies sent with a `POST` and `PUT` requests are valid when the request body is either empty, or when it's valid formatted JSON. Adding a `Content-Type: application/json` header is required. If you send a request with invalid formatted JSON, a `400 Bad Request` response is returned.
106
+ Request bodies sent with a `POST`, `PATCH`, and `PUT` requests are valid when the request body is either empty, or when it's valid formatted JSON. Adding a `Content-Type: application/json` header is required. If you send a request with invalid formatted JSON, a `400 Bad Request` response is returned.
104
107
 
105
108
  Any valid formatted JSON is accepted and stored. If you want to validate or even change the JSON in the request bodies, check out the [`requestBodyValidator`](#request-body-validation-or-mutation) callbacks.
106
109
 
@@ -134,29 +137,37 @@ Requests on these resources only give a `404 Not Found` if the ID does not exist
134
137
 
135
138
  ### Static assets
136
139
 
137
- If you want to host static assets next to the REST API, configure the `staticFolder`:
140
+ If you want to host static assets, next to the API, configure the `staticFolder`:
138
141
 
139
142
  ```js
140
143
  const config = { staticFolder: 'build' }
141
144
  const server = temba.create(config)
142
145
  ```
143
146
 
144
- This way, you could create a REST API, and the web app consuming it, in one project.
147
+ With this setting, sending a `GET` request to the root URL, returns the content that is in the `'./build'` folder in your project.
145
148
 
146
- However, to avoid possible conflicts between the API resources and the web app routes you might want to add an `apiPrefix` to the REST API:
149
+ This way, you could create an API, and the web app consuming it, in one project.
147
150
 
148
- ### REST URIs prefixes
151
+ Without configuring a `staticFolder`, a `GET` to the root URL returns `"It works! ツ"`. When the `staticFolder` is configured, it returns whatever is in the `build` folder in your project, for example an HTML page.
149
152
 
150
- With the `apiPrefix` config setting, all REST resources get an extra path segment in front of them. If the `apiPrefix` is `'api'`, then `/movies/12345` becomes `/api/movies/12345`:
153
+ However, this might cause conflicts between the API resources and the web app routes: If the web app in the `build` folder has a route to `/products`, but there is also a `/products` API resource, the web app route is returned.
154
+
155
+ To be able to still access the `/products` API resource, configure an `apiPrefix`:
156
+
157
+ ### API prefix
158
+
159
+ With the `apiPrefix` config setting, all resources get an extra path segment in front of them. If the `apiPrefix` is `'api'`, then `/movies/12345` becomes `/api/movies/12345`:
151
160
 
152
161
  ```js
153
162
  const config = { apiPrefix: 'api' }
154
163
  const server = temba.create(config)
155
164
  ```
156
165
 
157
- After configuring the `apiPrefix`, requests to the root URL will either return a `404 Not Found` or a `405 Method Not Allowed`, depending on the HTTP method.
166
+ A request to the `apiPrefix` (e.g. http://localhost:1234/api) will now return the `"It works! ツ"` response message.
167
+
168
+ After configuring the `apiPrefix`, requests to the root URL (e.g. http://localhost:1234/), instead of the `"It works! ツ"` response message, will now either return a `404 Not Found` on `GET` requests, or a `405 Method Not Allowed` for all other HTTP methods.
158
169
 
159
- If you have configured both an `apiPrefix` and a `staticFolder`, a `GET` on the root URL will return the `index.html` in the `staticFolder`, if there is one.
170
+ However, if you configured both an `apiPrefix` and a `staticFolder`, a `GET` on the root URL will return the content in the `staticFolder`.
160
171
 
161
172
  ### Request body validation or mutation
162
173
 
@@ -178,9 +189,7 @@ POST /movies
178
189
  }
179
190
  ```
180
191
 
181
- You can even omit a request body when doing a `POST` or `PUT`. If you don't want that, and build proper validation, use the `requestBodyValidator` config setting:
182
-
183
- If you want to do input validation before the `POST` or `PUT` request body is saved to the database, configure Temba as follows:
192
+ You can even omit a request body when doing a `POST`, `PATCH`, or `PUT`. If you don't want that, and want to have proper validation, use the `requestBodyValidator` config setting:
184
193
 
185
194
  ```js
186
195
  const config = {
@@ -191,13 +200,16 @@ const config = {
191
200
  put: (resourceName, requestBody) => {
192
201
  // Validate, or even change the requestBody
193
202
  },
203
+ patch: (resourceName, requestBody) => {
204
+ // Validate, or even change the requestBody
205
+ },
194
206
  },
195
207
  }
196
208
 
197
209
  const server = temba.create(config)
198
210
  ```
199
211
 
200
- The `requestBodyValidator` is an object with a `post` and/or `put` field, which contains the callback function you want Temba to call before the JSON is saved to the database.
212
+ The `requestBodyValidator` is an object with a `post`, and/or `patch`, and/or `put` field, which contains the callback function you want Temba to call before the JSON is saved to the database.
201
213
 
202
214
  The callback function receives two arguments: The `resourceName`, which for example is `movies` if you request `POST /movies`. The second argument is the `requestBody`, which is the JSON object in the request body.
203
215
 
@@ -214,14 +226,10 @@ const config = {
214
226
  requestBodyValidator: {
215
227
  post: (resourceName, requestBody) => {
216
228
  // Do not allow Pokemons to be created: 400 Bad Request
217
- if (resourceName === 'pokemons')
218
- return 'You are not allowed to create new Pokemons'
229
+ if (resourceName === 'pokemons') return 'You are not allowed to create new Pokemons'
219
230
 
220
231
  // Add a genre to Star Trek films:
221
- if (
222
- resourceName === 'movies' &&
223
- requestBody.title.startsWith('Star Trek')
224
- )
232
+ if (resourceName === 'movies' && requestBody.title.startsWith('Star Trek'))
225
233
  return { ...requestBody, genre: 'Science Fiction' }
226
234
 
227
235
  // If you end up here, void will be returned, so the request will just be saved.
@@ -232,6 +240,121 @@ const config = {
232
240
  const server = temba.create(config)
233
241
  ```
234
242
 
243
+ ## Response body interception
244
+
245
+ To change the response body of a `GET` request, configure a `responseBodyInterceptor`, and return the updated response body:
246
+
247
+ ```js
248
+ const config = {
249
+ responseBodyInterceptor: (resourceName, id, responseBody) => {
250
+ return resourceName === 'movies' ? {
251
+ if (id) {
252
+ // responseBody is an object
253
+ return {
254
+ ...responseBody,
255
+ stuff: 'more stuff',
256
+ }
257
+ } : {
258
+ // responseBody is an array
259
+ return responseBody.map(x => (
260
+ {
261
+ ...x,
262
+ stuff: 'more stuff'
263
+ }
264
+ ))
265
+ }
266
+ }
267
+
268
+ // If you end up here, the response body will just be returned unchanged.
269
+ },
270
+ }
271
+
272
+ const server = temba.create(config)
273
+ ```
274
+
275
+ `responseBodyInterceptor` is a callback function that provides the `resourceName`, `responseBody`, and the `id`. Depending on whether it's a collection or item request, the `responseBody` is either an array or object, and the `id' can be `undefined`.
276
+
277
+ In the example above we check for the `id` being defined, but a runtime check to determine the type of `responseBody` would also suffice.
278
+
279
+ Whatever you return in this function will become the response body and will be serialized as JSON and returned to the client.
280
+
281
+ If you don't return anything, the response body will be sent as-is.
282
+
283
+ The `responseBodyInterceptor` will only be called when the response was successful, i.e. a `200 OK` status code.
284
+
285
+ ## Custom router
286
+
287
+ Although `temba.create()` returns an Express instance, adding your own routes, as you would normally do with Express, is not possible:
288
+
289
+ ```js
290
+ const server = temba.create()
291
+
292
+ // 🛑 Although `server` is an Express instance, the following does not work:
293
+ server.get('/hello', (req, res) => {
294
+ res.send('hello world')
295
+ })
296
+ ```
297
+
298
+ The reason is that Temba is in charge of all Express routes, to make sure only resource routes can be overruled by a custom router. To add your own routes, create an Express router, and configure it as a `customRouter`:
299
+
300
+ ```js
301
+ // Example code of how to create an Express router, from the official Express docs at https://expressjs.com/en/guide/routing.html:
302
+ const express = require('express')
303
+ const router = express.Router()
304
+
305
+ // middleware that is specific to this router
306
+ router.use((req, res, next) => {
307
+ console.log('Time: ', Date.now())
308
+ next()
309
+ })
310
+ // define the home page route
311
+ router.get('/', (req, res) => {
312
+ res.send('Birds home page')
313
+ })
314
+ // define the about route
315
+ router.get('/about', (req, res) => {
316
+ res.send('About birds')
317
+ })
318
+
319
+ // Add the custom router to Temba config
320
+ const config = {
321
+ customRouter: router,
322
+ }
323
+
324
+ const server = temba.create(config)
325
+ ```
326
+
327
+ > 💁 Don't overuse `customRouter`, as it defeats the purpose of Temba being a simple out-of-the-box solution.
328
+
329
+ A `customRouter` can only overrule resource routes. The root URL (with or without `staticFolder`) will always be handled by Temba.
330
+
331
+ So for the following router and config:
332
+
333
+ ```
334
+ router.get('/', (req, res) => {
335
+ res.send('Birds home page')
336
+ })
337
+ router.get('/stuff', (req, res) => {
338
+ res.send('Some stuff')
339
+ })
340
+ router.get('api/stuff', (req, res) => {
341
+ res.send('Some API stuff')
342
+ })
343
+
344
+ const config = {
345
+ resourceNames: ['stuff'],
346
+ staticFolder: 'build',
347
+ apiPrefix: 'api',
348
+ customRouter: router,
349
+ }
350
+ const server = temba.create(config)
351
+ ```
352
+
353
+ - `/` will be handled by Temba, and will return the `staticFolder` (`build`) folder contents
354
+ - `/stuff` and `/api/stuff` will be handled by the custom router
355
+ - `/movies` will return a `404 Not Found`, because of `apiPrefix`
356
+ - `/api/movies` will return movies, handled by Temba
357
+
235
358
  ### Config settings overview
236
359
 
237
360
  Configuring Temba is optional, it already works out of the box.
@@ -244,12 +367,16 @@ const config = {
244
367
  connectionString: 'mongodb://localhost:27017',
245
368
  staticFolder: 'build',
246
369
  apiPrefix: 'api',
370
+ customRouter: router,
247
371
  cacheControl: 'public, max-age=300',
248
372
  delay: 500,
249
373
  requestBodyValidator: {
250
374
  post: (resourceName, requestBody) => {
251
375
  // Validate, or even change the requestBody
252
376
  },
377
+ patch: (resourceName, requestBody) => {
378
+ // Validate, or even change the requestBody
379
+ },
253
380
  put: (resourceName, requestBody) => {
254
381
  // Validate, or even change the requestBody
255
382
  },
@@ -258,7 +385,7 @@ const config = {
258
385
  const server = temba.create(config)
259
386
  ```
260
387
 
261
- None of the settings are required, and none of them have default values that actually do something. So only the settings you define are used.
388
+ None of the settings are required, and only the settings you define are used.
262
389
 
263
390
  These are all the possible settings:
264
391
 
@@ -267,7 +394,8 @@ These are all the possible settings:
267
394
  | `resourceNames` | See [Allowing specific resources only](#allowing-specific-resources-only) |
268
395
  | `connectionString` | See [MongoDB](#mongodb) |
269
396
  | `staticFolder` | See [Static assets](#static-assets) |
270
- | `apiPrefix` | See [REST URIs prefixes](#rest-uris-prefixes) |
397
+ | `apiPrefix` | See [API prefix](#api-prefix) |
398
+ | `customRouter` | See [Custom router](#custom-router) |
271
399
  | `cacheControl` | The `Cache-control` response header value for each GET request. |
272
400
  | `delay` | After processing the request, the delay in milliseconds before the request should be sent. |
273
401
  | `requestBodyValidator` | See [Request body validation or mutation](#request-body-validation-or-mutation) |
@@ -276,17 +404,13 @@ These are all the possible settings:
276
404
 
277
405
  The following features would be very nice for Temba to support:
278
406
 
279
- ### `PATCH` requests
280
-
281
- Partial updates using `PATCH`, or other HTTP methods are not (yet?) supported.
282
-
283
407
  ### Auth
284
408
 
285
409
  Temba offers no ways for authentication or authorization (yet?), so if someone knows how to reach the API, they can read and mutate all your data, unless you restrict this in another way.
286
410
 
287
411
  ### Nested parent-child resources
288
412
 
289
- Also nested (parent-child) routes are not supported (yet?), so every URI has the /:resource/:id structure and there is no way to indicate any relation, apart from within the JSON itself perhaps.
413
+ Also nested (parent-child) URI routes are not supported (yet?). So every URI has the /:resource/:id structure and there is no way to indicate any deeper relation through the URI.
290
414
 
291
415
  ### Filtering and sorting
292
416
 
@@ -294,7 +418,7 @@ And there is no filtering, sorting, searching, etc. (yet?).
294
418
 
295
419
  ## Under the hood
296
420
 
297
- Temba is built with JavaScript, [Node](https://nodejs.org), [Express](https://expressjs.com/), [Jest](https://jestjs.io/), [Testing Library](https://testing-library.com/), [Supertest](https://www.npmjs.com/package/supertest), and [@rakered/mongo](https://www.npmjs.com/package/@rakered/mongo).
421
+ Temba is built with JavaScript, [Node](https://nodejs.org), [Express](https://expressjs.com/), [Jest](https://jestjs.io/), [Supertest](https://www.npmjs.com/package/supertest), and [@rakered/mongo](https://www.npmjs.com/package/@rakered/mongo).
298
422
 
299
423
  ## Which problem does Temba solve?
300
424
 
package/config/index.d.ts CHANGED
@@ -1,5 +1,19 @@
1
- import { RequestBodyValidator } from '../routes/types';
1
+ import { Router } from 'express';
2
+ import { RequestBodyValidator, ResponseBodyInterceptor } from '../routes/types';
2
3
  export declare type Config = {
4
+ validateResources: boolean;
5
+ resourceNames: string[];
6
+ apiPrefix: string;
7
+ cacheControl: string;
8
+ requestBodyValidator: RequestBodyValidator;
9
+ responseBodyInterceptor: ResponseBodyInterceptor;
10
+ staticFolder: string;
11
+ connectionString: string;
12
+ delay: number;
13
+ customRouter: Router;
14
+ };
15
+ export declare type RouterConfig = Pick<Config, 'validateResources' | 'resourceNames' | 'apiPrefix' | 'cacheControl' | 'requestBodyValidator' | 'responseBodyInterceptor'>;
16
+ export declare type UserConfig = {
3
17
  resourceNames?: string[];
4
18
  validateResources?: boolean;
5
19
  staticFolder?: string;
@@ -8,5 +22,7 @@ export declare type Config = {
8
22
  cacheControl?: string;
9
23
  delay?: number;
10
24
  requestBodyValidator?: RequestBodyValidator;
25
+ responseBodyInterceptor?: ResponseBodyInterceptor;
26
+ customRouter?: Router;
11
27
  };
12
- export declare function initConfig(userConfig: Config): Config;
28
+ export declare function initConfig(userConfig: UserConfig): Config;
package/config/index.js CHANGED
@@ -13,10 +13,17 @@ const defaultConfig = {
13
13
  post: () => {
14
14
  // do nothing
15
15
  },
16
+ patch: () => {
17
+ // do nothing
18
+ },
16
19
  put: () => {
17
20
  // do nothing
18
21
  },
19
22
  },
23
+ responseBodyInterceptor: (resourceName, responseBody, id) => {
24
+ return responseBody;
25
+ },
26
+ customRouter: null,
20
27
  };
21
28
  function initConfig(userConfig) {
22
29
  if (!userConfig)
@@ -51,11 +58,21 @@ function initConfig(userConfig) {
51
58
  typeof userConfig.requestBodyValidator.post === 'function') {
52
59
  config.requestBodyValidator.post = userConfig.requestBodyValidator.post;
53
60
  }
61
+ if (userConfig.requestBodyValidator.patch &&
62
+ typeof userConfig.requestBodyValidator.patch === 'function') {
63
+ config.requestBodyValidator.patch = userConfig.requestBodyValidator.patch;
64
+ }
54
65
  if (userConfig.requestBodyValidator.put &&
55
66
  typeof userConfig.requestBodyValidator.put === 'function') {
56
67
  config.requestBodyValidator.put = userConfig.requestBodyValidator.put;
57
68
  }
58
69
  }
70
+ if (userConfig.responseBodyInterceptor) {
71
+ config.responseBodyInterceptor = userConfig.responseBodyInterceptor;
72
+ }
73
+ if (userConfig.customRouter) {
74
+ config.customRouter = userConfig.customRouter;
75
+ }
59
76
  return config;
60
77
  }
61
78
  exports.initConfig = initConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":";;;AAaA,MAAM,aAAa,GAAW;IAC5B,aAAa,EAAE,EAAE;IACjB,iBAAiB,EAAE,KAAK;IACxB,YAAY,EAAE,IAAI;IAClB,SAAS,EAAE,EAAE;IACb,gBAAgB,EAAE,IAAI;IACtB,YAAY,EAAE,UAAU;IACxB,KAAK,EAAE,CAAC;IACR,oBAAoB,EAAE;QACpB,IAAI,EAAE,GAAG,EAAE;YACT,aAAa;QACf,CAAC;QACD,GAAG,EAAE,GAAG,EAAE;YACR,aAAa;QACf,CAAC;KACF;CACF,CAAA;AAED,SAAgB,UAAU,CAAC,UAAkB;IAC3C,IAAI,CAAC,UAAU;QAAE,OAAO,aAAa,CAAA;IAErC,MAAM,MAAM,qBAAQ,aAAa,CAAE,CAAA;IAEnC,IAAI,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE;QACnE,MAAM,CAAC,aAAa,GAAG,UAAU,CAAC,aAAa,CAAA;QAC/C,MAAM,CAAC,iBAAiB,GAAG,IAAI,CAAA;KAChC;IAED,IAAI,UAAU,CAAC,YAAY,EAAE;QAC3B,MAAM,CAAC,YAAY,GAAG,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;KAC3E;IAED,IAAI,UAAU,CAAC,SAAS,EAAE;QACxB,MAAM,CAAC,SAAS;YACd,GAAG,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,GAAG,GAAG,CAAA;KAChE;IAED,IAAI,UAAU,CAAC,gBAAgB,IAAI,UAAU,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE;QACzE,MAAM,CAAC,gBAAgB,GAAG,UAAU,CAAC,gBAAgB,CAAA;KACtD;IAED,IAAI,UAAU,CAAC,YAAY,IAAI,UAAU,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;QACjE,MAAM,CAAC,YAAY,GAAG,UAAU,CAAC,YAAY,CAAA;KAC9C;IAED,IACE,UAAU,CAAC,KAAK;QAChB,UAAU,CAAC,KAAK,KAAK,CAAC;QACtB,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,QAAQ;QAC5C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5B,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,KAAK,EAChC;QACA,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;KACxC;IAED,IAAI,UAAU,CAAC,oBAAoB,EAAE;QACnC,IACE,UAAU,CAAC,oBAAoB,CAAC,IAAI;YACpC,OAAO,UAAU,CAAC,oBAAoB,CAAC,IAAI,KAAK,UAAU,EAC1D;YACA,MAAM,CAAC,oBAAoB,CAAC,IAAI,GAAG,UAAU,CAAC,oBAAoB,CAAC,IAAI,CAAA;SACxE;QACD,IACE,UAAU,CAAC,oBAAoB,CAAC,GAAG;YACnC,OAAO,UAAU,CAAC,oBAAoB,CAAC,GAAG,KAAK,UAAU,EACzD;YACA,MAAM,CAAC,oBAAoB,CAAC,GAAG,GAAG,UAAU,CAAC,oBAAoB,CAAC,GAAG,CAAA;SACtE;KACF;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AArDD,gCAqDC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"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"}
package/index.js CHANGED
@@ -44,6 +44,8 @@ function createServer(userConfig) {
44
44
  const delayMiddleware = (0, delayMiddleware_1.createDelayMiddleware)(config.delay);
45
45
  app.use(delayMiddleware);
46
46
  }
47
+ //TODO customRoutes:
48
+ // - Al deze routing code naar een aparte functie
47
49
  // Serve a static folder, if configured.
48
50
  if (config.staticFolder) {
49
51
  app.use(express_1.default.static(config.staticFolder));
@@ -51,11 +53,15 @@ function createServer(userConfig) {
51
53
  // On the root URL (with apiPrefix if applicable) only a GET is allowed.
52
54
  const rootPath = config.apiPrefix ? `${config.apiPrefix}` : '/';
53
55
  app.use(rootPath, routes_1.rootRouter);
56
+ if (config.customRouter) {
57
+ app.use(config.customRouter);
58
+ }
54
59
  // For all other URLs, only GET, POST, PUT and DELETE are allowed and handled.
55
60
  const resourceRouter = (0, routes_1.createResourceRouter)(queries, config);
56
61
  const resourcePath = config.apiPrefix ? `${config.apiPrefix}*` : '*';
57
62
  app.use(resourcePath, resourceRouter);
58
63
  // In case of an API prefix, GET, POST, PUT and DELETE requests to all other URLs return a 404 Not Found.
64
+ //TODO Hier missen toch HTTP methods?
59
65
  if (config.apiPrefix) {
60
66
  app.get('*', routes_1.handleNotFound);
61
67
  app.post('*', routes_1.handleNotFound);
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAuC;AACvC,oDAA2B;AAC3B,4CAA8C;AAC9C,4CAKwB;AACxB,+CAAiD;AACjD,qCAA6C;AAC7C,gDAAuB;AACvB,6DAA+D;AAE/D,SAAS,YAAY,CAAC,UAAmB;IACvC,MAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,UAAU,CAAC,CAAA;IAErC,MAAM,OAAO,GAAG,IAAA,uBAAa,EAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAEtD,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAA;IACrB,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAA;IAEf,4BAA4B;IAC5B,GAAG,CAAC,GAAG,CAAC,IAAA,gBAAM,EAAC,MAAM,CAAC,CAAC,CAAA;IAEvB,gCAAgC;IAChC,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAElD,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE;QACpB,MAAM,eAAe,GAAG,IAAA,uCAAqB,EAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC3D,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;KACzB;IAED,wCAAwC;IACxC,IAAI,MAAM,CAAC,YAAY,EAAE;QACvB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;KAC7C;IAED,wEAAwE;IACxE,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;IAC/D,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,mBAAU,CAAC,CAAA;IAE7B,8EAA8E;IAC9E,MAAM,cAAc,GAAG,IAAA,6BAAoB,EAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC5D,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IACpE,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC,CAAA;IAErC,yGAAyG;IACzG,IAAI,MAAM,CAAC,SAAS,EAAE;QACpB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;QAC5B,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;QAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;QAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;KAChC;IAED,gDAAgD;IAChD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,+BAAsB,CAAC,CAAA;IACpC,IAAI,MAAM,CAAC,SAAS;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,SAAS,GAAG,EAAE,+BAAsB,CAAC,CAAA;IAE7E,oBAAoB;IACpB,GAAG,CAAC,GAAG,CAAC,qBAAY,CAAC,CAAA;IAErB,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAgB,MAAM,CAAC,UAAmB;IACxC,OAAO,YAAY,CAAC,UAAU,CAAC,CAAA;AACjC,CAAC;AAFD,wBAEC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAuC;AACvC,oDAA2B;AAC3B,4CAA8C;AAC9C,4CAKwB;AACxB,+CAAiD;AACjD,qCAAyD;AACzD,gDAAuB;AACvB,6DAA+D;AAE/D,SAAS,YAAY,CAAC,UAAuB;IAC3C,MAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,UAAU,CAAC,CAAA;IAErC,MAAM,OAAO,GAAG,IAAA,uBAAa,EAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAEtD,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAA;IACrB,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAA;IAEf,4BAA4B;IAC5B,GAAG,CAAC,GAAG,CAAC,IAAA,gBAAM,EAAC,MAAM,CAAC,CAAC,CAAA;IAEvB,gCAAgC;IAChC,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAElD,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE;QACpB,MAAM,eAAe,GAAG,IAAA,uCAAqB,EAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC3D,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;KACzB;IAED,oBAAoB;IACpB,iDAAiD;IAEjD,wCAAwC;IACxC,IAAI,MAAM,CAAC,YAAY,EAAE;QACvB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;KAC7C;IAED,wEAAwE;IACxE,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;IAC/D,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,mBAAU,CAAC,CAAA;IAE7B,IAAI,MAAM,CAAC,YAAY,EAAE;QACvB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;KAC7B;IAED,8EAA8E;IAC9E,MAAM,cAAc,GAAG,IAAA,6BAAoB,EAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC5D,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IACpE,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC,CAAA;IAErC,yGAAyG;IACzG,qCAAqC;IACrC,IAAI,MAAM,CAAC,SAAS,EAAE;QACpB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;QAC5B,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;QAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;QAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,uBAAc,CAAC,CAAA;KAChC;IAED,gDAAgD;IAChD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,+BAAsB,CAAC,CAAA;IACpC,IAAI,MAAM,CAAC,SAAS;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,SAAS,GAAG,EAAE,+BAAsB,CAAC,CAAA;IAE7E,oBAAoB;IACpB,GAAG,CAAC,GAAG,CAAC,qBAAY,CAAC,CAAA;IAErB,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAgB,MAAM,CAAC,UAAmB;IACxC,OAAO,YAAY,CAAC,UAAU,CAAC,CAAA;AACjC,CAAC;AAFD,wBAEC"}
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "temba",
3
- "version": "0.10.13",
3
+ "version": "0.12.0",
4
4
  "description": "Get a simple MongoDB REST API with zero coding in less than 30 seconds (seriously).",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "build": "rm -rf dist && tsc && cp package.json README.md ./dist",
8
- "test": "jest --watch",
8
+ "test": "jest --silent --no-cache",
9
+ "test:watch": "jest --watch --no-cache",
9
10
  "lint": "eslint --ignore-path .gitignore --ext .js,.ts .",
10
11
  "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
11
12
  },
package/routes/get.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare function createGetRoutes(queries: any, cacheControl: any): {
1
+ declare function createGetRoutes(queries: any, cacheControl: any, responseBodyInterceptor: any): {
2
2
  handleGetResource: (req: any, res: any, next: any) => Promise<any>;
3
3
  };
4
4
  export { createGetRoutes };
package/routes/get.js CHANGED
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.createGetRoutes = void 0;
13
- function createGetRoutes(queries, cacheControl) {
13
+ function createGetRoutes(queries, cacheControl, responseBodyInterceptor) {
14
14
  function handleGetResource(req, res, next) {
15
15
  return __awaiter(this, void 0, void 0, function* () {
16
16
  try {
@@ -22,13 +22,43 @@ function createGetRoutes(queries, cacheControl) {
22
22
  res.status(404);
23
23
  return res.send();
24
24
  }
25
+ let theItem = item;
26
+ if (responseBodyInterceptor) {
27
+ try {
28
+ theItem = responseBodyInterceptor(resourceName, item, id);
29
+ if (!theItem)
30
+ theItem = item;
31
+ }
32
+ catch (error) {
33
+ return res
34
+ .status(500)
35
+ .json({
36
+ message: 'Error in responseBodyInterceptor: ' + error.message,
37
+ });
38
+ }
39
+ }
25
40
  res.status(200);
26
- res.json(item);
41
+ res.json(theItem);
27
42
  return res.send();
28
43
  }
29
44
  const items = yield queries.getAll(resourceName);
45
+ let theItems = items;
46
+ if (responseBodyInterceptor) {
47
+ try {
48
+ theItems = responseBodyInterceptor(resourceName, items);
49
+ if (!theItems)
50
+ theItems = items;
51
+ }
52
+ catch (error) {
53
+ return res
54
+ .status(500)
55
+ .json({
56
+ message: 'Error in responseBodyInterceptor: ' + error.message,
57
+ });
58
+ }
59
+ }
30
60
  res.status(200);
31
- res.json(items);
61
+ res.json(theItems);
32
62
  return res.send();
33
63
  }
34
64
  catch (error) {
package/routes/get.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/routes/get.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,SAAS,eAAe,CAAC,OAAO,EAAE,YAAY;IAC5C,SAAe,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YAC7C,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAE5C,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,YAAY,CAAC,CAAA;gBAEtC,IAAI,EAAE,EAAE;oBACN,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;oBAEpD,IAAI,CAAC,IAAI,EAAE;wBACT,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACf,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;qBAClB;oBAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBACf,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACd,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;iBAClB;gBAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;gBAChD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBACf,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACf,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;aAClB;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;QACH,CAAC;KAAA;IAED,OAAO;QACL,iBAAiB;KAClB,CAAA;AACH,CAAC;AAEQ,0CAAe"}
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/routes/get.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,SAAS,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,uBAAuB;IACrE,SAAe,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YAC7C,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAE5C,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,YAAY,CAAC,CAAA;gBAEtC,IAAI,EAAE,EAAE;oBACN,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;oBAEpD,IAAI,CAAC,IAAI,EAAE;wBACT,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACf,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;qBAClB;oBAED,IAAI,OAAO,GAAG,IAAI,CAAA;oBAClB,IAAI,uBAAuB,EAAE;wBAC3B,IAAI;4BACF,OAAO,GAAG,uBAAuB,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;4BACzD,IAAI,CAAC,OAAO;gCAAE,OAAO,GAAG,IAAI,CAAA;yBAC7B;wBAAC,OAAO,KAAK,EAAE;4BACd,OAAO,GAAG;iCACP,MAAM,CAAC,GAAG,CAAC;iCACX,IAAI,CAAC;gCACJ,OAAO,EAAE,oCAAoC,GAAG,KAAK,CAAC,OAAO;6BAC9D,CAAC,CAAA;yBACL;qBACF;oBAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBACf,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;oBACjB,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;iBAClB;gBAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;gBAEhD,IAAI,QAAQ,GAAG,KAAK,CAAA;gBACpB,IAAI,uBAAuB,EAAE;oBAC3B,IAAI;wBACF,QAAQ,GAAG,uBAAuB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAA;wBACvD,IAAI,CAAC,QAAQ;4BAAE,QAAQ,GAAG,KAAK,CAAA;qBAChC;oBAAC,OAAO,KAAK,EAAE;wBACd,OAAO,GAAG;6BACP,MAAM,CAAC,GAAG,CAAC;6BACX,IAAI,CAAC;4BACJ,OAAO,EAAE,oCAAoC,GAAG,KAAK,CAAC,OAAO;yBAC9D,CAAC,CAAA;qBACL;iBACF;gBAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBACf,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBAClB,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;aAClB;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;QACH,CAAC;KAAA;IAED,OAAO;QACL,iBAAiB;KAClB,CAAA;AACH,CAAC;AAEQ,0CAAe"}
@@ -0,0 +1,4 @@
1
+ declare function createPatchRoutes(queries: any, requestBodyValidator: any): {
2
+ handlePatch: (req: any, res: any, next: any) => Promise<any>;
3
+ };
4
+ export { createPatchRoutes };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createPatchRoutes = void 0;
13
+ const errors_1 = require("../errors/errors");
14
+ const validator_1 = require("./validator");
15
+ function createPatchRoutes(queries, requestBodyValidator) {
16
+ function handlePatch(req, res, next) {
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ try {
19
+ const { resourceName, id } = req.requestInfo;
20
+ const requestBody = (0, validator_1.validateRequestBody)(requestBodyValidator.patch, req);
21
+ if (typeof requestBody === 'string')
22
+ return res.status(400).json({ message: requestBody }).send();
23
+ let item = null;
24
+ if (id)
25
+ item = yield queries.getById(resourceName, id);
26
+ // TODO return a response instead of calling next
27
+ if (!item)
28
+ return next((0, errors_1.new404NotFoundError)(`ID '${id}' not found`));
29
+ item = Object.assign(Object.assign(Object.assign({}, item), requestBody), { id });
30
+ const updatedItem = yield queries.update(resourceName, item);
31
+ return res.status(200).json(updatedItem).send();
32
+ }
33
+ catch (error) {
34
+ return next(error);
35
+ }
36
+ });
37
+ }
38
+ return {
39
+ handlePatch,
40
+ };
41
+ }
42
+ exports.createPatchRoutes = createPatchRoutes;
43
+ //# sourceMappingURL=patch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.js","sourceRoot":"","sources":["../../src/routes/patch.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6CAAsD;AACtD,2CAAiD;AAEjD,SAAS,iBAAiB,CAAC,OAAO,EAAE,oBAAoB;IACtD,SAAe,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YACvC,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAE5C,MAAM,WAAW,GAAG,IAAA,+BAAmB,EAAC,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;gBAExE,IAAI,OAAO,WAAW,KAAK,QAAQ;oBACjC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;gBAE9D,IAAI,IAAI,GAAG,IAAI,CAAA;gBACf,IAAI,EAAE;oBAAE,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;gBAEtD,iDAAiD;gBACjD,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,IAAA,4BAAmB,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;gBAEnE,IAAI,iDAAQ,IAAI,GAAK,WAAW,KAAE,EAAE,GAAE,CAAA;gBAEtC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;gBAE5D,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;aAChD;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;QACH,CAAC;KAAA;IAED,OAAO;QACL,WAAW;KACZ,CAAA;AACH,CAAC;AAEQ,8CAAiB"}
package/routes/put.js CHANGED
@@ -27,8 +27,8 @@ function createPutRoutes(queries, requestBodyValidator) {
27
27
  if (!item)
28
28
  return next((0, errors_1.new404NotFoundError)(`ID '${id}' not found`));
29
29
  item = Object.assign(Object.assign({}, requestBody), { id });
30
- const updatedItem = yield queries.update(resourceName, item);
31
- return res.status(200).json(updatedItem).send();
30
+ const replacedItem = yield queries.update(resourceName, item);
31
+ return res.status(200).json(replacedItem).send();
32
32
  }
33
33
  catch (error) {
34
34
  return next(error);
package/routes/put.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"put.js","sourceRoot":"","sources":["../../src/routes/put.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6CAAsD;AACtD,2CAAiD;AAEjD,SAAS,eAAe,CAAC,OAAO,EAAE,oBAAoB;IACpD,SAAe,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YACrC,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAE5C,MAAM,WAAW,GAAG,IAAA,+BAAmB,EAAC,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAEtE,IAAI,OAAO,WAAW,KAAK,QAAQ;oBACjC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;gBAE9D,IAAI,IAAI,GAAG,IAAI,CAAA;gBACf,IAAI,EAAE;oBAAE,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;gBAEtD,iDAAiD;gBACjD,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,IAAA,4BAAmB,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;gBAEnE,IAAI,mCAAQ,WAAW,KAAE,EAAE,GAAE,CAAA;gBAE7B,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;gBAE5D,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;aAChD;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;QACH,CAAC;KAAA;IAED,OAAO;QACL,SAAS;KACV,CAAA;AACH,CAAC;AAEQ,0CAAe"}
1
+ {"version":3,"file":"put.js","sourceRoot":"","sources":["../../src/routes/put.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6CAAsD;AACtD,2CAAiD;AAEjD,SAAS,eAAe,CAAC,OAAO,EAAE,oBAAoB;IACpD,SAAe,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YACrC,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAE5C,MAAM,WAAW,GAAG,IAAA,+BAAmB,EAAC,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAEtE,IAAI,OAAO,WAAW,KAAK,QAAQ;oBACjC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;gBAE9D,IAAI,IAAI,GAAG,IAAI,CAAA;gBACf,IAAI,EAAE;oBAAE,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;gBAEtD,iDAAiD;gBACjD,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,IAAA,4BAAmB,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;gBAEnE,IAAI,mCAAQ,WAAW,KAAE,EAAE,GAAE,CAAA;gBAE7B,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;gBAE7D,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAA;aACjD;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;QACH,CAAC;KAAA;IAED,OAAO;QACL,SAAS;KACV,CAAA;AACH,CAAC;AAEQ,0CAAe"}
@@ -1,5 +1,5 @@
1
- import { Config } from '../config';
2
- declare function createResourceRouter(queries: any, { validateResources, resourceNames, apiPrefix, cacheControl, requestBodyValidator, }: Config): import("express-serve-static-core").Router;
1
+ import { RouterConfig } from '../config';
2
+ declare function createResourceRouter(queries: any, routerConfig: RouterConfig): import("express-serve-static-core").Router;
3
3
  declare const rootRouter: import("express-serve-static-core").Router;
4
4
  declare function handleMethodNotAllowed(_: any, res: any): void;
5
5
  declare function handleNotFound(_: any, res: any): void;
package/routes/routes.js CHANGED
@@ -16,21 +16,26 @@ exports.handleNotFound = exports.handleMethodNotAllowed = exports.rootRouter = e
16
16
  const get_1 = require("./get");
17
17
  const post_1 = require("./post");
18
18
  const put_1 = require("./put");
19
+ const patch_1 = require("./patch");
19
20
  const delete_1 = require("./delete");
20
21
  const urlMiddleware_1 = require("../urls/urlMiddleware");
21
22
  const express_1 = __importDefault(require("express"));
22
- function createResourceRouter(queries, { validateResources, resourceNames, apiPrefix, cacheControl, requestBodyValidator, }) {
23
- const { handleGetResource } = (0, get_1.createGetRoutes)(queries, cacheControl);
23
+ function createResourceRouter(queries, routerConfig) {
24
+ const { validateResources, resourceNames, apiPrefix, cacheControl, requestBodyValidator, responseBodyInterceptor, } = routerConfig;
25
+ const { handleGetResource } = (0, get_1.createGetRoutes)(queries, cacheControl, responseBodyInterceptor);
24
26
  const { handlePost } = (0, post_1.createPostRoutes)(queries, requestBodyValidator);
25
27
  const { handlePut } = (0, put_1.createPutRoutes)(queries, requestBodyValidator);
28
+ const { handlePatch } = (0, patch_1.createPatchRoutes)(queries, requestBodyValidator);
26
29
  const { handleDelete } = (0, delete_1.createDeleteRoutes)(queries);
27
30
  const validateResource = (0, urlMiddleware_1.createValidateResourceMiddleware)(validateResources, resourceNames);
28
31
  const getResourceAndId = (0, urlMiddleware_1.createResourceAndIdParser)(apiPrefix);
29
32
  const resourceRouter = express_1.default.Router();
30
33
  resourceRouter
34
+ // The router.get() function automatically handles HEAD requests as well, unless router.head is called first.
31
35
  .get('*', getResourceAndId, validateResource, handleGetResource)
32
36
  .post('*', getResourceAndId, validateResource, handlePost)
33
37
  .put('*', getResourceAndId, validateResource, handlePut)
38
+ .patch('*', getResourceAndId, validateResource, handlePatch)
34
39
  .delete('*', getResourceAndId, validateResource, handleDelete);
35
40
  return resourceRouter;
36
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/routes/routes.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,+BAAuC;AACvC,iCAAyC;AACzC,+BAAuC;AACvC,qCAA6C;AAC7C,yDAG8B;AAE9B,sDAA6B;AAG7B,SAAS,oBAAoB,CAC3B,OAAO,EACP,EACE,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,YAAY,EACZ,oBAAoB,GACb;IAET,MAAM,EAAE,iBAAiB,EAAE,GAAG,IAAA,qBAAe,EAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACpE,MAAM,EAAE,UAAU,EAAE,GAAG,IAAA,uBAAgB,EAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;IACtE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAA,qBAAe,EAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;IACpE,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,2BAAkB,EAAC,OAAO,CAAC,CAAA;IAEpD,MAAM,gBAAgB,GAAG,IAAA,gDAAgC,EACvD,iBAAiB,EACjB,aAAa,CACd,CAAA;IACD,MAAM,gBAAgB,GAAG,IAAA,yCAAyB,EAAC,SAAS,CAAC,CAAA;IAE7D,MAAM,cAAc,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAA;IAEvC,cAAc;SACX,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,CAAC;SAC/D,IAAI,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,CAAC;SACzD,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,CAAC;SACvD,MAAM,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAA;IAEhE,OAAO,cAAc,CAAA;AACvB,CAAC;AAsBC,oDAAoB;AApBtB,iDAAiD;AACjD,MAAM,UAAU,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAA;AAoBjC,gCAAU;AAnBZ,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAO,CAAC,EAAE,GAAG,EAAE,EAAE;IACnC,OAAO,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AAChC,CAAC,CAAA,CAAC,CAAA;AAEF,sDAAsD;AACtD,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAA;AAE3C,0CAA0C;AAC1C,SAAS,sBAAsB,CAAC,CAAC,EAAE,GAAG;IACpC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAA;AACzD,CAAC;AAUC,wDAAsB;AARxB,gCAAgC;AAChC,SAAS,cAAc,CAAC,CAAC,EAAE,GAAG;IAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAA;AAChD,CAAC;AAMC,wCAAc"}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/routes/routes.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,+BAAuC;AACvC,iCAAyC;AACzC,+BAAuC;AACvC,mCAA2C;AAC3C,qCAA6C;AAC7C,yDAG8B;AAE9B,sDAA6B;AAG7B,SAAS,oBAAoB,CAAC,OAAO,EAAE,YAA0B;IAC/D,MAAM,EACJ,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,uBAAuB,GACxB,GAAG,YAAY,CAAA;IAEhB,MAAM,EAAE,iBAAiB,EAAE,GAAG,IAAA,qBAAe,EAC3C,OAAO,EACP,YAAY,EACZ,uBAAuB,CACxB,CAAA;IACD,MAAM,EAAE,UAAU,EAAE,GAAG,IAAA,uBAAgB,EAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;IACtE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAA,qBAAe,EAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;IACpE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAA,yBAAiB,EAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;IACxE,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,2BAAkB,EAAC,OAAO,CAAC,CAAA;IAEpD,MAAM,gBAAgB,GAAG,IAAA,gDAAgC,EACvD,iBAAiB,EACjB,aAAa,CACd,CAAA;IACD,MAAM,gBAAgB,GAAG,IAAA,yCAAyB,EAAC,SAAS,CAAC,CAAA;IAE7D,MAAM,cAAc,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAA;IAEvC,cAAc;QACZ,6GAA6G;SAC5G,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,CAAC;SAC/D,IAAI,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,CAAC;SACzD,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,CAAC;SACvD,KAAK,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,CAAC;SAC3D,MAAM,CAAC,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAA;IAEhE,OAAO,cAAc,CAAA;AACvB,CAAC;AAsBC,oDAAoB;AApBtB,iDAAiD;AACjD,MAAM,UAAU,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAA;AAoBjC,gCAAU;AAnBZ,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAO,CAAC,EAAE,GAAG,EAAE,EAAE;IACnC,OAAO,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AAChC,CAAC,CAAA,CAAC,CAAA;AAEF,sDAAsD;AACtD,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAA;AAE3C,0CAA0C;AAC1C,SAAS,sBAAsB,CAAC,CAAC,EAAE,GAAG;IACpC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAA;AACzD,CAAC;AAUC,wDAAsB;AARxB,gCAAgC;AAChC,SAAS,cAAc,CAAC,CAAC,EAAE,GAAG;IAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAA;AAChD,CAAC;AAMC,wCAAc"}
package/routes/types.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export declare type ValidatorCallback = (resourceName: string, requestBody: unknown) => void | string | object;
2
2
  export declare type RequestBodyValidator = {
3
- post: ValidatorCallback;
4
- put: ValidatorCallback;
3
+ post?: ValidatorCallback;
4
+ patch?: ValidatorCallback;
5
+ put?: ValidatorCallback;
5
6
  };
7
+ export declare type ResponseBodyInterceptor = (resourceName: string, responseBody: unknown, id?: string) => unknown;