@flink-app/flink 0.12.1-alpha.4 → 0.12.1-alpha.44

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 (31) hide show
  1. package/dist/src/FlinkApp.d.ts +2 -1
  2. package/dist/src/FlinkApp.js +21 -11
  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/{GetCarWithOmitSchema.js → PostLogout.js} +16 -18
  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 +20 -5
  28. package/src/FlinkHttpHandler.ts +95 -96
  29. package/src/FlinkRepo.ts +24 -7
  30. package/src/TypeScriptCompiler.ts +81 -23
  31. package/spec/mock-project/dist/src/handlers/GetCarWithTypeSchema.js +0 -60
@@ -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 };
@@ -14,7 +14,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
14
14
  function verb(n) { return function (v) { return step([n, v]); }; }
15
15
  function step(op) {
16
16
  if (f) throw new TypeError("Generator is already executing.");
17
- while (_) try {
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
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
19
  if (y = 0, t) op = [op[0] & 2, t.value];
20
20
  switch (op[0]) {
@@ -38,22 +38,20 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.__schemas = exports.__params = exports.__query = exports.__file = exports.__assumedHttpMethod = exports.Route = void 0;
40
40
  exports.Route = {
41
- path: "/car-with-omit-schema",
41
+ path: "/logout",
42
42
  };
43
- // interface CarWithoutId extends Omit<Car, "_id"> {}
44
- var GetCarWithOmitSchema = function (_a) {
45
- var ctx = _a.ctx, req = _a.req;
46
- return __awaiter(void 0, void 0, void 0, function () {
47
- return __generator(this, function (_b) {
48
- return [2 /*return*/, {
49
- data: {
50
- model: "Volvo",
51
- foo: 1,
52
- },
53
- }];
54
- });
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
+ }];
55
51
  });
56
- };
57
- exports.default = GetCarWithOmitSchema;
58
- exports.__assumedHttpMethod = "get", exports.__file = "GetCarWithOmitSchema.ts", exports.__query = [], exports.__params = [];
59
- exports.__schemas = { reqSchema: undefined, resSchema: undefined };
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;
@@ -529,7 +530,7 @@ export class FlinkApp<C extends FlinkContext> {
529
530
  try {
530
531
  // 👇 This is where the actual handler gets invoked
531
532
  handlerRes = await handler({
532
- req,
533
+ req: req as FlinkRequest,
533
534
  ctx: this.ctx,
534
535
  origin: routeProps.origin,
535
536
  });
@@ -593,7 +594,7 @@ export class FlinkApp<C extends FlinkContext> {
593
594
  * Will not register any handlers added programmatically.
594
595
  */
595
596
  private async registerAutoRegisterableHandlers() {
596
- 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))) {
597
598
  if (!handler.Route) {
598
599
  log.error(`Missing Props in handler ${handler.__file}`);
599
600
  continue;
@@ -740,7 +741,7 @@ export class FlinkApp<C extends FlinkContext> {
740
741
  private async buildContext() {
741
742
  if (this.dbOpts) {
742
743
  for (const { collectionName, repoInstanceName, Repo } of autoRegisteredRepos) {
743
- const repoInstance: FlinkRepo<C, any> = new Repo(collectionName, this.db);
744
+ const repoInstance: FlinkRepo<C, any> = new Repo(collectionName, this.db, this.dbClient);
744
745
 
745
746
  this.repos[repoInstanceName] = repoInstance;
746
747
 
@@ -776,8 +777,10 @@ export class FlinkApp<C extends FlinkContext> {
776
777
  if (this.dbOpts) {
777
778
  try {
778
779
  log.debug("Connecting to db");
780
+
779
781
  const client = await MongoClient.connect(this.dbOpts.uri, this.getMongoConnectionOptions());
780
782
  this.db = client.db();
783
+ this.dbClient = client;
781
784
  } catch (err) {
782
785
  log.error("Failed to connect to db: " + err);
783
786
  process.exit(1);
@@ -820,7 +823,7 @@ export class FlinkApp<C extends FlinkContext> {
820
823
  if (!this.auth) {
821
824
  throw new Error(`Attempting to authenticate request (${req.method} ${req.path}) but no authPlugin is set`);
822
825
  }
823
- return await this.auth.authenticateRequest(req, permissions);
826
+ return await this.auth.authenticateRequest(req as FlinkRequest, permissions);
824
827
  }
825
828
 
826
829
  public getRegisteredRoutes() {
@@ -836,6 +839,18 @@ export class FlinkApp<C extends FlinkContext> {
836
839
  throw new Error("No db configured");
837
840
  }
838
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
+
839
854
  return {
840
855
  serverApi: {
841
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") {
@@ -1,8 +1,8 @@
1
- import { promises as fsPromises } from "fs";
1
+ import fs, { promises as fsPromises } from "fs";
2
2
  import { JSONSchema7 } from "json-schema";
3
3
  import { join } from "path";
4
4
  import glob from "tiny-glob";
5
- import { CompletedConfig, Config, createFormatter, createParser, Schema, SchemaGenerator } from "ts-json-schema-generator";
5
+ import { CompletedConfig, createFormatter, createParser, Schema, SchemaGenerator } from "ts-json-schema-generator";
6
6
  import {
7
7
  ArrayLiteralExpression,
8
8
  DiagnosticCategory,
@@ -24,6 +24,7 @@ import { getCollectionNameForRepo, getHttpMethodFromHandlerName, getRepoInstance
24
24
  class TypeScriptCompiler {
25
25
  private project: Project;
26
26
  private schemaGenerator?: SchemaGenerator;
27
+ private isEsm: boolean;
27
28
 
28
29
  /**
29
30
  * Parsed typescript schemas that will be added to intermediate
@@ -41,16 +42,68 @@ class TypeScriptCompiler {
41
42
  private tsSchemasSymbolsToImports: Symbol[] = [];
42
43
 
43
44
  constructor(private cwd: string) {
45
+ // Detect if project is using ESM based solely on package.json "type": "module"
46
+ this.isEsm = this.isEsmProject(cwd);
47
+
48
+ const compilerOptions: ts.CompilerOptions = {
49
+ noEmit: false, // We need to emit files
50
+ outDir: join(cwd, "dist"),
51
+ };
52
+
53
+ // Set appropriate module settings based on detected module system
54
+ if (this.isEsm) {
55
+ // For ESM projects, use ESNext module with Node resolution
56
+ compilerOptions.module = ts.ModuleKind.ESNext;
57
+ compilerOptions.moduleResolution = ts.ModuleResolutionKind.NodeJs;
58
+ compilerOptions.esModuleInterop = true;
59
+ } else {
60
+ // For CommonJS projects, use CommonJS module with Node resolution
61
+ compilerOptions.module = ts.ModuleKind.CommonJS;
62
+ compilerOptions.moduleResolution = ts.ModuleResolutionKind.NodeJs;
63
+ }
64
+
44
65
  this.project = new Project({
45
66
  tsConfigFilePath: join(cwd, "tsconfig.json"),
46
- compilerOptions: {
47
- noEmit: false,
48
- outDir: join(cwd, "dist"),
49
- // incremental: true,
50
- },
67
+ compilerOptions,
51
68
  });
52
69
 
53
70
  console.log("Loaded", this.project.getSourceFiles().length, "source file(s) from", cwd);
71
+ console.log("Module system:", this.isEsm ? "ESM" : "CommonJS");
72
+ console.log("Using module:", compilerOptions.module === ts.ModuleKind.ESNext ? "ESNext" : "CommonJS");
73
+ }
74
+
75
+ /**
76
+ * Detects if the project is using ESM (ECMAScript Modules)
77
+ * by checking type in package.json.
78
+ */
79
+ private isEsmProject(cwd: string): boolean {
80
+ try {
81
+ // Check package.json for "type": "module"
82
+ const packageJsonPath = join(cwd, "package.json");
83
+ if (fs.existsSync(packageJsonPath)) {
84
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
85
+ return packageJson.type === "module";
86
+ }
87
+ } catch (error) {
88
+ // If we can't determine, default to CommonJS
89
+ console.warn("Error detecting module system, defaulting to CommonJS:", error);
90
+ }
91
+
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * Gets the module specifier for imports, adding .js extension for ESM
97
+ */
98
+ private getModuleSpecifier(fromFile: SourceFile, toFile: SourceFile): string {
99
+ let moduleSpecifier = fromFile.getRelativePathAsModuleSpecifierTo(toFile);
100
+
101
+ // Add .js extension for ESM imports (only for relative paths)
102
+ if (this.isEsm && !moduleSpecifier.startsWith("@") && !moduleSpecifier.endsWith(".js")) {
103
+ moduleSpecifier += ".js";
104
+ }
105
+
106
+ return moduleSpecifier;
54
107
  }
55
108
 
56
109
  /**
@@ -173,7 +226,7 @@ autoRegisteredHandlers.push(...handlers);
173
226
 
174
227
  imports.push({
175
228
  defaultImport: "* as " + namespaceImport,
176
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
229
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
177
230
  });
178
231
 
179
232
  const assumedHttpMethod = getHttpMethodFromHandlerName(sf.getBaseName());
@@ -252,7 +305,7 @@ autoRegisteredHandlers.push(...handlers);
252
305
 
253
306
  imports.push({
254
307
  defaultImport: sf.getBaseNameWithoutExtension(),
255
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
308
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
256
309
  });
257
310
 
258
311
  reposArr.insertElement(
@@ -288,10 +341,11 @@ autoRegisteredHandlers.push(...handlers);
288
341
  const sf = this.createSourceFile(
289
342
  ["start.ts"],
290
343
  `// Generated ${new Date()}
291
- import "./generatedHandlers";
292
- import "./generatedRepos";
293
- import "./generatedJobs";
294
- import "..${appEntryScript.replace(/\.ts/g, "")}";
344
+ import "./generatedHandlers${this.isEsm ? ".js" : ""}";
345
+ import "./generatedRepos${this.isEsm ? ".js" : ""}";
346
+ import "./generatedJobs${this.isEsm ? ".js" : ""}";
347
+ import "..${appEntryScript.replace(/\.ts/g, "")}${this.isEsm ? ".js" : ""}";
348
+ export default {}; // Export an empty object to make it a module
295
349
  `
296
350
  );
297
351
 
@@ -410,15 +464,19 @@ import "..${appEntryScript.replace(/\.ts/g, "")}";
410
464
  * We need extract `{car: Car}` into its own interface and make sure
411
465
  * to import types if needed to
412
466
  */
413
- const declaration = schema.getSymbolOrThrow().getDeclarations()[0];
414
-
415
- const typeRefIdentifiers = declaration
416
- .getDescendantsOfKind(SyntaxKind.TypeReference)
417
- .map((typeRef) => typeRef.getFirstChildByKindOrThrow(SyntaxKind.Identifier));
418
-
419
- typeRefIdentifiers.forEach((tr) => {
420
- this.tsSchemasSymbolsToImports.push(tr.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow());
421
- });
467
+ const declarations = schema.getSymbolOrThrow().getDeclarations();
468
+ const declaration = declarations[0];
469
+
470
+ // Only extract type references if declaration exists (won't exist for empty object literals like {})
471
+ if (declaration) {
472
+ const typeRefIdentifiers = declaration
473
+ .getDescendantsOfKind(SyntaxKind.TypeReference)
474
+ .map((typeRef) => typeRef.getFirstChildByKindOrThrow(SyntaxKind.Identifier));
475
+
476
+ typeRefIdentifiers.forEach((tr) => {
477
+ this.tsSchemasSymbolsToImports.push(tr.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow());
478
+ });
479
+ }
422
480
 
423
481
  generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} { ${schema
424
482
  .getProperties()
@@ -660,7 +718,7 @@ autoRegisteredJobs.push(...jobs);
660
718
 
661
719
  imports.push({
662
720
  defaultImport: "* as " + namespaceImport,
663
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
721
+ moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
664
722
  });
665
723
 
666
724
  // Append metadata to source file that will be part of emitted dist bundle (javascript)