@vertz/codegen 0.2.0 → 0.2.4

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/README.md CHANGED
@@ -1,242 +1,31 @@
1
1
  # @vertz/codegen
2
2
 
3
- > ⚠️ **Internal package** — This package is an implementation detail of the Vertz framework. It is published for use by other `@vertz/*` packages. No API stability is guaranteed between versions.
3
+ > **Internal package** — You don't use this directly. It powers `vertz codegen` behind the scenes.
4
4
 
5
- Code generation for Vertz applications. Generates TypeScript SDKs, CLIs, and type definitions from your Vertz app's intermediate representation (IR).
5
+ Generates TypeScript SDKs, CLI clients, and type definitions from your Vertz app's compiled intermediate representation (IR). The generated code is fully type-safe — input/output types, schema types, and streaming event types are all preserved.
6
6
 
7
- ## What it does
7
+ ## Who uses this
8
8
 
9
- `@vertz/codegen` transforms your Vertz app's compiled IR into:
9
+ - **`@vertz/cli`** The `vertz codegen` command invokes this package.
10
+ - **Framework contributors** — See [INTERNALS.md](./INTERNALS.md) for generator architecture, custom generators, and the IR adapter.
10
11
 
11
- - **TypeScript SDK** Type-safe client with generated methods for every route
12
- - **CLI** — Command-line interface with auto-generated commands
13
- - **Type definitions** — Input/output types for operations, schemas, and streaming events
12
+ ## How it fits in
14
13
 
15
- The codegen runs automatically during compilation (via `@vertz/compiler`) but can also be used standalone for custom generation workflows.
16
-
17
- ## When it's used
18
-
19
- **Typical workflow:**
20
-
21
- 1. You define routes, schemas, and modules in your Vertz app
22
- 2. The Vertz compiler analyzes your code and produces an AppIR
23
- 3. `@vertz/codegen` transforms the AppIR into SDK/CLI code
24
- 4. Generated code is written to `.vertz/generated/` (or your custom output dir)
25
-
26
- **Manual usage:**
27
-
28
- If you need custom generation (e.g., generating code from an external API schema), you can use the codegen API directly:
29
-
30
- ```typescript
31
- import { generate } from '@vertz/codegen';
32
- import type { AppIR } from '@vertz/compiler';
33
-
34
- const result = await generate(appIR, {
35
- generators: ['typescript', 'cli'],
36
- outputDir: './generated',
37
- typescript: {
38
- clientName: 'createMyClient',
39
- schemas: true,
40
- },
41
- cli: {
42
- enabled: true,
43
- },
44
- });
45
-
46
- console.log(`Generated ${result.fileCount} files`);
47
14
  ```
48
-
49
- ## Configuration
50
-
51
- Use `defineCodegenConfig()` to configure code generation in your Vertz app:
52
-
53
- ```typescript
54
- import { defineCodegenConfig } from '@vertz/codegen';
55
-
56
- export default defineCodegenConfig({
57
- generators: ['typescript', 'cli'],
58
- outputDir: '.vertz/generated',
59
- format: true, // Format with Biome (default: true)
60
- incremental: true, // Only write changed files (default: true)
61
-
62
- typescript: {
63
- clientName: 'createClient', // SDK function name
64
- schemas: true, // Re-export schemas (default: true)
65
- publishable: {
66
- name: '@myapp/sdk',
67
- outputDir: './packages/sdk',
68
- version: '1.0.0',
69
- },
70
- },
71
-
72
- cli: {
73
- enabled: true,
74
- publishable: {
75
- name: '@myapp/cli',
76
- binName: 'myapp',
77
- outputDir: './packages/cli',
78
- version: '1.0.0',
79
- },
80
- },
81
- });
82
- ```
83
-
84
- ## Public API
85
-
86
- ### Configuration
87
-
88
- - **`defineCodegenConfig(config)`** — Define code generation config with type safety
89
- - **`resolveCodegenConfig(config?)`** — Resolve config with defaults
90
- - **`validateCodegenConfig(config)`** — Validate config and return errors
91
-
92
- ### Generation
93
-
94
- - **`generate(appIR, config)`** — Generate code from AppIR
95
- - Returns `GenerateResult` with file list, IR, and stats
96
- - **`createCodegenPipeline()`** — Create reusable generation pipeline
97
- - Supports custom generators and incremental regeneration
98
-
99
- ### TypeScript Generator
100
-
101
- Emit SDK components:
102
-
103
- - **`emitClientFile(ir)`** — Main SDK client file
104
- - **`emitModuleFile(module)`** — Module-specific client code
105
- - **`emitOperationMethod(op)`** — Individual operation methods
106
- - **`emitStreamingMethod(op)`** — Streaming operation methods
107
- - **`emitAuthStrategyBuilder(auth)`** — Auth strategy builders
108
- - **`emitSDKConfig(ir)`** — SDK configuration types
109
-
110
- Emit type definitions:
111
-
112
- - **`emitModuleTypesFile(module, schemas)`** — Module types
113
- - **`emitSharedTypesFile(schemas)`** — Shared types
114
- - **`emitOperationInputType(op)`** — Input types
115
- - **`emitOperationResponseType(op)`** — Response types
116
- - **`emitStreamingEventType(op)`** — Streaming event types
117
- - **`emitInterfaceFromSchema(schema)`** — Schema → TypeScript interface
118
-
119
- Emit package structure:
120
-
121
- - **`emitBarrelIndex(modules)`** — Barrel index file
122
- - **`emitSchemaReExports(schemas)`** — Schema re-exports
123
- - **`emitPackageJson(options)`** — package.json for publishable SDK
124
-
125
- ### CLI Generator
126
-
127
- - **`emitManifestFile(ir)`** — CLI manifest (command definitions)
128
- - **`emitCommandDefinition(op)`** — Command definition from operation
129
- - **`emitModuleCommands(module)`** — Module commands
130
- - **`emitBinEntryPoint(options)`** — Executable entry point
131
- - **`scaffoldCLIPackageJson(options)`** — package.json for publishable CLI
132
- - **`scaffoldCLIRootIndex()`** — CLI root index file
133
-
134
- ### Utilities
135
-
136
- - **`adaptIR(appIR)`** — Transform AppIR → CodegenIR
137
- - **`jsonSchemaToTS(schema)`** — Convert JSON Schema → TypeScript
138
- - **`hashContent(content)`** — Content-based hashing for incremental generation
139
- - **`writeIncremental(files, outputDir, options)`** — Write only changed files
140
- - **`formatWithBiome(code)`** — Format generated code
141
-
142
- Naming utilities:
143
-
144
- - **`toPascalCase(str)`** — `hello_world` → `HelloWorld`
145
- - **`toCamelCase(str)`** — `hello_world` → `helloWorld`
146
- - **`toKebabCase(str)`** — `HelloWorld` → `hello-world`
147
- - **`toSnakeCase(str)`** — `HelloWorld` → `hello_world`
148
-
149
- Import management:
150
-
151
- - **`mergeImports(imports)`** — Merge import statements
152
- - **`renderImports(imports)`** — Render imports as code
153
-
154
- ## Custom Generators
155
-
156
- To create a custom generator, implement the `Generator` interface:
157
-
158
- ```typescript
159
- import type { Generator, CodegenIR, GeneratedFile } from '@vertz/codegen';
160
-
161
- const myGenerator: Generator = {
162
- name: 'my-generator',
163
- run(ir: CodegenIR): GeneratedFile[] {
164
- return [
165
- {
166
- path: 'output.txt',
167
- content: `Generated from ${ir.modules.length} modules`,
168
- },
169
- ];
170
- },
171
- };
172
-
173
- // Use in pipeline
174
- import { createCodegenPipeline } from '@vertz/codegen';
175
-
176
- const pipeline = createCodegenPipeline();
177
- pipeline.addGenerator(myGenerator);
178
- const result = await pipeline.run(appIR, config);
179
- ```
180
-
181
- ## Incremental Regeneration
182
-
183
- By default, codegen uses incremental mode to avoid rewriting unchanged files:
184
-
185
- ```typescript
186
- const result = await generate(appIR, {
187
- incremental: true, // default
188
- outputDir: './generated',
189
- });
190
-
191
- console.log(result.incremental?.stats);
192
- // {
193
- // written: 3, // Files written (changed)
194
- // skipped: 12, // Files skipped (unchanged)
195
- // deleted: 1, // Stale files removed
196
- // }
197
- ```
198
-
199
- This improves performance by:
200
-
201
- - Skipping file writes when content is identical (preserves timestamps)
202
- - Avoiding unnecessary TypeScript recompilation
203
- - Detecting and removing stale generated files
204
-
205
- ## Type Safety
206
-
207
- All generated code is fully type-safe. The codegen preserves:
208
-
209
- - Input/output types from your routes
210
- - Schema types from `@vertz/schema`
211
- - Generic type parameters (e.g., auth context, streaming events)
212
- - Discriminated unions for operation responses
213
-
214
- Example generated SDK usage:
215
-
216
- ```typescript
217
- import { createClient } from './.vertz/generated';
218
-
219
- const client = createClient({ baseURL: 'https://api.example.com' });
220
-
221
- // Fully typed
222
- const user = await client.users.getUser({ id: '123' });
223
- // ^? { id: string; name: string; email: string }
224
-
225
- // Streaming with typed events
226
- const stream = client.events.subscribe();
227
- for await (const event of stream) {
228
- if (event.type === 'user.created') {
229
- console.log(event.data.user.name);
230
- // ^? string
231
- }
232
- }
15
+ Your Vertz app (*.ts)
16
+
17
+ @vertz/compiler → AppIR (intermediate representation)
18
+
19
+ @vertz/codegen → Generated SDK, CLI, types
20
+
21
+ .vertz/generated/
233
22
  ```
234
23
 
235
24
  ## Related Packages
236
25
 
237
- - **[@vertz/compiler](../compiler)**Analyzes Vertz apps and produces AppIR
238
- - **[@vertz/cli](../cli)** — Provides `vertz codegen` command
239
- - **[@vertz/cli-runtime](../cli-runtime)** — Runtime for generated CLIs
26
+ - [`@vertz/compiler`](../compiler) — Produces the IR that codegen consumes
27
+ - [`@vertz/cli`](../cli) — Provides the `vertz codegen` command
28
+ - [`@vertz/cli-runtime`](../cli-runtime) — Runtime for generated CLIs
240
29
 
241
30
  ## License
242
31
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type GeneratorName = "typescript" | "cli";
1
+ type GeneratorName = "typescript";
2
2
  interface CodegenPublishableConfig {
3
3
  /** Package name, e.g., '@myapp/sdk' */
4
4
  name: string;
@@ -17,16 +17,6 @@ interface CodegenTypescriptConfig {
17
17
  /** Augmentable types for customer-specific type narrowing */
18
18
  augmentableTypes?: string[];
19
19
  }
20
- interface CodegenCLIPublishableConfig extends CodegenPublishableConfig {
21
- /** CLI binary name, e.g., 'myapp' */
22
- binName: string;
23
- }
24
- interface CodegenCLIConfig {
25
- /** Include in generation. Default: false */
26
- enabled?: boolean;
27
- /** Generate as publishable npm package. Default: false */
28
- publishable?: CodegenCLIPublishableConfig;
29
- }
30
20
  interface CodegenConfig {
31
21
  /** Generators to run. Default: ['typescript'] */
32
22
  generators: GeneratorName[];
@@ -38,8 +28,6 @@ interface CodegenConfig {
38
28
  incremental?: boolean;
39
29
  /** TypeScript SDK options */
40
30
  typescript?: CodegenTypescriptConfig;
41
- /** CLI options */
42
- cli?: CodegenCLIConfig;
43
31
  }
44
32
  interface ResolvedCodegenConfig {
45
33
  generators: GeneratorName[];
@@ -47,7 +35,6 @@ interface ResolvedCodegenConfig {
47
35
  format?: boolean;
48
36
  incremental?: boolean;
49
37
  typescript?: CodegenTypescriptConfig;
50
- cli?: CodegenCLIConfig;
51
38
  }
52
39
  declare function defineCodegenConfig(config: CodegenConfig): CodegenConfig;
53
40
  declare function resolveCodegenConfig(config?: CodegenConfig): ResolvedCodegenConfig;
@@ -59,6 +46,7 @@ interface CodegenIR {
59
46
  version?: string;
60
47
  modules: CodegenModule[];
61
48
  schemas: CodegenSchema[];
49
+ entities: CodegenEntityModule[];
62
50
  auth: CodegenAuth;
63
51
  }
64
52
  interface CodegenModule {
@@ -150,6 +138,38 @@ interface SchemaNamingParts {
150
138
  entity?: string;
151
139
  part?: string;
152
140
  }
141
+ interface CodegenEntityModule {
142
+ entityName: string;
143
+ operations: CodegenEntityOperation[];
144
+ actions: CodegenEntityAction[];
145
+ }
146
+ interface CodegenEntityOperation {
147
+ kind: "list" | "get" | "create" | "update" | "delete";
148
+ method: string;
149
+ path: string;
150
+ operationId: string;
151
+ inputSchema?: string;
152
+ outputSchema?: string;
153
+ resolvedFields?: CodegenResolvedField[];
154
+ responseFields?: CodegenResolvedField[];
155
+ }
156
+ /** Structured field info for schema generation. */
157
+ interface CodegenResolvedField {
158
+ name: string;
159
+ tsType: "string" | "number" | "boolean" | "date" | "unknown";
160
+ optional: boolean;
161
+ }
162
+ interface CodegenEntityAction {
163
+ name: string;
164
+ method: string;
165
+ operationId: string;
166
+ path: string;
167
+ hasId: boolean;
168
+ inputSchema?: string;
169
+ outputSchema?: string;
170
+ resolvedInputFields?: CodegenResolvedField[];
171
+ resolvedOutputFields?: CodegenResolvedField[];
172
+ }
153
173
  interface Generator {
154
174
  readonly name: string;
155
175
  generate(ir: CodegenIR, config: GeneratorConfig): GeneratedFile[];
@@ -212,6 +232,12 @@ interface GenerateResult {
212
232
  incremental?: IncrementalResult;
213
233
  }
214
234
  /**
235
+ * Merges the `imports` field from the generated package.json into the
236
+ * project's root package.json. This enables `#generated` and
237
+ * `#generated/types` subpath imports that enforce the public API surface.
238
+ */
239
+ declare function mergeImportsToPackageJson(files: GeneratedFile[], outputDir: string): Promise<boolean>;
240
+ /**
215
241
  * Top-level orchestrator that ties together the full codegen pipeline:
216
242
  * 1. Converts AppIR to CodegenIR via the IR adapter
217
243
  * 2. Runs configured generators to produce GeneratedFile[]
@@ -219,45 +245,34 @@ interface GenerateResult {
219
245
  * 4. Writes files to disk (incrementally when enabled)
220
246
  */
221
247
  declare function generate(appIR: AppIR, config: ResolvedCodegenConfig): Promise<GenerateResult>;
222
- declare function emitCommandDefinition(op: CodegenOperation): string;
223
- declare function emitModuleCommands(module: CodegenModule): string;
224
- declare function emitManifestFile(ir: CodegenIR): GeneratedFile;
225
- interface BinEntryPointOptions {
226
- cliName: string;
227
- cliVersion: string;
228
- }
229
- declare function emitBinEntryPoint(options: BinEntryPointOptions): GeneratedFile;
230
- interface CLIPackageOptions {
231
- packageName: string;
232
- packageVersion?: string;
233
- cliName: string;
234
- }
235
- declare function scaffoldCLIPackageJson(options: CLIPackageOptions): GeneratedFile;
236
- declare function scaffoldCLIRootIndex(): GeneratedFile;
237
- declare function emitSDKConfig(auth: CodegenAuth): FileFragment;
238
- declare function emitAuthStrategyBuilder(auth: CodegenAuth): FileFragment;
239
- declare function emitOperationMethod(op: CodegenOperation): FileFragment;
240
- declare function emitStreamingMethod(op: CodegenOperation): FileFragment;
241
- declare function emitModuleFile(module: CodegenModule): GeneratedFile;
242
- declare function emitClientFile(ir: CodegenIR): GeneratedFile;
243
- declare function emitSchemaReExports(schemas: CodegenSchema[]): GeneratedFile;
244
- interface PackageOptions {
245
- packageName: string;
246
- packageVersion?: string;
247
- }
248
- declare function emitBarrelIndex(ir: CodegenIR): GeneratedFile;
249
- declare function emitPackageJson(ir: CodegenIR, options: PackageOptions): GeneratedFile;
250
- declare function emitInterfaceFromSchema(schema: CodegenSchema): FileFragment;
251
- declare function emitOperationInputType(op: CodegenOperation): FileFragment;
252
- declare function emitOperationResponseType(op: CodegenOperation): FileFragment;
253
- declare function emitStreamingEventType(op: CodegenOperation): FileFragment;
254
- declare function emitModuleTypesFile(module: CodegenModule, schemas: CodegenSchema[]): GeneratedFile;
255
- declare function emitSharedTypesFile(schemas: CodegenSchema[]): GeneratedFile;
256
- /**
257
- * Emits a route map interface that maps route keys (e.g., 'GET /users/:id')
258
- * to their input/output types for type-safe test app usage.
259
- */
260
- declare function emitRouteMapType(ir: CodegenIR): GeneratedFile;
248
+ declare class ClientGenerator implements Generator {
249
+ readonly name = "client";
250
+ generate(ir: CodegenIR, config: GeneratorConfig): GeneratedFile[];
251
+ private generateClient;
252
+ private generatePackageJson;
253
+ private generateReadme;
254
+ private getEntityMethods;
255
+ }
256
+ declare class EntitySchemaGenerator implements Generator {
257
+ readonly name = "entity-schema";
258
+ generate(ir: CodegenIR, _config: GeneratorConfig): GeneratedFile[];
259
+ private generateEntitySchema;
260
+ private generateIndex;
261
+ }
262
+ declare class EntitySdkGenerator implements Generator {
263
+ readonly name = "entity-sdk";
264
+ generate(ir: CodegenIR, _config: GeneratorConfig): GeneratedFile[];
265
+ private generateEntitySdk;
266
+ private generateIndex;
267
+ }
268
+ declare class EntityTypesGenerator implements Generator {
269
+ readonly name = "entity-types";
270
+ generate(ir: CodegenIR, _config: GeneratorConfig): GeneratedFile[];
271
+ private generateEntityTypes;
272
+ private emitBodyType;
273
+ private emitResponseType;
274
+ private generateIndex;
275
+ }
261
276
  /**
262
277
  * Returns a SHA-256 hex hash of the given content string.
263
278
  * Used for comparing generated file content against what is already on disk.
@@ -287,4 +302,4 @@ declare function toPascalCase(input: string): string;
287
302
  declare function toCamelCase(input: string): string;
288
303
  declare function toKebabCase(input: string): string;
289
304
  declare function toSnakeCase(input: string): string;
290
- export { writeIncremental, validateCodegenConfig, toSnakeCase, toPascalCase, toKebabCase, toCamelCase, scaffoldCLIRootIndex, scaffoldCLIPackageJson, resolveCodegenConfig, renderImports, mergeImports, jsonSchemaToTS, hashContent, generate, formatWithBiome, emitStreamingMethod, emitStreamingEventType, emitSharedTypesFile, emitSchemaReExports, emitSDKConfig, emitRouteMapType, emitPackageJson, emitOperationResponseType, emitOperationMethod, emitOperationInputType, emitModuleTypesFile, emitModuleFile, emitModuleCommands, emitManifestFile, emitInterfaceFromSchema, emitCommandDefinition, emitClientFile, emitBinEntryPoint, emitBarrelIndex, emitAuthStrategyBuilder, defineCodegenConfig, createCodegenPipeline, adaptIR, StreamingConfig, SchemaNamingParts, SchemaAnnotations, ResolvedCodegenConfig, PackageOptions, OperationSchemaRefs, OperationAuth, OAuthFlows, JsonSchema, IncrementalResult, IncrementalOptions, Import, HttpMethod, GeneratorName, GeneratorConfig, Generator, GeneratedFile, GenerateResult, FileFragment, ConversionResult, ConversionContext, CodegenTypescriptConfig, CodegenSchema, CodegenPublishableConfig, CodegenPipeline, CodegenOperation, CodegenModule, CodegenIR, CodegenConfig, CodegenCLIPublishableConfig, CodegenCLIConfig, CodegenAuthScheme, CodegenAuth, CLIPackageOptions, BinEntryPointOptions };
305
+ export { writeIncremental, validateCodegenConfig, toSnakeCase, toPascalCase, toKebabCase, toCamelCase, resolveCodegenConfig, renderImports, mergeImportsToPackageJson, mergeImports, jsonSchemaToTS, hashContent, generate, formatWithBiome, defineCodegenConfig, createCodegenPipeline, adaptIR, StreamingConfig, SchemaNamingParts, SchemaAnnotations, ResolvedCodegenConfig, OperationSchemaRefs, OperationAuth, OAuthFlows, JsonSchema, IncrementalResult, IncrementalOptions, Import, HttpMethod, GeneratorName, GeneratorConfig, Generator, GeneratedFile, GenerateResult, FileFragment, EntityTypesGenerator, EntitySdkGenerator, EntitySchemaGenerator, ConversionResult, ConversionContext, CodegenTypescriptConfig, CodegenSchema, CodegenResolvedField, CodegenPublishableConfig, CodegenPipeline, CodegenOperation, CodegenModule, CodegenIR, CodegenEntityOperation, CodegenEntityModule, CodegenEntityAction, CodegenConfig, CodegenAuthScheme, CodegenAuth, ClientGenerator };