@apso/cli 0.18.0 → 0.19.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.
- package/dist/commands/generate.js +28 -8
- package/dist/commands/mcp/serve.js +15 -0
- package/dist/lib/apsorc-parser.d.ts +7 -0
- package/dist/lib/apsorc-parser.js +4 -0
- package/dist/lib/generators/typescript.js +2 -1
- package/dist/lib/templates/rest/module-rest.eta +4 -5
- package/dist/lib/types/entity.d.ts +21 -0
- package/dist/lib/types/generator.d.ts +10 -0
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -10,10 +10,11 @@ const perf_hooks_1 = require("perf_hooks");
|
|
|
10
10
|
const file_system_1 = require("../lib/utils/file-system");
|
|
11
11
|
class Generate extends base_command_1.default {
|
|
12
12
|
async run() {
|
|
13
|
+
var _a, _b;
|
|
13
14
|
const { flags } = await this.parse(Generate);
|
|
14
15
|
const skipFormat = flags["skip-format"];
|
|
15
16
|
const totalBuildStart = perf_hooks_1.performance.now();
|
|
16
|
-
const { rootFolder, entities, relationshipMap, apiType, auth, emitEvents, language: configLanguage } = (0, lib_1.parseApsorc)();
|
|
17
|
+
const { rootFolder, entities, relationshipMap, apiType, auth, emitEvents, http, language: configLanguage } = (0, lib_1.parseApsorc)();
|
|
17
18
|
// Resolve language: flag > .apsorc > prompt
|
|
18
19
|
let language;
|
|
19
20
|
if (flags.language) {
|
|
@@ -64,6 +65,7 @@ class Generate extends base_command_1.default {
|
|
|
64
65
|
relationshipMap,
|
|
65
66
|
auth,
|
|
66
67
|
emitEvents,
|
|
68
|
+
http,
|
|
67
69
|
};
|
|
68
70
|
const generator = (0, lib_1.createGenerator)(generatorConfig);
|
|
69
71
|
const validationResult = generator.validateConfig(generatorConfig);
|
|
@@ -94,8 +96,21 @@ class Generate extends base_command_1.default {
|
|
|
94
96
|
console.log(`[apso] Building... ${entity.name}`);
|
|
95
97
|
const entityBuildStart = perf_hooks_1.performance.now();
|
|
96
98
|
const entityRelationships = relationshipMap[entity.name] || [];
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
+
// Effective HTTP-controller flag (TypeScript REST only for now): entity
|
|
100
|
+
// overrides the top-level default, which defaults to true. When false, the
|
|
101
|
+
// controller is neither generated nor wired into the module (a hand-written
|
|
102
|
+
// controller owns the route). GraphQL uses resolvers, so the flag doesn't
|
|
103
|
+
// apply there. Python (FastAPI) and Go (Gin) wire routers differently and
|
|
104
|
+
// don't yet honor this flag — keep it a no-op for them (controllers always
|
|
105
|
+
// generated) so we never emit broken wiring (a skipped router still
|
|
106
|
+
// referenced by the index module). Tracked separately for Python/Go.
|
|
107
|
+
const includeController = language === "typescript" && lowerCaseApiType === "rest"
|
|
108
|
+
? ((_b = (_a = entity.http) !== null && _a !== void 0 ? _a : http) !== null && _b !== void 0 ? _b : true)
|
|
109
|
+
: true;
|
|
110
|
+
if (!includeController) {
|
|
111
|
+
console.log(`[apso] ${entity.name}: http=false — skipping generated controller`);
|
|
112
|
+
}
|
|
113
|
+
const generationTasks = [
|
|
99
114
|
generator.generateEntity({
|
|
100
115
|
entity,
|
|
101
116
|
relationships: entityRelationships,
|
|
@@ -115,20 +130,25 @@ class Generate extends base_command_1.default {
|
|
|
115
130
|
apiType: lowerCaseApiType,
|
|
116
131
|
relationshipMap,
|
|
117
132
|
}),
|
|
118
|
-
generator.
|
|
133
|
+
generator.generateModule({
|
|
119
134
|
entity,
|
|
120
135
|
relationships: entityRelationships,
|
|
121
136
|
allEntities: entities,
|
|
122
137
|
apiType: lowerCaseApiType,
|
|
123
|
-
|
|
138
|
+
includeController,
|
|
124
139
|
}),
|
|
125
|
-
|
|
140
|
+
];
|
|
141
|
+
if (includeController) {
|
|
142
|
+
generationTasks.push(generator.generateController({
|
|
126
143
|
entity,
|
|
127
144
|
relationships: entityRelationships,
|
|
128
145
|
allEntities: entities,
|
|
129
146
|
apiType: lowerCaseApiType,
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
relationshipMap,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
// eslint-disable-next-line no-await-in-loop
|
|
151
|
+
const allFiles = await Promise.all(generationTasks);
|
|
132
152
|
// eslint-disable-next-line no-await-in-loop
|
|
133
153
|
for (const files of allFiles) {
|
|
134
154
|
for (const file of files) {
|
|
@@ -924,6 +924,7 @@ const INLINE_SCHEMA_REFERENCE = `# Schema Quick Reference
|
|
|
924
924
|
"primaryKeyType": "serial",
|
|
925
925
|
"scopeBy": "organizationId",
|
|
926
926
|
"emitEvents": true,
|
|
927
|
+
"http": false,
|
|
927
928
|
"fields": [
|
|
928
929
|
{ "name": "fieldName", "type": "text", "length": 100 }
|
|
929
930
|
],
|
|
@@ -946,6 +947,20 @@ manifest at \`autogen/events/event-emitting.entities.ts\` exporting
|
|
|
946
947
|
\`@apso/domain-events\` library and is wired by the \`domain-events\` skill; the
|
|
947
948
|
CLI no longer generates the engine code.
|
|
948
949
|
|
|
950
|
+
## HTTP controllers (http)
|
|
951
|
+
Controllers are generated by default. Set \`http: false\` on an entity (or a
|
|
952
|
+
top-level \`http: false\` default) to NOT generate/mount its HTTP controller while
|
|
953
|
+
still generating the entity, service, DTOs, and module (\`TypeOrmModule.forFeature\`
|
|
954
|
+
+ the service). Use this when a hand-written controller owns the route for that
|
|
955
|
+
entity (e.g. a custom/Stripe-shaped surface). Effective value is
|
|
956
|
+
\`entity.http ?? <top-level http> ?? true\`.
|
|
957
|
+
|
|
958
|
+
Per framework: NestJS and Gin (Go) actually suppress the controller — those
|
|
959
|
+
frameworks can't cleanly override a generated route. For FastAPI it's a no-op:
|
|
960
|
+
override by registering your router ahead of the generated \`autogen_router\`
|
|
961
|
+
(FastAPI matches in registration order). Currently implemented for NestJS; Go
|
|
962
|
+
is tracked separately.
|
|
963
|
+
|
|
949
964
|
## Relationship Types
|
|
950
965
|
- OneToMany: parent has many children
|
|
951
966
|
- ManyToOne: child belongs to parent
|
|
@@ -20,6 +20,11 @@ export type ApsorcType = {
|
|
|
20
20
|
* its own `emitEvents: false`.
|
|
21
21
|
*/
|
|
22
22
|
emitEvents?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Top-level default for HTTP controller generation. When false, no entity
|
|
25
|
+
* gets a generated controller unless it opts back in with `http: true`.
|
|
26
|
+
*/
|
|
27
|
+
http?: boolean;
|
|
23
28
|
};
|
|
24
29
|
type ParsedApsorcData = {
|
|
25
30
|
entities: Entity[];
|
|
@@ -34,6 +39,8 @@ type ParsedApsorc = {
|
|
|
34
39
|
language?: TargetLanguage;
|
|
35
40
|
/** Top-level default for the DomainEvent ("emitEvents") feature. */
|
|
36
41
|
emitEvents?: boolean;
|
|
42
|
+
/** Top-level default for HTTP controller generation. */
|
|
43
|
+
http?: boolean;
|
|
37
44
|
};
|
|
38
45
|
export declare const parseApsorcV1: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
39
46
|
export declare const parseApsorcV2: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
@@ -37,6 +37,7 @@ const parseRc = () => {
|
|
|
37
37
|
const auth = apsoConfig.auth;
|
|
38
38
|
const language = apsoConfig.language;
|
|
39
39
|
const emitEvents = apsoConfig.emitEvents;
|
|
40
|
+
const http = apsoConfig.http;
|
|
40
41
|
return {
|
|
41
42
|
rootFolder,
|
|
42
43
|
apiType,
|
|
@@ -46,6 +47,7 @@ const parseRc = () => {
|
|
|
46
47
|
auth,
|
|
47
48
|
language,
|
|
48
49
|
emitEvents,
|
|
50
|
+
http,
|
|
49
51
|
};
|
|
50
52
|
};
|
|
51
53
|
const parseApsorc = () => {
|
|
@@ -66,6 +68,7 @@ const parseApsorc = () => {
|
|
|
66
68
|
auth: apsoConfig.auth,
|
|
67
69
|
language: apsoConfig.language,
|
|
68
70
|
emitEvents: apsoConfig.emitEvents,
|
|
71
|
+
http: apsoConfig.http,
|
|
69
72
|
...(0, exports.parseApsorcV1)(apsoConfig),
|
|
70
73
|
};
|
|
71
74
|
if (debug) {
|
|
@@ -83,6 +86,7 @@ const parseApsorc = () => {
|
|
|
83
86
|
auth: apsoConfig.auth,
|
|
84
87
|
language: apsoConfig.language,
|
|
85
88
|
emitEvents: apsoConfig.emitEvents,
|
|
89
|
+
http: apsoConfig.http,
|
|
86
90
|
...(() => {
|
|
87
91
|
const relStart = perf_hooks_1.performance.now();
|
|
88
92
|
const parsed = (0, exports.parseApsorcV2)(apsoConfig);
|
|
@@ -249,7 +249,7 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
|
|
|
249
249
|
];
|
|
250
250
|
}
|
|
251
251
|
async generateModule(options) {
|
|
252
|
-
const { entity, apiType } = options;
|
|
252
|
+
const { entity, apiType, includeController = true } = options;
|
|
253
253
|
const { name: entityName } = entity;
|
|
254
254
|
const moduleName = `${entityName}Module`;
|
|
255
255
|
const svcName = `${entityName}Service`;
|
|
@@ -261,6 +261,7 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
|
|
|
261
261
|
ctrlName,
|
|
262
262
|
resolverName,
|
|
263
263
|
entityName,
|
|
264
|
+
includeController,
|
|
264
265
|
};
|
|
265
266
|
const templatePath = apiType === "graphql"
|
|
266
267
|
? "./graphql/gql-module-graphql"
|
|
@@ -4,13 +4,12 @@ import { Module } from "@nestjs/common";
|
|
|
4
4
|
import {<%= it.entityName %>} from './<%= it.entityName%>.entity'
|
|
5
5
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
6
6
|
import {<%= it.svcName %>} from "./<%= it.entityName %>.service";
|
|
7
|
-
import {<%= it.ctrlName %>} from "./<%= it.entityName %>.controller";
|
|
8
|
-
|
|
7
|
+
<% if (it.includeController) { %>import {<%= it.ctrlName %>} from "./<%= it.entityName %>.controller";
|
|
8
|
+
<% } %>
|
|
9
9
|
@Module({
|
|
10
10
|
imports: [TypeOrmModule.forFeature([<%= it.entityName %>])],
|
|
11
11
|
providers: [<%= it.svcName %>],
|
|
12
12
|
exports: [<%= it.svcName %>],
|
|
13
|
-
controllers: [<%= it.ctrlName %>],
|
|
14
|
-
})
|
|
13
|
+
<% if (it.includeController) { %> controllers: [<%= it.ctrlName %>],
|
|
14
|
+
<% } %>})
|
|
15
15
|
export class <%= it.moduleName %> {}
|
|
16
|
-
|
|
@@ -55,5 +55,26 @@ export interface Entity {
|
|
|
55
55
|
* an individual entity can opt out with `emitEvents: false`.
|
|
56
56
|
*/
|
|
57
57
|
emitEvents?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether to generate the HTTP controller for this entity. Defaults to true.
|
|
60
|
+
* When false, the generator still emits the entity, service, DTOs, and a module
|
|
61
|
+
* (with `TypeOrmModule.forFeature` + the service), but omits the controller —
|
|
62
|
+
* leaving the HTTP surface to a hand-written controller without a route
|
|
63
|
+
* collision with the generated CRUD.
|
|
64
|
+
*
|
|
65
|
+
* Per-framework behavior (the flag is generic, but only matters where a
|
|
66
|
+
* generated route can't be cleanly overridden):
|
|
67
|
+
* - **NestJS** and **Gin (Go)**: actually suppresses the controller/route —
|
|
68
|
+
* these frameworks can't override a generated route (Nest controllers aren't
|
|
69
|
+
* DI-swappable; Gin panics on duplicate paths). Implemented for NestJS;
|
|
70
|
+
* Go is tracked separately.
|
|
71
|
+
* - **FastAPI**: effectively a **no-op** — FastAPI matches routes in
|
|
72
|
+
* registration order, so an app overrides by registering its router ahead of
|
|
73
|
+
* the generated `autogen_router` (no suppression needed).
|
|
74
|
+
*
|
|
75
|
+
* Resolution: the effective value is `entity.http ?? <global http> ?? true`, so
|
|
76
|
+
* controllers are on by default and opted out per entity (or globally).
|
|
77
|
+
*/
|
|
78
|
+
http?: boolean;
|
|
58
79
|
associations?: Association[];
|
|
59
80
|
}
|
|
@@ -109,6 +109,11 @@ export interface GeneratorConfig {
|
|
|
109
109
|
* Effective per-entity value is `entity.emitEvents ?? emitEvents ?? false`.
|
|
110
110
|
*/
|
|
111
111
|
emitEvents?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Top-level default for HTTP controller generation.
|
|
114
|
+
* Effective per-entity value is `entity.http ?? http ?? true`.
|
|
115
|
+
*/
|
|
116
|
+
http?: boolean;
|
|
112
117
|
/**
|
|
113
118
|
* Language-specific configuration options
|
|
114
119
|
*/
|
|
@@ -134,6 +139,11 @@ export interface EntityGenerationOptions {
|
|
|
134
139
|
* API type being generated
|
|
135
140
|
*/
|
|
136
141
|
apiType: string;
|
|
142
|
+
/**
|
|
143
|
+
* Whether the entity's HTTP controller is generated/wired. Defaults to true.
|
|
144
|
+
* When false, the module omits the controller (see Entity.http).
|
|
145
|
+
*/
|
|
146
|
+
includeController?: boolean;
|
|
137
147
|
}
|
|
138
148
|
/**
|
|
139
149
|
* Options for generating controllers/routers
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@apso/cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.19.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@electric-sql/pglite": "^0.2.17",
|
package/oclif.manifest.json
CHANGED