@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
@@ -1,7 +1,7 @@
1
1
  import { OptionsJson } from "body-parser";
2
2
  import { Express } from "express";
3
3
  import { JSONSchema7 } from "json-schema";
4
- import { Db } from "mongodb";
4
+ import { Db, MongoClient } from "mongodb";
5
5
  import { ToadScheduler } from "toad-scheduler";
6
6
  import { FlinkAuthPlugin } from "./auth/FlinkAuthPlugin";
7
7
  import { FlinkContext } from "./FlinkContext";
@@ -156,6 +156,7 @@ export declare class FlinkApp<C extends FlinkContext> {
156
156
  name: string;
157
157
  expressApp?: Express;
158
158
  db?: Db;
159
+ dbClient?: MongoClient;
159
160
  handlers: HandlerConfig[];
160
161
  port?: number;
161
162
  started: boolean;
@@ -171,6 +172,7 @@ export declare class FlinkApp<C extends FlinkContext> {
171
172
  private rawContentTypes?;
172
173
  private schedulingOptions?;
173
174
  private disableHttpServer;
175
+ private expressServer;
174
176
  private repos;
175
177
  /**
176
178
  * Internal cache used to track registered handlers and potentially any overlapping routes
@@ -181,6 +183,7 @@ export declare class FlinkApp<C extends FlinkContext> {
181
183
  constructor(opts: FlinkOptions);
182
184
  get ctx(): C;
183
185
  start(): Promise<this>;
186
+ stop(): Promise<void>;
184
187
  /**
185
188
  * Manually registers a handler.
186
189
  *
@@ -231,7 +231,7 @@ var FlinkApp = /** @class */ (function () {
231
231
  this.started = true;
232
232
  }
233
233
  else {
234
- (_d = this.expressApp) === null || _d === void 0 ? void 0 : _d.listen(this.port, function () {
234
+ this.expressServer = (_d = this.expressApp) === null || _d === void 0 ? void 0 : _d.listen(this.port, function () {
235
235
  FlinkLog_1.log.fontColorLog("magenta", "\u26A1\uFE0F HTTP server '".concat(_this.name, "' is running and waiting for connections on ").concat(_this.port));
236
236
  _this.started = true;
237
237
  });
@@ -241,6 +241,36 @@ var FlinkApp = /** @class */ (function () {
241
241
  });
242
242
  });
243
243
  };
244
+ FlinkApp.prototype.stop = function () {
245
+ return __awaiter(this, void 0, void 0, function () {
246
+ var _this = this;
247
+ return __generator(this, function (_a) {
248
+ switch (_a.label) {
249
+ case 0:
250
+ FlinkLog_1.log.info("🛑 Stopping Flink app...");
251
+ if (!this.scheduler) return [3 /*break*/, 2];
252
+ return [4 /*yield*/, this.scheduler.stop()];
253
+ case 1:
254
+ _a.sent();
255
+ _a.label = 2;
256
+ case 2:
257
+ if (this.expressServer) {
258
+ return [2 /*return*/, new Promise(function (resolve, reject) {
259
+ var int = setTimeout(function () {
260
+ reject("Failed to stop HTTP server in time");
261
+ }, 2000);
262
+ _this.expressServer.close(function () {
263
+ clearInterval(int);
264
+ FlinkLog_1.log.info("HTTP server stopped");
265
+ resolve();
266
+ });
267
+ })];
268
+ }
269
+ return [2 /*return*/];
270
+ }
271
+ });
272
+ });
273
+ };
244
274
  /**
245
275
  * Manually registers a handler.
246
276
  *
@@ -412,11 +442,11 @@ var FlinkApp = /** @class */ (function () {
412
442
  */
413
443
  FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
414
444
  return __awaiter(this, void 0, void 0, function () {
415
- var _i, autoRegisteredHandlers_1, _a, handler, assumedHttpMethod, pathParams, _b, _c, param;
416
- var _d, _e, _f;
417
- return __generator(this, function (_g) {
418
- for (_i = 0, autoRegisteredHandlers_1 = exports.autoRegisteredHandlers; _i < autoRegisteredHandlers_1.length; _i++) {
419
- _a = autoRegisteredHandlers_1[_i], handler = _a.handler, assumedHttpMethod = _a.assumedHttpMethod;
445
+ var _i, _a, _b, handler, assumedHttpMethod, pathParams, _c, _d, param;
446
+ var _e, _f, _g;
447
+ return __generator(this, function (_h) {
448
+ for (_i = 0, _a = exports.autoRegisteredHandlers.sort(function (a, b) { var _a, _b; return (((_a = a.handler.Route) === null || _a === void 0 ? void 0 : _a.order) || 0) - (((_b = b.handler.Route) === null || _b === void 0 ? void 0 : _b.order) || 0); }); _i < _a.length; _i++) {
449
+ _b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod;
420
450
  if (!handler.Route) {
421
451
  FlinkLog_1.log.error("Missing Props in handler ".concat(handler.__file));
422
452
  continue;
@@ -425,10 +455,10 @@ var FlinkApp = /** @class */ (function () {
425
455
  FlinkLog_1.log.error("Missing exported handler function in handler ".concat(handler.__file));
426
456
  continue;
427
457
  }
428
- if (!!((_d = handler.__params) === null || _d === void 0 ? void 0 : _d.length)) {
458
+ if (!!((_e = handler.__params) === null || _e === void 0 ? void 0 : _e.length)) {
429
459
  pathParams = (0, utils_1.getPathParams)(handler.Route.path);
430
- for (_b = 0, _c = handler.__params; _b < _c.length; _b++) {
431
- param = _c[_b];
460
+ for (_c = 0, _d = handler.__params; _c < _d.length; _c++) {
461
+ param = _d[_c];
432
462
  if (!pathParams.includes(param.name)) {
433
463
  FlinkLog_1.log.error("Handler ".concat(handler.__file, " has param ").concat(param.name, " but it is not present in the path '").concat(handler.Route.path, "'"));
434
464
  throw new Error("Invalid/missing handler path param");
@@ -441,8 +471,8 @@ var FlinkApp = /** @class */ (function () {
441
471
  this.registerHandler({
442
472
  routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
443
473
  schema: {
444
- reqSchema: (_e = handler.__schemas) === null || _e === void 0 ? void 0 : _e.reqSchema,
445
- resSchema: (_f = handler.__schemas) === null || _f === void 0 ? void 0 : _f.resSchema,
474
+ reqSchema: (_f = handler.__schemas) === null || _f === void 0 ? void 0 : _f.reqSchema,
475
+ resSchema: (_g = handler.__schemas) === null || _g === void 0 ? void 0 : _g.resSchema,
446
476
  },
447
477
  queryMetadata: handler.__query || [],
448
478
  paramsMetadata: handler.__params || [],
@@ -553,7 +583,7 @@ var FlinkApp = /** @class */ (function () {
553
583
  if (this.dbOpts) {
554
584
  for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
555
585
  _a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
556
- repoInstance = new Repo(collectionName, this.db);
586
+ repoInstance = new Repo(collectionName, this.db, this.dbClient);
557
587
  this.repos[repoInstanceName] = repoInstance;
558
588
  FlinkLog_1.log.info("Registered repo ".concat(repoInstanceName));
559
589
  }
@@ -599,6 +629,7 @@ var FlinkApp = /** @class */ (function () {
599
629
  case 2:
600
630
  client = _a.sent();
601
631
  this.db = client.db();
632
+ this.dbClient = client;
602
633
  return [3 /*break*/, 4];
603
634
  case 3:
604
635
  err_2 = _a.sent();
@@ -685,6 +716,15 @@ var FlinkApp = /** @class */ (function () {
685
716
  if (!this.dbOpts) {
686
717
  throw new Error("No db configured");
687
718
  }
719
+ var driverVersion = require("mongodb/package.json").version;
720
+ if (driverVersion.startsWith("3")) {
721
+ FlinkLog_1.log.debug("Using legacy mongodb connection options as mongo client is version ".concat(driverVersion));
722
+ return {
723
+ useNewUrlParser: true,
724
+ useUnifiedTopology: true,
725
+ };
726
+ }
727
+ FlinkLog_1.log.debug("Using modern MongoDB client options (driver version ".concat(driverVersion, ")"));
688
728
  return {
689
729
  serverApi: {
690
730
  version: mongodb_1.ServerApiVersion.v1,
@@ -10,7 +10,12 @@ export declare enum HttpMethod {
10
10
  delete = "delete"
11
11
  }
12
12
  type Params = Request["params"];
13
- type Query = Request["query"];
13
+ /**
14
+ * Query type for request query parameters.
15
+ * Does currently not allow nested objects, although
16
+ * underlying express Request does allow it.
17
+ */
18
+ type Query = Record<string, string | string[] | undefined>;
14
19
  /**
15
20
  * Flink request extends express Request but adds reqId and user object.
16
21
  */
@@ -63,6 +68,14 @@ export interface RouteProps {
63
68
  * I.e. filename or plugin name that describes where handler origins from
64
69
  */
65
70
  origin?: string;
71
+ /**
72
+ * Order handler should be registered in.
73
+ *
74
+ * By default all handlers has order 0 and in most cases this is fine,
75
+ * but if for example you want to register a handler before all others
76
+ * to avoid conflicts you can set a negative order.
77
+ */
78
+ order?: number;
66
79
  }
67
80
  /**
68
81
  * Http handler function that handlers implements in order to
@@ -1,22 +1,38 @@
1
- import { Collection, Db, Document, ObjectId } from "mongodb";
1
+ import { Collection, Db, Document, MongoClient, ObjectId } from "mongodb";
2
2
  import { FlinkContext } from "./FlinkContext";
3
+ /**
4
+ * Partial model to have intellisense for partial updates but
5
+ * also allow any other properties to be set such as nested objects etc.
6
+ */
7
+ type PartialModel<Model> = Partial<Model> & {
8
+ [x: string]: any;
9
+ };
3
10
  export declare abstract class FlinkRepo<C extends FlinkContext, Model extends Document> {
4
- private collectionName;
5
- private db;
11
+ collectionName: string;
12
+ db: Db;
13
+ client?: MongoClient | undefined;
6
14
  collection: Collection;
7
15
  private _ctx?;
8
16
  set ctx(ctx: FlinkContext);
9
17
  get ctx(): FlinkContext;
10
- constructor(collectionName: string, db: Db);
18
+ constructor(collectionName: string, db: Db, client?: MongoClient | undefined);
11
19
  findAll(query?: {}): Promise<Model[]>;
12
20
  getById(id: string | ObjectId): Promise<Model | null>;
13
21
  getOne(query?: {}): Promise<Model | null>;
14
22
  create<C = Omit<Model, "_id">>(model: C): Promise<C & {
15
23
  _id: string;
16
24
  }>;
17
- updateOne(id: string | ObjectId, model: Partial<Model>): Promise<Model | null>;
18
- updateMany<U = Partial<Model>>(query: any, model: U): Promise<number>;
25
+ updateOne(id: string | ObjectId, model: PartialModel<Model>): Promise<Model | null>;
26
+ updateMany<U = PartialModel<Model>>(query: any, model: U): Promise<number>;
19
27
  deleteById(id: string | ObjectId): Promise<number>;
20
- private buildId;
28
+ /**
29
+ * Helper to ensure the id is always an ObjectId.
30
+ * If a string is passed, it will be converted to an ObjectId.
31
+ * If an ObjectId is passed, it will be returned as is.
32
+ * @param id
33
+ * @returns
34
+ */
35
+ buildId(id: string | ObjectId): ObjectId;
21
36
  private objectIdToString;
22
37
  }
38
+ export {};
@@ -46,13 +46,25 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
47
  }
48
48
  };
49
+ var __rest = (this && this.__rest) || function (s, e) {
50
+ var t = {};
51
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
52
+ t[p] = s[p];
53
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
54
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
55
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
56
+ t[p[i]] = s[p[i]];
57
+ }
58
+ return t;
59
+ };
49
60
  Object.defineProperty(exports, "__esModule", { value: true });
50
61
  exports.FlinkRepo = void 0;
51
62
  var mongodb_1 = require("mongodb");
52
63
  var FlinkRepo = /** @class */ (function () {
53
- function FlinkRepo(collectionName, db) {
64
+ function FlinkRepo(collectionName, db, client) {
54
65
  this.collectionName = collectionName;
55
66
  this.db = db;
67
+ this.client = client;
56
68
  this.collection = db.collection(this.collectionName);
57
69
  }
58
70
  Object.defineProperty(FlinkRepo.prototype, "ctx", {
@@ -129,12 +141,13 @@ var FlinkRepo = /** @class */ (function () {
129
141
  };
130
142
  FlinkRepo.prototype.updateOne = function (id, model) {
131
143
  return __awaiter(this, void 0, void 0, function () {
132
- var oid, res;
144
+ var oid, _id, modelWithoutId, res;
133
145
  return __generator(this, function (_a) {
134
146
  switch (_a.label) {
135
147
  case 0:
136
148
  oid = this.buildId(id);
137
- return [4 /*yield*/, this.collection.updateOne({ _id: oid }, { $set: model })];
149
+ _id = model._id, modelWithoutId = __rest(model, ["_id"]);
150
+ return [4 /*yield*/, this.collection.updateOne({ _id: oid }, { $set: modelWithoutId })];
138
151
  case 1:
139
152
  _a.sent();
140
153
  return [4 /*yield*/, this.collection.findOne({ _id: oid })];
@@ -150,14 +163,16 @@ var FlinkRepo = /** @class */ (function () {
150
163
  };
151
164
  FlinkRepo.prototype.updateMany = function (query, model) {
152
165
  return __awaiter(this, void 0, void 0, function () {
153
- var modifiedCount;
154
- return __generator(this, function (_a) {
155
- switch (_a.label) {
156
- case 0: return [4 /*yield*/, this.collection.updateMany(query, {
157
- $set: model,
158
- })];
166
+ var _a, _id, modelWithoutId, modifiedCount;
167
+ return __generator(this, function (_b) {
168
+ switch (_b.label) {
169
+ case 0:
170
+ _a = model, _id = _a._id, modelWithoutId = __rest(_a, ["_id"]);
171
+ return [4 /*yield*/, this.collection.updateMany(query, {
172
+ $set: modelWithoutId,
173
+ })];
159
174
  case 1:
160
- modifiedCount = (_a.sent()).modifiedCount;
175
+ modifiedCount = (_b.sent()).modifiedCount;
161
176
  return [2 /*return*/, modifiedCount];
162
177
  }
163
178
  });
@@ -178,6 +193,13 @@ var FlinkRepo = /** @class */ (function () {
178
193
  });
179
194
  });
180
195
  };
196
+ /**
197
+ * Helper to ensure the id is always an ObjectId.
198
+ * If a string is passed, it will be converted to an ObjectId.
199
+ * If an ObjectId is passed, it will be returned as is.
200
+ * @param id
201
+ * @returns
202
+ */
181
203
  FlinkRepo.prototype.buildId = function (id) {
182
204
  var oid;
183
205
  if (typeof id === "string") {
@@ -3,6 +3,7 @@ declare class TypeScriptCompiler {
3
3
  private cwd;
4
4
  private project;
5
5
  private schemaGenerator?;
6
+ private isEsm;
6
7
  /**
7
8
  * Parsed typescript schemas that will be added to intermediate
8
9
  * schemas.ts file.
@@ -17,6 +18,15 @@ declare class TypeScriptCompiler {
17
18
  */
18
19
  private tsSchemasSymbolsToImports;
19
20
  constructor(cwd: string);
21
+ /**
22
+ * Detects if the project is using ESM (ECMAScript Modules)
23
+ * by checking type in package.json.
24
+ */
25
+ private isEsmProject;
26
+ /**
27
+ * Gets the module specifier for imports, adding .js extension for ESM
28
+ */
29
+ private getModuleSpecifier;
20
30
  /**
21
31
  * Deletes all generated files.
22
32
  * @param cwd
@@ -10,6 +10,29 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
13
36
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
37
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
38
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -59,7 +82,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
59
82
  return (mod && mod.__esModule) ? mod : { "default": mod };
60
83
  };
61
84
  Object.defineProperty(exports, "__esModule", { value: true });
62
- var fs_1 = require("fs");
85
+ var fs_1 = __importStar(require("fs"));
63
86
  var path_1 = require("path");
64
87
  var tiny_glob_1 = __importDefault(require("tiny-glob"));
65
88
  var ts_json_schema_generator_1 = require("ts-json-schema-generator");
@@ -83,16 +106,62 @@ var TypeScriptCompiler = /** @class */ (function () {
83
106
  * This will be added to file in a batch for performance reasons.
84
107
  */
85
108
  this.tsSchemasSymbolsToImports = [];
109
+ // Detect if project is using ESM based solely on package.json "type": "module"
110
+ this.isEsm = this.isEsmProject(cwd);
111
+ var compilerOptions = {
112
+ noEmit: false, // We need to emit files
113
+ outDir: (0, path_1.join)(cwd, "dist"),
114
+ };
115
+ // Set appropriate module settings based on detected module system
116
+ if (this.isEsm) {
117
+ // For ESM projects, use ESNext module with Node resolution
118
+ compilerOptions.module = ts_morph_1.ts.ModuleKind.ESNext;
119
+ compilerOptions.moduleResolution = ts_morph_1.ts.ModuleResolutionKind.NodeJs;
120
+ compilerOptions.esModuleInterop = true;
121
+ }
122
+ else {
123
+ // For CommonJS projects, use CommonJS module with Node resolution
124
+ compilerOptions.module = ts_morph_1.ts.ModuleKind.CommonJS;
125
+ compilerOptions.moduleResolution = ts_morph_1.ts.ModuleResolutionKind.NodeJs;
126
+ }
86
127
  this.project = new ts_morph_1.Project({
87
128
  tsConfigFilePath: (0, path_1.join)(cwd, "tsconfig.json"),
88
- compilerOptions: {
89
- noEmit: false,
90
- outDir: (0, path_1.join)(cwd, "dist"),
91
- // incremental: true,
92
- },
129
+ compilerOptions: compilerOptions,
93
130
  });
94
131
  console.log("Loaded", this.project.getSourceFiles().length, "source file(s) from", cwd);
132
+ console.log("Module system:", this.isEsm ? "ESM" : "CommonJS");
133
+ console.log("Using module:", compilerOptions.module === ts_morph_1.ts.ModuleKind.ESNext ? "ESNext" : "CommonJS");
95
134
  }
135
+ /**
136
+ * Detects if the project is using ESM (ECMAScript Modules)
137
+ * by checking type in package.json.
138
+ */
139
+ TypeScriptCompiler.prototype.isEsmProject = function (cwd) {
140
+ try {
141
+ // Check package.json for "type": "module"
142
+ var packageJsonPath = (0, path_1.join)(cwd, "package.json");
143
+ if (fs_1.default.existsSync(packageJsonPath)) {
144
+ var packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf8"));
145
+ return packageJson.type === "module";
146
+ }
147
+ }
148
+ catch (error) {
149
+ // If we can't determine, default to CommonJS
150
+ console.warn("Error detecting module system, defaulting to CommonJS:", error);
151
+ }
152
+ return false;
153
+ };
154
+ /**
155
+ * Gets the module specifier for imports, adding .js extension for ESM
156
+ */
157
+ TypeScriptCompiler.prototype.getModuleSpecifier = function (fromFile, toFile) {
158
+ var moduleSpecifier = fromFile.getRelativePathAsModuleSpecifierTo(toFile);
159
+ // Add .js extension for ESM imports (only for relative paths)
160
+ if (this.isEsm && !moduleSpecifier.startsWith("@") && !moduleSpecifier.endsWith(".js")) {
161
+ moduleSpecifier += ".js";
162
+ }
163
+ return moduleSpecifier;
164
+ };
96
165
  /**
97
166
  * Deletes all generated files.
98
167
  * @param cwd
@@ -233,7 +302,7 @@ var TypeScriptCompiler = /** @class */ (function () {
233
302
  namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
234
303
  imports.push({
235
304
  defaultImport: "* as " + namespaceImport,
236
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
305
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
237
306
  });
238
307
  assumedHttpMethod = (0, utils_1.getHttpMethodFromHandlerName)(sf.getBaseName());
239
308
  return [4 /*yield*/, this.extractSchemasFromHandlerSourceFile(sf)];
@@ -309,7 +378,7 @@ var TypeScriptCompiler = /** @class */ (function () {
309
378
  console.log("Detected repo ".concat(sf.getBaseName()));
310
379
  imports.push({
311
380
  defaultImport: sf.getBaseNameWithoutExtension(),
312
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
381
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
313
382
  });
314
383
  reposArr.insertElement(i, "{collectionName: \"".concat((0, utils_1.getCollectionNameForRepo)(sf.getBaseName()), "\", repoInstanceName: \"").concat((0, utils_1.getRepoInstanceName)(sf.getBaseName()), "\", Repo: ").concat(sf.getBaseNameWithoutExtension(), "}"));
315
384
  i++;
@@ -341,7 +410,7 @@ var TypeScriptCompiler = /** @class */ (function () {
341
410
  console.error("Cannot find entry script '".concat(appEntryScript, "'"));
342
411
  return [2 /*return*/, process.exit(1)];
343
412
  }
344
- sf = this.createSourceFile(["start.ts"], "// Generated ".concat(new Date(), "\nimport \"./generatedHandlers\";\nimport \"./generatedRepos\";\nimport \"./generatedJobs\";\nimport \"..").concat(appEntryScript.replace(/\.ts/g, ""), "\";\n"));
413
+ sf = this.createSourceFile(["start.ts"], "// Generated ".concat(new Date(), "\nimport \"./generatedHandlers").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedRepos").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedJobs").concat(this.isEsm ? ".js" : "", "\";\nimport \"..").concat(appEntryScript.replace(/\.ts/g, "")).concat(this.isEsm ? ".js" : "", "\";\nexport default {}; // Export an empty object to make it a module\n"));
345
414
  return [4 /*yield*/, sf.save()];
346
415
  case 1:
347
416
  _a.sent();
@@ -397,7 +466,7 @@ var TypeScriptCompiler = /** @class */ (function () {
397
466
  };
398
467
  TypeScriptCompiler.prototype.saveIntermediateTsSchema = function (schema, handlerFile, suffix) {
399
468
  return __awaiter(this, void 0, void 0, function () {
400
- var handlerFileName, generatedSchemaInterfaceStr, schemaInterfaceName, schemaSymbol, interfaceName, declaration, _i, _a, typeToImport, arrayTypeArg, schemaSymbol, interfaceName, declaration, props, _b, _c, typeToImport, declaration, typeRefIdentifiers;
469
+ var handlerFileName, generatedSchemaInterfaceStr, schemaInterfaceName, schemaSymbol, interfaceName, declaration, _i, _a, typeToImport, arrayTypeArg, schemaSymbol, interfaceName, declaration, props, _b, _c, typeToImport, declarations, declaration, typeRefIdentifiers;
401
470
  var _this = this;
402
471
  return __generator(this, function (_d) {
403
472
  if (schema.isAny()) {
@@ -454,13 +523,17 @@ var TypeScriptCompiler = /** @class */ (function () {
454
523
  }
455
524
  }
456
525
  else if (schema.isObject()) {
457
- declaration = schema.getSymbolOrThrow().getDeclarations()[0];
458
- typeRefIdentifiers = declaration
459
- .getDescendantsOfKind(ts_morph_1.SyntaxKind.TypeReference)
460
- .map(function (typeRef) { return typeRef.getFirstChildByKindOrThrow(ts_morph_1.SyntaxKind.Identifier); });
461
- typeRefIdentifiers.forEach(function (tr) {
462
- _this.tsSchemasSymbolsToImports.push(tr.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow());
463
- });
526
+ declarations = schema.getSymbolOrThrow().getDeclarations();
527
+ declaration = declarations[0];
528
+ // Only extract type references if declaration exists (won't exist for empty object literals like {})
529
+ if (declaration) {
530
+ typeRefIdentifiers = declaration
531
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.TypeReference)
532
+ .map(function (typeRef) { return typeRef.getFirstChildByKindOrThrow(ts_morph_1.SyntaxKind.Identifier); });
533
+ typeRefIdentifiers.forEach(function (tr) {
534
+ _this.tsSchemasSymbolsToImports.push(tr.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow());
535
+ });
536
+ }
464
537
  generatedSchemaInterfaceStr = "export interface ".concat(schemaInterfaceName, " { ").concat(schema
465
538
  .getProperties()
466
539
  .map(function (p) { return p.getValueDeclarationOrThrow().getText(); })
@@ -672,7 +745,7 @@ var TypeScriptCompiler = /** @class */ (function () {
672
745
  namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
673
746
  imports.push({
674
747
  defaultImport: "* as " + namespaceImport,
675
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
748
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
676
749
  });
677
750
  // Append metadata to source file that will be part of emitted dist bundle (javascript)
678
751
  sf.addVariableStatement({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/flink",
3
- "version": "0.12.1-alpha.3",
3
+ "version": "0.12.1-alpha.35",
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",
@@ -9,7 +9,7 @@
9
9
  "test": "jasmine-ts --preserve-symlinks --config=./spec/support/jasmine.json",
10
10
  "test:watch": "nodemon --ext ts --exec 'jasmine-ts --config=./spec/support/jasmine.json'",
11
11
  "start": "ts-node src/index.ts",
12
- "prepublish": "npm run build",
12
+ "prepare": "npm run build",
13
13
  "build": "tsc --project tsconfig.dist.json",
14
14
  "watch": "nodemon --exec 'tsc --project tsconfig.dist.json'",
15
15
  "clean": "rimraf dist"
@@ -38,7 +38,6 @@
38
38
  "fs-extra": "^10.0.0",
39
39
  "mkdirp": "^1.0.4",
40
40
  "mock-json-schema": "^1.0.8",
41
- "mongodb": "^6.15.0",
42
41
  "morgan": "^1.10.0",
43
42
  "ms": "^2.0.0",
44
43
  "node-color-log": "^10.0.2",
@@ -62,9 +61,13 @@
62
61
  "jasmine": "^3.7.0",
63
62
  "jasmine-spec-reporter": "^7.0.0",
64
63
  "jasmine-ts": "^0.3.3",
64
+ "mongodb": "^6.15.0",
65
65
  "nodemon": "^2.0.7",
66
66
  "rimraf": "^3.0.2",
67
67
  "ts-node": "^9.1.1"
68
68
  },
69
- "gitHead": "51b6524e233acb2430953ffaed6382909f34db8e"
69
+ "peerDependencies": {
70
+ "mongodb": ">=3.7.0 <7.0.0"
71
+ },
72
+ "gitHead": "f8e8c6565a9ca1dd3e5fdb4c2a791c99ae3ba51a"
70
73
  }
@@ -4,6 +4,7 @@ import { FlinkRepo } from "../src/FlinkRepo";
4
4
  interface Model {
5
5
  _id: string;
6
6
  name: string;
7
+ nested?: { field: number };
7
8
  }
8
9
 
9
10
  class Repo extends FlinkRepo<any, Model> {}
@@ -61,9 +62,19 @@ describe("FlinkRepo", () => {
61
62
 
62
63
  const updatedDoc = await repo.updateOne(createdDoc._id + "", {
63
64
  name: "foo",
65
+ "nested.field": 1,
64
66
  });
65
67
 
66
68
  expect(updatedDoc).toBeDefined();
67
69
  expect(updatedDoc?.name).toBe("foo");
70
+ expect(updatedDoc?.nested?.field).toBe(1);
71
+ });
72
+
73
+ it("should update many documents", async () => {
74
+ await collection.insertMany([{ name: "foo" }, { name: "foo" }, { name: "foo" }]);
75
+
76
+ const updatedCount = await repo.updateMany({ name: "foo" }, { name: "bar", "nested.field": 1 });
77
+
78
+ expect(updatedCount).toBe(3);
68
79
  });
69
80
  });
@@ -30,6 +30,14 @@ describe("TypeScriptCompiler", () => {
30
30
  // );
31
31
  });
32
32
 
33
+ it("should handle empty object literal {} as request schema", async () => {
34
+ const generatedFile = await compiler.parseHandlers();
35
+ compiler.emit();
36
+
37
+ // Should successfully parse PostLogout handler with Handler<Ctx, {}, Response> syntax
38
+ expect(generatedFile.getText()).toContain(`PostLogout`);
39
+ });
40
+
33
41
  it("should generate start script", async () => {
34
42
  const startScript = await compiler.generateStartScript();
35
43
 
@@ -55,3 +55,5 @@ var GetCar = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b
55
55
  exports.default = GetCar;
56
56
  exports.__assumedHttpMethod = "get", exports.__file = "GetCar.ts", exports.__query = [{ description: "For pagination", name: "page" }], exports.__params = [{ description: "", name: "id" }];
57
57
  exports.__schemas = { reqSchema: undefined, 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": {} } };
58
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCar.ts", exports.__query = [{ description: "For pagination", name: "page" }], exports.__params = [{ description: "", name: "id" }];
59
+ exports.__schemas = { reqSchema: undefined, 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": {} } };
@@ -57,3 +57,5 @@ var GetCar2 = function (_a) { return __awaiter(void 0, [_a], void 0, function (_
57
57
  exports.default = GetCar2;
58
58
  exports.__assumedHttpMethod = "get", exports.__file = "GetCar2.ts", exports.__query = [], exports.__params = [];
59
59
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "model": { "type": "object", "properties": { "name": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["name"], "additionalProperties": false }, "engine": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"], "additionalProperties": false } }, "required": ["model", "engine"], "additionalProperties": false, "definitions": {} } };
60
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCar2.ts", exports.__query = [], exports.__params = [];
61
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "model": { "type": "object", "properties": { "name": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["name"], "additionalProperties": false }, "engine": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"], "additionalProperties": false } }, "required": ["model", "engine"], "additionalProperties": false, "definitions": {} } };
@@ -51,3 +51,5 @@ var GetCarWithArraySchema = function (_a) { return __awaiter(void 0, [_a], void
51
51
  exports.default = GetCarWithArraySchema;
52
52
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithArraySchema.ts", exports.__query = [], exports.__params = [];
53
53
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false }, "definitions": {} } };
54
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithArraySchema.ts", exports.__query = [], exports.__params = [];
55
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "type": "object", "properties": { "model": { "type": "string" }, "metadata": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" } }, "additionalProperties": false } }, "required": ["model"], "additionalProperties": false }, "definitions": {} } };
@@ -51,3 +51,5 @@ var GetCarWithArraySchema2 = function (_a) { return __awaiter(void 0, [_a], void
51
51
  exports.default = GetCarWithArraySchema2;
52
52
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithArraySchema2.ts", exports.__query = [], exports.__params = [];
53
53
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "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": {} } };
54
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithArraySchema2.ts", exports.__query = [], exports.__params = [];
55
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "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": {} } };
@@ -51,3 +51,5 @@ var GetCarWithArraySchema3 = function (_a) { return __awaiter(void 0, [_a], void
51
51
  exports.default = GetCarWithArraySchema3;
52
52
  exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithArraySchema3.ts", exports.__query = [], exports.__params = [];
53
53
  exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "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 }, "year": { "type": "number" } }, "required": ["car", "year"], "additionalProperties": false }, "definitions": {} } };
54
+ exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithArraySchema3.ts", exports.__query = [], exports.__params = [];
55
+ exports.__schemas = { reqSchema: undefined, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "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 }, "year": { "type": "number" } }, "required": ["car", "year"], "additionalProperties": false }, "definitions": {} } };