@postxl/generator 0.74.2 → 1.0.3

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 (189) hide show
  1. package/LICENSE +50 -0
  2. package/README.md +79 -1
  3. package/dist/generator-manager.class.d.ts +59 -0
  4. package/dist/generator-manager.class.js +221 -0
  5. package/dist/generator.class.d.ts +90 -0
  6. package/dist/generator.class.js +32 -0
  7. package/dist/generator.context.d.ts +174 -0
  8. package/dist/generator.context.js +125 -0
  9. package/dist/helpers/branded.types.d.ts +149 -0
  10. package/dist/helpers/branded.types.js +111 -0
  11. package/dist/helpers/config-builder.class.d.ts +27 -0
  12. package/dist/helpers/config-builder.class.js +54 -0
  13. package/dist/helpers/import-generator.class.d.ts +70 -0
  14. package/dist/helpers/import-generator.class.js +166 -0
  15. package/dist/helpers/importable.types.d.ts +52 -0
  16. package/dist/helpers/importable.types.js +15 -0
  17. package/dist/helpers/index-generator.class.d.ts +10 -0
  18. package/dist/helpers/index-generator.class.js +46 -0
  19. package/dist/helpers/index.d.ts +8 -0
  20. package/dist/helpers/index.js +24 -0
  21. package/dist/helpers/package-json.generator.d.ts +56 -0
  22. package/dist/helpers/package-json.generator.js +36 -0
  23. package/dist/helpers/tsconfig.generator.d.ts +1 -0
  24. package/dist/helpers/tsconfig.generator.js +14 -0
  25. package/dist/helpers/verify-context.d.ts +4 -0
  26. package/dist/helpers/verify-context.js +23 -0
  27. package/dist/index.d.ts +5 -0
  28. package/dist/index.js +21 -0
  29. package/dist/utils/checksum.d.ts +10 -0
  30. package/dist/utils/checksum.js +132 -0
  31. package/dist/utils/fs-utils.d.ts +34 -0
  32. package/dist/utils/fs-utils.js +126 -0
  33. package/dist/utils/index.d.ts +10 -0
  34. package/dist/utils/index.js +26 -0
  35. package/dist/utils/jsdoc.d.ts +12 -0
  36. package/dist/utils/jsdoc.js +37 -0
  37. package/dist/utils/lint.d.ts +46 -0
  38. package/dist/utils/lint.js +154 -0
  39. package/dist/utils/lockfile.d.ts +7 -0
  40. package/dist/utils/lockfile.js +80 -0
  41. package/dist/utils/logger.class.d.ts +25 -0
  42. package/dist/utils/logger.class.js +55 -0
  43. package/dist/utils/merge-conflict.d.ts +55 -0
  44. package/dist/utils/merge-conflict.js +264 -0
  45. package/dist/utils/path.d.ts +52 -0
  46. package/dist/utils/path.js +183 -0
  47. package/dist/utils/prettier-config.d.ts +2 -0
  48. package/dist/utils/prettier-config.js +13 -0
  49. package/dist/utils/prettier.d.ts +5 -0
  50. package/dist/utils/prettier.js +67 -0
  51. package/dist/utils/prettier.skiptest.d.ts +1 -0
  52. package/dist/utils/prettier.skiptest.js +22 -0
  53. package/dist/utils/promise.d.ts +2 -0
  54. package/dist/utils/promise.js +10 -0
  55. package/dist/utils/string-functions.d.ts +9 -0
  56. package/dist/utils/string-functions.js +23 -0
  57. package/dist/utils/sync-log-result.d.ts +9 -0
  58. package/dist/utils/sync-log-result.js +90 -0
  59. package/dist/utils/sync.d.ts +143 -0
  60. package/dist/utils/sync.js +325 -0
  61. package/dist/utils/template.d.ts +66 -0
  62. package/dist/utils/template.js +159 -0
  63. package/dist/utils/vfs.class.d.ts +115 -0
  64. package/dist/utils/vfs.class.js +239 -0
  65. package/dist/utils/zip.d.ts +13 -0
  66. package/dist/utils/zip.js +40 -0
  67. package/package.json +53 -31
  68. package/dist/generator.d.ts +0 -13
  69. package/dist/generator.js +0 -455
  70. package/dist/generators/enums/react.generator.d.ts +0 -10
  71. package/dist/generators/enums/react.generator.js +0 -110
  72. package/dist/generators/enums/types.generator.d.ts +0 -10
  73. package/dist/generators/enums/types.generator.js +0 -39
  74. package/dist/generators/indices/data/module.generator.d.ts +0 -9
  75. package/dist/generators/indices/data/module.generator.js +0 -60
  76. package/dist/generators/indices/data/service.generator.d.ts +0 -9
  77. package/dist/generators/indices/data/service.generator.js +0 -249
  78. package/dist/generators/indices/data/types.generator.d.ts +0 -9
  79. package/dist/generators/indices/data/types.generator.js +0 -49
  80. package/dist/generators/indices/dispatcher-service.generator.d.ts +0 -9
  81. package/dist/generators/indices/dispatcher-service.generator.js +0 -107
  82. package/dist/generators/indices/export/class.generator.d.ts +0 -9
  83. package/dist/generators/indices/export/class.generator.js +0 -140
  84. package/dist/generators/indices/export/encoder.generator.d.ts +0 -9
  85. package/dist/generators/indices/export/encoder.generator.js +0 -50
  86. package/dist/generators/indices/import/convert-functions.generator.d.ts +0 -9
  87. package/dist/generators/indices/import/convert-functions.generator.js +0 -509
  88. package/dist/generators/indices/import/decoder.generator.d.ts +0 -9
  89. package/dist/generators/indices/import/decoder.generator.js +0 -40
  90. package/dist/generators/indices/import/service.generator.d.ts +0 -9
  91. package/dist/generators/indices/import/service.generator.js +0 -573
  92. package/dist/generators/indices/import/types.generator.d.ts +0 -9
  93. package/dist/generators/indices/import/types.generator.js +0 -242
  94. package/dist/generators/indices/repositories.generator.d.ts +0 -9
  95. package/dist/generators/indices/repositories.generator.js +0 -25
  96. package/dist/generators/indices/routes.generator.d.ts +0 -9
  97. package/dist/generators/indices/routes.generator.js +0 -29
  98. package/dist/generators/indices/seed-migration.generator.d.ts +0 -9
  99. package/dist/generators/indices/seed-migration.generator.js +0 -36
  100. package/dist/generators/indices/seed-template.generator.d.ts +0 -9
  101. package/dist/generators/indices/seed-template.generator.js +0 -80
  102. package/dist/generators/indices/testids.generator.d.ts +0 -7
  103. package/dist/generators/indices/testids.generator.js +0 -71
  104. package/dist/generators/indices/types.generator.d.ts +0 -10
  105. package/dist/generators/indices/types.generator.js +0 -35
  106. package/dist/generators/indices/update/actiontypes.generator.d.ts +0 -9
  107. package/dist/generators/indices/update/actiontypes.generator.js +0 -49
  108. package/dist/generators/indices/update/module.generator.d.ts +0 -9
  109. package/dist/generators/indices/update/module.generator.js +0 -41
  110. package/dist/generators/indices/update/service.generator.d.ts +0 -9
  111. package/dist/generators/indices/update/service.generator.js +0 -34
  112. package/dist/generators/indices/view/module.generator.d.ts +0 -9
  113. package/dist/generators/indices/view/module.generator.js +0 -39
  114. package/dist/generators/indices/view/service.generator.d.ts +0 -9
  115. package/dist/generators/indices/view/service.generator.js +0 -34
  116. package/dist/generators/models/admin.page.generator.d.ts +0 -7
  117. package/dist/generators/models/admin.page.generator.js +0 -74
  118. package/dist/generators/models/export/encoder.generator.d.ts +0 -9
  119. package/dist/generators/models/export/encoder.generator.js +0 -51
  120. package/dist/generators/models/import/decoder.generator.d.ts +0 -9
  121. package/dist/generators/models/import/decoder.generator.js +0 -148
  122. package/dist/generators/models/react/context.generator.d.ts +0 -9
  123. package/dist/generators/models/react/context.generator.js +0 -71
  124. package/dist/generators/models/react/index.d.ts +0 -10
  125. package/dist/generators/models/react/index.js +0 -31
  126. package/dist/generators/models/react/library.generator.d.ts +0 -10
  127. package/dist/generators/models/react/library.generator.js +0 -94
  128. package/dist/generators/models/react/lookup.generator.d.ts +0 -9
  129. package/dist/generators/models/react/lookup.generator.js +0 -175
  130. package/dist/generators/models/react/modals.generator.d.ts +0 -23
  131. package/dist/generators/models/react/modals.generator.js +0 -710
  132. package/dist/generators/models/repository.generator.d.ts +0 -9
  133. package/dist/generators/models/repository.generator.js +0 -955
  134. package/dist/generators/models/route.generator.d.ts +0 -9
  135. package/dist/generators/models/route.generator.js +0 -92
  136. package/dist/generators/models/seed.generator.d.ts +0 -21
  137. package/dist/generators/models/seed.generator.js +0 -285
  138. package/dist/generators/models/stub.generator.d.ts +0 -9
  139. package/dist/generators/models/stub.generator.js +0 -92
  140. package/dist/generators/models/types.generator.d.ts +0 -9
  141. package/dist/generators/models/types.generator.js +0 -125
  142. package/dist/generators/models/update/service.generator.d.ts +0 -10
  143. package/dist/generators/models/update/service.generator.js +0 -302
  144. package/dist/generators/models/view/service.generator.d.ts +0 -10
  145. package/dist/generators/models/view/service.generator.js +0 -239
  146. package/dist/lib/attributes.d.ts +0 -114
  147. package/dist/lib/attributes.js +0 -2
  148. package/dist/lib/exports.d.ts +0 -45
  149. package/dist/lib/exports.js +0 -90
  150. package/dist/lib/imports.d.ts +0 -65
  151. package/dist/lib/imports.js +0 -114
  152. package/dist/lib/meta.d.ts +0 -1191
  153. package/dist/lib/meta.js +0 -434
  154. package/dist/lib/schema/fields.d.ts +0 -46
  155. package/dist/lib/schema/fields.js +0 -62
  156. package/dist/lib/schema/schema.d.ts +0 -466
  157. package/dist/lib/schema/schema.js +0 -18
  158. package/dist/lib/schema/types.d.ts +0 -201
  159. package/dist/lib/schema/types.js +0 -112
  160. package/dist/lib/serializer.d.ts +0 -15
  161. package/dist/lib/serializer.js +0 -24
  162. package/dist/lib/test-id-collector.d.ts +0 -42
  163. package/dist/lib/test-id-collector.js +0 -53
  164. package/dist/lib/types.d.ts +0 -7
  165. package/dist/lib/types.js +0 -13
  166. package/dist/lib/typescript.d.ts +0 -5
  167. package/dist/lib/typescript.js +0 -22
  168. package/dist/lib/utils/ast.d.ts +0 -29
  169. package/dist/lib/utils/ast.js +0 -23
  170. package/dist/lib/utils/error.d.ts +0 -17
  171. package/dist/lib/utils/error.js +0 -52
  172. package/dist/lib/utils/file.d.ts +0 -10
  173. package/dist/lib/utils/file.js +0 -56
  174. package/dist/lib/utils/jsdoc.d.ts +0 -9
  175. package/dist/lib/utils/jsdoc.js +0 -37
  176. package/dist/lib/utils/logger.d.ts +0 -17
  177. package/dist/lib/utils/logger.js +0 -12
  178. package/dist/lib/utils/string.d.ts +0 -40
  179. package/dist/lib/utils/string.js +0 -187
  180. package/dist/lib/utils/types.d.ts +0 -12
  181. package/dist/lib/utils/types.js +0 -2
  182. package/dist/lib/zod.d.ts +0 -8
  183. package/dist/lib/zod.js +0 -60
  184. package/dist/prisma/attributes.d.ts +0 -21
  185. package/dist/prisma/attributes.js +0 -175
  186. package/dist/prisma/client-path.d.ts +0 -7
  187. package/dist/prisma/client-path.js +0 -29
  188. package/dist/prisma/parse.d.ts +0 -12
  189. package/dist/prisma/parse.js +0 -452
package/LICENSE ADDED
@@ -0,0 +1,50 @@
1
+ PostXL Non-Commercial License
2
+
3
+ Copyright (c) 2025 PostXL GmbH
4
+
5
+ NON-COMMERCIAL USE
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to use,
9
+ copy, modify, and distribute the Software for non-commercial purposes only,
10
+ subject to the following conditions:
11
+
12
+ 1. The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ 2. The Software may not be used for commercial purposes without obtaining a
16
+ commercial license from PostXL GmbH.
17
+
18
+ DEFINITION OF NON-COMMERCIAL USE
19
+
20
+ "Non-commercial use" means personal, educational, research, or evaluation use
21
+ where the primary purpose is not to generate revenue or commercial advantage.
22
+
23
+ Non-commercial use includes:
24
+
25
+ - Personal projects and learning
26
+ - Academic research and education
27
+ - Open source projects that are not commercially monetized
28
+ - Evaluation and testing of the Software
29
+
30
+ COMMERCIAL USE
31
+
32
+ For commercial use of this Software, including but not limited to:
33
+
34
+ - Use in products or services sold or licensed to third parties
35
+ - Use in internal business operations that generate revenue
36
+ - Use by for-profit organizations in production environments
37
+
38
+ You must obtain a commercial license from PostXL GmbH.
39
+
40
+ Contact: licensing@postxl.com
41
+
42
+ DISCLAIMER
43
+
44
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,81 @@
1
1
  # PXL Generator
2
2
 
3
- > A utility package that lets you move quickly and generate basic components of your app easily.
3
+ The core package that orchestrates the code generation of a PXL project
4
+
5
+ ## Overview & context
6
+
7
+ Each PXL project is initially generated using two components:
8
+
9
+ - The `ProjectSchema` defines the overall structure of the project, including the models, enums, generators, etc.
10
+ - The `generate` program that uses the `ProjectSchema` to generate the code for the project.
11
+
12
+ The `ProjectSchema` is defined and explained in the [Schema package](../schema/README.md).
13
+
14
+ This package provides the tooling for the `generate` program.
15
+
16
+ ## Composable generators
17
+
18
+ The `generate` program is composed of multiple generators.
19
+
20
+ Here is a simplified example program:
21
+
22
+ ```ts
23
+ import { Generator } from '@postxl/generator'
24
+ import { generateTypes } from '@postxl/generators/types'
25
+ import { registerApiContext, generateApi}* from '@postxl/generators/nestjs-backend'
26
+ import { generateRepositories } from '@postxl/generators/prisma-repositories'
27
+
28
+ import { zProjectSchema } from '@postxl/schema'
29
+
30
+ import { projectSchemaJSON } from './project-schema.json'
31
+
32
+ async function generateProject() {
33
+
34
+ const projectSchema = zProjectSchema.parse(projectSchemaJSON)
35
+
36
+ const generator = new Generator(projectSchema)
37
+ await generator
38
+ // some global generators (API, E2E, CmdK, ?) register some "collectors" in the context,
39
+ // so other generator can store data in these collectors.
40
+ // Upon generation, these global generators use this data to generator the code.
41
+ // Example: E2E Selector collector: frontend generators provide selectors that will be
42
+ // used by the E2E generators to generate the selectors object
43
+ .register(registerApiContext)
44
+
45
+ // these generators will create the relevant files in the virtual file system and provide more context
46
+ // to subsequent generators
47
+ .generate(generateTypes)
48
+ .generate(generateRepositories)
49
+
50
+ // this will generate the API code using the registered collector
51
+ .generate(generateApi)
52
+
53
+ // prettify files, add disclaimer, calculate checksums and write files to disk
54
+ // that have not been ejected
55
+ .flush()
56
+ }
57
+ ```
58
+
59
+ ## Running the generator and updating the result on disk
60
+
61
+ Running the generator will create the code of the project, given the `ProjectSchema` and the generator configs.
62
+
63
+ From there, two things will happen over time:
64
+
65
+ - The models (or generator configs) will change,
66
+ - The (initially generated) code will be manually changed (formerly known as "ejected")
67
+
68
+ In case the file was not manually changed but the model was changed, the generator will
69
+ automatically update the file on disk. In case the file was manually changed and the model
70
+ was not changed, nothing will happen. Only in case the file was manually changed and the model
71
+ was changed, we will need to decide how to handle the conflict. For this, the generator will
72
+ update the existing file in a way that is compatible with a git merge conflict. This way, the
73
+ developer must decide how to resolve the conflict.
74
+
75
+ Under the hood, the above logic is implemented leveraging the "postxl-lock.json" file.
76
+ This file contains the hash values for each generated file. With this hash value, we can determine:
77
+
78
+ - If the file was manually changed: in this case the hash of the current file will be different from
79
+ the hash in the lock file
80
+ - If the file was changed by the latest generator run: in this case the hash of the newly generated
81
+ file will be different from the hash in the lock file
@@ -0,0 +1,59 @@
1
+ import { GeneratorInterfaceId } from './helpers/branded.types';
2
+ import { GeneratorInterface } from './generator.class';
3
+ import { Context } from './generator.context';
4
+ /**
5
+ * The GeneratorManager coordinates the generation process across multiple Generators.
6
+ *
7
+ * Initially in the design/implementation of PostXL generators, this was not required as we could simply chain the generators's `register` and `generate` functions.
8
+ * However, after about 12 generators, we hit TypeScript's recursion limit and thus had to implement this manager.
9
+ *
10
+ * The idea is that individual generators still are fully type-safe and come with proper auto-completion, ie each generator is self-standing and only imports
11
+ * the generators in requires. However, in the projects' main `generate` function. instead of calling the individual generators' `register` and `generate` functions,
12
+ * we now instantiate a `GeneratorManager` and register all generators. The `GeneratorManager` ensures (at generation time) that all dependencies are met and
13
+ * and runs the generators in the correct order.
14
+ *
15
+ * Each Generator is responsible for generating a specific part of the project, such as the frontend, backend, or data.
16
+ *
17
+ * High-level, the generation process is as follows:
18
+ * - The GeneratorManager is instantiated
19
+ * - Generators are registered with the GeneratorManager. In the registration, each Generator provides:
20
+ * - Its own requirements for the context (e.g. `Repositories` generator needs a `Database` generator)
21
+ * - How it extends the context (e.g. `Repositories` generator adds a `Repository` object with detailed definitions to each model in the context)
22
+ * - When a Generator is registered, the GeneratorManager verifies that dependencies are met
23
+ * - The GeneratorManager runs each Generator's `generate` method
24
+ * - The GeneratorManager syncs the generated files to the project
25
+ */
26
+ export declare class GeneratorManager {
27
+ private readonly generators;
28
+ private readonly implementedBy;
29
+ constructor(generator?: GeneratorInterface | GeneratorInterface[]);
30
+ registerTestGenerator(generator: GeneratorInterface, dependencies: Record<GeneratorInterfaceId, GeneratorInterface>): this;
31
+ private registerMockDependencies;
32
+ /**
33
+ * Helper method that registers all provided generators as mock generators,
34
+ * i.e. it will execute the `register` function of the generator, but not
35
+ * the `generate` function.
36
+ */
37
+ registerMockGenerator(generator: GeneratorInterface | GeneratorInterface[]): this;
38
+ /**
39
+ * Helper method that creates a mock of the original generator interface.
40
+ *
41
+ * If only an id is provided, i mocks a blank generator interface with the given id. If a full
42
+ * generator interface is provided, it will mock the same generator interface, i.e.
43
+ * - Same `id`, `implementsInterface` properties
44
+ * - Same `register` function
45
+ * - Blank `generate` function
46
+ * - Blank `requires` property
47
+ *
48
+ * This way, one can provide the interface of a generator without
49
+ * requiring all dependencies or slowing down the generation process.
50
+ */
51
+ static mockGenerator(generator: GeneratorInterface | string): GeneratorInterface;
52
+ registerGenerator(generators: GeneratorInterface | GeneratorInterface[]): this;
53
+ generate(context: Context): Promise<Context>;
54
+ private validateImplementedInterfaces;
55
+ private checkCircularDependencies;
56
+ private getDependencies;
57
+ private topologicalSort;
58
+ private topologicalSortVisit;
59
+ }
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeneratorManager = void 0;
4
+ const utils_1 = require("@postxl/utils");
5
+ const branded_types_1 = require("./helpers/branded.types");
6
+ /**
7
+ * The GeneratorManager coordinates the generation process across multiple Generators.
8
+ *
9
+ * Initially in the design/implementation of PostXL generators, this was not required as we could simply chain the generators's `register` and `generate` functions.
10
+ * However, after about 12 generators, we hit TypeScript's recursion limit and thus had to implement this manager.
11
+ *
12
+ * The idea is that individual generators still are fully type-safe and come with proper auto-completion, ie each generator is self-standing and only imports
13
+ * the generators in requires. However, in the projects' main `generate` function. instead of calling the individual generators' `register` and `generate` functions,
14
+ * we now instantiate a `GeneratorManager` and register all generators. The `GeneratorManager` ensures (at generation time) that all dependencies are met and
15
+ * and runs the generators in the correct order.
16
+ *
17
+ * Each Generator is responsible for generating a specific part of the project, such as the frontend, backend, or data.
18
+ *
19
+ * High-level, the generation process is as follows:
20
+ * - The GeneratorManager is instantiated
21
+ * - Generators are registered with the GeneratorManager. In the registration, each Generator provides:
22
+ * - Its own requirements for the context (e.g. `Repositories` generator needs a `Database` generator)
23
+ * - How it extends the context (e.g. `Repositories` generator adds a `Repository` object with detailed definitions to each model in the context)
24
+ * - When a Generator is registered, the GeneratorManager verifies that dependencies are met
25
+ * - The GeneratorManager runs each Generator's `generate` method
26
+ * - The GeneratorManager syncs the generated files to the project
27
+ */
28
+ class GeneratorManager {
29
+ generators = new Map();
30
+ implementedBy = new Map();
31
+ constructor(generator) {
32
+ if (generator) {
33
+ if (Array.isArray(generator)) {
34
+ for (const g of generator) {
35
+ this.registerGenerator(g);
36
+ }
37
+ return;
38
+ }
39
+ else {
40
+ this.registerGenerator([generator]);
41
+ return;
42
+ }
43
+ }
44
+ }
45
+ registerTestGenerator(generator, dependencies) {
46
+ this.registerGenerator(generator);
47
+ this.registerMockDependencies(generator.requires, dependencies);
48
+ return this;
49
+ }
50
+ registerMockDependencies(requiredInterfaces, dependencies) {
51
+ if (!requiredInterfaces) {
52
+ return;
53
+ }
54
+ for (const requiredInterfaceId of requiredInterfaces) {
55
+ const dependency = dependencies[requiredInterfaceId];
56
+ if (!dependency) {
57
+ throw new Error(`No generator definition provided for ${(0, utils_1.yellow)(requiredInterfaceId)}`);
58
+ }
59
+ this.registerGenerator(GeneratorManager.mockGenerator(dependency));
60
+ this.registerMockDependencies(dependency.requires, dependencies);
61
+ }
62
+ }
63
+ /**
64
+ * Helper method that registers all provided generators as mock generators,
65
+ * i.e. it will execute the `register` function of the generator, but not
66
+ * the `generate` function.
67
+ */
68
+ registerMockGenerator(generator) {
69
+ if (!Array.isArray(generator)) {
70
+ generator = [generator];
71
+ }
72
+ const requiredInterfaces = new Set();
73
+ for (const g of generator) {
74
+ const mockGenerator = GeneratorManager.mockGenerator(g);
75
+ this.registerGenerator(mockGenerator);
76
+ for (const requiredInterfaceId of g.requires ?? []) {
77
+ requiredInterfaces.add(requiredInterfaceId);
78
+ }
79
+ }
80
+ for (const interfaceId of requiredInterfaces) {
81
+ this.registerGenerator(GeneratorManager.mockGenerator(interfaceId));
82
+ }
83
+ return this;
84
+ }
85
+ /**
86
+ * Helper method that creates a mock of the original generator interface.
87
+ *
88
+ * If only an id is provided, i mocks a blank generator interface with the given id. If a full
89
+ * generator interface is provided, it will mock the same generator interface, i.e.
90
+ * - Same `id`, `implementsInterface` properties
91
+ * - Same `register` function
92
+ * - Blank `generate` function
93
+ * - Blank `requires` property
94
+ *
95
+ * This way, one can provide the interface of a generator without
96
+ * requiring all dependencies or slowing down the generation process.
97
+ */
98
+ static mockGenerator(generator) {
99
+ if (typeof generator === 'string') {
100
+ return {
101
+ id: (0, branded_types_1.toGeneratorInterfaceId)(generator),
102
+ requires: [],
103
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
104
+ register: () => { },
105
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
106
+ generate: () => { },
107
+ };
108
+ }
109
+ return {
110
+ id: generator.id,
111
+ requires: generator.requires,
112
+ implementsInterface: generator.implementsInterface,
113
+ register: generator.register,
114
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
115
+ generate: () => { },
116
+ };
117
+ }
118
+ registerGenerator(generators) {
119
+ if (!Array.isArray(generators)) {
120
+ this.registerGenerator([generators]);
121
+ return this;
122
+ }
123
+ for (const generator of generators) {
124
+ if (this.generators.has(generator.id)) {
125
+ // The generator was already registered
126
+ continue;
127
+ }
128
+ this.generators.set(generator.id, generator);
129
+ for (const interfaceId of generator.implementsInterface ?? [generator.id]) {
130
+ if (this.implementedBy.has(interfaceId)) {
131
+ throw new Error(`Interface ${(0, utils_1.yellow)(interfaceId)} implementation is already registered by ${(0, utils_1.yellow)(this.implementedBy.get(interfaceId))}. Double registration (by ${(0, utils_1.yellow)(generator.id)}) is not allowed!`);
132
+ }
133
+ this.implementedBy.set(interfaceId, generator.id);
134
+ }
135
+ }
136
+ return this;
137
+ }
138
+ async generate(context) {
139
+ this.validateImplementedInterfaces();
140
+ this.checkCircularDependencies();
141
+ const generatorsSorted = this.topologicalSort();
142
+ for (const generator of generatorsSorted) {
143
+ // Not all generators extend the context and hence do not need to return it.
144
+ // Therefore, if the generator does not return the context, we use the original context.
145
+ context = (await generator.register(context)) ?? context;
146
+ }
147
+ for (const generator of generatorsSorted) {
148
+ if (!generator.afterRegistrations) {
149
+ continue;
150
+ }
151
+ await generator.afterRegistrations(context);
152
+ }
153
+ for (const generator of generatorsSorted) {
154
+ // Not all generators extend the context and hence do not need to return it.
155
+ // Therefore, if the generator does not return the context, we use the original context.
156
+ context = (await generator.generate(context)) ?? context;
157
+ }
158
+ return context;
159
+ }
160
+ validateImplementedInterfaces() {
161
+ for (const generator of this.generators.values()) {
162
+ for (const requiredInterfaceId of generator.requires ?? []) {
163
+ if (!this.implementedBy.has(requiredInterfaceId)) {
164
+ throw new Error(`Generator ${(0, utils_1.yellow)(generator.id)} requires generator ${(0, utils_1.yellow)(requiredInterfaceId)} but it is not registered`);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ checkCircularDependencies() {
170
+ const dependenciesCache = new Map();
171
+ for (const generatorId of this.generators.keys()) {
172
+ this.getDependencies(generatorId, dependenciesCache);
173
+ }
174
+ }
175
+ getDependencies(generatorId, dependenciesCache, rootGeneratorId) {
176
+ if (dependenciesCache.has(generatorId)) {
177
+ return dependenciesCache.get(generatorId);
178
+ }
179
+ const dependencies = new Set();
180
+ dependenciesCache.set(generatorId, dependencies);
181
+ const generator = this.generators.get(generatorId);
182
+ for (const requiredInterfaceId of generator.requires ?? []) {
183
+ const implementedById = this.implementedBy.get(requiredInterfaceId);
184
+ if (!implementedById) {
185
+ throw new Error(`Generator ${generatorId} is not registered`);
186
+ }
187
+ dependencies.add(implementedById);
188
+ for (const dependency of this.getDependencies(implementedById, dependenciesCache, rootGeneratorId ?? generatorId)) {
189
+ if (dependency === rootGeneratorId) {
190
+ throw new Error(`Circular dependency detected: ${Array.from(dependencies.values()).join(' -> ')} -> ${dependency}`);
191
+ }
192
+ dependencies.add(dependency);
193
+ }
194
+ }
195
+ return dependencies;
196
+ }
197
+ topologicalSort() {
198
+ const visited = new Set();
199
+ const queue = [];
200
+ for (const generator of this.generators.values()) {
201
+ if (!visited.has(generator.id)) {
202
+ this.topologicalSortVisit(generator, visited, queue);
203
+ }
204
+ }
205
+ return queue;
206
+ }
207
+ topologicalSortVisit(generator, visited, queue, level = 0) {
208
+ visited.add(generator.id);
209
+ for (const requiredInterfaceId of generator.requires ?? []) {
210
+ const implementedBy = this.implementedBy.get(requiredInterfaceId);
211
+ const requiredGenerator = this.generators.get(implementedBy);
212
+ if (requiredGenerator) {
213
+ if (!visited.has(requiredGenerator.id)) {
214
+ this.topologicalSortVisit(requiredGenerator, visited, queue, level + 1);
215
+ }
216
+ }
217
+ }
218
+ queue.push(generator);
219
+ }
220
+ }
221
+ exports.GeneratorManager = GeneratorManager;
@@ -0,0 +1,90 @@
1
+ import { GeneratorInterfaceId } from './helpers/branded.types';
2
+ import { Context as BaseContext } from './generator.context';
3
+ /**
4
+ * Generic function definition for a Generator Function:
5
+ *
6
+ * Each Generator Function takes a context (with ModelContext and EnumContext) and returns a new extended context type
7
+ */
8
+ export type GeneratorFunction<ContextRequirements extends BaseContext, ContextResult extends ContextRequirements = ContextRequirements> = (context: ContextRequirements) => Promise<ContextResult> | ContextResult;
9
+ export type GeneratorInterface = {
10
+ /**
11
+ * The id of the generator
12
+ */
13
+ readonly id: GeneratorInterfaceId;
14
+ /**
15
+ * The id(s) of interfaces that this generator implements.
16
+ * By default this is the id of the generator itself and does not need to be set. However, some generators
17
+ * may implement multiple interfaces or implement generic interfaces (e.g. the different database generators).
18
+ * These interfaces are stored in this property.
19
+ */
20
+ readonly implementsInterface?: GeneratorInterfaceId[] | undefined;
21
+ /**
22
+ * The id(s) of interfaces that this generator requires.
23
+ */
24
+ readonly requires?: GeneratorInterfaceId[] | undefined;
25
+ /**
26
+ * The function that registers generator specific properties in the context..
27
+ * Typically, it extends the context object with one additional property with the same name as the generator.
28
+ * Additionally, it may also extend context.models and context.enums with additional properties.
29
+ *
30
+ * The actual function signature is:
31
+ * ```ts
32
+ * register: GeneratorFunction<ContextRequirements, ContextResult>(context: ContextRequirements) => ContextResult
33
+ * ```
34
+ * With the generic types being defined as follows:
35
+ * ```ts
36
+ * export type GeneratorInterface<
37
+ * ContextRequirements extends Generator.Context,
38
+ * ContextResult extends ContextRequirements = ContextRequirements,
39
+ * >
40
+ * ```
41
+ *
42
+ * However, as each implementation of this interface should actually narrows the type of the context (e.g.
43
+ * `register: <
44
+ * ContextRequirements extends WithDatabase<Generator.Context>,
45
+ * ContextResult extends WithRepository<ContextRequirements>
46
+ * >(context: ContextRequirements): ContextResult
47
+ * `), this would not comply to the interface definition. Therefore, we use the non-generic `any` escape hatch here.
48
+ *
49
+ * @param context The (generic) context that fulfills the requirements of the generator
50
+ * @returns The context with the generator specific properties added
51
+ */
52
+ register: (context: any) => Promise<BaseContext | void> | BaseContext | void;
53
+ /**
54
+ * Optional lifecycle function that gets executed after all `register` functions were executed.
55
+ *
56
+ * This can be useful, e.g. when the TRPC generator needs to transfer the module registrations from backend to frontend.
57
+ */
58
+ afterRegistrations?: (context: any) => Promise<BaseContext | void> | BaseContext | void;
59
+ /**
60
+ * The function that generates the actual files/code for this generator.
61
+ *
62
+ * The actual function signature is:
63
+ * ```ts
64
+ * generate: GeneratorFunction<ContextResult>(context: ContextResult) => ContextResult
65
+ * ```
66
+ * See the `register` description for more details, why we use the non-generic `any` escape hatch here.
67
+ */
68
+ generate: (context: any) => Promise<BaseContext | void> | BaseContext | void;
69
+ };
70
+ export declare class Generator<Context extends BaseContext> {
71
+ context: Context;
72
+ constructor(context: Context);
73
+ /**
74
+ * Applies the given generator function to the current context and returns a new generator instance.
75
+ *
76
+ * Example usage:
77
+ * ```ts
78
+ * const generator = new Generator({ schema, vfs })
79
+ * generator
80
+ * .generate(NestJSGenerator.generate)
81
+ * .then((g) => g.generate(NextJSGenerator.generate))
82
+ * .then((g) => g.context.vfs.flush())
83
+ * ```
84
+ */
85
+ generate<ExtendedContext extends Context = Context>(generatorFn: GeneratorFunction<Context, ExtendedContext>): Promise<Generator<ExtendedContext>>;
86
+ /**
87
+ * Alias for `generate` to allow for a more fluent API.
88
+ */
89
+ register<ExtendedContext extends Context = Context>(generatorFn: GeneratorFunction<Context, ExtendedContext>): Promise<Generator<ExtendedContext>>;
90
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Generator = void 0;
4
+ class Generator {
5
+ context;
6
+ constructor(context) {
7
+ this.context = context;
8
+ }
9
+ /**
10
+ * Applies the given generator function to the current context and returns a new generator instance.
11
+ *
12
+ * Example usage:
13
+ * ```ts
14
+ * const generator = new Generator({ schema, vfs })
15
+ * generator
16
+ * .generate(NestJSGenerator.generate)
17
+ * .then((g) => g.generate(NextJSGenerator.generate))
18
+ * .then((g) => g.context.vfs.flush())
19
+ * ```
20
+ */
21
+ async generate(generatorFn) {
22
+ const newContext = await generatorFn(this.context);
23
+ return new Generator(newContext);
24
+ }
25
+ /**
26
+ * Alias for `generate` to allow for a more fluent API.
27
+ */
28
+ async register(generatorFn) {
29
+ return this.generate(generatorFn);
30
+ }
31
+ }
32
+ exports.Generator = Generator;