@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.
- package/dist/src/FlinkApp.d.ts +2 -1
- package/dist/src/FlinkApp.js +21 -11
- package/dist/src/FlinkHttpHandler.d.ts +14 -1
- package/dist/src/FlinkRepo.d.ts +23 -7
- package/dist/src/FlinkRepo.js +32 -10
- package/dist/src/TypeScriptCompiler.d.ts +10 -0
- package/dist/src/TypeScriptCompiler.js +91 -18
- package/package.json +7 -4
- package/spec/FlinkRepo.spec.ts +11 -0
- package/spec/TypeScriptCompiler.spec.ts +8 -0
- package/spec/mock-project/dist/src/handlers/GetCar.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCar2.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +2 -0
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +2 -0
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +2 -0
- package/spec/mock-project/dist/src/handlers/PostCar.js +2 -0
- package/spec/mock-project/dist/src/handlers/PostLogin.js +2 -0
- package/spec/mock-project/dist/src/handlers/{GetCarWithOmitSchema.js → PostLogout.js} +16 -18
- package/spec/mock-project/dist/src/handlers/PutCar.js +2 -0
- package/spec/mock-project/src/handlers/PostLogout.ts +19 -0
- package/src/FlinkApp.ts +20 -5
- package/src/FlinkHttpHandler.ts +95 -96
- package/src/FlinkRepo.ts +24 -7
- package/src/TypeScriptCompiler.ts +81 -23
- 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: "/
|
|
41
|
+
path: "/logout",
|
|
42
42
|
};
|
|
43
|
-
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 =
|
|
58
|
-
exports.__assumedHttpMethod = "
|
|
59
|
-
exports.__schemas = { reqSchema:
|
|
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,
|
package/src/FlinkHttpHandler.ts
CHANGED
|
@@ -5,24 +5,25 @@ import { FlinkError } from "./FlinkErrors";
|
|
|
5
5
|
import { FlinkResponse } from "./FlinkResponse";
|
|
6
6
|
|
|
7
7
|
export enum HttpMethod {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
get = "get",
|
|
9
|
+
post = "post",
|
|
10
|
+
put = "put",
|
|
11
|
+
delete = "delete",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
type Params = Request["params"];
|
|
15
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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(
|
|
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:
|
|
54
|
+
async updateOne(id: string | ObjectId, model: PartialModel<Model>): Promise<Model | null> {
|
|
49
55
|
const oid = this.buildId(id);
|
|
50
56
|
|
|
51
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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:
|
|
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)
|