temba 0.7.10 → 0.9.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 +321 -0
- package/dist/config/index.js +66 -61
- package/dist/config/index.js.map +1 -0
- package/dist/delay/middleware.js +14 -14
- package/dist/delay/middleware.js.map +1 -0
- package/dist/errors/index.js +11 -11
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/middleware.js +9 -12
- package/dist/errors/middleware.js.map +1 -0
- package/dist/errors/types.js +13 -0
- package/dist/errors/types.js.map +1 -0
- package/dist/logging/index.js +37 -36
- package/dist/logging/index.js.map +1 -0
- package/dist/queries/in-memory.js +48 -79
- package/dist/queries/in-memory.js.map +1 -0
- package/dist/queries/index.js +15 -19
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/mongo.js +73 -277
- package/dist/queries/mongo.js.map +1 -0
- package/dist/routes/delete.js +37 -80
- package/dist/routes/delete.js.map +1 -0
- package/dist/routes/get.js +42 -83
- package/dist/routes/get.js.map +1 -0
- package/dist/routes/index.js +51 -89
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/post.js +42 -59
- package/dist/routes/post.js.map +1 -0
- package/dist/routes/put.js +38 -85
- package/dist/routes/put.js.map +1 -0
- package/dist/routes/validator.js +18 -0
- package/dist/routes/validator.js.map +1 -0
- package/dist/server.js +72 -75
- package/dist/server.js.map +1 -0
- package/dist/urls/middleware.js +26 -38
- package/dist/urls/middleware.js.map +1 -0
- package/dist/urls/urlParser.js +11 -20
- package/dist/urls/urlParser.js.map +1 -0
- package/package.json +21 -11
- package/readme.md +0 -214
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRequestBody = void 0;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
function validateRequestBody(validator, resourceName, requestBody) {
|
|
6
|
+
const validationResult = validator(resourceName, requestBody);
|
|
7
|
+
if (!validationResult)
|
|
8
|
+
return requestBody;
|
|
9
|
+
if (typeof validationResult === 'string') {
|
|
10
|
+
throw (0, errors_1.new400BadRequestError)(validationResult);
|
|
11
|
+
}
|
|
12
|
+
// The requestBody was replaced by something else.
|
|
13
|
+
if (validationResult)
|
|
14
|
+
requestBody = validationResult;
|
|
15
|
+
return requestBody;
|
|
16
|
+
}
|
|
17
|
+
exports.validateRequestBody = validateRequestBody;
|
|
18
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/routes/validator.ts"],"names":[],"mappings":";;;AAAA,sCAAiD;AAEjD,SAAS,mBAAmB,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW;IAC/D,MAAM,gBAAgB,GAAG,SAAS,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;IAE7D,IAAI,CAAC,gBAAgB;QAAE,OAAO,WAAW,CAAA;IAEzC,IAAI,OAAO,gBAAgB,KAAK,QAAQ,EAAE;QACxC,MAAM,IAAA,8BAAqB,EAAC,gBAAgB,CAAC,CAAA;KAC9C;IAED,kDAAkD;IAClD,IAAI,gBAAgB;QAAE,WAAW,GAAG,gBAAgB,CAAA;IAEpD,OAAO,WAAW,CAAA;AACpB,CAAC;AAEQ,kDAAmB"}
|
package/dist/server.js
CHANGED
|
@@ -1,80 +1,77 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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;
|
|
9
13
|
});
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
31
|
-
|
|
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 middleware_1 = require("./errors/middleware");
|
|
29
|
+
const routes_1 = require("./routes");
|
|
30
|
+
const queries_1 = require("./queries");
|
|
31
|
+
const config_1 = require("./config");
|
|
32
|
+
const cors_1 = __importDefault(require("cors"));
|
|
33
|
+
const middleware_2 = require("./delay/middleware");
|
|
32
34
|
function createServer(userConfig) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
app.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
app.all('*', _routes.handleMethodNotAllowed);
|
|
72
|
-
if (config.apiPrefix) app.all("".concat(config.apiPrefix, "*"), _routes.handleMethodNotAllowed); // Error middleware.
|
|
73
|
-
|
|
74
|
-
app.use(_middleware.errorHandler);
|
|
75
|
-
return app;
|
|
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, middleware_2.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(middleware_1.errorHandler);
|
|
71
|
+
return app;
|
|
76
72
|
}
|
|
77
|
-
|
|
78
73
|
function create(userConfig) {
|
|
79
|
-
|
|
80
|
-
}
|
|
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,oDAAkD;AAClD,qCAKiB;AACjB,uCAAyC;AACzC,qCAAqC;AACrC,gDAAuB;AACvB,mDAA0D;AAE1D,SAAS,YAAY,CAAC,UAAU;IAC9B,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,kCAAqB,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,yBAAY,CAAC,CAAA;IAErB,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAgB,MAAM,CAAC,UAAU;IAC/B,OAAO,YAAY,CAAC,UAAU,CAAC,CAAA;AACjC,CAAC;AAFD,wBAEC"}
|
package/dist/urls/middleware.js
CHANGED
|
@@ -1,42 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
value: true
|
|
7
|
-
});
|
|
8
|
-
exports.createResourceAndIdParser = createResourceAndIdParser;
|
|
9
|
-
exports.createValidateResourceMiddleware = createValidateResourceMiddleware;
|
|
10
|
-
|
|
11
|
-
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
12
|
-
|
|
13
|
-
var _urlParser = require("./urlParser");
|
|
14
|
-
|
|
15
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
|
|
16
|
-
|
|
17
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
18
|
-
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createValidateResourceMiddleware = exports.createResourceAndIdParser = void 0;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
const urlParser_1 = require("./urlParser");
|
|
19
6
|
function createResourceAndIdParser(apiPrefix) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
return function getResourceAndId(req, _, next) {
|
|
8
|
+
const url = req.baseUrl.replace(apiPrefix, '');
|
|
9
|
+
let urlInfo = (0, urlParser_1.parseUrl)(url);
|
|
10
|
+
req.requestInfo = Object.assign(Object.assign({}, req.requestInfo), urlInfo);
|
|
11
|
+
return next();
|
|
12
|
+
};
|
|
26
13
|
}
|
|
27
|
-
|
|
14
|
+
exports.createResourceAndIdParser = createResourceAndIdParser;
|
|
28
15
|
function createValidateResourceMiddleware(validateResources, resourceNames) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
const error = (0, errors_1.new404NotFoundError)(`'${resourceName}' is an unknown resource`);
|
|
24
|
+
return next(error);
|
|
25
|
+
}
|
|
26
|
+
return next();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
exports.createValidateResourceMiddleware = createValidateResourceMiddleware;
|
|
30
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/urls/middleware.ts"],"names":[],"mappings":";;;AAAA,sCAA+C;AAC/C,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,IAAI,OAAO,GAAG,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAA;QAE3B,GAAG,CAAC,WAAW,mCAAQ,GAAG,CAAC,WAAW,GAAK,OAAO,CAAE,CAAA;QAEpD,OAAO,IAAI,EAAE,CAAA;IACf,CAAC,CAAA;AACH,CAAC;AAqBQ,8DAAyB;AAnBlC,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,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"}
|
package/dist/urls/urlParser.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.parseUrl = parseUrl;
|
|
7
|
-
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseUrl = void 0;
|
|
8
4
|
function parseUrl(url) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
resourceName: resourceName,
|
|
20
|
-
id: id
|
|
21
|
-
};
|
|
22
|
-
}
|
|
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,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "temba",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Get a simple MongoDB REST API with zero coding in less than 30 seconds (seriously).",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "
|
|
8
|
-
"start": "
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "tsc && node dist/server.ts",
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"lint": "eslint --ignore-path .eslintignore --ext .js,.ts .",
|
|
11
|
+
"format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
|
|
9
12
|
},
|
|
10
13
|
"repository": {
|
|
11
14
|
"type": "git",
|
|
@@ -20,16 +23,23 @@
|
|
|
20
23
|
"!__tests__"
|
|
21
24
|
],
|
|
22
25
|
"devDependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
26
|
+
"@types/cors": "^2.8.12",
|
|
27
|
+
"@types/express": "^4.17.13",
|
|
28
|
+
"@types/jest": "^27.4.0",
|
|
29
|
+
"@types/morgan": "^1.9.3",
|
|
30
|
+
"@types/supertest": "^2.0.11",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
|
32
|
+
"@typescript-eslint/parser": "^5.10.0",
|
|
33
|
+
"eslint": "^8.7.0",
|
|
34
|
+
"eslint-config-prettier": "^8.3.0",
|
|
35
|
+
"jest": "^27.4.7",
|
|
36
|
+
"jest-extended": "^0.11.5",
|
|
37
|
+
"prettier": "^2.5.1",
|
|
38
|
+
"supertest": "^6.2.2",
|
|
39
|
+
"ts-jest": "^27.1.3",
|
|
40
|
+
"typescript": "^4.5.5"
|
|
30
41
|
},
|
|
31
42
|
"dependencies": {
|
|
32
|
-
"@babel/runtime": "^7.15.4",
|
|
33
43
|
"@rakered/mongo": "^1.6.0",
|
|
34
44
|
"connect-pause": "^0.1.0",
|
|
35
45
|
"cors": "^2.8.5",
|
package/readme.md
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Temba
|
|
2
|
-
|
|
3
|
-
**Get a simple MongoDB REST API with zero coding in less than 30 seconds (seriously).**
|
|
4
|
-
|
|
5
|
-
For developers who need a quick backend for small projects.
|
|
6
|
-
|
|
7
|
-
Powered by NodeJS, Express and MongoDB.
|
|
8
|
-
|
|
9
|
-
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.
|
|
10
|
-
|
|
11
|
-
## Table of contents
|
|
12
|
-
|
|
13
|
-
[Temba?](#temba-1)
|
|
14
|
-
|
|
15
|
-
[Getting Started](#getting-started)
|
|
16
|
-
|
|
17
|
-
[Usage](#usage)
|
|
18
|
-
|
|
19
|
-
## Temba?
|
|
20
|
-
|
|
21
|
-
> _"Temba, at rest"_
|
|
22
|
-
|
|
23
|
-
A metaphor for the declining of a gift, from the [Star Trek - The Next Generation, episode "Darmok"](https://memory-alpha.fandom.com/wiki/Temba).
|
|
24
|
-
|
|
25
|
-
In the fictional Tamarian language the word _"Temba"_ means something like _"gift"_.
|
|
26
|
-
|
|
27
|
-
## Getting Started
|
|
28
|
-
|
|
29
|
-
Prerequisites you need to have:
|
|
30
|
-
|
|
31
|
-
- Node, NPM
|
|
32
|
-
- Optional: A MongoDB database, either locally or in the cloud
|
|
33
|
-
|
|
34
|
-
> Wthout a database, Temba also works. However, then data is kept in memory and flushed every time the server restarts.
|
|
35
|
-
|
|
36
|
-
### Use `npx`
|
|
37
|
-
|
|
38
|
-
Create your own Temba server with the following command and you are up and running!
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx create-temba-server my-rest-api
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Manually adding to an existing app
|
|
45
|
-
|
|
46
|
-
If you don't want to (or can't) use the starter, add Temba to your app manually:
|
|
47
|
-
|
|
48
|
-
1. `npm i temba`
|
|
49
|
-
|
|
50
|
-
2. Example code to create a Temba server:
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
import temba from ("temba");
|
|
54
|
-
const server = temba.create();
|
|
55
|
-
|
|
56
|
-
const port = process.env.PORT || 3000;
|
|
57
|
-
server.listen(port, () => {
|
|
58
|
-
console.log(`Temba is running on port ${port}`);
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Configuration
|
|
63
|
-
|
|
64
|
-
By passing a config object to the `create` function you can customize Temba's behavior. Refer to the documentation below for the various possibilities.
|
|
65
|
-
|
|
66
|
-
## Usage
|
|
67
|
-
|
|
68
|
-
### Introduction
|
|
69
|
-
|
|
70
|
-
Out of the box, Temba gives you a CRUD REST API to any resource name you can think of.
|
|
71
|
-
|
|
72
|
-
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.
|
|
73
|
-
|
|
74
|
-
For a every resource, for example `/movies`, Temba supports the following requests:
|
|
75
|
-
|
|
76
|
-
- `GET /movies` - Get all movies
|
|
77
|
-
- `GET /movies/:id` - Get a movie by its ID
|
|
78
|
-
- `POST /movies` - Create a new movie
|
|
79
|
-
- `PUT /movies/:id` - Update (fully replace) a movie by its ID
|
|
80
|
-
- `DELETE /movies` - Delete all movies
|
|
81
|
-
- `DELETE /movies/:id` - Delete a movie by its ID
|
|
82
|
-
|
|
83
|
-
### Supported HTTP methods
|
|
84
|
-
|
|
85
|
-
Requests with an HTTP method that is not supported, so everything but `GET`, `POST`, `PUT` and `DELETE`, a `405 Method Not Allowed` response will be returned.
|
|
86
|
-
|
|
87
|
-
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.
|
|
88
|
-
|
|
89
|
-
### MongoDB
|
|
90
|
-
|
|
91
|
-
When starting Temba, you can send your requests to it immediately. However, then the data resides in memory and is flushed as soon as the server restarts. To persist your data, provide the `connectionString` config setting for your MongoDB database:
|
|
92
|
-
|
|
93
|
-
```js
|
|
94
|
-
const config = {
|
|
95
|
-
connectionString: 'mongodb://localhost:27017',
|
|
96
|
-
}
|
|
97
|
-
const server = temba.create(config)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
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`.
|
|
101
|
-
|
|
102
|
-
### Allowing specific resources only
|
|
103
|
-
|
|
104
|
-
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:
|
|
105
|
-
|
|
106
|
-
```js
|
|
107
|
-
const config = { resourceNames: ['movies', 'actors'] }
|
|
108
|
-
const server = temba.create(config)
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
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`.
|
|
112
|
-
|
|
113
|
-
### JSON
|
|
114
|
-
|
|
115
|
-
Temba only supports JSON. If you send a request with invalid formatted JSON, a `400 Bad Request` response is returned.
|
|
116
|
-
|
|
117
|
-
When sending JSON data (`POST` and `PUT` requests), adding a `Content-Type: application/json` header is required.
|
|
118
|
-
|
|
119
|
-
IDs are auto generated when creating resources. IDs in the JSON request body are ignored.
|
|
120
|
-
|
|
121
|
-
### Static assets
|
|
122
|
-
|
|
123
|
-
If you want to host static assets next to the REST API, configure the `staticFolder`:
|
|
124
|
-
|
|
125
|
-
```js
|
|
126
|
-
const config = { staticFolder: 'build' }
|
|
127
|
-
const server = temba.create(config)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
This way, you could create a REST API, and the web app consuming it, in one project.
|
|
131
|
-
|
|
132
|
-
However, to avoid possible conflicts between the API resources and the routes in your web app you might want to add an `apiPrefix` to the REST API:
|
|
133
|
-
|
|
134
|
-
### REST URIs prefixes
|
|
135
|
-
|
|
136
|
-
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`:
|
|
137
|
-
|
|
138
|
-
```js
|
|
139
|
-
const config = { apiPrefix: 'api' }
|
|
140
|
-
const server = temba.create(config)
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
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.
|
|
144
|
-
|
|
145
|
-
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.
|
|
146
|
-
|
|
147
|
-
### Config settings overview
|
|
148
|
-
|
|
149
|
-
Configuring Temba is optional, it already works out of the box. None of the settings are used until you configure them:
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
const config = {
|
|
153
|
-
resourceNames: ['movies', 'actors'],
|
|
154
|
-
connectionString: 'mongodb://localhost:27017',
|
|
155
|
-
staticFolder: 'build',
|
|
156
|
-
apiPrefix: 'api',
|
|
157
|
-
cacheControl: 'public, max-age=300',
|
|
158
|
-
delay: 500,
|
|
159
|
-
}
|
|
160
|
-
const server = temba.create(config)
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
These are all the possible settings:
|
|
164
|
-
|
|
165
|
-
| Config setting | Description |
|
|
166
|
-
| :----------------- | :----------------------------------------------------------------------------------------- |
|
|
167
|
-
| `resourceNames` | See [Allowing specific resources only](#allowing-specific-resources-only) |
|
|
168
|
-
| `connectionString` | See [MongoDB](#mongodb) |
|
|
169
|
-
| `staticFolder` | See [Static assets](#static-assets) |
|
|
170
|
-
| `apiPrefix` | See [REST URIs prefixes](#rest-uris-prefixes) |
|
|
171
|
-
| `cacheControl` | The `Cache-control` response header value for each GET request. |
|
|
172
|
-
| `delay` | After processing the request, the delay in milliseconds before the request should be sent. |
|
|
173
|
-
|
|
174
|
-
## Not supported (yet?)
|
|
175
|
-
|
|
176
|
-
Temba is still very basic. It does not have any model validation, so you can store your resources in any format you like.
|
|
177
|
-
|
|
178
|
-
So creating the following two (very different) movies is perfectly fine:
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
POST /movies
|
|
182
|
-
{
|
|
183
|
-
"title": "O Brother, Where Art Thou?",
|
|
184
|
-
"description": "In the deep south during the 1930s, three escaped convicts search for hidden treasure while a relentless lawman pursues them."
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
POST /movies
|
|
188
|
-
{
|
|
189
|
-
"foo": "bar",
|
|
190
|
-
"baz": "boo"
|
|
191
|
-
}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Partial updates using `PATCH`, or other HTTP methods are not (yet?) supported.
|
|
195
|
-
|
|
196
|
-
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.
|
|
197
|
-
|
|
198
|
-
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.
|
|
199
|
-
|
|
200
|
-
And there is no filtering, sorting, searching, custom routes, etc. (yet?).
|
|
201
|
-
|
|
202
|
-
## Under the hood
|
|
203
|
-
|
|
204
|
-
Temba is built with JavaScript, Node, Express, Jest, Testing Library, Supertest, and [@rakered/mongo](https://www.npmjs.com/package/@rakered/mongo).
|
|
205
|
-
|
|
206
|
-
## Which problem does Temba solve?
|
|
207
|
-
|
|
208
|
-
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.
|
|
209
|
-
|
|
210
|
-
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.
|
|
211
|
-
|
|
212
|
-
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).
|
|
213
|
-
|
|
214
|
-
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.
|