@postxl/generator 0.74.2 → 1.0.1
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/LICENSE +50 -0
- package/README.md +79 -1
- package/dist/generator-manager.class.d.ts +59 -0
- package/dist/generator-manager.class.js +221 -0
- package/dist/generator.class.d.ts +90 -0
- package/dist/generator.class.js +32 -0
- package/dist/generator.context.d.ts +174 -0
- package/dist/generator.context.js +125 -0
- package/dist/helpers/branded.types.d.ts +149 -0
- package/dist/helpers/branded.types.js +111 -0
- package/dist/helpers/config-builder.class.d.ts +27 -0
- package/dist/helpers/config-builder.class.js +54 -0
- package/dist/helpers/import-generator.class.d.ts +70 -0
- package/dist/helpers/import-generator.class.js +166 -0
- package/dist/helpers/importable.types.d.ts +52 -0
- package/dist/helpers/importable.types.js +15 -0
- package/dist/helpers/index-generator.class.d.ts +10 -0
- package/dist/helpers/index-generator.class.js +46 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.js +24 -0
- package/dist/helpers/package-json.generator.d.ts +56 -0
- package/dist/helpers/package-json.generator.js +36 -0
- package/dist/helpers/tsconfig.generator.d.ts +1 -0
- package/dist/helpers/tsconfig.generator.js +14 -0
- package/dist/helpers/verify-context.d.ts +4 -0
- package/dist/helpers/verify-context.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/utils/checksum.d.ts +10 -0
- package/dist/utils/checksum.js +132 -0
- package/dist/utils/fs-utils.d.ts +34 -0
- package/dist/utils/fs-utils.js +126 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/jsdoc.d.ts +12 -0
- package/dist/utils/jsdoc.js +37 -0
- package/dist/utils/lint.d.ts +46 -0
- package/dist/utils/lint.js +154 -0
- package/dist/utils/lockfile.d.ts +7 -0
- package/dist/utils/lockfile.js +80 -0
- package/dist/utils/logger.class.d.ts +25 -0
- package/dist/utils/logger.class.js +55 -0
- package/dist/utils/merge-conflict.d.ts +55 -0
- package/dist/utils/merge-conflict.js +264 -0
- package/dist/utils/path.d.ts +52 -0
- package/dist/utils/path.js +183 -0
- package/dist/utils/prettier-config.d.ts +2 -0
- package/dist/utils/prettier-config.js +13 -0
- package/dist/utils/prettier.d.ts +5 -0
- package/dist/utils/prettier.js +67 -0
- package/dist/utils/prettier.skiptest.d.ts +1 -0
- package/dist/utils/prettier.skiptest.js +22 -0
- package/dist/utils/promise.d.ts +2 -0
- package/dist/utils/promise.js +10 -0
- package/dist/utils/string-functions.d.ts +9 -0
- package/dist/utils/string-functions.js +23 -0
- package/dist/utils/sync-log-result.d.ts +9 -0
- package/dist/utils/sync-log-result.js +90 -0
- package/dist/utils/sync.d.ts +143 -0
- package/dist/utils/sync.js +325 -0
- package/dist/utils/template.d.ts +66 -0
- package/dist/utils/template.js +159 -0
- package/dist/utils/vfs.class.d.ts +115 -0
- package/dist/utils/vfs.class.js +239 -0
- package/dist/utils/zip.d.ts +13 -0
- package/dist/utils/zip.js +40 -0
- package/package.json +57 -34
- package/dist/generator.d.ts +0 -13
- package/dist/generator.js +0 -455
- package/dist/generators/enums/react.generator.d.ts +0 -10
- package/dist/generators/enums/react.generator.js +0 -110
- package/dist/generators/enums/types.generator.d.ts +0 -10
- package/dist/generators/enums/types.generator.js +0 -39
- package/dist/generators/indices/data/module.generator.d.ts +0 -9
- package/dist/generators/indices/data/module.generator.js +0 -60
- package/dist/generators/indices/data/service.generator.d.ts +0 -9
- package/dist/generators/indices/data/service.generator.js +0 -249
- package/dist/generators/indices/data/types.generator.d.ts +0 -9
- package/dist/generators/indices/data/types.generator.js +0 -49
- package/dist/generators/indices/dispatcher-service.generator.d.ts +0 -9
- package/dist/generators/indices/dispatcher-service.generator.js +0 -107
- package/dist/generators/indices/export/class.generator.d.ts +0 -9
- package/dist/generators/indices/export/class.generator.js +0 -140
- package/dist/generators/indices/export/encoder.generator.d.ts +0 -9
- package/dist/generators/indices/export/encoder.generator.js +0 -50
- package/dist/generators/indices/import/convert-functions.generator.d.ts +0 -9
- package/dist/generators/indices/import/convert-functions.generator.js +0 -509
- package/dist/generators/indices/import/decoder.generator.d.ts +0 -9
- package/dist/generators/indices/import/decoder.generator.js +0 -40
- package/dist/generators/indices/import/service.generator.d.ts +0 -9
- package/dist/generators/indices/import/service.generator.js +0 -573
- package/dist/generators/indices/import/types.generator.d.ts +0 -9
- package/dist/generators/indices/import/types.generator.js +0 -242
- package/dist/generators/indices/repositories.generator.d.ts +0 -9
- package/dist/generators/indices/repositories.generator.js +0 -25
- package/dist/generators/indices/routes.generator.d.ts +0 -9
- package/dist/generators/indices/routes.generator.js +0 -29
- package/dist/generators/indices/seed-migration.generator.d.ts +0 -9
- package/dist/generators/indices/seed-migration.generator.js +0 -36
- package/dist/generators/indices/seed-template.generator.d.ts +0 -9
- package/dist/generators/indices/seed-template.generator.js +0 -80
- package/dist/generators/indices/testids.generator.d.ts +0 -7
- package/dist/generators/indices/testids.generator.js +0 -71
- package/dist/generators/indices/types.generator.d.ts +0 -10
- package/dist/generators/indices/types.generator.js +0 -35
- package/dist/generators/indices/update/actiontypes.generator.d.ts +0 -9
- package/dist/generators/indices/update/actiontypes.generator.js +0 -49
- package/dist/generators/indices/update/module.generator.d.ts +0 -9
- package/dist/generators/indices/update/module.generator.js +0 -41
- package/dist/generators/indices/update/service.generator.d.ts +0 -9
- package/dist/generators/indices/update/service.generator.js +0 -34
- package/dist/generators/indices/view/module.generator.d.ts +0 -9
- package/dist/generators/indices/view/module.generator.js +0 -39
- package/dist/generators/indices/view/service.generator.d.ts +0 -9
- package/dist/generators/indices/view/service.generator.js +0 -34
- package/dist/generators/models/admin.page.generator.d.ts +0 -7
- package/dist/generators/models/admin.page.generator.js +0 -74
- package/dist/generators/models/export/encoder.generator.d.ts +0 -9
- package/dist/generators/models/export/encoder.generator.js +0 -51
- package/dist/generators/models/import/decoder.generator.d.ts +0 -9
- package/dist/generators/models/import/decoder.generator.js +0 -148
- package/dist/generators/models/react/context.generator.d.ts +0 -9
- package/dist/generators/models/react/context.generator.js +0 -71
- package/dist/generators/models/react/index.d.ts +0 -10
- package/dist/generators/models/react/index.js +0 -31
- package/dist/generators/models/react/library.generator.d.ts +0 -10
- package/dist/generators/models/react/library.generator.js +0 -94
- package/dist/generators/models/react/lookup.generator.d.ts +0 -9
- package/dist/generators/models/react/lookup.generator.js +0 -175
- package/dist/generators/models/react/modals.generator.d.ts +0 -23
- package/dist/generators/models/react/modals.generator.js +0 -710
- package/dist/generators/models/repository.generator.d.ts +0 -9
- package/dist/generators/models/repository.generator.js +0 -955
- package/dist/generators/models/route.generator.d.ts +0 -9
- package/dist/generators/models/route.generator.js +0 -92
- package/dist/generators/models/seed.generator.d.ts +0 -21
- package/dist/generators/models/seed.generator.js +0 -285
- package/dist/generators/models/stub.generator.d.ts +0 -9
- package/dist/generators/models/stub.generator.js +0 -92
- package/dist/generators/models/types.generator.d.ts +0 -9
- package/dist/generators/models/types.generator.js +0 -125
- package/dist/generators/models/update/service.generator.d.ts +0 -10
- package/dist/generators/models/update/service.generator.js +0 -302
- package/dist/generators/models/view/service.generator.d.ts +0 -10
- package/dist/generators/models/view/service.generator.js +0 -239
- package/dist/lib/attributes.d.ts +0 -114
- package/dist/lib/attributes.js +0 -2
- package/dist/lib/exports.d.ts +0 -45
- package/dist/lib/exports.js +0 -90
- package/dist/lib/imports.d.ts +0 -65
- package/dist/lib/imports.js +0 -114
- package/dist/lib/meta.d.ts +0 -1191
- package/dist/lib/meta.js +0 -434
- package/dist/lib/schema/fields.d.ts +0 -46
- package/dist/lib/schema/fields.js +0 -62
- package/dist/lib/schema/schema.d.ts +0 -466
- package/dist/lib/schema/schema.js +0 -18
- package/dist/lib/schema/types.d.ts +0 -201
- package/dist/lib/schema/types.js +0 -112
- package/dist/lib/serializer.d.ts +0 -15
- package/dist/lib/serializer.js +0 -24
- package/dist/lib/test-id-collector.d.ts +0 -42
- package/dist/lib/test-id-collector.js +0 -53
- package/dist/lib/types.d.ts +0 -7
- package/dist/lib/types.js +0 -13
- package/dist/lib/typescript.d.ts +0 -5
- package/dist/lib/typescript.js +0 -22
- package/dist/lib/utils/ast.d.ts +0 -29
- package/dist/lib/utils/ast.js +0 -23
- package/dist/lib/utils/error.d.ts +0 -17
- package/dist/lib/utils/error.js +0 -52
- package/dist/lib/utils/file.d.ts +0 -10
- package/dist/lib/utils/file.js +0 -56
- package/dist/lib/utils/jsdoc.d.ts +0 -9
- package/dist/lib/utils/jsdoc.js +0 -37
- package/dist/lib/utils/logger.d.ts +0 -17
- package/dist/lib/utils/logger.js +0 -12
- package/dist/lib/utils/string.d.ts +0 -40
- package/dist/lib/utils/string.js +0 -187
- package/dist/lib/utils/types.d.ts +0 -12
- package/dist/lib/utils/types.js +0 -2
- package/dist/lib/zod.d.ts +0 -8
- package/dist/lib/zod.js +0 -60
- package/dist/prisma/attributes.d.ts +0 -21
- package/dist/prisma/attributes.js +0 -175
- package/dist/prisma/client-path.d.ts +0 -7
- package/dist/prisma/client-path.js +0 -29
- package/dist/prisma/parse.d.ts +0 -12
- 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
|
-
|
|
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;
|