@flink-app/flink 0.12.1-alpha.3 → 0.12.1-alpha.35

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.
Files changed (30) hide show
  1. package/dist/src/FlinkApp.d.ts +4 -1
  2. package/dist/src/FlinkApp.js +52 -12
  3. package/dist/src/FlinkHttpHandler.d.ts +14 -1
  4. package/dist/src/FlinkRepo.d.ts +23 -7
  5. package/dist/src/FlinkRepo.js +32 -10
  6. package/dist/src/TypeScriptCompiler.d.ts +10 -0
  7. package/dist/src/TypeScriptCompiler.js +91 -18
  8. package/package.json +7 -4
  9. package/spec/FlinkRepo.spec.ts +11 -0
  10. package/spec/TypeScriptCompiler.spec.ts +8 -0
  11. package/spec/mock-project/dist/src/handlers/GetCar.js +2 -0
  12. package/spec/mock-project/dist/src/handlers/GetCar2.js +2 -0
  13. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +2 -0
  14. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +2 -0
  15. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +2 -0
  16. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +2 -0
  17. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +2 -0
  18. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +2 -0
  19. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +2 -0
  20. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +2 -0
  21. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +2 -0
  22. package/spec/mock-project/dist/src/handlers/PostCar.js +2 -0
  23. package/spec/mock-project/dist/src/handlers/PostLogin.js +2 -0
  24. package/spec/mock-project/dist/src/handlers/PostLogout.js +57 -0
  25. package/spec/mock-project/dist/src/handlers/PutCar.js +2 -0
  26. package/spec/mock-project/src/handlers/PostLogout.ts +19 -0
  27. package/src/FlinkApp.ts +44 -6
  28. package/src/FlinkHttpHandler.ts +95 -96
  29. package/src/FlinkRepo.ts +24 -7
  30. package/src/TypeScriptCompiler.ts +81 -23
@@ -53,3 +53,5 @@ var GetCarWithLiteralSchema = function (_a) { return __awaiter(void 0, [_a], voi
53
53
  exports.default = GetCarWithLiteralSchema;
54
54
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithLiteralSchema.ts", exports.__query = [], exports.__params = [];
55
55
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "car": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false } }, "required": ["car"], "additionalProperties": false, "definitions": {} } };
56
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithLiteralSchema.ts", exports.__query = [], exports.__params = [];
57
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "car": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false } }, "required": ["car"], "additionalProperties": false, "definitions": {} } };
@@ -53,3 +53,5 @@ var GetCarWithLiteralSchema2 = function (_a) { return __awaiter(void 0, [_a], vo
53
53
  exports.default = GetCarWithLiteralSchema2;
54
54
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithLiteralSchema2.ts", exports.__query = [], exports.__params = [];
55
55
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "car": { "type": "object", "properties": { "nestedCar": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false } }, "required": ["nestedCar"], "additionalProperties": false } }, "required": ["car"], "additionalProperties": false, "definitions": {} } };
56
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithLiteralSchema2.ts", exports.__query = [], exports.__params = [];
57
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "car": { "type": "object", "properties": { "nestedCar": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false } }, "required": ["nestedCar"], "additionalProperties": false } }, "required": ["car"], "additionalProperties": false, "definitions": {} } };
@@ -56,3 +56,5 @@ var GetCarWithSchemaInFile = function (_a) { return __awaiter(void 0, [_a], void
56
56
  exports.default = GetCarWithSchemaInFile;
57
57
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithSchemaInFile.ts", exports.__query = [], exports.__params = [];
58
58
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "model": { "type": "string" } }, "required": ["model"], "additionalProperties": false, "definitions": {} } };
59
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithSchemaInFile.ts", exports.__query = [], exports.__params = [];
60
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "model": { "type": "string" } }, "required": ["model"], "additionalProperties": false, "definitions": {} } };
@@ -56,3 +56,5 @@ var GetCarWithSchemaInFile2 = function (_a) { return __awaiter(void 0, [_a], voi
56
56
  exports.default = GetCarWithSchemaInFile2;
57
57
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithSchemaInFile2.ts", exports.__query = [], exports.__params = [];
58
58
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "car": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false } }, "required": ["car"], "additionalProperties": false, "definitions": {} } };
59
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithSchemaInFile2.ts", exports.__query = [], exports.__params = [];
60
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "car": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false } }, "required": ["car"], "additionalProperties": false, "definitions": {} } };
@@ -52,3 +52,5 @@ var manuallyAddedHandler = function (_a) { return __awaiter(void 0, [_a], void 0
52
52
  exports.default = manuallyAddedHandler;
53
53
  exports.__assumedHttpMethod = "", exports.__file = "ManuallyAddedHandler.ts", exports.__query = [], exports.__params = [];
54
54
  exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: undefined };
55
+ exports.__assumedHttpMethod = "", exports.__file = "ManuallyAddedHandler.ts", exports.__query = [], exports.__params = [];
56
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: undefined };
@@ -54,3 +54,5 @@ var manuallyAddedHandler = function (_a) { return __awaiter(void 0, [_a], void 0
54
54
  exports.default = manuallyAddedHandler;
55
55
  exports.__assumedHttpMethod = "", exports.__file = "ManuallyAddedHandler2.ts", exports.__query = [], exports.__params = [];
56
56
  exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: undefined };
57
+ exports.__assumedHttpMethod = "", exports.__file = "ManuallyAddedHandler2.ts", exports.__query = [], exports.__params = [];
58
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: undefined };
@@ -53,3 +53,5 @@ var PostCar = function (_a) { return __awaiter(void 0, [_a], void 0, function (_
53
53
  exports.default = PostCar;
54
54
  exports.__assumedHttpMethod = "post", exports.__file = "PostCar.ts", exports.__query = [], exports.__params = [];
55
55
  exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} } };
56
+ exports.__assumedHttpMethod = "post", exports.__file = "PostCar.ts", exports.__query = [], exports.__params = [];
57
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} } };
@@ -54,3 +54,5 @@ var PostLogin = function (_a) { return __awaiter(void 0, [_a], void 0, function
54
54
  exports.default = PostLogin;
55
55
  exports.__assumedHttpMethod = "post", exports.__file = "PostLogin.ts", exports.__query = [], exports.__params = [];
56
56
  exports.__schemas = { reqSchema: undefined, resSchema: undefined };
57
+ exports.__assumedHttpMethod = "post", exports.__file = "PostLogin.ts", exports.__query = [], exports.__params = [];
58
+ exports.__schemas = { reqSchema: undefined, resSchema: undefined };
@@ -0,0 +1,57 @@
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 __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.__schemas = exports.__params = exports.__query = exports.__file = exports.__assumedHttpMethod = exports.Route = void 0;
40
+ exports.Route = {
41
+ path: "/logout",
42
+ };
43
+ var PostLogout = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
44
+ var ctx = _b.ctx, req = _b.req;
45
+ return __generator(this, function (_c) {
46
+ return [2 /*return*/, {
47
+ data: {
48
+ success: true,
49
+ },
50
+ }];
51
+ });
52
+ }); };
53
+ exports.default = PostLogout;
54
+ exports.__assumedHttpMethod = "post", exports.__file = "PostLogout.ts", exports.__query = [], exports.__params = [];
55
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "definitions": {} }, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "success": { "type": "boolean" } }, "required": ["success"], "additionalProperties": false, "definitions": {} } };
56
+ exports.__assumedHttpMethod = "post", exports.__file = "PostLogout.ts", exports.__query = [], exports.__params = [];
57
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "definitions": {} }, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "success": { "type": "boolean" } }, "required": ["success"], "additionalProperties": false, "definitions": {} } };
@@ -53,3 +53,5 @@ var PutCar = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b
53
53
  exports.default = PutCar;
54
54
  exports.__assumedHttpMethod = "put", exports.__file = "PutCar.ts", exports.__query = [], exports.__params = [];
55
55
  exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: undefined };
56
+ exports.__assumedHttpMethod = "put", exports.__file = "PutCar.ts", exports.__query = [], exports.__params = [];
57
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "definitions": {} }, resSchema: undefined };
@@ -0,0 +1,19 @@
1
+ import { Handler, RouteProps } from "@flink-app/flink";
2
+
3
+ interface LogoutResponse {
4
+ success: boolean;
5
+ }
6
+
7
+ export const Route: RouteProps = {
8
+ path: "/logout",
9
+ };
10
+
11
+ const PostLogout: Handler<any, {}, LogoutResponse> = async ({ ctx, req }) => {
12
+ return {
13
+ data: {
14
+ success: true,
15
+ },
16
+ };
17
+ };
18
+
19
+ export default PostLogout;
package/src/FlinkApp.ts CHANGED
@@ -12,7 +12,7 @@ import { v4 } from "uuid";
12
12
  import { FlinkAuthPlugin } from "./auth/FlinkAuthPlugin";
13
13
  import { FlinkContext } from "./FlinkContext";
14
14
  import { internalServerError, notFound, unauthorized } from "./FlinkErrors";
15
- import { Handler, HandlerFile, HttpMethod, QueryParamMetadata, RouteProps } from "./FlinkHttpHandler";
15
+ import { FlinkRequest, Handler, HandlerFile, HttpMethod, QueryParamMetadata, RouteProps } from "./FlinkHttpHandler";
16
16
  import { FlinkJobFile } from "./FlinkJob";
17
17
  import { log } from "./FlinkLog";
18
18
  import { FlinkPlugin } from "./FlinkPlugin";
@@ -217,6 +217,7 @@ export class FlinkApp<C extends FlinkContext> {
217
217
  public name: string;
218
218
  public expressApp?: Express;
219
219
  public db?: Db;
220
+ public dbClient?: MongoClient;
220
221
  public handlers: HandlerConfig[] = [];
221
222
  public port?: number;
222
223
  public started = false;
@@ -234,6 +235,7 @@ export class FlinkApp<C extends FlinkContext> {
234
235
  private rawContentTypes?: string[];
235
236
  private schedulingOptions?: FlinkOptions["scheduling"];
236
237
  private disableHttpServer = false;
238
+ private expressServer: any; // for simplicity, we don't want to import types from express/node here
237
239
 
238
240
  private repos: { [x: string]: FlinkRepo<C, any> } = {};
239
241
 
@@ -367,7 +369,7 @@ export class FlinkApp<C extends FlinkContext> {
367
369
  log.info("🚧 HTTP server is disabled, but flink app is running");
368
370
  this.started = true;
369
371
  } else {
370
- this.expressApp?.listen(this.port, () => {
372
+ this.expressServer = this.expressApp?.listen(this.port, () => {
371
373
  log.fontColorLog("magenta", `⚡️ HTTP server '${this.name}' is running and waiting for connections on ${this.port}`);
372
374
  this.started = true;
373
375
  });
@@ -376,6 +378,28 @@ export class FlinkApp<C extends FlinkContext> {
376
378
  return this;
377
379
  }
378
380
 
381
+ async stop() {
382
+ log.info("🛑 Stopping Flink app...");
383
+
384
+ if (this.scheduler) {
385
+ await this.scheduler.stop();
386
+ }
387
+
388
+ if (this.expressServer) {
389
+ return new Promise<void>((resolve, reject) => {
390
+ const int = setTimeout(() => {
391
+ reject("Failed to stop HTTP server in time");
392
+ }, 2000);
393
+
394
+ this.expressServer.close(() => {
395
+ clearInterval(int);
396
+ log.info("HTTP server stopped");
397
+ resolve();
398
+ });
399
+ });
400
+ }
401
+ }
402
+
379
403
  /**
380
404
  * Manually registers a handler.
381
405
  *
@@ -506,7 +530,7 @@ export class FlinkApp<C extends FlinkContext> {
506
530
  try {
507
531
  // 👇 This is where the actual handler gets invoked
508
532
  handlerRes = await handler({
509
- req,
533
+ req: req as FlinkRequest,
510
534
  ctx: this.ctx,
511
535
  origin: routeProps.origin,
512
536
  });
@@ -570,7 +594,7 @@ export class FlinkApp<C extends FlinkContext> {
570
594
  * Will not register any handlers added programmatically.
571
595
  */
572
596
  private async registerAutoRegisterableHandlers() {
573
- for (const { handler, assumedHttpMethod } of autoRegisteredHandlers) {
597
+ for (const { handler, assumedHttpMethod } of autoRegisteredHandlers.sort((a, b) => (a.handler.Route?.order || 0) - (b.handler.Route?.order || 0))) {
574
598
  if (!handler.Route) {
575
599
  log.error(`Missing Props in handler ${handler.__file}`);
576
600
  continue;
@@ -717,7 +741,7 @@ export class FlinkApp<C extends FlinkContext> {
717
741
  private async buildContext() {
718
742
  if (this.dbOpts) {
719
743
  for (const { collectionName, repoInstanceName, Repo } of autoRegisteredRepos) {
720
- const repoInstance: FlinkRepo<C, any> = new Repo(collectionName, this.db);
744
+ const repoInstance: FlinkRepo<C, any> = new Repo(collectionName, this.db, this.dbClient);
721
745
 
722
746
  this.repos[repoInstanceName] = repoInstance;
723
747
 
@@ -753,8 +777,10 @@ export class FlinkApp<C extends FlinkContext> {
753
777
  if (this.dbOpts) {
754
778
  try {
755
779
  log.debug("Connecting to db");
780
+
756
781
  const client = await MongoClient.connect(this.dbOpts.uri, this.getMongoConnectionOptions());
757
782
  this.db = client.db();
783
+ this.dbClient = client;
758
784
  } catch (err) {
759
785
  log.error("Failed to connect to db: " + err);
760
786
  process.exit(1);
@@ -797,7 +823,7 @@ export class FlinkApp<C extends FlinkContext> {
797
823
  if (!this.auth) {
798
824
  throw new Error(`Attempting to authenticate request (${req.method} ${req.path}) but no authPlugin is set`);
799
825
  }
800
- return await this.auth.authenticateRequest(req, permissions);
826
+ return await this.auth.authenticateRequest(req as FlinkRequest, permissions);
801
827
  }
802
828
 
803
829
  public getRegisteredRoutes() {
@@ -813,6 +839,18 @@ export class FlinkApp<C extends FlinkContext> {
813
839
  throw new Error("No db configured");
814
840
  }
815
841
 
842
+ const { version: driverVersion } = require("mongodb/package.json");
843
+
844
+ if (driverVersion.startsWith("3")) {
845
+ log.debug(`Using legacy mongodb connection options as mongo client is version ${driverVersion}`);
846
+ return {
847
+ useNewUrlParser: true,
848
+ useUnifiedTopology: true,
849
+ };
850
+ }
851
+
852
+ log.debug(`Using modern MongoDB client options (driver version ${driverVersion})`);
853
+
816
854
  return {
817
855
  serverApi: {
818
856
  version: ServerApiVersion.v1,
@@ -5,24 +5,25 @@ import { FlinkError } from "./FlinkErrors";
5
5
  import { FlinkResponse } from "./FlinkResponse";
6
6
 
7
7
  export enum HttpMethod {
8
- get = "get",
9
- post = "post",
10
- put = "put",
11
- delete = "delete",
8
+ get = "get",
9
+ post = "post",
10
+ put = "put",
11
+ delete = "delete",
12
12
  }
13
13
 
14
14
  type Params = Request["params"];
15
- type Query = Request["query"];
15
+
16
+ /**
17
+ * Query type for request query parameters.
18
+ * Does currently not allow nested objects, although
19
+ * underlying express Request does allow it.
20
+ */
21
+ type Query = Record<string, string | string[] | undefined>;
16
22
 
17
23
  /**
18
24
  * Flink request extends express Request but adds reqId and user object.
19
25
  */
20
- export type FlinkRequest<T = any, P = Params, Q = Query> = Request<
21
- P,
22
- any,
23
- T,
24
- Q
25
- > & { reqId: string; user?: any };
26
+ export type FlinkRequest<T = any, P = Params, Q = Query> = Request<P, any, T, Q> & { reqId: string; user?: any };
26
27
 
27
28
  /**
28
29
  * Route props to control routing.
@@ -31,66 +32,69 @@ export type FlinkRequest<T = any, P = Params, Q = Query> = Request<
31
32
  * instructs express web server how to route traffic.
32
33
  */
33
34
  export interface RouteProps {
34
- /**
35
- * HTTP method which this handlers responds to.
36
- *
37
- * Will if not set attempt to extract HTTP method based
38
- * on handler file name prefix, for example `GetFoo.ts` will assume
39
- * HTTP method `GET`.
40
- */
41
- method?: HttpMethod;
42
-
43
- /**
44
- * Route path including any path params.
45
- * Example: `/user/:id`
46
- */
47
- path: string;
48
-
49
- /**
50
- * Generates mock response based on handlers response schema.
51
- *
52
- * Will be ignored if handler does not have any response schema defined.
53
- *
54
- * This should only be used during development 💥
55
- */
56
- mockApi?: boolean;
57
-
58
- /**
59
- * Set permissions needed to access route if route requires authentication.
60
- */
61
- permissions?: string | string[];
62
-
63
- /**
64
- * Optional documentation of endpoint. Can be used for example in API docs.
65
- * Supports markdown strings.
66
- */
67
- docs?: string; // TODO
68
-
69
- /**
70
- * If handler should not be auto registered
71
- */
72
- skipAutoRegister?: boolean;
73
-
74
- /**
75
- * I.e. filename or plugin name that describes where handler origins from
76
- */
77
- origin?: string;
35
+ /**
36
+ * HTTP method which this handlers responds to.
37
+ *
38
+ * Will if not set attempt to extract HTTP method based
39
+ * on handler file name prefix, for example `GetFoo.ts` will assume
40
+ * HTTP method `GET`.
41
+ */
42
+ method?: HttpMethod;
43
+
44
+ /**
45
+ * Route path including any path params.
46
+ * Example: `/user/:id`
47
+ */
48
+ path: string;
49
+
50
+ /**
51
+ * Generates mock response based on handlers response schema.
52
+ *
53
+ * Will be ignored if handler does not have any response schema defined.
54
+ *
55
+ * This should only be used during development 💥
56
+ */
57
+ mockApi?: boolean;
58
+
59
+ /**
60
+ * Set permissions needed to access route if route requires authentication.
61
+ */
62
+ permissions?: string | string[];
63
+
64
+ /**
65
+ * Optional documentation of endpoint. Can be used for example in API docs.
66
+ * Supports markdown strings.
67
+ */
68
+ docs?: string; // TODO
69
+
70
+ /**
71
+ * If handler should not be auto registered
72
+ */
73
+ skipAutoRegister?: boolean;
74
+
75
+ /**
76
+ * I.e. filename or plugin name that describes where handler origins from
77
+ */
78
+ origin?: string;
79
+
80
+ /**
81
+ * Order handler should be registered in.
82
+ *
83
+ * By default all handlers has order 0 and in most cases this is fine,
84
+ * but if for example you want to register a handler before all others
85
+ * to avoid conflicts you can set a negative order.
86
+ */
87
+ order?: number;
78
88
  }
79
89
 
80
90
  /**
81
91
  * Http handler function that handlers implements in order to
82
92
  * handle HTTP requests and return a JSON response.
83
93
  */
84
- export type Handler<
85
- Ctx extends FlinkContext,
86
- ReqSchema = any,
87
- ResSchema = any,
88
- P extends Params = Params,
89
- Q extends Query = Query
90
- > = (props: {
91
- req: FlinkRequest<ReqSchema, P, Q>;
92
- ctx: Ctx;
93
- origin?: string;
94
+ export type Handler<Ctx extends FlinkContext, ReqSchema = any, ResSchema = any, P extends Params = Params, Q extends Query = Query> = (props: {
95
+ req: FlinkRequest<ReqSchema, P, Q>;
96
+ ctx: Ctx;
97
+ origin?: string;
94
98
  }) => Promise<FlinkResponse<ResSchema | FlinkError>>;
95
99
 
96
100
  /**
@@ -99,12 +103,7 @@ export type Handler<
99
103
  *
100
104
  * Just syntactic sugar on top op `HandlerFn`
101
105
  */
102
- export type GetHandler<
103
- Ctx extends FlinkContext,
104
- ResSchema = any,
105
- P extends Params = Params,
106
- Q extends Query = Query
107
- > = Handler<Ctx, any, ResSchema, P, Q>;
106
+ export type GetHandler<Ctx extends FlinkContext, ResSchema = any, P extends Params = Params, Q extends Query = Query> = Handler<Ctx, any, ResSchema, P, Q>;
108
107
 
109
108
  /**
110
109
  * Type for Handler file. Describes shape of exports when using
@@ -113,32 +112,32 @@ export type GetHandler<
113
112
  * `import * as FooHandler from "./src/handlers/FooHandler"
114
113
  */
115
114
  export type HandlerFile = {
116
- default: Handler<any, any, any, any, any>;
117
- Route?: RouteProps;
118
- /**
119
- * Name of schemas, is set at compile time by Flink compiler.
120
- */
121
- __schemas?: {
122
- reqSchema?: JSONSchema;
123
- resSchema?: JSONSchema;
124
- };
125
- /**
126
- * Typescript source file name, is set at compile time by Flink compiler.
127
- */
128
- __file?: string;
129
-
130
- /**
131
- * Description of query params, is set at compile time by Flink compiler.
132
- */
133
- __query?: QueryParamMetadata[];
134
-
135
- /**
136
- * Description of path params, is set at compile time by Flink compiler.
137
- */
138
- __params?: QueryParamMetadata[];
115
+ default: Handler<any, any, any, any, any>;
116
+ Route?: RouteProps;
117
+ /**
118
+ * Name of schemas, is set at compile time by Flink compiler.
119
+ */
120
+ __schemas?: {
121
+ reqSchema?: JSONSchema;
122
+ resSchema?: JSONSchema;
123
+ };
124
+ /**
125
+ * Typescript source file name, is set at compile time by Flink compiler.
126
+ */
127
+ __file?: string;
128
+
129
+ /**
130
+ * Description of query params, is set at compile time by Flink compiler.
131
+ */
132
+ __query?: QueryParamMetadata[];
133
+
134
+ /**
135
+ * Description of path params, is set at compile time by Flink compiler.
136
+ */
137
+ __params?: QueryParamMetadata[];
139
138
  };
140
139
 
141
140
  export type QueryParamMetadata = {
142
- name: string;
143
- description: string;
141
+ name: string;
142
+ description: string;
144
143
  };
package/src/FlinkRepo.ts CHANGED
@@ -1,6 +1,12 @@
1
- import { Collection, Db, Document, InsertOneResult, ObjectId } from "mongodb";
1
+ import { Collection, Db, Document, InsertOneResult, MongoClient, ObjectId } from "mongodb";
2
2
  import { FlinkContext } from "./FlinkContext";
3
3
 
4
+ /**
5
+ * Partial model to have intellisense for partial updates but
6
+ * also allow any other properties to be set such as nested objects etc.
7
+ */
8
+ type PartialModel<Model> = Partial<Model> & { [x: string]: any };
9
+
4
10
  export abstract class FlinkRepo<C extends FlinkContext, Model extends Document> {
5
11
  collection: Collection;
6
12
 
@@ -15,7 +21,7 @@ export abstract class FlinkRepo<C extends FlinkContext, Model extends Document>
15
21
  return this._ctx;
16
22
  }
17
23
 
18
- constructor(private collectionName: string, private db: Db) {
24
+ constructor(public collectionName: string, public db: Db, public client?: MongoClient) {
19
25
  this.collection = db.collection(this.collectionName);
20
26
  }
21
27
 
@@ -45,10 +51,12 @@ export abstract class FlinkRepo<C extends FlinkContext, Model extends Document>
45
51
  return { ...model, _id: result.insertedId.toString() };
46
52
  }
47
53
 
48
- async updateOne(id: string | ObjectId, model: Partial<Model>): Promise<Model | null> {
54
+ async updateOne(id: string | ObjectId, model: PartialModel<Model>): Promise<Model | null> {
49
55
  const oid = this.buildId(id);
50
56
 
51
- await this.collection.updateOne({ _id: oid }, { $set: model });
57
+ const { _id, ...modelWithoutId } = model;
58
+
59
+ await this.collection.updateOne({ _id: oid }, { $set: modelWithoutId });
52
60
 
53
61
  const res = await this.collection.findOne<Model>({ _id: oid });
54
62
 
@@ -58,9 +66,11 @@ export abstract class FlinkRepo<C extends FlinkContext, Model extends Document>
58
66
  return null;
59
67
  }
60
68
 
61
- async updateMany<U = Partial<Model>>(query: any, model: U): Promise<number> {
69
+ async updateMany<U = PartialModel<Model>>(query: any, model: U): Promise<number> {
70
+ const { _id, ...modelWithoutId } = model as any;
71
+
62
72
  const { modifiedCount } = await this.collection.updateMany(query, {
63
- $set: model as any,
73
+ $set: modelWithoutId as any,
64
74
  });
65
75
  return modifiedCount;
66
76
  }
@@ -72,7 +82,14 @@ export abstract class FlinkRepo<C extends FlinkContext, Model extends Document>
72
82
  return deletedCount || 0;
73
83
  }
74
84
 
75
- private buildId(id: string | ObjectId) {
85
+ /**
86
+ * Helper to ensure the id is always an ObjectId.
87
+ * If a string is passed, it will be converted to an ObjectId.
88
+ * If an ObjectId is passed, it will be returned as is.
89
+ * @param id
90
+ * @returns
91
+ */
92
+ buildId(id: string | ObjectId) {
76
93
  let oid: ObjectId | string;
77
94
 
78
95
  if (typeof id === "string") {