@vertz/codegen 0.2.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/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # @vertz/codegen
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.
4
+
5
+ Code generation for Vertz applications. Generates TypeScript SDKs, CLIs, and type definitions from your Vertz app's intermediate representation (IR).
6
+
7
+ ## What it does
8
+
9
+ `@vertz/codegen` transforms your Vertz app's compiled IR into:
10
+
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
14
+
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
+ ```
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
+ }
233
+ ```
234
+
235
+ ## Related Packages
236
+
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
240
+
241
+ ## License
242
+
243
+ MIT
@@ -0,0 +1,290 @@
1
+ type GeneratorName = "typescript" | "cli";
2
+ interface CodegenPublishableConfig {
3
+ /** Package name, e.g., '@myapp/sdk' */
4
+ name: string;
5
+ /** Output directory for the package */
6
+ outputDir: string;
7
+ /** Package version. Default: '0.0.0' */
8
+ version?: string;
9
+ }
10
+ interface CodegenTypescriptConfig {
11
+ /** Generate schema re-exports. Default: true */
12
+ schemas?: boolean;
13
+ /** SDK client function name. Default: 'createClient' */
14
+ clientName?: string;
15
+ /** Generate as publishable npm package. Default: false */
16
+ publishable?: CodegenPublishableConfig;
17
+ /** Augmentable types for customer-specific type narrowing */
18
+ augmentableTypes?: string[];
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
+ interface CodegenConfig {
31
+ /** Generators to run. Default: ['typescript'] */
32
+ generators: GeneratorName[];
33
+ /** Output directory. Default: '.vertz/generated' */
34
+ outputDir?: string;
35
+ /** Whether to format output with Biome. Defaults to true. */
36
+ format?: boolean;
37
+ /** Whether to use incremental regeneration (only write changed files). Defaults to true. */
38
+ incremental?: boolean;
39
+ /** TypeScript SDK options */
40
+ typescript?: CodegenTypescriptConfig;
41
+ /** CLI options */
42
+ cli?: CodegenCLIConfig;
43
+ }
44
+ interface ResolvedCodegenConfig {
45
+ generators: GeneratorName[];
46
+ outputDir: string;
47
+ format?: boolean;
48
+ incremental?: boolean;
49
+ typescript?: CodegenTypescriptConfig;
50
+ cli?: CodegenCLIConfig;
51
+ }
52
+ declare function defineCodegenConfig(config: CodegenConfig): CodegenConfig;
53
+ declare function resolveCodegenConfig(config?: CodegenConfig): ResolvedCodegenConfig;
54
+ declare function validateCodegenConfig(config: CodegenConfig): string[];
55
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
56
+ type JsonSchema = Record<string, unknown>;
57
+ interface CodegenIR {
58
+ basePath: string;
59
+ version?: string;
60
+ modules: CodegenModule[];
61
+ schemas: CodegenSchema[];
62
+ auth: CodegenAuth;
63
+ }
64
+ interface CodegenModule {
65
+ name: string;
66
+ operations: CodegenOperation[];
67
+ }
68
+ interface CodegenOperation {
69
+ operationId: string;
70
+ method: HttpMethod;
71
+ path: string;
72
+ description?: string;
73
+ tags: string[];
74
+ params?: JsonSchema;
75
+ query?: JsonSchema;
76
+ body?: JsonSchema;
77
+ headers?: JsonSchema;
78
+ response?: JsonSchema;
79
+ streaming?: StreamingConfig;
80
+ schemaRefs: OperationSchemaRefs;
81
+ auth?: OperationAuth;
82
+ }
83
+ interface StreamingConfig {
84
+ format: "sse" | "ndjson";
85
+ eventSchema?: JsonSchema;
86
+ }
87
+ interface OperationAuth {
88
+ required: boolean;
89
+ schemes: string[];
90
+ }
91
+ interface OperationSchemaRefs {
92
+ params?: string;
93
+ query?: string;
94
+ body?: string;
95
+ headers?: string;
96
+ response?: string;
97
+ }
98
+ interface CodegenAuth {
99
+ schemes: CodegenAuthScheme[];
100
+ }
101
+ type CodegenAuthScheme = {
102
+ type: "bearer";
103
+ name: string;
104
+ description?: string;
105
+ } | {
106
+ type: "basic";
107
+ name: string;
108
+ description?: string;
109
+ } | {
110
+ type: "apiKey";
111
+ name: string;
112
+ in: "header" | "query" | "cookie";
113
+ paramName: string;
114
+ description?: string;
115
+ } | {
116
+ type: "oauth2";
117
+ name: string;
118
+ flows: OAuthFlows;
119
+ description?: string;
120
+ };
121
+ interface OAuthFlows {
122
+ authorizationCode?: {
123
+ authorizationUrl: string;
124
+ tokenUrl: string;
125
+ scopes: Record<string, string>;
126
+ };
127
+ clientCredentials?: {
128
+ tokenUrl: string;
129
+ scopes: Record<string, string>;
130
+ };
131
+ deviceCode?: {
132
+ deviceAuthorizationUrl: string;
133
+ tokenUrl: string;
134
+ scopes: Record<string, string>;
135
+ };
136
+ }
137
+ interface CodegenSchema {
138
+ name: string;
139
+ jsonSchema: JsonSchema;
140
+ annotations: SchemaAnnotations;
141
+ }
142
+ interface SchemaAnnotations {
143
+ description?: string;
144
+ deprecated?: boolean;
145
+ brand?: string;
146
+ namingParts: SchemaNamingParts;
147
+ }
148
+ interface SchemaNamingParts {
149
+ operation?: string;
150
+ entity?: string;
151
+ part?: string;
152
+ }
153
+ interface Generator {
154
+ readonly name: string;
155
+ generate(ir: CodegenIR, config: GeneratorConfig): GeneratedFile[];
156
+ }
157
+ interface GeneratedFile {
158
+ path: string;
159
+ content: string;
160
+ }
161
+ interface GeneratorConfig {
162
+ outputDir: string;
163
+ options: Record<string, unknown>;
164
+ }
165
+ interface Import {
166
+ from: string;
167
+ name: string;
168
+ isType: boolean;
169
+ alias?: string;
170
+ }
171
+ interface FileFragment {
172
+ content: string;
173
+ imports: Import[];
174
+ }
175
+ /**
176
+ * Format generated files using Biome.
177
+ *
178
+ * Writes files to a temp directory with a standalone biome.json config,
179
+ * runs `biome format --write --config-path <tempDir>`,
180
+ * reads them back, and cleans up.
181
+ */
182
+ declare function formatWithBiome(files: GeneratedFile[]): Promise<GeneratedFile[]>;
183
+ import { AppIR } from "@vertz/compiler";
184
+ interface IncrementalResult {
185
+ /** Files that were written (new or changed). */
186
+ written: string[];
187
+ /** Files that were skipped (content unchanged). */
188
+ skipped: string[];
189
+ /** Files that were removed (stale, only in clean mode). */
190
+ removed: string[];
191
+ }
192
+ interface IncrementalOptions {
193
+ /** If true, remove files in outputDir that are not in the generated set. */
194
+ clean?: boolean;
195
+ }
196
+ /**
197
+ * Write generated files to disk incrementally:
198
+ * - Only writes files whose content has changed (or are new).
199
+ * - Optionally removes stale files that are no longer generated.
200
+ */
201
+ declare function writeIncremental(files: GeneratedFile[], outputDir: string, options?: IncrementalOptions): Promise<IncrementalResult>;
202
+ interface GenerateResult {
203
+ /** The files that were generated (paths relative to outputDir). */
204
+ files: GeneratedFile[];
205
+ /** The CodegenIR that was derived from the AppIR. */
206
+ ir: CodegenIR;
207
+ /** Number of files generated. */
208
+ fileCount: number;
209
+ /** Which generators were run. */
210
+ generators: string[];
211
+ /** Incremental write stats (only present when incremental mode is used). */
212
+ incremental?: IncrementalResult;
213
+ }
214
+ /**
215
+ * Top-level orchestrator that ties together the full codegen pipeline:
216
+ * 1. Converts AppIR to CodegenIR via the IR adapter
217
+ * 2. Runs configured generators to produce GeneratedFile[]
218
+ * 3. Optionally formats output with Biome
219
+ * 4. Writes files to disk (incrementally when enabled)
220
+ */
221
+ 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;
261
+ /**
262
+ * Returns a SHA-256 hex hash of the given content string.
263
+ * Used for comparing generated file content against what is already on disk.
264
+ */
265
+ declare function hashContent(content: string): string;
266
+ import { AppIR as AppIR2 } from "@vertz/compiler";
267
+ declare function adaptIR(appIR: AppIR2): CodegenIR;
268
+ interface ConversionContext {
269
+ namedTypes: Map<string, string>;
270
+ resolving: Set<string>;
271
+ }
272
+ interface ConversionResult {
273
+ type: string;
274
+ extractedTypes: Map<string, string>;
275
+ }
276
+ declare function jsonSchemaToTS(schema: JsonSchema, ctx?: ConversionContext): ConversionResult;
277
+ interface CodegenPipeline {
278
+ validate(config: CodegenConfig): string[];
279
+ generate(ir: CodegenIR, config: CodegenConfig): GenerateResult;
280
+ resolveOutputDir(config: CodegenConfig): string;
281
+ resolveConfig(config: CodegenConfig): ReturnType<typeof resolveCodegenConfig>;
282
+ }
283
+ declare function createCodegenPipeline(): CodegenPipeline;
284
+ declare function mergeImports(imports: Import[]): Import[];
285
+ declare function renderImports(imports: Import[]): string;
286
+ declare function toPascalCase(input: string): string;
287
+ declare function toCamelCase(input: string): string;
288
+ declare function toKebabCase(input: string): string;
289
+ 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 };