@flink-app/flink 0.4.6 → 0.5.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/dist/src/FlinkApp.d.ts +22 -0
- package/dist/src/FlinkApp.js +74 -33
- package/dist/src/utils.d.ts +7 -0
- package/dist/src/utils.js +13 -1
- package/package.json +4 -2
- package/src/FlinkApp.ts +99 -29
- package/src/utils.ts +12 -0
package/dist/src/FlinkApp.d.ts
CHANGED
|
@@ -107,6 +107,26 @@ export interface FlinkOptions {
|
|
|
107
107
|
*/
|
|
108
108
|
enabled?: boolean;
|
|
109
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* If true, the HTTP server will be disabled.
|
|
112
|
+
* Only useful when starting a Flink app for testing purposes.
|
|
113
|
+
*/
|
|
114
|
+
disableHttpServer?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Configuration for access logs.
|
|
117
|
+
*/
|
|
118
|
+
accessLog?: {
|
|
119
|
+
/**
|
|
120
|
+
* Enables access logs for all requests.
|
|
121
|
+
* Defaults to true.
|
|
122
|
+
*/
|
|
123
|
+
enabled?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Optional custom format.
|
|
126
|
+
* Uses `morgan` format and defaults to `dev`.
|
|
127
|
+
*/
|
|
128
|
+
format?: string;
|
|
129
|
+
};
|
|
110
130
|
}
|
|
111
131
|
export interface HandlerConfig {
|
|
112
132
|
schema?: {
|
|
@@ -145,12 +165,14 @@ export declare class FlinkApp<C extends FlinkContext> {
|
|
|
145
165
|
private routingConfigured;
|
|
146
166
|
private jsonOptions?;
|
|
147
167
|
private schedulingOptions?;
|
|
168
|
+
private disableHttpServer;
|
|
148
169
|
private repos;
|
|
149
170
|
/**
|
|
150
171
|
* Internal cache used to track registered handlers and potentially any overlapping routes
|
|
151
172
|
*/
|
|
152
173
|
private handlerRouteCache;
|
|
153
174
|
scheduler?: ToadScheduler;
|
|
175
|
+
private accessLog;
|
|
154
176
|
constructor(opts: FlinkOptions);
|
|
155
177
|
get ctx(): C;
|
|
156
178
|
start(): Promise<this>;
|
package/dist/src/FlinkApp.js
CHANGED
|
@@ -57,6 +57,7 @@ var body_parser_1 = __importDefault(require("body-parser"));
|
|
|
57
57
|
var cors_1 = __importDefault(require("cors"));
|
|
58
58
|
var express_1 = __importDefault(require("express"));
|
|
59
59
|
var mongodb_1 = __importDefault(require("mongodb"));
|
|
60
|
+
var morgan_1 = __importDefault(require("morgan"));
|
|
60
61
|
var ms_1 = __importDefault(require("ms"));
|
|
61
62
|
var toad_scheduler_1 = require("toad-scheduler");
|
|
62
63
|
var uuid_1 = require("uuid");
|
|
@@ -93,6 +94,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
93
94
|
this.debug = false;
|
|
94
95
|
this.plugins = [];
|
|
95
96
|
this.routingConfigured = false;
|
|
97
|
+
this.disableHttpServer = false;
|
|
96
98
|
this.repos = {};
|
|
97
99
|
/**
|
|
98
100
|
* Internal cache used to track registered handlers and potentially any overlapping routes
|
|
@@ -108,6 +110,8 @@ var FlinkApp = /** @class */ (function () {
|
|
|
108
110
|
this.auth = opts.auth;
|
|
109
111
|
this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
|
|
110
112
|
this.schedulingOptions = opts.scheduling;
|
|
113
|
+
this.disableHttpServer = !!opts.disableHttpServer;
|
|
114
|
+
this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
|
|
111
115
|
}
|
|
112
116
|
Object.defineProperty(FlinkApp.prototype, "ctx", {
|
|
113
117
|
get: function () {
|
|
@@ -146,13 +150,21 @@ var FlinkApp = /** @class */ (function () {
|
|
|
146
150
|
if (this.isSchedulingEnabled) {
|
|
147
151
|
this.scheduler = new toad_scheduler_1.ToadScheduler();
|
|
148
152
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
else {
|
|
154
|
+
FlinkLog_1.log.info("🚫 Scheduling is disabled");
|
|
155
|
+
}
|
|
156
|
+
if (!this.disableHttpServer) {
|
|
157
|
+
this.expressApp = express_1.default();
|
|
158
|
+
this.expressApp.use(cors_1.default(this.corsOpts));
|
|
159
|
+
this.expressApp.use(body_parser_1.default.json(this.jsonOptions));
|
|
160
|
+
if (this.accessLog.enabled) {
|
|
161
|
+
this.expressApp.use(morgan_1.default(this.accessLog.format));
|
|
162
|
+
}
|
|
163
|
+
this.expressApp.use(function (req, res, next) {
|
|
164
|
+
req.reqId = uuid_1.v4();
|
|
165
|
+
next();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
156
168
|
_i = 0, _b = this.plugins;
|
|
157
169
|
_c.label = 3;
|
|
158
170
|
case 3:
|
|
@@ -196,15 +208,23 @@ var FlinkApp = /** @class */ (function () {
|
|
|
196
208
|
// Register 404 with slight delay to allow all manually added routes to be added
|
|
197
209
|
// TODO: Is there a better solution to force this handler to always run last?
|
|
198
210
|
setTimeout(function () {
|
|
199
|
-
_this.
|
|
200
|
-
|
|
201
|
-
|
|
211
|
+
if (!_this.disableHttpServer) {
|
|
212
|
+
_this.expressApp.use(function (req, res, next) {
|
|
213
|
+
res.status(404).json(FlinkErrors_1.notFound());
|
|
214
|
+
});
|
|
215
|
+
}
|
|
202
216
|
_this.routingConfigured = true;
|
|
203
217
|
});
|
|
204
|
-
|
|
205
|
-
FlinkLog_1.log.
|
|
206
|
-
|
|
207
|
-
}
|
|
218
|
+
if (this.disableHttpServer) {
|
|
219
|
+
FlinkLog_1.log.info("🚧 HTTP server is disabled, but flink app is running");
|
|
220
|
+
this.started = true;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
(_a = this.expressApp) === null || _a === void 0 ? void 0 : _a.listen(this.port, function () {
|
|
224
|
+
FlinkLog_1.log.fontColorLog("magenta", "\u26A1\uFE0F HTTP server '" + _this.name + "' is running and waiting for connections on " + _this.port);
|
|
225
|
+
_this.started = true;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
208
228
|
return [2 /*return*/, this];
|
|
209
229
|
}
|
|
210
230
|
});
|
|
@@ -258,14 +278,24 @@ var FlinkApp = /** @class */ (function () {
|
|
|
258
278
|
this.handlers.push(handlerConfig);
|
|
259
279
|
var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
|
|
260
280
|
var method = routeProps.method;
|
|
261
|
-
var app = this.expressApp;
|
|
262
281
|
if (!method) {
|
|
263
282
|
FlinkLog_1.log.error("Route " + routeProps.path + " is missing http method");
|
|
264
283
|
}
|
|
265
284
|
if (method) {
|
|
266
285
|
var methodAndRoute_1 = method.toUpperCase() + " " + routeProps.path;
|
|
267
|
-
|
|
268
|
-
|
|
286
|
+
if (this.disableHttpServer) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
var validateReq_1;
|
|
290
|
+
var validateRes_1;
|
|
291
|
+
if (schema.reqSchema) {
|
|
292
|
+
validateReq_1 = ajv.compile(schema.reqSchema);
|
|
293
|
+
}
|
|
294
|
+
if (schema.resSchema) {
|
|
295
|
+
validateRes_1 = ajv.compile(schema.resSchema);
|
|
296
|
+
}
|
|
297
|
+
this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
298
|
+
var valid, data, handlerRes, err_1, valid;
|
|
269
299
|
return __generator(this, function (_a) {
|
|
270
300
|
switch (_a.label) {
|
|
271
301
|
case 0:
|
|
@@ -277,18 +307,17 @@ var FlinkApp = /** @class */ (function () {
|
|
|
277
307
|
}
|
|
278
308
|
_a.label = 2;
|
|
279
309
|
case 2:
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
valid = validate(req.body);
|
|
310
|
+
if (validateReq_1) {
|
|
311
|
+
valid = validateReq_1(req.body);
|
|
283
312
|
if (!valid) {
|
|
284
|
-
FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(
|
|
313
|
+
FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(validateReq_1.errors, null, 2));
|
|
285
314
|
FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(req.body));
|
|
286
315
|
return [2 /*return*/, res.status(400).json({
|
|
287
316
|
status: 400,
|
|
288
317
|
error: {
|
|
289
318
|
id: uuid_1.v4(),
|
|
290
319
|
title: "Bad request",
|
|
291
|
-
detail: "Schema did not validate " + JSON.stringify(
|
|
320
|
+
detail: "Schema did not validate " + JSON.stringify(validateReq_1.errors),
|
|
292
321
|
},
|
|
293
322
|
})];
|
|
294
323
|
}
|
|
@@ -320,11 +349,10 @@ var FlinkApp = /** @class */ (function () {
|
|
|
320
349
|
console.error(err_1);
|
|
321
350
|
return [2 /*return*/, res.status(500).json(FlinkErrors_1.internalServerError(err_1))];
|
|
322
351
|
case 6:
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
352
|
+
if (validateRes_1 && !utils_1.isError(handlerRes)) {
|
|
353
|
+
valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
326
354
|
if (!valid) {
|
|
327
|
-
FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(
|
|
355
|
+
FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(validateRes_1.errors, null, 2));
|
|
328
356
|
FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(handlerRes.data));
|
|
329
357
|
// log.debug(JSON.stringify(schema, null, 2));
|
|
330
358
|
return [2 /*return*/, res.status(500).json({
|
|
@@ -332,7 +360,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
332
360
|
error: {
|
|
333
361
|
id: uuid_1.v4(),
|
|
334
362
|
title: "Bad response",
|
|
335
|
-
detail: "Schema did not validate " + JSON.stringify(
|
|
363
|
+
detail: "Schema did not validate " + JSON.stringify(validateRes_1.errors),
|
|
336
364
|
},
|
|
337
365
|
})];
|
|
338
366
|
}
|
|
@@ -360,12 +388,12 @@ var FlinkApp = /** @class */ (function () {
|
|
|
360
388
|
* Will not register any handlers added programmatically.
|
|
361
389
|
*/
|
|
362
390
|
FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
|
|
363
|
-
var _a, _b;
|
|
391
|
+
var _a, _b, _c;
|
|
364
392
|
return __awaiter(this, void 0, void 0, function () {
|
|
365
|
-
var _i, autoRegisteredHandlers_1,
|
|
366
|
-
return __generator(this, function (
|
|
393
|
+
var _i, autoRegisteredHandlers_1, _d, handler, assumedHttpMethod, pathParams, _e, _f, param;
|
|
394
|
+
return __generator(this, function (_g) {
|
|
367
395
|
for (_i = 0, autoRegisteredHandlers_1 = exports.autoRegisteredHandlers; _i < autoRegisteredHandlers_1.length; _i++) {
|
|
368
|
-
|
|
396
|
+
_d = autoRegisteredHandlers_1[_i], handler = _d.handler, assumedHttpMethod = _d.assumedHttpMethod;
|
|
369
397
|
if (!handler.Route) {
|
|
370
398
|
FlinkLog_1.log.error("Missing Props in handler " + handler.__file);
|
|
371
399
|
continue;
|
|
@@ -374,11 +402,24 @@ var FlinkApp = /** @class */ (function () {
|
|
|
374
402
|
FlinkLog_1.log.error("Missing exported handler function in handler " + handler.__file);
|
|
375
403
|
continue;
|
|
376
404
|
}
|
|
405
|
+
if (!!((_a = handler.__params) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
406
|
+
pathParams = utils_1.getPathParams(handler.Route.path);
|
|
407
|
+
for (_e = 0, _f = handler.__params; _e < _f.length; _e++) {
|
|
408
|
+
param = _f[_e];
|
|
409
|
+
if (!pathParams.includes(param.name)) {
|
|
410
|
+
FlinkLog_1.log.error("Handler " + handler.__file + " has param " + param.name + " but it is not present in the path '" + handler.Route.path + "'");
|
|
411
|
+
throw new Error("Invalid/missing handler path param");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (pathParams.length !== handler.__params.length) {
|
|
415
|
+
FlinkLog_1.log.warn("Handler " + handler.__file + " has " + handler.__params.length + " typed params but the path '" + handler.Route.path + "' has " + pathParams.length + " params");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
377
418
|
this.registerHandler({
|
|
378
419
|
routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
|
|
379
420
|
schema: {
|
|
380
|
-
reqSchema: (
|
|
381
|
-
resSchema: (
|
|
421
|
+
reqSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.reqSchema,
|
|
422
|
+
resSchema: (_c = handler.__schemas) === null || _c === void 0 ? void 0 : _c.resSchema,
|
|
382
423
|
},
|
|
383
424
|
queryMetadata: handler.__query || [],
|
|
384
425
|
paramsMetadata: handler.__params || [],
|
package/dist/src/utils.d.ts
CHANGED
|
@@ -18,3 +18,10 @@ export declare function getRepoInstanceName(fn: string): string;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function getHttpMethodFromHandlerName(handlerFilename: string): HttpMethod | undefined;
|
|
20
20
|
export declare function getJsDocComment(comment: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Returns array of path params from path string.
|
|
23
|
+
* For example `/user/:id` will return `["id"]`
|
|
24
|
+
* @param path
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
export declare function getPathParams(path: string): string[];
|
package/dist/src/utils.js
CHANGED
|
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.getJsDocComment = exports.getHttpMethodFromHandlerName = exports.getRepoInstanceName = exports.getCollectionNameForRepo = exports.getSchemaFiles = exports.getHandlerFiles = exports.isError = exports.isRouteMatch = exports.schemasPath = exports.handlersPath = void 0;
|
|
42
|
+
exports.getPathParams = exports.getJsDocComment = exports.getHttpMethodFromHandlerName = exports.getRepoInstanceName = exports.getCollectionNameForRepo = exports.getSchemaFiles = exports.getHandlerFiles = exports.isError = exports.isRouteMatch = exports.schemasPath = exports.handlersPath = void 0;
|
|
43
43
|
var path_1 = require("path");
|
|
44
44
|
var tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
45
45
|
var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
|
|
@@ -149,3 +149,15 @@ function getJsDocComment(comment) {
|
|
|
149
149
|
return rows.join("\n").trim();
|
|
150
150
|
}
|
|
151
151
|
exports.getJsDocComment = getJsDocComment;
|
|
152
|
+
var pathParamsRegex = /:([a-zA-Z0-9]+)/g;
|
|
153
|
+
/**
|
|
154
|
+
* Returns array of path params from path string.
|
|
155
|
+
* For example `/user/:id` will return `["id"]`
|
|
156
|
+
* @param path
|
|
157
|
+
* @returns
|
|
158
|
+
*/
|
|
159
|
+
function getPathParams(path) {
|
|
160
|
+
var _a;
|
|
161
|
+
return ((_a = path.match(pathParamsRegex)) === null || _a === void 0 ? void 0 : _a.map(function (match) { return match.slice(1); })) || [];
|
|
162
|
+
}
|
|
163
|
+
exports.getPathParams = getPathParams;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/flink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
|
|
5
5
|
"types": "dist/src/index.d.ts",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"mkdirp": "^1.0.4",
|
|
41
41
|
"mock-json-schema": "^1.0.8",
|
|
42
42
|
"mongodb": "^3.6.6",
|
|
43
|
+
"morgan": "^1.10.0",
|
|
43
44
|
"ms": "^2.0.0",
|
|
44
45
|
"node-color-log": "^10.0.2",
|
|
45
46
|
"passport": "^0.4.1",
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"@types/jasmine": "^3.7.1",
|
|
58
59
|
"@types/json-schema": "^7.0.7",
|
|
59
60
|
"@types/mkdirp": "^1.0.1",
|
|
61
|
+
"@types/morgan": "^1.9.4",
|
|
60
62
|
"@types/node": "^15.0.1",
|
|
61
63
|
"jasmine": "^3.7.0",
|
|
62
64
|
"jasmine-spec-reporter": "^7.0.0",
|
|
@@ -65,5 +67,5 @@
|
|
|
65
67
|
"rimraf": "^3.0.2",
|
|
66
68
|
"ts-node": "^9.1.1"
|
|
67
69
|
},
|
|
68
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "3c877c210da19119c074c7482e97a485d2334e80"
|
|
69
71
|
}
|
package/src/FlinkApp.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import Ajv from "ajv";
|
|
1
|
+
import Ajv, { ValidateFunction } from "ajv";
|
|
2
2
|
import addFormats from "ajv-formats";
|
|
3
3
|
import bodyParser, { OptionsJson } from "body-parser";
|
|
4
4
|
import cors from "cors";
|
|
5
5
|
import express, { Express, Request } from "express";
|
|
6
6
|
import { JSONSchema7 } from "json-schema";
|
|
7
7
|
import mongodb, { Db } from "mongodb";
|
|
8
|
+
import morgan from "morgan";
|
|
8
9
|
import ms from "ms";
|
|
9
10
|
import { AsyncTask, CronJob, SimpleIntervalJob, ToadScheduler } from "toad-scheduler";
|
|
10
11
|
import { v4 } from "uuid";
|
|
@@ -18,7 +19,7 @@ import { FlinkPlugin } from "./FlinkPlugin";
|
|
|
18
19
|
import { FlinkRepo } from "./FlinkRepo";
|
|
19
20
|
import { FlinkResponse } from "./FlinkResponse";
|
|
20
21
|
import generateMockData from "./mock-data-generator";
|
|
21
|
-
import { isError } from "./utils";
|
|
22
|
+
import { getPathParams, isError } from "./utils";
|
|
22
23
|
|
|
23
24
|
const ajv = new Ajv();
|
|
24
25
|
addFormats(ajv);
|
|
@@ -162,6 +163,29 @@ export interface FlinkOptions {
|
|
|
162
163
|
// */
|
|
163
164
|
// autoAssignCollection?: string;
|
|
164
165
|
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* If true, the HTTP server will be disabled.
|
|
169
|
+
* Only useful when starting a Flink app for testing purposes.
|
|
170
|
+
*/
|
|
171
|
+
disableHttpServer?: boolean;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Configuration for access logs.
|
|
175
|
+
*/
|
|
176
|
+
accessLog?: {
|
|
177
|
+
/**
|
|
178
|
+
* Enables access logs for all requests.
|
|
179
|
+
* Defaults to true.
|
|
180
|
+
*/
|
|
181
|
+
enabled?: boolean;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Optional custom format.
|
|
185
|
+
* Uses `morgan` format and defaults to `dev`.
|
|
186
|
+
*/
|
|
187
|
+
format?: string;
|
|
188
|
+
};
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
export interface HandlerConfig {
|
|
@@ -203,6 +227,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
203
227
|
private routingConfigured = false;
|
|
204
228
|
private jsonOptions?: OptionsJson;
|
|
205
229
|
private schedulingOptions?: FlinkOptions["scheduling"];
|
|
230
|
+
private disableHttpServer = false;
|
|
206
231
|
|
|
207
232
|
private repos: { [x: string]: FlinkRepo<C> } = {};
|
|
208
233
|
|
|
@@ -213,6 +238,8 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
213
238
|
|
|
214
239
|
public scheduler?: ToadScheduler;
|
|
215
240
|
|
|
241
|
+
private accessLog: { enabled: boolean; format: string };
|
|
242
|
+
|
|
216
243
|
constructor(opts: FlinkOptions) {
|
|
217
244
|
this.name = opts.name;
|
|
218
245
|
this.port = opts.port || 3333;
|
|
@@ -224,6 +251,8 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
224
251
|
this.auth = opts.auth;
|
|
225
252
|
this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
|
|
226
253
|
this.schedulingOptions = opts.scheduling;
|
|
254
|
+
this.disableHttpServer = !!opts.disableHttpServer;
|
|
255
|
+
this.accessLog = { enabled: true, format: "dev", ...opts.accessLog };
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
get ctx() {
|
|
@@ -253,18 +282,24 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
253
282
|
|
|
254
283
|
if (this.isSchedulingEnabled) {
|
|
255
284
|
this.scheduler = new ToadScheduler();
|
|
285
|
+
} else {
|
|
286
|
+
log.info("🚫 Scheduling is disabled");
|
|
256
287
|
}
|
|
257
288
|
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
289
|
+
if (!this.disableHttpServer) {
|
|
290
|
+
this.expressApp = express();
|
|
291
|
+
this.expressApp.use(cors(this.corsOpts));
|
|
292
|
+
this.expressApp.use(bodyParser.json(this.jsonOptions));
|
|
261
293
|
|
|
262
|
-
|
|
294
|
+
if (this.accessLog.enabled) {
|
|
295
|
+
this.expressApp.use(morgan(this.accessLog.format));
|
|
296
|
+
}
|
|
263
297
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
298
|
+
this.expressApp.use((req, res, next) => {
|
|
299
|
+
req.reqId = v4();
|
|
300
|
+
next();
|
|
301
|
+
});
|
|
302
|
+
}
|
|
268
303
|
|
|
269
304
|
// TODO: Add better more fine grained control when plugins are initialized, i.e. in what order
|
|
270
305
|
|
|
@@ -301,18 +336,24 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
301
336
|
// Register 404 with slight delay to allow all manually added routes to be added
|
|
302
337
|
// TODO: Is there a better solution to force this handler to always run last?
|
|
303
338
|
setTimeout(() => {
|
|
304
|
-
this.
|
|
305
|
-
|
|
306
|
-
|
|
339
|
+
if (!this.disableHttpServer) {
|
|
340
|
+
this.expressApp!.use((req, res, next) => {
|
|
341
|
+
res.status(404).json(notFound());
|
|
342
|
+
});
|
|
343
|
+
}
|
|
307
344
|
|
|
308
345
|
this.routingConfigured = true;
|
|
309
346
|
});
|
|
310
347
|
|
|
311
|
-
|
|
312
|
-
log.
|
|
313
|
-
|
|
348
|
+
if (this.disableHttpServer) {
|
|
349
|
+
log.info("🚧 HTTP server is disabled, but flink app is running");
|
|
314
350
|
this.started = true;
|
|
315
|
-
}
|
|
351
|
+
} else {
|
|
352
|
+
this.expressApp?.listen(this.port, () => {
|
|
353
|
+
log.fontColorLog("magenta", `⚡️ HTTP server '${this.name}' is running and waiting for connections on ${this.port}`);
|
|
354
|
+
this.started = true;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
316
357
|
|
|
317
358
|
return this;
|
|
318
359
|
}
|
|
@@ -381,7 +422,6 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
381
422
|
|
|
382
423
|
const { routeProps, schema = {} } = handlerConfig;
|
|
383
424
|
const { method } = routeProps;
|
|
384
|
-
const app = this.expressApp!;
|
|
385
425
|
|
|
386
426
|
if (!method) {
|
|
387
427
|
log.error(`Route ${routeProps.path} is missing http method`);
|
|
@@ -390,19 +430,33 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
390
430
|
if (method) {
|
|
391
431
|
const methodAndRoute = `${method.toUpperCase()} ${routeProps.path}`;
|
|
392
432
|
|
|
393
|
-
|
|
433
|
+
if (this.disableHttpServer) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let validateReq: ValidateFunction<any> | undefined;
|
|
438
|
+
let validateRes: ValidateFunction<any> | undefined;
|
|
439
|
+
|
|
440
|
+
if (schema.reqSchema) {
|
|
441
|
+
validateReq = ajv.compile(schema.reqSchema);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (schema.resSchema) {
|
|
445
|
+
validateRes = ajv.compile(schema.resSchema);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
this.expressApp => {
|
|
394
449
|
if (routeProps.permissions) {
|
|
395
450
|
if (!(await this.authenticate(req, routeProps.permissions))) {
|
|
396
451
|
return res.status(401).json(unauthorized());
|
|
397
452
|
}
|
|
398
453
|
}
|
|
399
454
|
|
|
400
|
-
if (
|
|
401
|
-
const
|
|
402
|
-
const valid = validate(req.body);
|
|
455
|
+
if (validateReq) {
|
|
456
|
+
const valid = validateReq(req.body);
|
|
403
457
|
|
|
404
458
|
if (!valid) {
|
|
405
|
-
log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(
|
|
459
|
+
log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validateReq.errors, null, 2)}`);
|
|
406
460
|
|
|
407
461
|
log.debug(`Invalid json: ${JSON.stringify(req.body)}`);
|
|
408
462
|
|
|
@@ -411,7 +465,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
411
465
|
error: {
|
|
412
466
|
id: v4(),
|
|
413
467
|
title: "Bad request",
|
|
414
|
-
detail: `Schema did not validate ${JSON.stringify(
|
|
468
|
+
detail: `Schema did not validate ${JSON.stringify(validateReq.errors)}`,
|
|
415
469
|
},
|
|
416
470
|
});
|
|
417
471
|
}
|
|
@@ -444,12 +498,11 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
444
498
|
return res.status(500).json(internalServerError(err as any));
|
|
445
499
|
}
|
|
446
500
|
|
|
447
|
-
if (
|
|
448
|
-
const
|
|
449
|
-
const valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
501
|
+
if (validateRes && !isError(handlerRes)) {
|
|
502
|
+
const valid = validateRes(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
450
503
|
|
|
451
504
|
if (!valid) {
|
|
452
|
-
log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(
|
|
505
|
+
log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validateRes.errors, null, 2)}`);
|
|
453
506
|
log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
|
|
454
507
|
// log.debug(JSON.stringify(schema, null, 2));
|
|
455
508
|
|
|
@@ -458,7 +511,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
458
511
|
error: {
|
|
459
512
|
id: v4(),
|
|
460
513
|
title: "Bad response",
|
|
461
|
-
detail: `Schema did not validate ${JSON.stringify(
|
|
514
|
+
detail: `Schema did not validate ${JSON.stringify(validateRes.errors)}`,
|
|
462
515
|
},
|
|
463
516
|
});
|
|
464
517
|
}
|
|
@@ -497,6 +550,23 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
497
550
|
continue;
|
|
498
551
|
}
|
|
499
552
|
|
|
553
|
+
if (!!handler.__params?.length) {
|
|
554
|
+
const pathParams = getPathParams(handler.Route.path);
|
|
555
|
+
|
|
556
|
+
for (const param of handler.__params) {
|
|
557
|
+
if (!pathParams.includes(param.name)) {
|
|
558
|
+
log.error(`Handler ${handler.__file} has param ${param.name} but it is not present in the path '${handler.Route.path}'`);
|
|
559
|
+
throw new Error("Invalid/missing handler path param");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (pathParams.length !== handler.__params.length) {
|
|
564
|
+
log.warn(
|
|
565
|
+
`Handler ${handler.__file} has ${handler.__params.length} typed params but the path '${handler.Route.path}' has ${pathParams.length} params`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
500
570
|
this.registerHandler(
|
|
501
571
|
{
|
|
502
572
|
routeProps: {
|
package/src/utils.ts
CHANGED
|
@@ -88,3 +88,15 @@ export function getJsDocComment(comment: string) {
|
|
|
88
88
|
|
|
89
89
|
return rows.join("\n").trim();
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
const pathParamsRegex = /:([a-zA-Z0-9]+)/g;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns array of path params from path string.
|
|
96
|
+
* For example `/user/:id` will return `["id"]`
|
|
97
|
+
* @param path
|
|
98
|
+
* @returns
|
|
99
|
+
*/
|
|
100
|
+
export function getPathParams(path: string) {
|
|
101
|
+
return path.match(pathParamsRegex)?.map((match) => match.slice(1)) || [];
|
|
102
|
+
}
|