temba 0.10.4 → 0.10.5

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/dist/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # Temba
2
+
3
+ [![Temba on NPM](https://img.shields.io/npm/v/temba)](https://www.npmjs.com/package/temba)
4
+ <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
5
+ [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
6
+ <!-- ALL-CONTRIBUTORS-BADGE:END -->
7
+
8
+ **Get a simple MongoDB REST API with zero coding in less than 30 seconds (seriously).**
9
+
10
+ For developers who need a quick backend for small projects.
11
+
12
+ Powered by NodeJS, Express and MongoDB.
13
+
14
+ This project is inspired by the fantastic [json-server](https://github.com/typicode/json-server) project, but instead of a JSON file Temba uses a real database. The goal, however, is the same: Get you started with a REST API very quickly.
15
+
16
+ ## Table of contents
17
+
18
+ [Temba?](#temba-1)
19
+
20
+ [Getting Started](#getting-started)
21
+
22
+ [What Tema does](#what-temba-does)
23
+
24
+ [Usage](#usage)
25
+
26
+ ## Temba?
27
+
28
+ > _"Temba, at rest"_
29
+
30
+ A metaphor for the declining of a gift, from the [Star Trek - The Next Generation, episode "Darmok"](https://memory-alpha.fandom.com/wiki/Temba).
31
+
32
+ In the fictional Tamarian language the word _"Temba"_ means something like _"gift"_.
33
+
34
+ ## Getting Started
35
+
36
+ Prerequisites you need to have:
37
+
38
+ - Node, NPM
39
+ - Optional: A MongoDB database, either locally or in the cloud
40
+
41
+ > Wthout a database, Temba also works. However, then data is kept in memory and flushed every time the server restarts.
42
+
43
+ ### Use the starter with `npx`
44
+
45
+ Create your own Temba server with the following command and you are up and running!
46
+
47
+ ```bash
48
+ npx create-temba-server my-rest-api
49
+ ```
50
+
51
+ This command clones the [Temba-starter](https://github.com/bouwe77/temba-starter) repository and installs all dependencies.
52
+
53
+ ### Manually adding to an existing app
54
+
55
+ Alternatively, add Temba to your app manually:
56
+
57
+ 1. `npm i temba`
58
+
59
+ 2. Example code to create a Temba server:
60
+
61
+ ```js
62
+ import temba from ("temba");
63
+ const server = temba.create();
64
+
65
+ const port = process.env.PORT || 3000;
66
+ server.listen(port, () => {
67
+ console.log(`Temba is running on port ${port}`);
68
+ });
69
+ ```
70
+
71
+ ### Configuration
72
+
73
+ By passing a config object to the `create` function you can customize Temba's behavior. Refer to the [config settings](#config-settings-overview) below for the various possibilities.
74
+
75
+ ## What Temba does
76
+
77
+ Out of the box, Temba gives you a CRUD REST API to any resource name you can think of.
78
+
79
+ 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.
80
+
81
+ For every resource (`movies` is just an example), Temba supports the following requests:
82
+
83
+ - `GET /movies` - Get all movies
84
+ - `GET /movies/:id` - Get a movie by its ID
85
+ - `POST /movies` - Create a new movie
86
+ - `PUT /movies/:id` - Update (fully replace) a movie by its ID
87
+ - `DELETE /movies` - Delete all movies
88
+ - `DELETE /movies/:id` - Delete a movie by its ID
89
+
90
+ ### Supported HTTP methods
91
+
92
+ 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.
93
+
94
+ 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.
95
+
96
+ ### JSON
97
+
98
+ Temba supports JSON only.
99
+
100
+ 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.
101
+
102
+ 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.
103
+
104
+ IDs are auto generated when creating resources. IDs in the JSON request body are ignored for any request.
105
+
106
+ ## Usage
107
+
108
+ ### In-memory or MongoDB
109
+
110
+ By default data is stored in memory. This means the data is flushed when the server restarts. To persist your data, provide the `connectionString` config setting for your MongoDB database:
111
+
112
+ ```js
113
+ const config = {
114
+ connectionString: 'mongodb://localhost:27017',
115
+ }
116
+ const server = temba.create(config)
117
+ ```
118
+
119
+ For every resource you use in your requests, a collection is created in the database. However, not until you actually store (create) a resource with a `POST`.
120
+
121
+ ### Allowing specific resources only
122
+
123
+ If you only want to allow specific resource names, configure them by providing a `resourceNames` key in the config object when creating the Temba server:
124
+
125
+ ```js
126
+ const config = { resourceNames: ['movies', 'actors'] }
127
+ const server = temba.create(config)
128
+ ```
129
+
130
+ Requests on these resources only give a `404 Not Found` if the ID does not exist. Requests on any other resource will always return a `404 Not Found`.
131
+
132
+ ### Static assets
133
+
134
+ If you want to host static assets next to the REST API, configure the `staticFolder`:
135
+
136
+ ```js
137
+ const config = { staticFolder: 'build' }
138
+ const server = temba.create(config)
139
+ ```
140
+
141
+ This way, you could create a REST API, and the web app consuming it, in one project.
142
+
143
+ 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:
144
+
145
+ ### REST URIs prefixes
146
+
147
+ 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`:
148
+
149
+ ```js
150
+ const config = { apiPrefix: 'api' }
151
+ const server = temba.create(config)
152
+ ```
153
+
154
+ 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.
155
+
156
+ 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.
157
+
158
+ ### Request body validation or mutation
159
+
160
+ Temba does not validate request bodies.
161
+
162
+ This means you can store your resources in any format you like. So creating the following two (very different) _movies_ is perfectly fine:
163
+
164
+ ```
165
+ POST /movies
166
+ {
167
+ "title": "O Brother, Where Art Thou?",
168
+ "description": "In the deep south during the 1930s, three escaped convicts search for hidden treasure while a relentless lawman pursues them."
169
+ }
170
+
171
+ POST /movies
172
+ {
173
+ "foo": "bar",
174
+ "baz": "boo"
175
+ }
176
+ ```
177
+
178
+ 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:
179
+
180
+ If you want to do input validation before the `POST` or `PUT` request body is saved to the database, configure Temba as follows:
181
+
182
+ ```js
183
+ const config = {
184
+ requestBodyValidator: {
185
+ post: (resourceName, requestBody) => {
186
+ // Validate, or even change the requestBody
187
+ },
188
+ put: (resourceName, requestBody) => {
189
+ // Validate, or even change the requestBody
190
+ },
191
+ },
192
+ }
193
+
194
+ const server = temba.create(config)
195
+ ```
196
+
197
+ 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.
198
+
199
+ 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.
200
+
201
+ Your callback function can return the following things:
202
+
203
+ - `void`: Temba will just save the request body as-is. An example of this is when you have validated the request body and everything looks fine.
204
+ - `string`: If you return a string Temba will return a `400 Bad Request` with the string as error message.
205
+ - `object`: Return an object if you want to change the request body. Temba will save the returned object instead of the original request body.
206
+
207
+ Example:
208
+
209
+ ```js
210
+ const config = {
211
+ requestBodyValidator: {
212
+ post: (resourceName, requestBody) => {
213
+ // Do not allow Pokemons to be created: 400 Bad Request
214
+ if (resourceName === 'pokemons')
215
+ return 'You are not allowed to create new Pokemons'
216
+
217
+ // Add a genre to Star Trek films:
218
+ if (
219
+ resourceName === 'movies' &&
220
+ requestBody.title.startsWith('Star Trek')
221
+ )
222
+ return { ...requestBody, genre: 'Science Fiction' }
223
+
224
+ // If you end up here, void will be returned, so the request will just be saved.
225
+ },
226
+ },
227
+ }
228
+
229
+ const server = temba.create(config)
230
+ ```
231
+
232
+ ### Config settings overview
233
+
234
+ Configuring Temba is optional, it already works out of the box.
235
+
236
+ Here is an example of the config settings for Temba, and how you define them:
237
+
238
+ ```js
239
+ const config = {
240
+ resourceNames: ['movies', 'actors'],
241
+ connectionString: 'mongodb://localhost:27017',
242
+ staticFolder: 'build',
243
+ apiPrefix: 'api',
244
+ cacheControl: 'public, max-age=300',
245
+ delay: 500,
246
+ requestBodyValidator: {
247
+ post: (resourceName, requestBody) => {
248
+ // Validate, or even change the requestBody
249
+ },
250
+ put: (resourceName, requestBody) => {
251
+ // Validate, or even change the requestBody
252
+ },
253
+ },
254
+ }
255
+ const server = temba.create(config)
256
+ ```
257
+
258
+ 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.
259
+
260
+ These are all the possible settings:
261
+
262
+ | Config setting | Description |
263
+ | :--------------------- | :----------------------------------------------------------------------------------------- |
264
+ | `resourceNames` | See [Allowing specific resources only](#allowing-specific-resources-only) |
265
+ | `connectionString` | See [MongoDB](#mongodb) |
266
+ | `staticFolder` | See [Static assets](#static-assets) |
267
+ | `apiPrefix` | See [REST URIs prefixes](#rest-uris-prefixes) |
268
+ | `cacheControl` | The `Cache-control` response header value for each GET request. |
269
+ | `delay` | After processing the request, the delay in milliseconds before the request should be sent. |
270
+ | `requestBodyValidator` | See [Request body validation or mutation](#request-body-validation-or-mutation) |
271
+
272
+ ## Not supported (yet?)
273
+
274
+ The following features would be very nice for Temba to support:
275
+
276
+ ### `PATCH` requests
277
+
278
+ Partial updates using `PATCH`, or other HTTP methods are not (yet?) supported.
279
+
280
+ ### Auth
281
+
282
+ 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.
283
+
284
+ ### Nested parent-child resources
285
+
286
+ 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.
287
+
288
+ ### Filtering and sorting
289
+
290
+ And there is no filtering, sorting, searching, etc. (yet?).
291
+
292
+ ## Under the hood
293
+
294
+ 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).
295
+
296
+ ## Which problem does Temba solve?
297
+
298
+ The problem with JSON file solutions like json-server is the limitations you have when hosting your app, because your data is stored in a file.
299
+
300
+ For example, hosting json-server on GitHub Pages means your API is essentially readonly, because, although mutations are supported, your data is not really persisted.
301
+
302
+ And hosting json-server on Heroku does give you persistence, but is not reliable because of its [ephemeral filesystem](https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem).
303
+
304
+ These limitations are of course the whole idea behind json-server, it's for simple mocking and prototyping. But if you want more (persistence wise) and don't mind having a database, you might want to try Temba.
305
+
306
+ ## Contributors ✨
307
+
308
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
309
+
310
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
311
+ <!-- prettier-ignore-start -->
312
+ <!-- markdownlint-disable -->
313
+ <table>
314
+ <tr>
315
+ <td align="center"><a href="https://bouwe.io"><img src="https://avatars.githubusercontent.com/u/4126793?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bouwe K. Westerdijk</b></sub></a><br /><a href="https://github.com/bouwe77/temba/commits?author=bouwe77" title="Code">💻</a> <a href="https://github.com/bouwe77/temba/issues?q=author%3Abouwe77" title="Bug reports">🐛</a> <a href="https://github.com/bouwe77/temba/commits?author=bouwe77" title="Documentation">📖</a> <a href="#ideas-bouwe77" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/bouwe77/temba/commits?author=bouwe77" title="Tests">⚠️</a></td>
316
+ </tr>
317
+ </table>
318
+
319
+ <!-- markdownlint-restore -->
320
+ <!-- prettier-ignore-end -->
321
+
322
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
323
+
324
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initConfig = void 0;
4
+ const defaultConfig = {
5
+ resourceNames: [],
6
+ validateResources: false,
7
+ staticFolder: null,
8
+ apiPrefix: '',
9
+ connectionString: null,
10
+ cacheControl: 'no-store',
11
+ delay: 0,
12
+ requestBodyValidator: {
13
+ post: () => {
14
+ // do nothing
15
+ },
16
+ put: () => {
17
+ // do nothing
18
+ },
19
+ },
20
+ };
21
+ function initConfig(userConfig) {
22
+ if (!userConfig)
23
+ return defaultConfig;
24
+ const config = Object.assign({}, defaultConfig);
25
+ if (userConfig.resourceNames && userConfig.resourceNames.length > 0) {
26
+ config.resourceNames = userConfig.resourceNames;
27
+ config.validateResources = true;
28
+ }
29
+ if (userConfig.staticFolder) {
30
+ config.staticFolder = userConfig.staticFolder.replace(/[^a-zA-Z0-9]/g, '');
31
+ }
32
+ if (userConfig.apiPrefix) {
33
+ config.apiPrefix =
34
+ '/' + userConfig.apiPrefix.replace(/[^a-zA-Z0-9]/g, '') + '/';
35
+ }
36
+ if (userConfig.connectionString && userConfig.connectionString.length > 0) {
37
+ config.connectionString = userConfig.connectionString;
38
+ }
39
+ if (userConfig.cacheControl && userConfig.cacheControl.length > 0) {
40
+ config.cacheControl = userConfig.cacheControl;
41
+ }
42
+ if (userConfig.delay &&
43
+ userConfig.delay !== 0 &&
44
+ typeof Number(userConfig.delay) === 'number' &&
45
+ Number(userConfig.delay) > 0 &&
46
+ Number(userConfig.delay) < 10000) {
47
+ config.delay = Number(userConfig.delay);
48
+ }
49
+ if (userConfig.requestBodyValidator) {
50
+ if (userConfig.requestBodyValidator.post &&
51
+ typeof userConfig.requestBodyValidator.post === 'function') {
52
+ config.requestBodyValidator.post = userConfig.requestBodyValidator.post;
53
+ }
54
+ if (userConfig.requestBodyValidator.put &&
55
+ typeof userConfig.requestBodyValidator.put === 'function') {
56
+ config.requestBodyValidator.put = userConfig.requestBodyValidator.put;
57
+ }
58
+ }
59
+ return config;
60
+ }
61
+ exports.initConfig = initConfig;
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDelayMiddleware = void 0;
7
+ const connect_pause_1 = __importDefault(require("connect-pause"));
8
+ function createDelayMiddleware(delay) {
9
+ return function (req, res, next) {
10
+ console.log('Start delay...');
11
+ (0, connect_pause_1.default)(delay)(req, res, next);
12
+ console.log('Delay finished!');
13
+ };
14
+ }
15
+ exports.createDelayMiddleware = createDelayMiddleware;
16
+ //# sourceMappingURL=delayMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delayMiddleware.js","sourceRoot":"","sources":["../../src/delay/delayMiddleware.ts"],"names":[],"mappings":";;;;;;AAAA,kEAAiC;AAEjC,SAAS,qBAAqB,CAAC,KAAK;IAClC,OAAO,UAAU,GAAG,EAAE,GAAG,EAAE,IAAI;QAC7B,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;QAC7B,IAAA,uBAAK,EAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;QAC5B,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAChC,CAAC,CAAA;AACH,CAAC;AAEQ,sDAAqB"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.new500InternalServerError = exports.new400BadRequestError = exports.new404NotFoundError = exports.errorHandler = exports.HttpError = void 0;
4
+ class HttpError extends Error {
5
+ constructor(status, message) {
6
+ super(message);
7
+ this.status = status;
8
+ this.message = message;
9
+ }
10
+ }
11
+ exports.HttpError = HttpError;
12
+ // This is the error middleware that will be used by the server.
13
+ function errorHandler(e, _, res) {
14
+ const status = 500;
15
+ let message = 'Unknown error';
16
+ if (e instanceof Error) {
17
+ message = e.message;
18
+ }
19
+ return res.status(status).json({ message });
20
+ }
21
+ exports.errorHandler = errorHandler;
22
+ function new404NotFoundError(message = 'Not Found') {
23
+ return new HttpError(404, message);
24
+ }
25
+ exports.new404NotFoundError = new404NotFoundError;
26
+ function new400BadRequestError(message = 'Bad Request') {
27
+ return new HttpError(400, message);
28
+ }
29
+ exports.new400BadRequestError = new400BadRequestError;
30
+ function new500InternalServerError(message = 'Internal Server Error') {
31
+ return new HttpError(500, message);
32
+ }
33
+ exports.new500InternalServerError = new500InternalServerError;
34
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors/errors.ts"],"names":[],"mappings":";;;AAAA,MAAa,SAAU,SAAQ,KAAK;IAIlC,YAAY,MAAc,EAAE,OAAe;QACzC,KAAK,CAAC,OAAO,CAAC,CAAA;QAEd,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;CACF;AAVD,8BAUC;AAED,gEAAgE;AAChE,SAAS,YAAY,CAAC,CAAU,EAAE,CAAC,EAAE,GAAG;IACtC,MAAM,MAAM,GAAG,GAAG,CAAA;IAClB,IAAI,OAAO,GAAG,eAAe,CAAA;IAE7B,IAAI,CAAC,YAAY,KAAK,EAAE;QACtB,OAAO,GAAG,CAAC,CAAC,OAAO,CAAA;KACpB;IAED,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;AAC7C,CAAC;AAeC,oCAAY;AAbd,SAAS,mBAAmB,CAAC,OAAO,GAAG,WAAW;IAChD,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AAYC,kDAAmB;AAVrB,SAAS,qBAAqB,CAAC,OAAO,GAAG,aAAa;IACpD,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AASC,sDAAqB;AAPvB,SAAS,yBAAyB,CAAC,OAAO,GAAG,uBAAuB;IAClE,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AAMC,8DAAyB"}
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "temba",
3
+ "version": "0.10.5",
4
+ "description": "Get a simple MongoDB REST API with zero coding in less than 30 seconds (seriously).",
5
+ "main": "dist/server.ts",
6
+ "scripts": {
7
+ "build": "rm -rf dist && tsc && cp package.json README.md ./dist",
8
+ "test": "jest --watch",
9
+ "lint": "eslint --ignore-path .gitignore --ext .js,.ts .",
10
+ "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/bouwe77/temba.git"
15
+ },
16
+ "homepage": "https://github.com/bouwe77/temba#readme",
17
+ "author": "Bouwe (https://bouwe.io)",
18
+ "license": "ISC",
19
+ "files": [
20
+ "dist/**",
21
+ "package.json",
22
+ "README.md"
23
+ ],
24
+ "devDependencies": {
25
+ "@types/cors": "^2.8.12",
26
+ "@types/express": "^4.17.13",
27
+ "@types/jest": "^27.4.0",
28
+ "@types/morgan": "^1.9.3",
29
+ "@types/supertest": "^2.0.11",
30
+ "@typescript-eslint/eslint-plugin": "^5.10.0",
31
+ "@typescript-eslint/parser": "^5.10.0",
32
+ "eslint": "^8.7.0",
33
+ "eslint-config-prettier": "^8.3.0",
34
+ "jest": "^27.4.7",
35
+ "jest-extended": "^0.11.5",
36
+ "prettier": "^2.5.1",
37
+ "supertest": "^6.2.2",
38
+ "ts-jest": "^27.1.3",
39
+ "typescript": "^4.5.5"
40
+ },
41
+ "dependencies": {
42
+ "@rakered/mongo": "^1.6.0",
43
+ "connect-pause": "^0.1.0",
44
+ "cors": "^2.8.5",
45
+ "express": "^4.17.1",
46
+ "morgan": "^1.10.0"
47
+ }
48
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const data = {};
4
+ function connectToDatabase() {
5
+ // do nothing
6
+ }
7
+ function getAll(resourceName) {
8
+ createResourceArrayIfNecessary(resourceName);
9
+ return new Promise((resolve) => {
10
+ resolve(data[resourceName]);
11
+ });
12
+ }
13
+ function getById(resourceName, id) {
14
+ createResourceArrayIfNecessary(resourceName);
15
+ return new Promise((resolve) => {
16
+ resolve(data[resourceName].find((item) => item.id === id));
17
+ });
18
+ }
19
+ function create(resourceName, item) {
20
+ createResourceArrayIfNecessary(resourceName);
21
+ const newItem = Object.assign(Object.assign({}, item), { id: String(new Date().getTime()) });
22
+ data[resourceName] = [...data[resourceName], newItem];
23
+ return new Promise((resolve) => {
24
+ resolve(newItem);
25
+ });
26
+ }
27
+ function update(resourceName, item) {
28
+ createResourceArrayIfNecessary(resourceName);
29
+ const updatedItem = Object.assign({}, item);
30
+ data[resourceName] = [
31
+ ...data[resourceName].filter((r) => r.id !== item.id),
32
+ updatedItem,
33
+ ];
34
+ return new Promise((resolve) => {
35
+ resolve(updatedItem);
36
+ });
37
+ }
38
+ function deleteById(resourceName, id) {
39
+ createResourceArrayIfNecessary(resourceName);
40
+ data[resourceName] = data[resourceName].filter((item) => item.id !== id);
41
+ return new Promise((resolve) => {
42
+ resolve();
43
+ });
44
+ }
45
+ function deleteAll(resourceName) {
46
+ createResourceArrayIfNecessary(resourceName);
47
+ data[resourceName] = [];
48
+ return new Promise((resolve) => {
49
+ resolve([]);
50
+ });
51
+ }
52
+ function createResourceArrayIfNecessary(resourceName) {
53
+ if (!data.hasOwnProperty(resourceName))
54
+ data[resourceName] = [];
55
+ }
56
+ exports.default = {
57
+ connectToDatabase,
58
+ getAll,
59
+ getById,
60
+ create,
61
+ update,
62
+ deleteById,
63
+ deleteAll,
64
+ };
65
+ //# sourceMappingURL=in-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory.js","sourceRoot":"","sources":["../../src/queries/in-memory.ts"],"names":[],"mappings":";;AAAA,MAAM,IAAI,GAAG,EAAE,CAAA;AAEf,SAAS,iBAAiB;IACxB,aAAa;AACf,CAAC;AAED,SAAS,MAAM,CAAC,YAAY;IAC1B,8BAA8B,CAAC,YAAY,CAAC,CAAA;IAE5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,YAAY,EAAE,EAAE;IAC/B,8BAA8B,CAAC,YAAY,CAAC,CAAA;IAE5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,YAAY,EAAE,IAAI;IAChC,8BAA8B,CAAC,YAAY,CAAC,CAAA;IAE5C,MAAM,OAAO,mCAAQ,IAAI,KAAE,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,GAAE,CAAA;IAE7D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAA;IAErD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,OAAO,CAAC,CAAA;IAClB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,YAAY,EAAE,IAAI;IAChC,8BAA8B,CAAC,YAAY,CAAC,CAAA;IAE5C,MAAM,WAAW,qBAAQ,IAAI,CAAE,CAAA;IAC/B,IAAI,CAAC,YAAY,CAAC,GAAG;QACnB,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,WAAW;KACZ,CAAA;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,WAAW,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,YAAY,EAAE,EAAE;IAClC,8BAA8B,CAAC,YAAY,CAAC,CAAA;IAE5C,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IACxE,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,OAAO,EAAE,CAAA;IACX,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,YAAY;IAC7B,8BAA8B,CAAC,YAAY,CAAC,CAAA;IAE5C,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAA;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,EAAE,CAAC,CAAA;IACb,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,8BAA8B,CAAC,YAAY;IAClD,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC;QAAE,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAA;AACjE,CAAC;AAED,kBAAe;IACb,iBAAiB;IACjB,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;CACV,CAAA"}
@@ -0,0 +1,87 @@
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
+ const mongo_1 = require("@rakered/mongo");
13
+ let uri;
14
+ let db;
15
+ function createMongoQueries(connectionString) {
16
+ uri = connectionString;
17
+ return {
18
+ connectToDatabase,
19
+ getAll,
20
+ getById,
21
+ create,
22
+ update,
23
+ deleteById,
24
+ deleteAll,
25
+ };
26
+ }
27
+ exports.default = createMongoQueries;
28
+ function connectToDatabase() {
29
+ return __awaiter(this, void 0, void 0, function* () {
30
+ if (!db) {
31
+ console.log('Connecting...');
32
+ db = yield (0, mongo_1.connect)(uri);
33
+ }
34
+ });
35
+ }
36
+ function getAll(resourceName) {
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ yield connectToDatabase();
39
+ const items = yield db[resourceName].find({});
40
+ if (!items)
41
+ return [];
42
+ return items.map((item) => removeUnderscoreFromId(item));
43
+ });
44
+ }
45
+ function getById(resourceName, id) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ yield connectToDatabase();
48
+ const item = yield db[resourceName].findOne({ _id: id });
49
+ if (!item)
50
+ return null;
51
+ return removeUnderscoreFromId(item);
52
+ });
53
+ }
54
+ function create(resourceName, item) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ yield connectToDatabase();
57
+ const createdItem = yield db[resourceName].insertOne(item);
58
+ return removeUnderscoreFromId(createdItem.ops[0]);
59
+ });
60
+ }
61
+ function update(resourceName, item) {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ yield connectToDatabase();
64
+ const id = item.id;
65
+ delete item.id;
66
+ const updatedItem = yield db[resourceName].findOneAndUpdate({ _id: id }, { $set: item }, { returnOriginal: false });
67
+ return removeUnderscoreFromId(updatedItem.value);
68
+ });
69
+ }
70
+ function deleteById(resourceName, id) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ yield connectToDatabase();
73
+ yield db[resourceName].deleteOne({ _id: id });
74
+ });
75
+ }
76
+ function deleteAll(resourceName) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ yield connectToDatabase();
79
+ yield db[resourceName].deleteMany({});
80
+ });
81
+ }
82
+ function removeUnderscoreFromId(item) {
83
+ const updatedItem = Object.assign(Object.assign({}, item), { id: item._id });
84
+ delete updatedItem._id;
85
+ return updatedItem;
86
+ }
87
+ //# sourceMappingURL=mongo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongo.js","sourceRoot":"","sources":["../../src/queries/mongo.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,0CAAwC;AAExC,IAAI,GAAG,CAAA;AACP,IAAI,EAAE,CAAA;AAEN,SAAwB,kBAAkB,CAAC,gBAAgB;IACzD,GAAG,GAAG,gBAAgB,CAAA;IAEtB,OAAO;QACL,iBAAiB;QACjB,MAAM;QACN,OAAO;QACP,MAAM;QACN,MAAM;QACN,UAAU;QACV,SAAS;KACV,CAAA;AACH,CAAC;AAZD,qCAYC;AAED,SAAe,iBAAiB;;QAC9B,IAAI,CAAC,EAAE,EAAE;YACP,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;YAC5B,EAAE,GAAG,MAAM,IAAA,eAAO,EAAC,GAAG,CAAC,CAAA;SACxB;IACH,CAAC;CAAA;AAED,SAAe,MAAM,CAAC,YAAY;;QAChC,MAAM,iBAAiB,EAAE,CAAA;QAEzB,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAE7C,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAA;QAErB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAA;IAC1D,CAAC;CAAA;AAED,SAAe,OAAO,CAAC,YAAY,EAAE,EAAE;;QACrC,MAAM,iBAAiB,EAAE,CAAA;QAEzB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAA;QAExD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QAEtB,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC;CAAA;AAED,SAAe,MAAM,CAAC,YAAY,EAAE,IAAI;;QACtC,MAAM,iBAAiB,EAAE,CAAA;QAEzB,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QAE1D,OAAO,sBAAsB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACnD,CAAC;CAAA;AAED,SAAe,MAAM,CAAC,YAAY,EAAE,IAAI;;QACtC,MAAM,iBAAiB,EAAE,CAAA;QAEzB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAA;QAClB,OAAO,IAAI,CAAC,EAAE,CAAA;QAEd,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC,gBAAgB,CACzD,EAAE,GAAG,EAAE,EAAE,EAAE,EACX,EAAE,IAAI,EAAE,IAAI,EAAE,EACd,EAAE,cAAc,EAAE,KAAK,EAAE,CAC1B,CAAA;QAED,OAAO,sBAAsB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAClD,CAAC;CAAA;AAED,SAAe,UAAU,CAAC,YAAY,EAAE,EAAE;;QACxC,MAAM,iBAAiB,EAAE,CAAA;QAEzB,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAA;IAC/C,CAAC;CAAA;AAED,SAAe,SAAS,CAAC,YAAY;;QACnC,MAAM,iBAAiB,EAAE,CAAA;QAEzB,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;IACvC,CAAC;CAAA;AAED,SAAS,sBAAsB,CAAC,IAAI;IAClC,MAAM,WAAW,mCAAQ,IAAI,KAAE,EAAE,EAAE,IAAI,CAAC,GAAG,GAAE,CAAA;IAC7C,OAAO,WAAW,CAAC,GAAG,CAAA;IACtB,OAAO,WAAW,CAAA;AACpB,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createQueries = void 0;
7
+ const in_memory_1 = __importDefault(require("./in-memory"));
8
+ const mongo_1 = __importDefault(require("./mongo"));
9
+ function createQueries(connectionString) {
10
+ if (!connectionString) {
11
+ return in_memory_1.default;
12
+ }
13
+ const mongoQueries = (0, mongo_1.default)(connectionString);
14
+ return mongoQueries;
15
+ }
16
+ exports.createQueries = createQueries;
17
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/queries/queries.ts"],"names":[],"mappings":";;;;;;AAAA,4DAAyC;AACzC,oDAAwC;AAExC,SAAS,aAAa,CAAC,gBAAgB;IACrC,IAAI,CAAC,gBAAgB,EAAE;QACrB,OAAO,mBAAe,CAAA;KACvB;IAED,MAAM,YAAY,GAAG,IAAA,eAAkB,EAAC,gBAAgB,CAAC,CAAA;IACzD,OAAO,YAAY,CAAA;AACrB,CAAC;AAEQ,sCAAa"}
@@ -0,0 +1,39 @@
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.createDeleteRoutes = void 0;
13
+ function createDeleteRoutes(queries) {
14
+ function handleDelete(req, res, next) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ try {
17
+ const { resourceName, id } = req.requestInfo;
18
+ if (id) {
19
+ const item = yield queries.getById(resourceName, id);
20
+ if (item) {
21
+ yield queries.deleteById(resourceName, id);
22
+ }
23
+ }
24
+ else {
25
+ yield queries.deleteAll(resourceName);
26
+ }
27
+ }
28
+ catch (error) {
29
+ return next(error);
30
+ }
31
+ return res.status(204).send();
32
+ });
33
+ }
34
+ return {
35
+ handleDelete,
36
+ };
37
+ }
38
+ exports.createDeleteRoutes = createDeleteRoutes;
39
+ //# sourceMappingURL=delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/routes/delete.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,SAAS,kBAAkB,CAAC,OAAO;IACjC,SAAe,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YACxC,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAE5C,IAAI,EAAE,EAAE;oBACN,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;oBACpD,IAAI,IAAI,EAAE;wBACR,MAAM,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;qBAC3C;iBACF;qBAAM;oBACL,MAAM,OAAO,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;iBACtC;aACF;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;YAED,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;QAC/B,CAAC;KAAA;IAED,OAAO;QACL,YAAY;KACb,CAAA;AACH,CAAC;AAEQ,gDAAkB"}
@@ -0,0 +1,44 @@
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.createGetRoutes = void 0;
13
+ function createGetRoutes(queries, cacheControl) {
14
+ function handleGetResource(req, res, next) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ try {
17
+ const { resourceName, id } = req.requestInfo;
18
+ res.set('Cache-control', cacheControl);
19
+ if (id) {
20
+ const item = yield queries.getById(resourceName, id);
21
+ if (!item) {
22
+ res.status(404);
23
+ return res.send();
24
+ }
25
+ res.status(200);
26
+ res.json(item);
27
+ return res.send();
28
+ }
29
+ const items = yield queries.getAll(resourceName);
30
+ res.status(200);
31
+ res.json(items);
32
+ return res.send();
33
+ }
34
+ catch (error) {
35
+ return next(error);
36
+ }
37
+ });
38
+ }
39
+ return {
40
+ handleGetResource,
41
+ };
42
+ }
43
+ exports.createGetRoutes = createGetRoutes;
44
+ //# sourceMappingURL=get.js.map
@@ -0,0 +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"}
@@ -0,0 +1,46 @@
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.createPostRoutes = void 0;
13
+ const url_1 = require("url");
14
+ const validator_1 = require("./validator");
15
+ function createPostRoutes(queries, requestBodyValidator) {
16
+ function handlePost(req, res, next) {
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ try {
19
+ const { resourceName } = req.requestInfo;
20
+ const requestBody = (0, validator_1.validateRequestBody)(requestBodyValidator.post, req);
21
+ if (typeof requestBody === 'string')
22
+ return res.status(400).json({ message: requestBody }).send();
23
+ const newItem = yield queries.create(resourceName, requestBody);
24
+ return res
25
+ .set({
26
+ Location: (0, url_1.format)({
27
+ protocol: req.protocol,
28
+ host: req.get('host'),
29
+ pathname: `${resourceName}/${newItem.id}`,
30
+ }),
31
+ })
32
+ .status(201)
33
+ .json(newItem)
34
+ .send();
35
+ }
36
+ catch (error) {
37
+ return next(error);
38
+ }
39
+ });
40
+ }
41
+ return {
42
+ handlePost,
43
+ };
44
+ }
45
+ exports.createPostRoutes = createPostRoutes;
46
+ //# sourceMappingURL=post.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post.js","sourceRoot":"","sources":["../../src/routes/post.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAA4B;AAC5B,2CAAiD;AAEjD,SAAS,gBAAgB,CAAC,OAAO,EAAE,oBAAoB;IACrD,SAAe,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;;YACtC,IAAI;gBACF,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;gBAExC,MAAM,WAAW,GAAG,IAAA,+BAAmB,EAAC,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;gBAEvE,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,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;gBAE/D,OAAO,GAAG;qBACP,GAAG,CAAC;oBACH,QAAQ,EAAE,IAAA,YAAM,EAAC;wBACf,QAAQ,EAAE,GAAG,CAAC,QAAQ;wBACtB,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;wBACrB,QAAQ,EAAE,GAAG,YAAY,IAAI,OAAO,CAAC,EAAE,EAAE;qBAC1C,CAAC;iBACH,CAAC;qBACD,MAAM,CAAC,GAAG,CAAC;qBACX,IAAI,CAAC,OAAO,CAAC;qBACb,IAAI,EAAE,CAAA;aACV;YAAC,OAAO,KAAc,EAAE;gBACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;aACnB;QACH,CAAC;KAAA;IAED,OAAO;QACL,UAAU;KACX,CAAA;AACH,CAAC;AAEQ,4CAAgB"}
@@ -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.createPutRoutes = void 0;
13
+ const errors_1 = require("../errors/errors");
14
+ const validator_1 = require("./validator");
15
+ function createPutRoutes(queries, requestBodyValidator) {
16
+ function handlePut(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.put, 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({}, 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
+ handlePut,
40
+ };
41
+ }
42
+ exports.createPutRoutes = createPutRoutes;
43
+ //# sourceMappingURL=put.js.map
@@ -0,0 +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"}
@@ -0,0 +1,56 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.handleNotFound = exports.handleMethodNotAllowed = exports.rootRouter = exports.createResourceRouter = void 0;
16
+ const get_1 = require("./get");
17
+ const post_1 = require("./post");
18
+ const put_1 = require("./put");
19
+ const delete_1 = require("./delete");
20
+ const urlMiddleware_1 = require("../urls/urlMiddleware");
21
+ const express_1 = __importDefault(require("express"));
22
+ function createResourceRouter(queries, { validateResources, resourceNames, apiPrefix, cacheControl, requestBodyValidator, }) {
23
+ const { handleGetResource } = (0, get_1.createGetRoutes)(queries, cacheControl);
24
+ const { handlePost } = (0, post_1.createPostRoutes)(queries, requestBodyValidator);
25
+ const { handlePut } = (0, put_1.createPutRoutes)(queries, requestBodyValidator);
26
+ const { handleDelete } = (0, delete_1.createDeleteRoutes)(queries);
27
+ const validateResource = (0, urlMiddleware_1.createValidateResourceMiddleware)(validateResources, resourceNames);
28
+ const getResourceAndId = (0, urlMiddleware_1.createResourceAndIdParser)(apiPrefix);
29
+ const resourceRouter = express_1.default.Router();
30
+ resourceRouter
31
+ .get('*', getResourceAndId, validateResource, handleGetResource)
32
+ .post('*', getResourceAndId, validateResource, handlePost)
33
+ .put('*', getResourceAndId, validateResource, handlePut)
34
+ .delete('*', getResourceAndId, validateResource, handleDelete);
35
+ return resourceRouter;
36
+ }
37
+ exports.createResourceRouter = createResourceRouter;
38
+ // A GET to the root URL shows a default message.
39
+ const rootRouter = express_1.default.Router();
40
+ exports.rootRouter = rootRouter;
41
+ rootRouter.get('/', (_, res) => __awaiter(void 0, void 0, void 0, function* () {
42
+ return res.send('It works! ツ');
43
+ }));
44
+ // All other requests to the root URL are not allowed.
45
+ rootRouter.all('/', handleMethodNotAllowed);
46
+ // Route for handling not allowed methods.
47
+ function handleMethodNotAllowed(_, res) {
48
+ res.status(405).json({ message: 'Method Not Allowed' });
49
+ }
50
+ exports.handleMethodNotAllowed = handleMethodNotAllowed;
51
+ // Route for handling not found.
52
+ function handleNotFound(_, res) {
53
+ res.status(404).json({ message: 'Not Found' });
54
+ }
55
+ exports.handleNotFound = handleNotFound;
56
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateRequestBody = void 0;
4
+ function validateRequestBody(validator, req) {
5
+ const { resourceName } = req.requestInfo;
6
+ let requestBody = req.body;
7
+ const validationResult = validator(resourceName, requestBody);
8
+ if (!validationResult && typeof requestBody === 'object')
9
+ return requestBody;
10
+ if (typeof validationResult === 'string')
11
+ return validationResult;
12
+ // The requestBody was replaced by something else.
13
+ if (validationResult)
14
+ requestBody = validationResult;
15
+ if (typeof requestBody === 'object') {
16
+ return requestBody;
17
+ }
18
+ else
19
+ return req.body;
20
+ }
21
+ exports.validateRequestBody = validateRequestBody;
22
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/routes/validator.ts"],"names":[],"mappings":";;;AAEA,SAAS,mBAAmB,CAC1B,SAA4B,EAC5B,GAAG;IAEH,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;IACxC,IAAI,WAAW,GAAG,GAAG,CAAC,IAAI,CAAA;IAE1B,MAAM,gBAAgB,GAAG,SAAS,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;IAE7D,IAAI,CAAC,gBAAgB,IAAI,OAAO,WAAW,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAA;IAE5E,IAAI,OAAO,gBAAgB,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAEjE,kDAAkD;IAClD,IAAI,gBAAgB;QAAE,WAAW,GAAG,gBAAgB,CAAA;IAEpD,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE;QACnC,OAAO,WAAW,CAAA;KACnB;;QAAM,OAAO,GAAG,CAAC,IAAI,CAAA;AACxB,CAAC;AAEQ,kDAAmB"}
package/dist/server.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.create = void 0;
26
+ const express_1 = __importStar(require("express"));
27
+ const morgan_1 = __importDefault(require("morgan"));
28
+ const errors_1 = require("./errors/errors");
29
+ const routes_1 = require("./routes/routes");
30
+ const queries_1 = require("./queries/queries");
31
+ const config_1 = require("./config");
32
+ const cors_1 = __importDefault(require("cors"));
33
+ const delayMiddleware_1 = require("./delay/delayMiddleware");
34
+ function createServer(userConfig) {
35
+ const config = (0, config_1.initConfig)(userConfig);
36
+ const queries = (0, queries_1.createQueries)(config.connectionString);
37
+ const app = (0, express_1.default)();
38
+ app.use((0, express_1.json)());
39
+ // Add HTTP request logging.
40
+ app.use((0, morgan_1.default)('tiny'));
41
+ // Enable CORS for all requests.
42
+ app.use((0, cors_1.default)({ origin: true, credentials: true }));
43
+ if (config.delay > 0) {
44
+ const delayMiddleware = (0, delayMiddleware_1.createDelayMiddleware)(config.delay);
45
+ app.use(delayMiddleware);
46
+ }
47
+ // Serve a static folder, if configured.
48
+ if (config.staticFolder) {
49
+ app.use(express_1.default.static(config.staticFolder));
50
+ }
51
+ // On the root URL (with apiPrefix if applicable) only a GET is allowed.
52
+ const rootPath = config.apiPrefix ? `${config.apiPrefix}` : '/';
53
+ app.use(rootPath, routes_1.rootRouter);
54
+ // For all other URLs, only GET, POST, PUT and DELETE are allowed and handled.
55
+ const resourceRouter = (0, routes_1.createResourceRouter)(queries, config);
56
+ const resourcePath = config.apiPrefix ? `${config.apiPrefix}*` : '*';
57
+ app.use(resourcePath, resourceRouter);
58
+ // In case of an API prefix, GET, POST, PUT and DELETE requests to all other URLs return a 404 Not Found.
59
+ if (config.apiPrefix) {
60
+ app.get('*', routes_1.handleNotFound);
61
+ app.post('*', routes_1.handleNotFound);
62
+ app.put('*', routes_1.handleNotFound);
63
+ app.delete('*', routes_1.handleNotFound);
64
+ }
65
+ // All other methods to any URL are not allowed.
66
+ app.all('*', routes_1.handleMethodNotAllowed);
67
+ if (config.apiPrefix)
68
+ app.all(`${config.apiPrefix}*`, routes_1.handleMethodNotAllowed);
69
+ // Error middleware.
70
+ app.use(errors_1.errorHandler);
71
+ return app;
72
+ }
73
+ function create(userConfig) {
74
+ return createServer(userConfig);
75
+ }
76
+ exports.create = create;
77
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.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"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createValidateResourceMiddleware = exports.createResourceAndIdParser = void 0;
4
+ const errors_1 = require("../errors/errors");
5
+ const urlParser_1 = require("./urlParser");
6
+ function createResourceAndIdParser(apiPrefix) {
7
+ return function getResourceAndId(req, _, next) {
8
+ const url = req.baseUrl.replace(apiPrefix, '');
9
+ const urlInfo = (0, urlParser_1.parseUrl)(url);
10
+ req.requestInfo = Object.assign(Object.assign({}, req.requestInfo), urlInfo);
11
+ return next();
12
+ };
13
+ }
14
+ exports.createResourceAndIdParser = createResourceAndIdParser;
15
+ function createValidateResourceMiddleware(validateResources, resourceNames) {
16
+ return function validateResource(req, _, next) {
17
+ if (!validateResources)
18
+ return next();
19
+ const { resourceName } = req.requestInfo;
20
+ if (!resourceName)
21
+ return next();
22
+ if (!resourceNames.includes(resourceName.toLowerCase())) {
23
+ // TODO return a response instead of calling next
24
+ const error = (0, errors_1.new404NotFoundError)(`'${resourceName}' is an unknown resource`);
25
+ return next(error);
26
+ }
27
+ return next();
28
+ };
29
+ }
30
+ exports.createValidateResourceMiddleware = createValidateResourceMiddleware;
31
+ //# sourceMappingURL=urlMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlMiddleware.js","sourceRoot":"","sources":["../../src/urls/urlMiddleware.ts"],"names":[],"mappings":";;;AAAA,6CAAsD;AACtD,2CAAsC;AAEtC,SAAS,yBAAyB,CAAC,SAAS;IAC1C,OAAO,SAAS,gBAAgB,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI;QAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QAC9C,MAAM,OAAO,GAAG,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAA;QAE7B,GAAG,CAAC,WAAW,mCAAQ,GAAG,CAAC,WAAW,GAAK,OAAO,CAAE,CAAA;QAEpD,OAAO,IAAI,EAAE,CAAA;IACf,CAAC,CAAA;AACH,CAAC;AAsBQ,8DAAyB;AApBlC,SAAS,gCAAgC,CAAC,iBAAiB,EAAE,aAAa;IACxE,OAAO,SAAS,gBAAgB,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI;QAC3C,IAAI,CAAC,iBAAiB;YAAE,OAAO,IAAI,EAAE,CAAA;QAErC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,WAAW,CAAA;QAExC,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,EAAE,CAAA;QAEhC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,EAAE;YACvD,iDAAiD;YACjD,MAAM,KAAK,GAAG,IAAA,4BAAmB,EAC/B,IAAI,YAAY,0BAA0B,CAC3C,CAAA;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;SACnB;QAED,OAAO,IAAI,EAAE,CAAA;IACf,CAAC,CAAA;AACH,CAAC;AAEmC,4EAAgC"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseUrl = void 0;
4
+ function parseUrl(url) {
5
+ if (!url || (url && !url.trim()))
6
+ return { resourceName: null, id: null };
7
+ const urlSegments = url.split('/').filter((i) => i);
8
+ const resourceName = urlSegments.length > 0 ? urlSegments[0] : null;
9
+ const id = urlSegments.length > 1 ? urlSegments[1] : null;
10
+ return { resourceName, id };
11
+ }
12
+ exports.parseUrl = parseUrl;
13
+ //# sourceMappingURL=urlParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlParser.js","sourceRoot":"","sources":["../../src/urls/urlParser.ts"],"names":[],"mappings":";;;AAAA,SAAS,QAAQ,CAAC,GAAG;IACnB,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IAEzE,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAEnD,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACnE,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAEzD,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;AAC7B,CAAC;AAEQ,4BAAQ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temba",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "Get a simple MongoDB REST API with zero coding in less than 30 seconds (seriously).",
5
5
  "main": "dist/server.ts",
6
6
  "scripts": {
@@ -17,7 +17,9 @@
17
17
  "author": "Bouwe (https://bouwe.io)",
18
18
  "license": "ISC",
19
19
  "files": [
20
- "dist/**"
20
+ "dist/**",
21
+ "package.json",
22
+ "README.md"
21
23
  ],
22
24
  "devDependencies": {
23
25
  "@types/cors": "^2.8.12",