@flink-app/flink 0.3.12 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.vscode/notes.txt +11 -0
  2. package/cli/run.ts +23 -27
  3. package/dist/bin/flink.js +2 -2
  4. package/dist/cli/build.js +3 -3
  5. package/dist/cli/clean.js +2 -2
  6. package/dist/cli/cli-utils.js +1 -1
  7. package/dist/cli/generate-schemas.js +20 -16
  8. package/dist/cli/run.js +3 -7
  9. package/dist/src/FlinkApp.d.ts +21 -3
  10. package/dist/src/FlinkApp.js +163 -53
  11. package/dist/src/FlinkErrors.d.ts +1 -1
  12. package/dist/src/FlinkErrors.js +5 -5
  13. package/dist/src/FlinkHttpHandler.d.ts +7 -7
  14. package/dist/src/FlinkJob.d.ts +58 -0
  15. package/dist/src/FlinkJob.js +2 -0
  16. package/dist/src/FlinkLog.d.ts +16 -0
  17. package/dist/src/FlinkLog.js +11 -30
  18. package/dist/src/FlinkRepo.js +1 -1
  19. package/dist/src/FlinkResponse.d.ts +2 -2
  20. package/dist/src/FsUtils.js +4 -4
  21. package/dist/src/TypeScriptCompiler.d.ts +5 -1
  22. package/dist/src/TypeScriptCompiler.js +119 -91
  23. package/dist/src/TypeScriptUtils.js +1 -1
  24. package/dist/src/index.d.ts +2 -1
  25. package/dist/src/index.js +7 -2
  26. package/dist/src/mock-data-generator.js +1 -1
  27. package/dist/src/utils.js +9 -9
  28. package/package.json +7 -3
  29. package/spec/mock-project/dist/src/handlers/GetCar.js +59 -0
  30. package/spec/mock-project/dist/src/handlers/GetCar2.js +61 -0
  31. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +55 -0
  32. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +55 -0
  33. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +55 -0
  34. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +57 -0
  35. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +57 -0
  36. package/{dist/cli/generate.js → spec/mock-project/dist/src/handlers/GetCarWithOmitSchema.js} +19 -14
  37. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +60 -0
  38. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +60 -0
  39. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +56 -0
  40. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +58 -0
  41. package/spec/mock-project/dist/src/handlers/PostCar.js +57 -0
  42. package/{dist/cli/generate-schema.js → spec/mock-project/dist/src/handlers/PostLogin.js} +18 -14
  43. package/spec/mock-project/dist/src/handlers/PutCar.js +57 -0
  44. package/spec/mock-project/dist/src/index.js +79 -0
  45. package/spec/mock-project/dist/src/repos/CarRepo.js +26 -0
  46. package/spec/mock-project/dist/src/schemas/Car.js +2 -0
  47. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +2 -0
  48. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +2 -0
  49. package/src/FlinkApp.ts +146 -11
  50. package/src/FlinkJob.ts +64 -0
  51. package/src/FlinkLog.ts +13 -11
  52. package/src/TypeScriptCompiler.ts +593 -663
  53. package/src/index.ts +2 -1
  54. package/dist/cli/generate-schema.d.ts +0 -2
  55. package/dist/cli/generate.d.ts +0 -2
  56. package/dist/src/FlinkTsParser.d.ts +0 -2
  57. package/dist/src/FlinkTsParser.js +0 -219
  58. package/dist/src/FlinkTsUtils.d.ts +0 -16
  59. package/dist/src/FlinkTsUtils.js +0 -118
@@ -2,749 +2,679 @@ import { 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 { Config, createFormatter, createParser, Schema, SchemaGenerator } from "ts-json-schema-generator";
5
6
  import {
6
- Config,
7
- createFormatter,
8
- createParser,
9
- Schema,
10
- SchemaGenerator,
11
- } from "ts-json-schema-generator";
12
- import {
13
- ArrayLiteralExpression,
14
- DiagnosticCategory,
15
- ImportDeclarationStructure,
16
- OptionalKind,
17
- Project,
18
- SourceFile,
19
- Symbol,
20
- SyntaxKind,
21
- ts,
22
- Type,
23
- TypeReferenceNode,
24
- VariableDeclarationKind,
7
+ ArrayLiteralExpression,
8
+ DiagnosticCategory,
9
+ ImportDeclarationStructure,
10
+ OptionalKind,
11
+ Project,
12
+ SourceFile,
13
+ Symbol,
14
+ SyntaxKind,
15
+ ts,
16
+ Type,
17
+ TypeReferenceNode,
18
+ VariableDeclarationKind,
25
19
  } from "ts-morph";
26
20
  import { writeJsonFile } from "./FsUtils";
27
- import {
28
- addImports,
29
- getDefaultExport,
30
- getInterfaceName,
31
- getTypeMetadata,
32
- getTypesToImport,
33
- } from "./TypeScriptUtils";
34
- import {
35
- deRefSchema,
36
- getCollectionNameForRepo,
37
- getHttpMethodFromHandlerName,
38
- getRepoInstanceName,
39
- } from "./utils";
21
+ import { addImports, getDefaultExport, getInterfaceName, getTypeMetadata, getTypesToImport } from "./TypeScriptUtils";
22
+ import { deRefSchema, getCollectionNameForRepo, getHttpMethodFromHandlerName, getRepoInstanceName } from "./utils";
40
23
 
41
24
  class TypeScriptCompiler {
42
- private project: Project;
43
- private schemaGenerator?: SchemaGenerator;
44
-
45
- /**
46
- * Parsed typescript schemas that will be added to intermediate
47
- * schemas.ts file.
48
- *
49
- * This will be written to file in a batch for performance reasons.
50
- */
51
- private parsedTsSchemas: string[] = [];
52
-
53
- /**
54
- * Imports needed for schemas.ts.
55
- *
56
- * This will be added to file in a batch for performance reasons.
57
- */
58
- private tsSchemasSymbolsToImports: Symbol[] = [];
59
-
60
- constructor(private cwd: string) {
61
- this.project = new Project({
62
- tsConfigFilePath: join(cwd, "tsconfig.json"),
63
- compilerOptions: {
64
- noEmit: false,
65
- outDir: join(cwd, "dist"),
66
- // incremental: true,
67
- },
68
- });
69
-
70
- console.log(
71
- "Loaded",
72
- this.project.getSourceFiles().length,
73
- "source file(s) from",
74
- cwd
75
- );
76
- }
77
-
78
- /**
79
- * Deletes all generated files.
80
- * @param cwd
81
- */
82
- static async clean(cwd: string) {
83
- const flinkDir = join(cwd, ".flink");
84
-
85
- try {
86
- await fsPromises.access(flinkDir);
87
- } catch (err) {
88
- await fsPromises.mkdir(flinkDir);
25
+ private project: Project;
26
+ private schemaGenerator?: SchemaGenerator;
27
+
28
+ /**
29
+ * Parsed typescript schemas that will be added to intermediate
30
+ * schemas.ts file.
31
+ *
32
+ * This will be written to file in a batch for performance reasons.
33
+ */
34
+ private parsedTsSchemas: string[] = [];
35
+
36
+ /**
37
+ * Imports needed for schemas.ts.
38
+ *
39
+ * This will be added to file in a batch for performance reasons.
40
+ */
41
+ private tsSchemasSymbolsToImports: Symbol[] = [];
42
+
43
+ constructor(private cwd: string) {
44
+ this.project = new Project({
45
+ tsConfigFilePath: join(cwd, "tsconfig.json"),
46
+ compilerOptions: {
47
+ noEmit: false,
48
+ outDir: join(cwd, "dist"),
49
+ // incremental: true,
50
+ },
51
+ });
52
+
53
+ console.log("Loaded", this.project.getSourceFiles().length, "source file(s) from", cwd);
89
54
  }
90
55
 
91
- const files = await glob(".flink/**/*.ts", { cwd });
92
- for (const file of files) {
93
- await fsPromises.rm(join(cwd, file));
94
- }
95
-
96
- return flinkDir;
97
- }
98
-
99
- /**
100
- * Emits compiled javascript source to dist folder
101
- */
102
- emit() {
103
- this.project.emitSync();
104
- }
105
-
106
- /**
107
- * Catch any compilation errors. Will return false if any Errors
108
- * exists. Warnings will be passed thru but logged.
109
- */
110
- getPreEmitDiagnostics() {
111
- const preEmitDiag = this.project.getPreEmitDiagnostics();
56
+ /**
57
+ * Deletes all generated files.
58
+ * @param cwd
59
+ */
60
+ static async clean(cwd: string) {
61
+ const flinkDir = join(cwd, ".flink");
62
+
63
+ try {
64
+ await fsPromises.access(flinkDir);
65
+ } catch (err) {
66
+ await fsPromises.mkdir(flinkDir);
67
+ }
112
68
 
113
- if (preEmitDiag.length > 0) {
114
- let hasError = false;
69
+ const files = await glob(".flink/**/*.ts", { cwd });
70
+ for (const file of files) {
71
+ await fsPromises.rm(join(cwd, file));
72
+ }
115
73
 
116
- for (const diag of preEmitDiag) {
117
- let message = diag.getMessageText();
74
+ return flinkDir;
75
+ }
118
76
 
119
- while (typeof message !== "string") {
120
- message = message.getMessageText();
121
- }
77
+ /**
78
+ * Emits compiled javascript source to dist folder
79
+ */
80
+ emit() {
81
+ this.project.emitSync();
82
+ }
122
83
 
123
- if (diag.getCategory() === DiagnosticCategory.Error) {
124
- console.error(
125
- `[ERROR] ${diag
126
- .getSourceFile()
127
- ?.getBaseName()} (line ${diag.getLineNumber()}):`,
128
- message
129
- );
130
- hasError = true;
131
- }
132
- if (diag.getCategory() === DiagnosticCategory.Warning) {
133
- console.warn(
134
- `[WARNING] ${diag
135
- .getSourceFile()
136
- ?.getBaseName()} (line ${diag.getLineNumber()}):`,
137
- message
138
- );
84
+ /**
85
+ * Catch any compilation errors. Will return false if any Errors
86
+ * exists. Warnings will be passed thru but logged.
87
+ */
88
+ getPreEmitDiagnostics() {
89
+ const preEmitDiag = this.project.getPreEmitDiagnostics();
90
+
91
+ if (preEmitDiag.length > 0) {
92
+ let hasError = false;
93
+
94
+ for (const diag of preEmitDiag) {
95
+ let message = diag.getMessageText();
96
+
97
+ while (typeof message !== "string") {
98
+ message = message.getMessageText();
99
+ }
100
+
101
+ if (diag.getCategory() === DiagnosticCategory.Error) {
102
+ console.error(`[ERROR] ${diag.getSourceFile()?.getBaseName()} (line ${diag.getLineNumber()}):`, message);
103
+ hasError = true;
104
+ }
105
+ if (diag.getCategory() === DiagnosticCategory.Warning) {
106
+ console.warn(`[WARNING] ${diag.getSourceFile()?.getBaseName()} (line ${diag.getLineNumber()}):`, message);
107
+ }
108
+ }
109
+
110
+ if (hasError) {
111
+ return false;
112
+ }
139
113
  }
140
- }
141
-
142
- if (hasError) {
143
- return false;
144
- }
114
+ return true;
145
115
  }
146
- return true;
147
- }
148
-
149
- /**
150
- * Scans project for handlers and add those to Flink
151
- * "singleton" property `autoRegisteredHandlers` so they can
152
- * be registered during start.
153
- *
154
- * Also extract handlers request and response schemas from Handler
155
- * type arguments.
156
- */
157
- async parseHandlers(excludeDirs: string[] = []) {
158
- const generatedFile = this.createSourceFile(
159
- ["generatedHandlers.ts"],
160
- `// Generated ${new Date()}
116
+
117
+ /**
118
+ * Scans project for handlers and add those to Flink
119
+ * "singleton" property `autoRegisteredHandlers` so they can
120
+ * be registered during start.
121
+ *
122
+ * Also extract handlers request and response schemas from Handler
123
+ * type arguments.
124
+ */
125
+ async parseHandlers(excludeDirs: string[] = []) {
126
+ const generatedFile = this.createSourceFile(
127
+ ["generatedHandlers.ts"],
128
+ `// Generated ${new Date()}
161
129
  import { autoRegisteredHandlers, HttpMethod } from "@flink-app/flink";
162
130
  export const handlers = [];
163
131
  autoRegisteredHandlers.push(...handlers);
164
132
  `
165
- );
166
- const handlersArr = generatedFile
167
- .getVariableDeclarationOrThrow("handlers")
168
- .getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
169
-
170
- const handlers = await this.parseHandlerDir(generatedFile, handlersArr);
171
-
172
- generatedFile.addImportDeclarations(handlers.imports);
173
-
174
- await generatedFile.save();
175
-
176
- await this.createIntermediateSchemaFile();
177
-
178
- const jsonSchemas = await this.generateAndSaveJsonSchemas(
179
- handlers.schemasToGenerate
180
- );
181
-
182
- this.appendSchemasToHandlerSourceFiles(
183
- handlers.schemasToGenerate,
184
- jsonSchemas
185
- );
186
-
187
- return generatedFile;
188
- }
189
-
190
- /**
191
- * Scan `/src/handlers/*.ts` for handler files and register those.
192
- */
193
- private async parseHandlerDir(
194
- generatedFile: SourceFile,
195
- handlersArr: ArrayLiteralExpression
196
- ) {
197
- const imports: OptionalKind<ImportDeclarationStructure>[] = [];
198
- let i = 0;
199
- const schemasToGenerate: {
200
- reqSchemaType?: string;
201
- resSchemaType?: string;
202
- sourceFile: SourceFile;
203
- }[] = [];
204
-
205
- for (const sf of this.project.getSourceFiles()) {
206
- if (!sf.getFilePath().includes("src/handlers/")) {
207
- continue;
208
- }
209
-
210
- const isAutoRegister = this.isAutoRegisterableHandler(sf);
211
-
212
- console.log(
213
- `Detected handler ${sf.getBaseName()} ${
214
- !isAutoRegister ? "(requires manual registration)" : ""
215
- }`
216
- );
217
-
218
- const namespaceImport =
219
- sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
220
-
221
- imports.push({
222
- namespaceImport,
223
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
224
- });
225
-
226
- const assumedHttpMethod = getHttpMethodFromHandlerName(sf.getBaseName());
227
-
228
- const schemaTypes = await this.extractSchemasFromHandlerSourceFile(sf);
229
-
230
- // Append schemas and metadata to source file that will be part of emitted dist bundle (javascript)
231
- sf.addVariableStatement({
232
- declarationKind: VariableDeclarationKind.Const,
233
- isExported: true,
234
- declarations: [
235
- {
236
- name: "__assumedHttpMethod",
237
- initializer: `"${assumedHttpMethod || ""}"`,
238
- },
239
- {
240
- name: "__file",
241
- initializer: `"${sf.getBaseName()}"`,
242
- },
243
- {
244
- name: "__query",
245
- initializer: `[${(schemaTypes?.queryMetadata || [])
246
- .map(
247
- ({ description, name }) =>
248
- `{description: "${description}", name: "${name}"}`
249
- )
250
- .join(",")}]`,
251
- },
252
- {
253
- name: "__params",
254
- initializer: `[${(schemaTypes?.paramsMetadata || [])
255
- .map(
256
- ({ description, name }) =>
257
- `{description: "${description}", name: "${name}"}`
258
- )
259
- .join(",")}]`,
260
- },
261
- ],
262
- });
263
-
264
- if (isAutoRegister) {
265
- handlersArr.insertElement(
266
- i,
267
- `{handler: ${namespaceImport}, assumedHttpMethod: ${
268
- assumedHttpMethod ? "HttpMethod." + assumedHttpMethod : undefined
269
- }}`
270
133
  );
271
- i++;
272
- }
134
+ const handlersArr = generatedFile.getVariableDeclarationOrThrow("handlers").getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
135
+
136
+ const handlers = await this.parseHandlerDir(generatedFile, handlersArr);
137
+
138
+ generatedFile.addImportDeclarations(handlers.imports);
139
+
140
+ await generatedFile.save();
141
+
142
+ await this.createIntermediateSchemaFile();
143
+
144
+ const jsonSchemas = await this.generateAndSaveJsonSchemas(handlers.schemasToGenerate);
145
+
146
+ this.appendSchemasToHandlerSourceFiles(handlers.schemasToGenerate, jsonSchemas);
273
147
 
274
- if (schemaTypes) {
275
- schemasToGenerate.push({ ...schemaTypes, sourceFile: sf });
276
- }
148
+ return generatedFile;
277
149
  }
278
150
 
279
- return {
280
- imports,
281
- schemasToGenerate,
282
- };
283
- }
151
+ /**
152
+ * Scan `/src/handlers/*.ts` for handler files and register those.
153
+ */
154
+ private async parseHandlerDir(generatedFile: SourceFile, handlersArr: ArrayLiteralExpression) {
155
+ const imports: OptionalKind<ImportDeclarationStructure>[] = [];
156
+ let i = 0;
157
+ const schemasToGenerate: {
158
+ reqSchemaType?: string;
159
+ resSchemaType?: string;
160
+ sourceFile: SourceFile;
161
+ }[] = [];
162
+
163
+ for (const sf of this.project.getSourceFiles()) {
164
+ if (!sf.getFilePath().includes("src/handlers/")) {
165
+ continue;
166
+ }
167
+
168
+ const isAutoRegister = this.isAutoRegisterableHandler(sf);
169
+
170
+ console.log(`Detected handler ${sf.getBaseName()} ${!isAutoRegister ? "(requires manual registration)" : ""}`);
171
+
172
+ const namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
173
+
174
+ imports.push({
175
+ namespaceImport,
176
+ moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
177
+ });
178
+
179
+ const assumedHttpMethod = getHttpMethodFromHandlerName(sf.getBaseName());
180
+
181
+ const schemaTypes = await this.extractSchemasFromHandlerSourceFile(sf);
182
+
183
+ // Append schemas and metadata to source file that will be part of emitted dist bundle (javascript)
184
+ sf.addVariableStatement({
185
+ declarationKind: VariableDeclarationKind.Const,
186
+ isExported: true,
187
+ declarations: [
188
+ {
189
+ name: "__assumedHttpMethod",
190
+ initializer: `"${assumedHttpMethod || ""}"`,
191
+ },
192
+ {
193
+ name: "__file",
194
+ initializer: `"${sf.getBaseName()}"`,
195
+ },
196
+ {
197
+ name: "__query",
198
+ initializer: `[${(schemaTypes?.queryMetadata || [])
199
+ .map(({ description, name }) => `{description: "${description}", name: "${name}"}`)
200
+ .join(",")}]`,
201
+ },
202
+ {
203
+ name: "__params",
204
+ initializer: `[${(schemaTypes?.paramsMetadata || [])
205
+ .map(({ description, name }) => `{description: "${description}", name: "${name}"}`)
206
+ .join(",")}]`,
207
+ },
208
+ ],
209
+ });
210
+
211
+ if (isAutoRegister) {
212
+ handlersArr.insertElement(
213
+ i,
214
+ `{handler: ${namespaceImport}, assumedHttpMethod: ${assumedHttpMethod ? "HttpMethod." + assumedHttpMethod : undefined}}`
215
+ );
216
+ i++;
217
+ }
218
+
219
+ if (schemaTypes) {
220
+ schemasToGenerate.push({ ...schemaTypes, sourceFile: sf });
221
+ }
222
+ }
223
+
224
+ return {
225
+ imports,
226
+ schemasToGenerate,
227
+ };
228
+ }
284
229
 
285
- async parseRepos() {
286
- const generatedFile = this.createSourceFile(
287
- ["generatedRepos.ts"],
288
- `// Generated ${new Date()}
230
+ async parseRepos() {
231
+ const generatedFile = this.createSourceFile(
232
+ ["generatedRepos.ts"],
233
+ `// Generated ${new Date()}
289
234
  import { autoRegisteredRepos } from "@flink-app/flink";
290
235
  export const repos = [];
291
236
  autoRegisteredRepos.push(...repos);
292
237
  `
293
- );
238
+ );
294
239
 
295
- const reposArr = generatedFile
296
- .getVariableDeclarationOrThrow("repos")
297
- .getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
240
+ const reposArr = generatedFile.getVariableDeclarationOrThrow("repos").getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
298
241
 
299
- const imports: OptionalKind<ImportDeclarationStructure>[] = [];
242
+ const imports: OptionalKind<ImportDeclarationStructure>[] = [];
300
243
 
301
- let i = 0;
244
+ let i = 0;
302
245
 
303
- for (const sf of this.project.getSourceFiles()) {
304
- if (!sf.getFilePath().includes("src/repos/")) {
305
- continue;
306
- }
246
+ for (const sf of this.project.getSourceFiles()) {
247
+ if (!sf.getFilePath().includes("src/repos/")) {
248
+ continue;
249
+ }
307
250
 
308
- console.log(`Detected repo ${sf.getBaseName()}`);
251
+ console.log(`Detected repo ${sf.getBaseName()}`);
309
252
 
310
- imports.push({
311
- defaultImport: sf.getBaseNameWithoutExtension(),
312
- moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
313
- });
253
+ imports.push({
254
+ defaultImport: sf.getBaseNameWithoutExtension(),
255
+ moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
256
+ });
314
257
 
315
- reposArr.insertElement(
316
- i,
317
- `{collectionName: "${getCollectionNameForRepo(
318
- sf.getBaseName()
319
- )}", repoInstanceName: "${getRepoInstanceName(
320
- sf.getBaseName()
321
- )}", Repo: ${sf.getBaseNameWithoutExtension()}}`
322
- );
258
+ reposArr.insertElement(
259
+ i,
260
+ `{collectionName: "${getCollectionNameForRepo(sf.getBaseName())}", repoInstanceName: "${getRepoInstanceName(
261
+ sf.getBaseName()
262
+ )}", Repo: ${sf.getBaseNameWithoutExtension()}}`
263
+ );
323
264
 
324
- i++;
325
- }
265
+ i++;
266
+ }
326
267
 
327
- generatedFile.addImportDeclarations(imports);
328
-
329
- await generatedFile.save();
330
-
331
- return generatedFile;
332
- }
333
-
334
- /**
335
- * Generates a start script that will import references to handlers, repos and the
336
- * actual Flink app to start.
337
- *
338
- * Note that order is of importance so generated metadata are imported and initialized before start of flink app.
339
- * Otherwise singletons `autoRegisteredRepos` and `autoRegisteredHandlers` will not have been set.
340
- */
341
- async generateStartScript(appEntryScript = "/src/index.ts") {
342
- if (
343
- !this.project.getSourceFile((sf) =>
344
- sf.getFilePath().endsWith(appEntryScript)
345
- )
346
- ) {
347
- console.error(`Cannot find entry script '${appEntryScript}'`);
348
- return process.exit(1);
268
+ generatedFile.addImportDeclarations(imports);
269
+
270
+ await generatedFile.save();
271
+
272
+ return generatedFile;
349
273
  }
350
274
 
351
- const sf = this.createSourceFile(
352
- ["start.ts"],
353
- `// Generated ${new Date()}
275
+ /**
276
+ * Generates a start script that will import references to handlers, repos and the
277
+ * actual Flink app to start.
278
+ *
279
+ * Note that order is of importance so generated metadata are imported and initialized before start of flink app.
280
+ * Otherwise singletons `autoRegisteredRepos` and `autoRegisteredHandlers` will not have been set.
281
+ */
282
+ async generateStartScript(appEntryScript = "/src/index.ts") {
283
+ if (!this.project.getSourceFile((sf) => sf.getFilePath().endsWith(appEntryScript))) {
284
+ console.error(`Cannot find entry script '${appEntryScript}'`);
285
+ return process.exit(1);
286
+ }
287
+
288
+ const sf = this.createSourceFile(
289
+ ["start.ts"],
290
+ `// Generated ${new Date()}
354
291
  import "./generatedHandlers";
355
292
  import "./generatedRepos";
293
+ import "./generatedJobs";
356
294
  import "..${appEntryScript.replace(/\.ts/g, "")}";
357
295
  `
358
- );
359
-
360
- await sf.save();
361
-
362
- return sf;
363
- }
364
-
365
- private createSourceFile(filename: string[], contents: string) {
366
- return this.project.createSourceFile(
367
- join(this.cwd, ".flink", ...filename),
368
- contents,
369
- {
370
- overwrite: true,
371
- }
372
- );
373
- }
374
-
375
- /**
376
- * Parses handlers `Handler<...>` function and its type arguments to extract
377
- * which schemas to use.
378
- *
379
- * There are multiple ways of defining schema types as valid ts and this
380
- * implementation aims to support all of these.
381
- *
382
- * Some examples of different cases (check spec/mock-project/src/handlers for more):
383
- *
384
- * ```
385
- * // Interface reference
386
- * Handler<Ctx, Car>
387
- * // Inline type definition with reference to interface
388
- * Handler<Ctx, {car: Car}>
389
- * // Inline type definition with literal values
390
- * Handler<Ctx, {car: {model: string}}>
391
- * // Array
392
- * Handler<Ctx, Car[]>
393
- * // Array with inline type definition
394
- * Handler<Ctx, {car: Car}[]>
395
- * ```
396
- *
397
- * Return names of req and/or res schema types.
398
- */
399
- private async extractSchemasFromHandlerSourceFile(
400
- handlerSourceFile: SourceFile
401
- ) {
402
- const defaultExport = getDefaultExport(handlerSourceFile);
403
-
404
- if (defaultExport) {
405
- const handlerTypeRef = defaultExport.getFirstDescendantByKindOrThrow(
406
- SyntaxKind.TypeReference
407
- );
408
-
409
- return this.extractSchemaTypeFromHandler(handlerTypeRef);
410
- } else {
411
- console.warn(
412
- `Handler ${handlerSourceFile.getBaseName()} is missing default exported handler function`
413
- );
296
+ );
297
+
298
+ await sf.save();
299
+
300
+ return sf;
301
+ }
302
+
303
+ private createSourceFile(filename: string[], contents: string) {
304
+ return this.project.createSourceFile(join(this.cwd, ".flink", ...filename), contents, {
305
+ overwrite: true,
306
+ });
414
307
  }
415
- }
416
-
417
- private async saveIntermediateTsSchema(
418
- schema: Type<ts.Type>,
419
- handlerFile: SourceFile,
420
- suffix: string
421
- ) {
422
- if (schema.isAny()) {
423
- return; // 'any' indicates that no schema is used
308
+
309
+ /**
310
+ * Parses handlers `Handler<...>` function and its type arguments to extract
311
+ * which schemas to use.
312
+ *
313
+ * There are multiple ways of defining schema types as valid ts and this
314
+ * implementation aims to support all of these.
315
+ *
316
+ * Some examples of different cases (check spec/mock-project/src/handlers for more):
317
+ *
318
+ * ```
319
+ * // Interface reference
320
+ * Handler<Ctx, Car>
321
+ * // Inline type definition with reference to interface
322
+ * Handler<Ctx, {car: Car}>
323
+ * // Inline type definition with literal values
324
+ * Handler<Ctx, {car: {model: string}}>
325
+ * // Array
326
+ * Handler<Ctx, Car[]>
327
+ * // Array with inline type definition
328
+ * Handler<Ctx, {car: Car}[]>
329
+ * ```
330
+ *
331
+ * Return names of req and/or res schema types.
332
+ */
333
+ private async extractSchemasFromHandlerSourceFile(handlerSourceFile: SourceFile) {
334
+ const defaultExport = getDefaultExport(handlerSourceFile);
335
+
336
+ if (defaultExport) {
337
+ const handlerTypeRef = defaultExport.getFirstDescendantByKindOrThrow(SyntaxKind.TypeReference);
338
+
339
+ return this.extractSchemaTypeFromHandler(handlerTypeRef);
340
+ } else {
341
+ console.warn(`Handler ${handlerSourceFile.getBaseName()} is missing default exported handler function`);
342
+ }
424
343
  }
425
344
 
426
- const handlerFileName = handlerFile
427
- .getBaseNameWithoutExtension()
428
- .replace(/\./g, "_");
345
+ private async saveIntermediateTsSchema(schema: Type<ts.Type>, handlerFile: SourceFile, suffix: string) {
346
+ if (schema.isAny()) {
347
+ return; // 'any' indicates that no schema is used
348
+ }
349
+
350
+ const handlerFileName = handlerFile.getBaseNameWithoutExtension().replace(/\./g, "_");
429
351
 
430
- let generatedSchemaInterfaceStr = "";
352
+ let generatedSchemaInterfaceStr = "";
431
353
 
432
- const schemaInterfaceName = `${handlerFileName}_${suffix}`;
354
+ const schemaInterfaceName = `${handlerFileName}_${suffix}`;
433
355
 
434
- if (schema.isInterface()) {
435
- /*
436
- * Type argument is an interface. This should be normal case when
437
- * schema is defined directly for example `Handler<Ctx, Car>`
438
- */
439
- const schemaSymbol = schema.getSymbolOrThrow();
440
- const interfaceName = getInterfaceName(schemaSymbol);
441
- const declaration = schemaSymbol.getDeclarations()[0];
356
+ if (schema.isInterface()) {
357
+ /*
358
+ * Type argument is an interface. This should be normal case when
359
+ * schema is defined directly for example `Handler<Ctx, Car>`
360
+ */
361
+ const schemaSymbol = schema.getSymbolOrThrow();
362
+ const interfaceName = getInterfaceName(schemaSymbol);
363
+ const declaration = schemaSymbol.getDeclarations()[0];
442
364
 
443
- if (declaration.getSourceFile() === handlerFile) {
444
- // Interface is declared within handler file
445
- generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} {
365
+ if (declaration.getSourceFile() === handlerFile) {
366
+ // Interface is declared within handler file
367
+ generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} {
446
368
  ${schema
447
- .getProperties()
448
- .map((p) => p.getValueDeclarationOrThrow().getText())
449
- .join("\n")}
369
+ .getProperties()
370
+ .map((p) => p.getValueDeclarationOrThrow().getText())
371
+ .join("\n")}
450
372
  }`;
451
373
 
452
- for (const typeToImport of getTypesToImport(declaration)) {
453
- this.tsSchemasSymbolsToImports.push(
454
- typeToImport.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow()
455
- );
456
- }
457
- } else {
458
- // Interface is imported from other file
459
- generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends ${interfaceName} {}`;
460
- this.tsSchemasSymbolsToImports.push(schemaSymbol);
461
- }
462
- } else if (schema.isArray()) {
463
- const arrayTypeArg = schema.getTypeArguments()[0];
464
- const schemaSymbol = arrayTypeArg.getSymbolOrThrow();
465
- const interfaceName = schemaSymbol.getEscapedName();
466
- const declaration = schemaSymbol.getDeclarations()[0];
467
-
468
- if (declaration.getSourceFile() !== handlerFile) {
469
- generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends Array<${interfaceName}> {}`;
470
- this.tsSchemasSymbolsToImports.push(schemaSymbol);
471
- } else {
472
- if (arrayTypeArg.isInterface()) {
473
- const props = arrayTypeArg
474
- .getProperties()
475
- .map((p) => p.getValueDeclarationOrThrow().getText())
476
- .join(" ");
477
-
478
- generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends Array<{${props}}> {}`;
374
+ for (const typeToImport of getTypesToImport(declaration)) {
375
+ this.tsSchemasSymbolsToImports.push(typeToImport.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow());
376
+ }
377
+ } else {
378
+ // Interface is imported from other file
379
+ generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends ${interfaceName} {}`;
380
+ this.tsSchemasSymbolsToImports.push(schemaSymbol);
381
+ }
382
+ } else if (schema.isArray()) {
383
+ const arrayTypeArg = schema.getTypeArguments()[0];
384
+ const schemaSymbol = arrayTypeArg.getSymbolOrThrow();
385
+ const interfaceName = schemaSymbol.getEscapedName();
386
+ const declaration = schemaSymbol.getDeclarations()[0];
387
+
388
+ if (declaration.getSourceFile() !== handlerFile) {
389
+ generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends Array<${interfaceName}> {}`;
390
+ this.tsSchemasSymbolsToImports.push(schemaSymbol);
391
+ } else {
392
+ if (arrayTypeArg.isInterface()) {
393
+ const props = arrayTypeArg
394
+ .getProperties()
395
+ .map((p) => p.getValueDeclarationOrThrow().getText())
396
+ .join(" ");
397
+
398
+ generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends Array<{${props}}> {}`;
399
+ } else {
400
+ generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends Array<${declaration.getText()}> {}`;
401
+ }
402
+
403
+ for (const typeToImport of getTypesToImport(declaration)) {
404
+ this.tsSchemasSymbolsToImports.push(typeToImport.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow());
405
+ }
406
+ }
407
+ } else if (schema.isObject()) {
408
+ /*
409
+ * Schema is defined inline, for example `Handler<Ctx, {car: Car}>`
410
+ * We need extract `{car: Car}` into its own interface and make sure
411
+ * to import types if needed to
412
+ */
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
+ });
422
+
423
+ generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} { ${schema
424
+ .getProperties()
425
+ .map((p) => p.getValueDeclarationOrThrow().getText())
426
+ .join("\n")} }`;
479
427
  } else {
480
- generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} extends Array<${declaration.getText()}> {}`;
428
+ console.log("[WARN] Unknown schema type", schema.getText());
481
429
  }
482
430
 
483
- for (const typeToImport of getTypesToImport(declaration)) {
484
- this.tsSchemasSymbolsToImports.push(
485
- typeToImport.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow()
486
- );
431
+ if (generatedSchemaInterfaceStr) {
432
+ this.parsedTsSchemas.push(generatedSchemaInterfaceStr);
433
+
434
+ return schemaInterfaceName;
487
435
  }
488
- }
489
- } else if (schema.isObject()) {
490
- /*
491
- * Schema is defined inline, for example `Handler<Ctx, {car: Car}>`
492
- * We need extract `{car: Car}` into its own interface and make sure
493
- * to import types if needed to
494
- */
495
- const declaration = schema.getSymbolOrThrow().getDeclarations()[0];
496
-
497
- const typeRefIdentifiers = declaration
498
- .getDescendantsOfKind(SyntaxKind.TypeReference)
499
- .map((typeRef) =>
500
- typeRef.getFirstChildByKindOrThrow(SyntaxKind.Identifier)
501
- );
436
+ return;
437
+ }
502
438
 
503
- typeRefIdentifiers.forEach((tr) => {
504
- this.tsSchemasSymbolsToImports.push(
505
- tr.getSymbolOrThrow().getDeclaredType().getSymbolOrThrow()
506
- );
507
- });
508
-
509
- generatedSchemaInterfaceStr = `export interface ${schemaInterfaceName} { ${schema
510
- .getProperties()
511
- .map((p) => p.getValueDeclarationOrThrow().getText())
512
- .join("\n")} }`;
513
- } else {
514
- console.log("[WARN] Unknown schema type", schema.getText());
439
+ private initJsonSchemaGenerator() {
440
+ const conf: Config = {
441
+ // expose: "",
442
+ // topRef: false,
443
+ };
444
+ const formatter = createFormatter(conf);
445
+ const parser = createParser(this.project.getProgram().compilerObject, conf);
446
+ const generator = new SchemaGenerator(this.project.getProgram().compilerObject, parser, formatter, conf);
447
+
448
+ return generator;
515
449
  }
516
450
 
517
- if (generatedSchemaInterfaceStr) {
518
- this.parsedTsSchemas.push(generatedSchemaInterfaceStr);
451
+ private async generateAndSaveJsonSchemas(schemas: { reqSchemaType?: string; resSchemaType?: string }[]) {
452
+ const jsonSchemas: Schema[] = [];
453
+
454
+ for (const { reqSchemaType, resSchemaType } of schemas) {
455
+ if (reqSchemaType) {
456
+ jsonSchemas.push(this.generateJsonSchema(reqSchemaType));
457
+ }
458
+ if (resSchemaType) {
459
+ jsonSchemas.push(this.generateJsonSchema(resSchemaType));
460
+ }
461
+ }
462
+
463
+ const mergedSchemas = jsonSchemas.reduce(
464
+ (out, schema) => {
465
+ if (schema.definitions) {
466
+ out.definitions = { ...out.definitions, ...schema.definitions };
467
+ }
468
+ return out;
469
+ },
470
+ {
471
+ $schema: "http://json-schema.org/draft-07/schema#",
472
+ $ref: "#/definitions/Schemas",
473
+ definitions: {},
474
+ }
475
+ );
519
476
 
520
- return schemaInterfaceName;
477
+ const filePath = join(this.cwd, ".flink", "schemas", "schemas.json");
478
+
479
+ await writeJsonFile(filePath, mergedSchemas);
480
+
481
+ this.project.addSourceFileAtPath(filePath);
482
+
483
+ return mergedSchemas;
521
484
  }
522
- return;
523
- }
524
-
525
- private initJsonSchemaGenerator() {
526
- const conf: Config = {
527
- // expose: "",
528
- // topRef: false,
529
- };
530
- const formatter = createFormatter(conf);
531
- const parser = createParser(this.project.getProgram().compilerObject, conf);
532
- const generator = new SchemaGenerator(
533
- this.project.getProgram().compilerObject,
534
- parser,
535
- formatter,
536
- conf
537
- );
538
-
539
- return generator;
540
- }
541
-
542
- private async generateAndSaveJsonSchemas(
543
- schemas: { reqSchemaType?: string; resSchemaType?: string }[]
544
- ) {
545
- const jsonSchemas: Schema[] = [];
546
-
547
- for (const { reqSchemaType, resSchemaType } of schemas) {
548
- if (reqSchemaType) {
549
- jsonSchemas.push(this.generateJsonSchema(reqSchemaType));
550
- }
551
- if (resSchemaType) {
552
- jsonSchemas.push(this.generateJsonSchema(resSchemaType));
553
- }
485
+
486
+ private generateJsonSchema(typeName: string) {
487
+ if (!this.schemaGenerator) {
488
+ this.schemaGenerator = this.initJsonSchemaGenerator();
489
+ }
490
+ return this.schemaGenerator.createSchema(typeName);
554
491
  }
555
492
 
556
- const mergedSchemas = jsonSchemas.reduce(
557
- (out, schema) => {
558
- if (schema.definitions) {
559
- out.definitions = { ...out.definitions, ...schema.definitions };
493
+ private async extractSchemaTypeFromHandler(handlerTypeReference: TypeReferenceNode) {
494
+ // Name of Handler function - should be either `Handler` or `GetHandler`
495
+ const handlerType = handlerTypeReference.getTypeName().getText();
496
+
497
+ // Get type arguments a.k.a. generics which holds schemas such as this `Handler<Ctx, ReqSchema, ResSchema>`
498
+ const handlerTypeArgs = handlerTypeReference.getType().getAliasTypeArguments();
499
+
500
+ let reqSchema: Type<ts.Type> | undefined;
501
+ let resSchema: Type<ts.Type> | undefined;
502
+ let params: Type<ts.Type> | undefined;
503
+ let query: Type<ts.Type> | undefined;
504
+
505
+ if (handlerType === "Handler") {
506
+ // `Handler<Ctx, ReqSchema, ResSchema, Params, Query>`
507
+ // 0 = Ctx, 1 = Req schema, 2 = Res schema, 3 = Params, 4 = Query
508
+ reqSchema = handlerTypeArgs[1];
509
+ resSchema = handlerTypeArgs[2];
510
+ params = handlerTypeArgs[3];
511
+ query = handlerTypeArgs[4];
512
+ } else if (handlerType === "GetHandler") {
513
+ // `GetHandler<Ctx, ResSchema, Params, Query>`
514
+ // 0 = Ctx, 1 = Res schema, 2 = Params, 3 = Query
515
+ resSchema = handlerTypeArgs[1];
516
+ params = handlerTypeArgs[2];
517
+ query = handlerTypeArgs[3];
518
+ } else {
519
+ throw new Error(`Unknown handler type ${handlerType} in ${handlerTypeReference.getSourceFile().getBaseName()} - should be Handler or GetHandler`);
560
520
  }
561
- return out;
562
- },
563
- {
564
- $schema: "http://json-schema.org/draft-07/schema#",
565
- $ref: "#/definitions/Schemas",
566
- definitions: {},
567
- }
568
- );
569
521
 
570
- const filePath = join(this.cwd, ".flink", "schemas", "schemas.json");
522
+ const sf = handlerTypeReference.getSourceFile();
571
523
 
572
- await writeJsonFile(filePath, mergedSchemas);
524
+ const createReqSchemaPromise = reqSchema
525
+ ? this.saveIntermediateTsSchema(reqSchema, sf, `${handlerTypeReference.getStartLineNumber()}_ReqSchema`)
526
+ : Promise.resolve("");
573
527
 
574
- this.project.addSourceFileAtPath(filePath);
528
+ const createResSchemaPromise = resSchema
529
+ ? this.saveIntermediateTsSchema(resSchema, sf, `${handlerTypeReference.getStartLineNumber()}_ResSchema`)
530
+ : Promise.resolve("");
575
531
 
576
- return mergedSchemas;
577
- }
532
+ const [reqSchemaType, resSchemaType] = await Promise.all([createReqSchemaPromise, createResSchemaPromise]);
578
533
 
579
- private generateJsonSchema(typeName: string) {
580
- if (!this.schemaGenerator) {
581
- this.schemaGenerator = this.initJsonSchemaGenerator();
582
- }
583
- return this.schemaGenerator.createSchema(typeName);
584
- }
585
-
586
- private async extractSchemaTypeFromHandler(
587
- handlerTypeReference: TypeReferenceNode
588
- ) {
589
- // Name of Handler function - should be either `Handler` or `GetHandler`
590
- const handlerType = handlerTypeReference.getTypeName().getText();
591
-
592
- // Get type arguments a.k.a. generics which holds schemas such as this `Handler<Ctx, ReqSchema, ResSchema>`
593
- const handlerTypeArgs = handlerTypeReference
594
- .getType()
595
- .getAliasTypeArguments();
596
-
597
- let reqSchema: Type<ts.Type> | undefined;
598
- let resSchema: Type<ts.Type> | undefined;
599
- let params: Type<ts.Type> | undefined;
600
- let query: Type<ts.Type> | undefined;
601
-
602
- if (handlerType === "Handler") {
603
- // `Handler<Ctx, ReqSchema, ResSchema, Params, Query>`
604
- // 0 = Ctx, 1 = Req schema, 2 = Res schema, 3 = Params, 4 = Query
605
- reqSchema = handlerTypeArgs[1];
606
- resSchema = handlerTypeArgs[2];
607
- params = handlerTypeArgs[3];
608
- query = handlerTypeArgs[4];
609
- } else if (handlerType === "GetHandler") {
610
- // `GetHandler<Ctx, ResSchema, Params, Query>`
611
- // 0 = Ctx, 1 = Res schema, 2 = Params, 3 = Query
612
- resSchema = handlerTypeArgs[1];
613
- params = handlerTypeArgs[2];
614
- query = handlerTypeArgs[3];
615
- } else {
616
- throw new Error(
617
- `Unknown handler type ${handlerType} in ${handlerTypeReference
618
- .getSourceFile()
619
- .getBaseName()} - should be Handler or GetHandler`
620
- );
534
+ return {
535
+ reqSchemaType,
536
+ resSchemaType,
537
+ queryMetadata: getTypeMetadata(query),
538
+ paramsMetadata: getTypeMetadata(params),
539
+ };
621
540
  }
622
541
 
623
- const sf = handlerTypeReference.getSourceFile();
624
-
625
- const createReqSchemaPromise = reqSchema
626
- ? this.saveIntermediateTsSchema(
627
- reqSchema,
628
- sf,
629
- `${handlerTypeReference.getStartLineNumber()}_ReqSchema`
630
- )
631
- : Promise.resolve("");
632
-
633
- const createResSchemaPromise = resSchema
634
- ? this.saveIntermediateTsSchema(
635
- resSchema,
636
- sf,
637
- `${handlerTypeReference.getStartLineNumber()}_ResSchema`
638
- )
639
- : Promise.resolve("");
640
-
641
- const [reqSchemaType, resSchemaType] = await Promise.all([
642
- createReqSchemaPromise,
643
- createResSchemaPromise,
644
- ]);
645
-
646
- return {
647
- reqSchemaType,
648
- resSchemaType,
649
- queryMetadata: getTypeMetadata(query),
650
- paramsMetadata: getTypeMetadata(params),
651
- };
652
- }
653
-
654
- /**
655
- * Creates generated source file that contains all
656
- * TypeScript schemas that has been derived from handlers.
657
- */
658
- private async createIntermediateSchemaFile() {
659
- const schemaSourceFile = this.createSourceFile(
660
- ["schemas", `schemas.ts`],
661
- `// Generated ${new Date()}
542
+ /**
543
+ * Creates generated source file that contains all
544
+ * TypeScript schemas that has been derived from handlers.
545
+ */
546
+ private async createIntermediateSchemaFile() {
547
+ const schemaSourceFile = this.createSourceFile(
548
+ ["schemas", `schemas.ts`],
549
+ `// Generated ${new Date()}
662
550
  ${this.parsedTsSchemas.join("\n\n")}`
663
- );
551
+ );
664
552
 
665
- addImports(schemaSourceFile, this.tsSchemasSymbolsToImports);
553
+ addImports(schemaSourceFile, this.tsSchemasSymbolsToImports);
666
554
 
667
- return schemaSourceFile.save();
668
- }
555
+ return schemaSourceFile.save();
556
+ }
669
557
 
670
- /**
671
- * Check if handler source file is up for auto registration by inspecting
672
- * the Route.
673
- *
674
- * @param sf handler file
675
- * @returns
676
- */
677
- private isAutoRegisterableHandler(sf: SourceFile) {
678
- const route = sf.getVariableDeclaration("Route");
558
+ /**
559
+ * Check if handler source file is up for auto registration by inspecting
560
+ * the Route.
561
+ *
562
+ * @param sf handler file
563
+ * @returns
564
+ */
565
+ private isAutoRegisterableHandler(sf: SourceFile) {
566
+ const route = sf.getVariableDeclaration("Route");
567
+
568
+ if (!route) {
569
+ return false;
570
+ }
679
571
 
680
- if (!route) {
681
- return false;
572
+ const routeProps = route.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression);
573
+
574
+ const skipAutoRegProp = routeProps.getProperty("skipAutoRegister");
575
+
576
+ return !skipAutoRegProp || skipAutoRegProp.getText().endsWith("false");
682
577
  }
683
578
 
684
- const routeProps = route.getFirstDescendantByKindOrThrow(
685
- SyntaxKind.ObjectLiteralExpression
686
- );
687
-
688
- const skipAutoRegProp = routeProps.getProperty("skipAutoRegister");
689
-
690
- return !skipAutoRegProp || skipAutoRegProp.getText().endsWith("false");
691
- }
692
-
693
- /**
694
- * Appends generated json schemas to handler source files.
695
- *
696
- * @param handlers
697
- * @param jsonSchemas
698
- */
699
- private appendSchemasToHandlerSourceFiles(
700
- handlers: {
701
- sourceFile: SourceFile;
702
- reqSchemaType?: string;
703
- resSchemaType?: string;
704
- }[],
705
- jsonSchemas: JSONSchema7
706
- ) {
707
- const jsonSchemaDefs = jsonSchemas.definitions || {};
708
-
709
- for (const { sourceFile, reqSchemaType, resSchemaType } of handlers) {
710
- if (reqSchemaType && !jsonSchemaDefs[reqSchemaType]) {
711
- console.error(
712
- `Handler ${sourceFile.getBaseName} has request schema (${reqSchemaType}) defined, but JSON schema has been generated`
713
- );
714
- continue;
715
- }
579
+ /**
580
+ * Appends generated json schemas to handler source files.
581
+ *
582
+ * @param handlers
583
+ * @param jsonSchemas
584
+ */
585
+ private appendSchemasToHandlerSourceFiles(
586
+ handlers: {
587
+ sourceFile: SourceFile;
588
+ reqSchemaType?: string;
589
+ resSchemaType?: string;
590
+ }[],
591
+ jsonSchemas: JSONSchema7
592
+ ) {
593
+ const jsonSchemaDefs = jsonSchemas.definitions || {};
594
+
595
+ for (const { sourceFile, reqSchemaType, resSchemaType } of handlers) {
596
+ if (reqSchemaType && !jsonSchemaDefs[reqSchemaType]) {
597
+ console.error(`Handler ${sourceFile.getBaseName} has request schema (${reqSchemaType}) defined, but JSON schema has been generated`);
598
+ continue;
599
+ }
600
+
601
+ if (resSchemaType && !jsonSchemaDefs[resSchemaType]) {
602
+ console.error(`Handler ${sourceFile.getBaseName} has response schema (${resSchemaType}) defined, but JSON schema has been generated`);
603
+ continue;
604
+ }
605
+
606
+ const reqJsonSchema = JSON.stringify(reqSchemaType ? deRefSchema(jsonSchemaDefs[reqSchemaType], jsonSchemas) : undefined);
607
+ const resJsonSchema = JSON.stringify(resSchemaType ? deRefSchema(jsonSchemaDefs[resSchemaType], jsonSchemas) : undefined);
608
+
609
+ sourceFile.addVariableStatement({
610
+ declarationKind: VariableDeclarationKind.Const,
611
+ isExported: true,
612
+ declarations: [
613
+ {
614
+ name: "__schemas",
615
+ type: "any",
616
+ initializer: `{ reqSchema: ${reqJsonSchema}, resSchema: ${resJsonSchema} }`,
617
+ },
618
+ ],
619
+ });
620
+ }
621
+ }
716
622
 
717
- if (resSchemaType && !jsonSchemaDefs[resSchemaType]) {
718
- console.error(
719
- `Handler ${sourceFile.getBaseName} has response schema (${resSchemaType}) defined, but JSON schema has been generated`
623
+ /**
624
+ * Scans project for jobs so they can be registered during start.
625
+ */
626
+ async parseJobs() {
627
+ const generatedFile = this.createSourceFile(
628
+ ["generatedJobs.ts"],
629
+ `// Generated ${new Date()}
630
+ import { autoRegisteredJobs } from "@flink-app/flink";
631
+ export const jobs = [];
632
+ autoRegisteredJobs.push(...jobs);
633
+ `
720
634
  );
721
- continue;
722
- }
723
-
724
- const reqJsonSchema = JSON.stringify(
725
- reqSchemaType
726
- ? deRefSchema(jsonSchemaDefs[reqSchemaType], jsonSchemas)
727
- : undefined
728
- );
729
- const resJsonSchema = JSON.stringify(
730
- resSchemaType
731
- ? deRefSchema(jsonSchemaDefs[resSchemaType], jsonSchemas)
732
- : undefined
733
- );
734
-
735
- sourceFile.addVariableStatement({
736
- declarationKind: VariableDeclarationKind.Const,
737
- isExported: true,
738
- declarations: [
739
- {
740
- name: "__schemas",
741
- type: "any",
742
- initializer: `{ reqSchema: ${reqJsonSchema}, resSchema: ${resJsonSchema} }`,
743
- },
744
- ],
745
- });
635
+
636
+ const jobsArr = generatedFile.getVariableDeclarationOrThrow("jobs").getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
637
+
638
+ const imports: OptionalKind<ImportDeclarationStructure>[] = [];
639
+ let i = 0;
640
+
641
+ for (const sf of this.project.getSourceFiles()) {
642
+ if (!sf.getFilePath().includes("src/jobs/")) {
643
+ continue;
644
+ }
645
+
646
+ console.log(`Detected job ${sf.getBaseName()}`);
647
+
648
+ const namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
649
+
650
+ imports.push({
651
+ namespaceImport,
652
+ moduleSpecifier: generatedFile.getRelativePathAsModuleSpecifierTo(sf),
653
+ });
654
+
655
+ // Append metadata to source file that will be part of emitted dist bundle (javascript)
656
+ sf.addVariableStatement({
657
+ declarationKind: VariableDeclarationKind.Const,
658
+ isExported: true,
659
+ declarations: [
660
+ {
661
+ name: "__file",
662
+ initializer: `"${sf.getBaseName()}"`,
663
+ },
664
+ ],
665
+ });
666
+
667
+ jobsArr.insertElement(i, namespaceImport);
668
+
669
+ i++;
670
+ }
671
+
672
+ generatedFile.addImportDeclarations(imports);
673
+
674
+ await generatedFile.save();
675
+
676
+ return generatedFile;
746
677
  }
747
- }
748
678
  }
749
679
 
750
680
  export default TypeScriptCompiler;