@flink-app/flink 0.4.7 → 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.
@@ -112,6 +112,21 @@ export interface FlinkOptions {
112
112
  * Only useful when starting a Flink app for testing purposes.
113
113
  */
114
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
+ };
115
130
  }
116
131
  export interface HandlerConfig {
117
132
  schema?: {
@@ -157,6 +172,7 @@ export declare class FlinkApp<C extends FlinkContext> {
157
172
  */
158
173
  private handlerRouteCache;
159
174
  scheduler?: ToadScheduler;
175
+ private accessLog;
160
176
  constructor(opts: FlinkOptions);
161
177
  get ctx(): C;
162
178
  start(): Promise<this>;
@@ -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");
@@ -110,6 +111,7 @@ var FlinkApp = /** @class */ (function () {
110
111
  this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
111
112
  this.schedulingOptions = opts.scheduling;
112
113
  this.disableHttpServer = !!opts.disableHttpServer;
114
+ this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
113
115
  }
114
116
  Object.defineProperty(FlinkApp.prototype, "ctx", {
115
117
  get: function () {
@@ -155,6 +157,9 @@ var FlinkApp = /** @class */ (function () {
155
157
  this.expressApp = express_1.default();
156
158
  this.expressApp.use(cors_1.default(this.corsOpts));
157
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
+ }
158
163
  this.expressApp.use(function (req, res, next) {
159
164
  req.reqId = uuid_1.v4();
160
165
  next();
@@ -281,8 +286,16 @@ var FlinkApp = /** @class */ (function () {
281
286
  if (this.disableHttpServer) {
282
287
  return;
283
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
+ }
284
297
  this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
285
- var validate, valid, data, handlerRes, err_1, validate, valid;
298
+ var valid, data, handlerRes, err_1, valid;
286
299
  return __generator(this, function (_a) {
287
300
  switch (_a.label) {
288
301
  case 0:
@@ -294,18 +307,17 @@ var FlinkApp = /** @class */ (function () {
294
307
  }
295
308
  _a.label = 2;
296
309
  case 2:
297
- if (schema.reqSchema) {
298
- validate = ajv.compile(schema.reqSchema);
299
- valid = validate(req.body);
310
+ if (validateReq_1) {
311
+ valid = validateReq_1(req.body);
300
312
  if (!valid) {
301
- FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(validate.errors, null, 2));
313
+ FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(validateReq_1.errors, null, 2));
302
314
  FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(req.body));
303
315
  return [2 /*return*/, res.status(400).json({
304
316
  status: 400,
305
317
  error: {
306
318
  id: uuid_1.v4(),
307
319
  title: "Bad request",
308
- detail: "Schema did not validate " + JSON.stringify(validate.errors),
320
+ detail: "Schema did not validate " + JSON.stringify(validateReq_1.errors),
309
321
  },
310
322
  })];
311
323
  }
@@ -337,11 +349,10 @@ var FlinkApp = /** @class */ (function () {
337
349
  console.error(err_1);
338
350
  return [2 /*return*/, res.status(500).json(FlinkErrors_1.internalServerError(err_1))];
339
351
  case 6:
340
- if (schema.resSchema && !utils_1.isError(handlerRes)) {
341
- validate = ajv.compile(schema.resSchema);
342
- 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)));
343
354
  if (!valid) {
344
- FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(validate.errors, null, 2));
355
+ FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(validateRes_1.errors, null, 2));
345
356
  FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(handlerRes.data));
346
357
  // log.debug(JSON.stringify(schema, null, 2));
347
358
  return [2 /*return*/, res.status(500).json({
@@ -349,7 +360,7 @@ var FlinkApp = /** @class */ (function () {
349
360
  error: {
350
361
  id: uuid_1.v4(),
351
362
  title: "Bad response",
352
- detail: "Schema did not validate " + JSON.stringify(validate.errors),
363
+ detail: "Schema did not validate " + JSON.stringify(validateRes_1.errors),
353
364
  },
354
365
  })];
355
366
  }
@@ -377,12 +388,12 @@ var FlinkApp = /** @class */ (function () {
377
388
  * Will not register any handlers added programmatically.
378
389
  */
379
390
  FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
380
- var _a, _b;
391
+ var _a, _b, _c;
381
392
  return __awaiter(this, void 0, void 0, function () {
382
- var _i, autoRegisteredHandlers_1, _c, handler, assumedHttpMethod;
383
- return __generator(this, function (_d) {
393
+ var _i, autoRegisteredHandlers_1, _d, handler, assumedHttpMethod, pathParams, _e, _f, param;
394
+ return __generator(this, function (_g) {
384
395
  for (_i = 0, autoRegisteredHandlers_1 = exports.autoRegisteredHandlers; _i < autoRegisteredHandlers_1.length; _i++) {
385
- _c = autoRegisteredHandlers_1[_i], handler = _c.handler, assumedHttpMethod = _c.assumedHttpMethod;
396
+ _d = autoRegisteredHandlers_1[_i], handler = _d.handler, assumedHttpMethod = _d.assumedHttpMethod;
386
397
  if (!handler.Route) {
387
398
  FlinkLog_1.log.error("Missing Props in handler " + handler.__file);
388
399
  continue;
@@ -391,11 +402,24 @@ var FlinkApp = /** @class */ (function () {
391
402
  FlinkLog_1.log.error("Missing exported handler function in handler " + handler.__file);
392
403
  continue;
393
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
+ }
394
418
  this.registerHandler({
395
419
  routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
396
420
  schema: {
397
- reqSchema: (_a = handler.__schemas) === null || _a === void 0 ? void 0 : _a.reqSchema,
398
- resSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.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,
399
423
  },
400
424
  queryMetadata: handler.__query || [],
401
425
  paramsMetadata: handler.__params || [],
@@ -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.4.7",
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": "b565d5987e08ba3aba3653325e935e6c56cab24c"
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);
@@ -168,6 +169,23 @@ export interface FlinkOptions {
168
169
  * Only useful when starting a Flink app for testing purposes.
169
170
  */
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
+ };
171
189
  }
172
190
 
173
191
  export interface HandlerConfig {
@@ -220,6 +238,8 @@ export class FlinkApp<C extends FlinkContext> {
220
238
 
221
239
  public scheduler?: ToadScheduler;
222
240
 
241
+ private accessLog: { enabled: boolean; format: string };
242
+
223
243
  constructor(opts: FlinkOptions) {
224
244
  this.name = opts.name;
225
245
  this.port = opts.port || 3333;
@@ -232,6 +252,7 @@ export class FlinkApp<C extends FlinkContext> {
232
252
  this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
233
253
  this.schedulingOptions = opts.scheduling;
234
254
  this.disableHttpServer = !!opts.disableHttpServer;
255
+ this.accessLog = { enabled: true, format: "dev", ...opts.accessLog };
235
256
  }
236
257
 
237
258
  get ctx() {
@@ -267,11 +288,13 @@ export class FlinkApp<C extends FlinkContext> {
267
288
 
268
289
  if (!this.disableHttpServer) {
269
290
  this.expressApp = express();
270
-
271
291
  this.expressApp.use(cors(this.corsOpts));
272
-
273
292
  this.expressApp.use(bodyParser.json(this.jsonOptions));
274
293
 
294
+ if (this.accessLog.enabled) {
295
+ this.expressApp.use(morgan(this.accessLog.format));
296
+ }
297
+
275
298
  this.expressApp.use((req, res, next) => {
276
299
  req.reqId = v4();
277
300
  next();
@@ -411,6 +434,17 @@ export class FlinkApp<C extends FlinkContext> {
411
434
  return;
412
435
  }
413
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
+
414
448
  this.expressApp![method](routeProps.path, async (req, res) => {
415
449
  if (routeProps.permissions) {
416
450
  if (!(await this.authenticate(req, routeProps.permissions))) {
@@ -418,12 +452,11 @@ export class FlinkApp<C extends FlinkContext> {
418
452
  }
419
453
  }
420
454
 
421
- if (schema.reqSchema) {
422
- const validate = ajv.compile(schema.reqSchema);
423
- const valid = validate(req.body);
455
+ if (validateReq) {
456
+ const valid = validateReq(req.body);
424
457
 
425
458
  if (!valid) {
426
- log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validate.errors, null, 2)}`);
459
+ log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validateReq.errors, null, 2)}`);
427
460
 
428
461
  log.debug(`Invalid json: ${JSON.stringify(req.body)}`);
429
462
 
@@ -432,7 +465,7 @@ export class FlinkApp<C extends FlinkContext> {
432
465
  error: {
433
466
  id: v4(),
434
467
  title: "Bad request",
435
- detail: `Schema did not validate ${JSON.stringify(validate.errors)}`,
468
+ detail: `Schema did not validate ${JSON.stringify(validateReq.errors)}`,
436
469
  },
437
470
  });
438
471
  }
@@ -465,12 +498,11 @@ export class FlinkApp<C extends FlinkContext> {
465
498
  return res.status(500).json(internalServerError(err as any));
466
499
  }
467
500
 
468
- if (schema.resSchema && !isError(handlerRes)) {
469
- const validate = ajv.compile(schema.resSchema);
470
- const valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
501
+ if (validateRes && !isError(handlerRes)) {
502
+ const valid = validateRes(JSON.parse(JSON.stringify(handlerRes.data)));
471
503
 
472
504
  if (!valid) {
473
- log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validate.errors, null, 2)}`);
505
+ log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validateRes.errors, null, 2)}`);
474
506
  log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
475
507
  // log.debug(JSON.stringify(schema, null, 2));
476
508
 
@@ -479,7 +511,7 @@ export class FlinkApp<C extends FlinkContext> {
479
511
  error: {
480
512
  id: v4(),
481
513
  title: "Bad response",
482
- detail: `Schema did not validate ${JSON.stringify(validate.errors)}`,
514
+ detail: `Schema did not validate ${JSON.stringify(validateRes.errors)}`,
483
515
  },
484
516
  });
485
517
  }
@@ -518,6 +550,23 @@ export class FlinkApp<C extends FlinkContext> {
518
550
  continue;
519
551
  }
520
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
+
521
570
  this.registerHandler(
522
571
  {
523
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
+ }