@vertz/codegen 0.2.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/config.ts
2
- var VALID_GENERATORS = new Set(["typescript", "cli"]);
2
+ var VALID_GENERATORS = new Set(["typescript"]);
3
3
  function defineCodegenConfig(config) {
4
4
  return config;
5
5
  }
@@ -9,8 +9,7 @@ function resolveCodegenConfig(config) {
9
9
  outputDir: config?.outputDir ?? ".vertz/generated",
10
10
  format: config?.format,
11
11
  incremental: config?.incremental,
12
- typescript: config?.typescript,
13
- cli: config?.cli
12
+ typescript: config?.typescript
14
13
  };
15
14
  }
16
15
  function validateCodegenConfig(config) {
@@ -32,18 +31,6 @@ function validateCodegenConfig(config) {
32
31
  errors.push("codegen.typescript.publishable.outputDir is required");
33
32
  }
34
33
  }
35
- if (config.cli?.publishable) {
36
- const pub = config.cli.publishable;
37
- if (!pub.name) {
38
- errors.push("codegen.cli.publishable.name is required");
39
- }
40
- if (!pub.outputDir) {
41
- errors.push("codegen.cli.publishable.outputDir is required");
42
- }
43
- if (!pub.binName) {
44
- errors.push("codegen.cli.publishable.binName is required");
45
- }
46
- }
47
34
  return errors;
48
35
  }
49
36
  // src/format.ts
@@ -128,8 +115,11 @@ async function formatWithBiome(files) {
128
115
  }
129
116
  }
130
117
  // src/generate.ts
131
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
132
- import { dirname as dirname3, join as join3 } from "node:path";
118
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
119
+ import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
120
+
121
+ // src/generators/client-generator.ts
122
+ import { posix } from "node:path";
133
123
 
134
124
  // src/utils/naming.ts
135
125
  function splitWords(input) {
@@ -149,890 +139,472 @@ function toSnakeCase(input) {
149
139
  return splitWords(input).map((w) => w.toLowerCase()).join("_");
150
140
  }
151
141
 
152
- // src/generators/typescript/emit-cli.ts
142
+ // src/generators/client-generator.ts
153
143
  var FILE_HEADER = `// Generated by @vertz/codegen — do not edit
154
- `;
155
- function mapJsonSchemaTypeToCLI(type) {
156
- if (type === "number" || type === "integer")
157
- return "number";
158
- if (type === "boolean")
159
- return "boolean";
160
- return "string";
161
- }
162
- function emitFieldDefinitions(schema, indent) {
163
- const properties = schema.properties;
164
- if (!properties)
165
- return [];
166
- const required = schema.required ?? [];
167
- const lines = [];
168
- for (const [key, propSchema] of Object.entries(properties)) {
169
- const cliType = mapJsonSchemaTypeToCLI(propSchema.type);
170
- const isRequired = required.includes(key);
171
- const parts = [];
172
- parts.push(`type: '${cliType}'`);
173
- if (propSchema.description) {
174
- parts.push(`description: '${propSchema.description}'`);
175
- }
176
- parts.push(`required: ${isRequired}`);
177
- if (propSchema.enum) {
178
- const enumValues = propSchema.enum.map((v) => `'${v}'`).join(", ");
179
- parts.push(`enum: [${enumValues}]`);
180
- }
181
- lines.push(`${indent} ${key}: { ${parts.join(", ")} },`);
182
- }
183
- return lines;
184
- }
185
- function emitCommandDefinition(op) {
186
- const lines = [];
187
- lines.push("{");
188
- lines.push(` method: '${op.method}',`);
189
- lines.push(` path: '${op.path}',`);
190
- lines.push(` description: '${op.description ?? op.operationId}',`);
191
- if (op.params) {
192
- const fields = emitFieldDefinitions(op.params, " ");
193
- if (fields.length > 0) {
194
- lines.push(" params: {");
195
- lines.push(...fields);
196
- lines.push(" },");
197
- }
198
- }
199
- if (op.query) {
200
- const fields = emitFieldDefinitions(op.query, " ");
201
- if (fields.length > 0) {
202
- lines.push(" query: {");
203
- lines.push(...fields);
204
- lines.push(" },");
205
- }
206
- }
207
- if (op.body) {
208
- const fields = emitFieldDefinitions(op.body, " ");
209
- if (fields.length > 0) {
210
- lines.push(" body: {");
211
- lines.push(...fields);
212
- lines.push(" },");
213
- }
214
- }
215
- lines.push("}");
216
- return lines.join(`
217
- `);
218
- }
219
- function emitModuleCommands(module) {
220
- if (module.operations.length === 0) {
221
- return "{}";
222
- }
223
- const entries = [];
224
- for (const op of module.operations) {
225
- const commandName = toKebabCase(op.operationId);
226
- const def = emitCommandDefinition(op);
227
- const indented = def.split(`
228
- `).map((line, i) => i === 0 ? line : ` ${line}`).join(`
229
- `);
230
- entries.push(` '${commandName}': ${indented},`);
231
- }
232
- return `{
233
- ${entries.join(`
234
- `)}
235
- }`;
236
- }
237
- function emitManifestFile(ir) {
238
- const sections = [FILE_HEADER];
239
- sections.push("import type { CommandManifest } from '@vertz/cli-runtime';");
240
- sections.push("");
241
- if (ir.modules.length === 0) {
242
- sections.push("export const commands: CommandManifest = {};");
243
- } else {
244
- const namespaceEntries = [];
245
- for (const mod of ir.modules) {
246
- const cmds = emitModuleCommands(mod);
247
- const indented = cmds.split(`
248
- `).map((line, i) => i === 0 ? line : ` ${line}`).join(`
249
- `);
250
- namespaceEntries.push(` ${mod.name}: ${indented},`);
251
- }
252
- sections.push(`export const commands: CommandManifest = {
253
- ${namespaceEntries.join(`
254
- `)}
255
- };`);
256
- }
257
- return {
258
- path: "cli/manifest.ts",
259
- content: sections.join(`
260
- `)
261
- };
262
- }
263
- function emitBinEntryPoint(options) {
264
- const lines = [];
265
- lines.push("#!/usr/bin/env node");
266
- lines.push(FILE_HEADER);
267
- lines.push("import { createCLI } from '@vertz/cli-runtime';");
268
- lines.push("import { commands } from './manifest';");
269
- lines.push("");
270
- lines.push("const cli = createCLI({");
271
- lines.push(` name: '${options.cliName}',`);
272
- lines.push(` version: '${options.cliVersion}',`);
273
- lines.push(" commands,");
274
- lines.push("});");
275
- lines.push("");
276
- lines.push("cli.run(process.argv.slice(2));");
277
- return {
278
- path: "cli/bin.ts",
279
- content: lines.join(`
280
- `)
281
- };
282
- }
283
- function scaffoldCLIPackageJson(options) {
284
- const pkg = {
285
- name: options.packageName,
286
- version: options.packageVersion ?? "0.0.0",
287
- description: `CLI for ${options.cliName} — generated by @vertz/codegen`,
288
- private: true,
289
- bin: {
290
- [options.cliName]: "./cli/bin.ts"
291
- },
292
- dependencies: {
293
- "@vertz/cli-runtime": "*",
294
- "@vertz/fetch": "*"
295
- }
296
- };
297
- return {
298
- path: "package.json",
299
- content: JSON.stringify(pkg, null, 2)
300
- };
301
- }
302
- function scaffoldCLIRootIndex() {
303
- const lines = [FILE_HEADER];
304
- lines.push("export { commands } from './cli/manifest';");
305
- return {
306
- path: "index.ts",
307
- content: lines.join(`
308
- `)
309
- };
310
- }
311
-
312
- // src/utils/imports.ts
313
- function mergeImports(imports) {
314
- const seen = new Map;
315
- for (const imp of imports) {
316
- const key = `${imp.from}::${imp.name}::${imp.isType}::${imp.alias ?? ""}`;
317
- if (!seen.has(key)) {
318
- seen.set(key, imp);
319
- }
320
- }
321
- return [...seen.values()].sort((a, b) => {
322
- const fromCmp = a.from.localeCompare(b.from);
323
- if (fromCmp !== 0)
324
- return fromCmp;
325
- return a.name.localeCompare(b.name);
326
- });
327
- }
328
- function renderImports(imports) {
329
- const grouped = new Map;
330
- for (const imp of imports) {
331
- let group = grouped.get(imp.from);
332
- if (!group) {
333
- group = { types: [], values: [] };
334
- grouped.set(imp.from, group);
335
- }
336
- const nameStr = imp.alias ? `${imp.name} as ${imp.alias}` : imp.name;
337
- if (imp.isType) {
338
- group.types.push(nameStr);
339
- } else {
340
- group.values.push(nameStr);
341
- }
342
- }
343
- const lines = [];
344
- for (const [from, group] of grouped) {
345
- if (group.types.length > 0) {
346
- lines.push(`import type { ${group.types.join(", ")} } from '${from}';`);
347
- }
348
- if (group.values.length > 0) {
349
- lines.push(`import { ${group.values.join(", ")} } from '${from}';`);
350
- }
351
- }
352
- return lines.join(`
353
- `);
354
- }
355
144
 
356
- // src/generators/typescript/emit-client.ts
357
- var FILE_HEADER2 = `// Generated by @vertz/codegen — do not edit
358
145
  `;
359
- function emitSDKConfig(auth) {
360
- const imports = [{ from: "@vertz/fetch", name: "FetchClientConfig", isType: true }];
361
- const fields = [];
362
- for (const scheme of auth.schemes) {
363
- if (scheme.type === "bearer") {
364
- fields.push(` /** Bearer token or function returning a token. */
365
- token?: string | (() => string | Promise<string>);`);
366
- } else if (scheme.type === "apiKey") {
367
- fields.push(` /** API key or function returning a key. */
368
- apiKey?: string | (() => string | Promise<string>);`);
369
- }
370
- }
371
- if (fields.length === 0) {
372
- return {
373
- content: "export interface SDKConfig extends FetchClientConfig {}",
374
- imports
375
- };
376
- }
377
- const content = `export interface SDKConfig extends FetchClientConfig {
378
- ${fields.join(`
379
- `)}
380
- }`;
381
- return { content, imports };
382
- }
383
- function emitAuthStrategyBuilder(auth) {
384
- const imports = [{ from: "@vertz/fetch", name: "AuthStrategy", isType: true }];
385
- const lines = [];
386
- lines.push("const authStrategies: AuthStrategy[] = [...(config.authStrategies ?? [])];");
387
- for (const scheme of auth.schemes) {
388
- if (scheme.type === "bearer") {
389
- lines.push(`if (config.token) {
390
- authStrategies.push({ type: 'bearer', token: config.token });
391
- }`);
392
- } else if (scheme.type === "apiKey") {
393
- lines.push(`if (config.apiKey) {
394
- authStrategies.push({ type: 'apiKey', key: config.apiKey, location: '${scheme.in}', name: '${scheme.paramName}' });
395
- }`);
396
- }
397
- }
398
- return { content: lines.join(`
399
- `), imports };
400
- }
401
- function buildPathExpression(path) {
402
- if (!path.includes(":")) {
403
- return `'${path}'`;
404
- }
405
- const interpolated = path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "${input.params.$1}");
406
- return `\`${interpolated}\``;
407
- }
408
- function buildRequestOptions(op) {
409
- const opts = [];
410
- if (op.query) {
411
- opts.push("query: input?.query");
412
- }
413
- if (op.body) {
414
- opts.push("body: input.body");
415
- }
416
- if (op.headers) {
417
- opts.push("headers: input?.headers");
418
- }
419
- if (opts.length === 0) {
420
- return "";
421
- }
422
- return `, { ${opts.join(", ")} }`;
423
- }
424
- function hasOperationInput(op) {
425
- return !!(op.params || op.query || op.body || op.headers);
426
- }
427
- function isInputOptional(op) {
428
- return !op.params && !op.body;
429
- }
430
- function emitOperationMethod(op) {
431
- const imports = [];
432
- const methodName = toCamelCase(op.operationId);
433
- const inputTypeName = `${toPascalCase(op.operationId)}Input`;
434
- const responseTypeName = `${toPascalCase(op.operationId)}Response`;
435
- const hasInput = hasOperationInput(op);
436
- if (hasInput) {
437
- const optional = isInputOptional(op);
438
- const inputParam = optional ? `input?: ${inputTypeName}` : `input: ${inputTypeName}`;
439
- imports.push({ from: "../types", name: inputTypeName, isType: true });
440
- imports.push({ from: "../types", name: responseTypeName, isType: true });
441
- const pathExpr2 = buildPathExpression(op.path);
442
- const reqOpts = buildRequestOptions(op);
443
- const content2 = `${methodName}(${inputParam}): Promise<SDKResult<${responseTypeName}>> {
444
- return client.request('${op.method}', ${pathExpr2}${reqOpts});
445
- }`;
446
- return { content: content2, imports };
447
- }
448
- imports.push({ from: "../types", name: responseTypeName, isType: true });
449
- const pathExpr = buildPathExpression(op.path);
450
- const content = `${methodName}(): Promise<SDKResult<${responseTypeName}>> {
451
- return client.request('${op.method}', ${pathExpr});
452
- }`;
453
- return { content, imports };
454
- }
455
- function emitStreamingMethod(op) {
456
- const imports = [];
457
- const methodName = toCamelCase(op.operationId);
458
- const inputTypeName = `${toPascalCase(op.operationId)}Input`;
459
- const eventTypeName = `${toPascalCase(op.operationId)}Event`;
460
- const format = op.streaming?.format ?? "sse";
461
- imports.push({ from: "../types", name: eventTypeName, isType: true });
462
- const hasInput = hasOperationInput(op);
463
- const pathExpr = buildPathExpression(op.path);
464
- const streamOpts = [];
465
- streamOpts.push(`method: '${op.method}'`);
466
- streamOpts.push(`path: ${pathExpr}`);
467
- if (op.query) {
468
- streamOpts.push("query: input?.query");
469
- }
470
- streamOpts.push(`format: '${format}'`);
471
- const optsStr = streamOpts.join(`,
472
- `);
473
- if (hasInput) {
474
- const optional = isInputOptional(op);
475
- const inputParam = optional ? `input?: ${inputTypeName}` : `input: ${inputTypeName}`;
476
- imports.push({ from: "../types", name: inputTypeName, isType: true });
477
- const content2 = `async *${methodName}(${inputParam}): AsyncGenerator<${eventTypeName}> {
478
- yield* client.requestStream<${eventTypeName}>({
479
- ${optsStr},
480
- });
481
- }`;
482
- return { content: content2, imports };
483
- }
484
- const content = `async *${methodName}(): AsyncGenerator<${eventTypeName}> {
485
- yield* client.requestStream<${eventTypeName}>({
486
- ${optsStr},
487
- });
488
- }`;
489
- return { content, imports };
490
- }
491
- function emitModuleFile(module) {
492
- const fragments = [];
493
- for (const op of module.operations) {
494
- if (op.streaming) {
495
- fragments.push(emitStreamingMethod(op));
496
- } else {
497
- fragments.push(emitOperationMethod(op));
498
- }
499
- }
500
- const allImports = mergeImports(fragments.flatMap((f) => f.imports));
501
- const importBlock = renderImports(allImports);
502
- const factoryName = `create${toPascalCase(module.name)}Module`;
503
- const methods = fragments.map((f) => ` ${f.content}`).join(`,
504
- `);
505
- const sections = [FILE_HEADER2];
506
- if (importBlock) {
507
- sections.push(importBlock);
508
- sections.push("");
509
- }
510
- sections.push(`export function ${factoryName}(client: FetchClient) {
511
- return {
512
- ${methods},
513
- };
514
- }`);
515
- const fetchClientImport = "import { FetchClient } from '@vertz/fetch';";
516
- const content = sections.join(`
517
- `);
518
- const withFetchImport = content.replace(FILE_HEADER2, `${FILE_HEADER2}
519
- ${fetchClientImport}
520
- `);
521
- return {
522
- path: `modules/${module.name}.ts`,
523
- content: withFetchImport
524
- };
525
- }
526
- function emitClientFile(ir) {
527
- const sections = [FILE_HEADER2];
528
- const imports = [];
529
- imports.push({ from: "@vertz/fetch", name: "FetchClient", isType: false });
530
- const configFragment = emitSDKConfig(ir.auth);
531
- imports.push(...configFragment.imports);
532
- const authFragment = emitAuthStrategyBuilder(ir.auth);
533
- imports.push(...authFragment.imports);
534
- for (const mod of ir.modules) {
535
- const factoryName = `create${toPascalCase(mod.name)}Module`;
536
- imports.push({ from: `./modules/${mod.name}`, name: factoryName, isType: false });
537
- }
538
- const allImports = mergeImports(imports);
539
- const importBlock = renderImports(allImports);
540
- if (importBlock) {
541
- sections.push(importBlock);
542
- sections.push("");
543
- }
544
- sections.push(`export interface SDKResult<T> {
545
- data: T;
546
- status: number;
547
- headers: Headers;
548
- }`);
549
- sections.push("");
550
- sections.push(configFragment.content);
551
- sections.push("");
552
- const moduleEntries = ir.modules.map((mod) => ` ${toCamelCase(mod.name)}: create${toPascalCase(mod.name)}Module(client)`).join(`,
553
- `);
554
- const createClientBody = [
555
- "export function createClient(config: SDKConfig) {",
556
- ` ${authFragment.content.split(`
557
- `).join(`
558
- `)}`,
559
- "",
560
- " const client = new FetchClient({",
561
- " ...config,",
562
- " authStrategies,",
563
- " });",
564
- "",
565
- " return {",
566
- moduleEntries,
567
- " };",
568
- "}"
569
- ].join(`
570
- `);
571
- sections.push(createClientBody);
572
- return {
573
- path: "client.ts",
574
- content: sections.join(`
575
- `)
576
- };
577
- }
578
146
 
579
- // src/generators/typescript/emit-sdk.ts
580
- var FILE_HEADER3 = `// Generated by @vertz/codegen — do not edit
581
- `;
582
- function jsonSchemaToSchemaCall(schema) {
583
- const type = schema.type;
584
- if (type === "string")
585
- return "s.string()";
586
- if (type === "number")
587
- return "s.number()";
588
- if (type === "integer")
589
- return "s.int()";
590
- if (type === "boolean")
591
- return "s.boolean()";
592
- if (type === "array") {
593
- const items = schema.items;
594
- if (items) {
595
- return `s.array(${jsonSchemaToSchemaCall(items)})`;
147
+ class ClientGenerator {
148
+ name = "client";
149
+ generate(ir, config) {
150
+ return [
151
+ this.generateClient(ir),
152
+ this.generatePackageJson(config.outputDir),
153
+ this.generateReadme(ir)
154
+ ];
155
+ }
156
+ generateClient(ir) {
157
+ const entities = ir.entities ?? [];
158
+ const lines = [FILE_HEADER];
159
+ if (entities.length > 0) {
160
+ lines.push("import { FetchClient } from '@vertz/fetch';");
161
+ for (const entity of entities) {
162
+ const pascal = toPascalCase(entity.entityName);
163
+ lines.push(`import { create${pascal}Sdk } from './entities/${entity.entityName}';`);
164
+ }
165
+ lines.push("");
596
166
  }
597
- return "s.array(s.unknown())";
598
- }
599
- if (type === "object") {
600
- const properties = schema.properties;
601
- if (!properties || Object.keys(properties).length === 0) {
602
- return "s.object({})";
167
+ lines.push("export interface ClientOptions {");
168
+ lines.push(" baseURL?: string;");
169
+ lines.push(" headers?: Record<string, string>;");
170
+ lines.push(" timeoutMs?: number;");
171
+ lines.push("}");
172
+ lines.push("");
173
+ lines.push("export function createClient(options: ClientOptions = {}) {");
174
+ if (entities.length > 0) {
175
+ lines.push(` const client = new FetchClient({ baseURL: options.baseURL ?? '/api', headers: options.headers, timeoutMs: options.timeoutMs });`);
176
+ lines.push(" return {");
177
+ for (const entity of entities) {
178
+ const pascal = toPascalCase(entity.entityName);
179
+ const camel = toCamelCase(entity.entityName);
180
+ lines.push(` ${camel}: create${pascal}Sdk(client),`);
181
+ }
182
+ lines.push(" };");
183
+ } else {
184
+ lines.push(" return {};");
603
185
  }
604
- const required = schema.required ?? [];
605
- const fields = Object.entries(properties).map(([key, propSchema]) => {
606
- const call = jsonSchemaToSchemaCall(propSchema);
607
- if (required.includes(key)) {
608
- return ` ${key}: ${call}`;
186
+ lines.push("}");
187
+ lines.push("");
188
+ lines.push("export type Client = ReturnType<typeof createClient>;");
189
+ return { path: "client.ts", content: lines.join(`
190
+ `) };
191
+ }
192
+ generatePackageJson(outputDir) {
193
+ const dir = `./${posix.normalize(outputDir.replace(/\\/g, "/"))}`;
194
+ const pkg = {
195
+ imports: {
196
+ "#generated": `${dir}/client.ts`,
197
+ "#generated/types": `${dir}/types/index.ts`
609
198
  }
610
- return ` ${key}: ${call}.optional()`;
611
- });
612
- return `s.object({
613
- ${fields.join(`,
614
- `)},
615
- })`;
616
- }
617
- return "s.unknown()";
618
- }
619
- function emitSchemaReExports(schemas) {
620
- const sections = [FILE_HEADER3];
621
- if (schemas.length > 0) {
622
- sections.push("import { s } from '@vertz/schema';");
623
- sections.push("");
624
- }
625
- for (const schema of schemas) {
626
- const validatorName = `${schema.name}Schema`;
627
- const schemaCall = jsonSchemaToSchemaCall(schema.jsonSchema);
628
- sections.push(`export const ${validatorName} = ${schemaCall};`);
629
- }
630
- return {
631
- path: "schemas.ts",
632
- content: sections.join(`
633
- `)
634
- };
635
- }
636
- function emitBarrelIndex(ir) {
637
- const lines = [FILE_HEADER3];
638
- lines.push("export { createClient } from './client';");
639
- lines.push("export type { SDKConfig, SDKResult } from './client';");
640
- for (const mod of ir.modules) {
641
- lines.push(`export * from './types/${mod.name}';`);
642
- }
643
- if (ir.schemas.length > 0) {
644
- lines.push("export * from './types/shared';");
645
- }
646
- if (ir.schemas.length > 0) {
647
- lines.push("export * from './schemas';");
648
- }
649
- return {
650
- path: "index.ts",
651
- content: lines.join(`
652
- `)
653
- };
654
- }
655
- function emitPackageJson(ir, options) {
656
- const dependencies = {
657
- "@vertz/fetch": "*"
658
- };
659
- if (ir.schemas.length > 0) {
660
- dependencies["@vertz/schema"] = "*";
199
+ };
200
+ return { path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
201
+ ` };
661
202
  }
662
- const pkg = {
663
- name: options.packageName,
664
- version: options.packageVersion ?? "0.0.0",
665
- description: "Generated by @vertz/codegen",
666
- private: true,
667
- main: "index.ts",
668
- types: "index.ts",
669
- dependencies
670
- };
671
- return {
672
- path: "package.json",
673
- content: JSON.stringify(pkg, null, 2)
674
- };
675
- }
676
-
677
- // src/json-schema-converter.ts
678
- var PRIMITIVE_MAP = {
679
- string: "string",
680
- number: "number",
681
- integer: "number",
682
- boolean: "boolean",
683
- null: "null"
684
- };
685
- function jsonSchemaToTS(schema, ctx) {
686
- const context = ctx ?? {
687
- namedTypes: new Map,
688
- resolving: new Set
689
- };
690
- const type = convert(schema, context);
691
- return { type, extractedTypes: context.namedTypes };
692
- }
693
- function convert(schema, _ctx) {
694
- if (schema.$defs && typeof schema.$defs === "object") {
695
- const defs = schema.$defs;
696
- for (const [name, defSchema] of Object.entries(defs)) {
697
- if (!_ctx.namedTypes.has(name)) {
698
- _ctx.resolving.add(name);
699
- const typeStr = convert(defSchema, _ctx);
700
- _ctx.resolving.delete(name);
701
- _ctx.namedTypes.set(name, typeStr);
203
+ generateReadme(ir) {
204
+ const entities = ir.entities ?? [];
205
+ const lines = [];
206
+ lines.push("# Generated Client");
207
+ lines.push("");
208
+ lines.push("Auto-generated by `@vertz/codegen`. Do not edit manually.");
209
+ lines.push("");
210
+ lines.push("## Usage");
211
+ lines.push("");
212
+ lines.push("```typescript");
213
+ lines.push("import { createClient } from '#generated';");
214
+ lines.push("");
215
+ lines.push("const api = createClient();");
216
+ lines.push("```");
217
+ lines.push("");
218
+ lines.push("## Type Imports");
219
+ lines.push("");
220
+ lines.push("```typescript");
221
+ lines.push("import type { ... } from '#generated/types';");
222
+ lines.push("```");
223
+ if (entities.length > 0) {
224
+ lines.push("");
225
+ lines.push("## Available Resources");
226
+ lines.push("");
227
+ for (const entity of entities) {
228
+ const camel = toCamelCase(entity.entityName);
229
+ const methods = this.getEntityMethods(entity);
230
+ lines.push(`- \`client.${camel}\` — ${methods}`);
702
231
  }
703
232
  }
233
+ lines.push("");
234
+ return { path: "README.md", content: lines.join(`
235
+ `) };
704
236
  }
705
- if (typeof schema.$ref === "string") {
706
- if (!schema.$ref.startsWith("#")) {
707
- throw new Error(`External $ref is not supported: ${schema.$ref}`);
237
+ getEntityMethods(entity) {
238
+ const methods = [];
239
+ for (const op of entity.operations) {
240
+ methods.push(op.kind);
708
241
  }
709
- return refToName(schema.$ref);
710
- }
711
- if (schema.const !== undefined) {
712
- return toLiteral(schema.const);
713
- }
714
- if (Array.isArray(schema.enum)) {
715
- return schema.enum.map((v) => toLiteral(v)).join(" | ");
716
- }
717
- if (Array.isArray(schema.oneOf)) {
718
- return schema.oneOf.map((s) => convert(s, _ctx)).join(" | ");
719
- }
720
- if (Array.isArray(schema.anyOf)) {
721
- return schema.anyOf.map((s) => convert(s, _ctx)).join(" | ");
722
- }
723
- if (Array.isArray(schema.allOf)) {
724
- return schema.allOf.map((s) => convert(s, _ctx)).join(" & ");
725
- }
726
- if (Array.isArray(schema.type)) {
727
- return schema.type.map((t) => PRIMITIVE_MAP[t] ?? t).join(" | ");
728
- }
729
- if (typeof schema.type === "string") {
730
- const type = schema.type;
731
- if (type === "array") {
732
- if (Array.isArray(schema.prefixItems)) {
733
- const items = schema.prefixItems.map((s) => convert(s, _ctx));
734
- return `[${items.join(", ")}]`;
735
- }
736
- if (schema.items && typeof schema.items === "object") {
737
- const itemType = convert(schema.items, _ctx);
738
- return itemType.includes(" | ") ? `(${itemType})[]` : `${itemType}[]`;
739
- }
740
- return "unknown[]";
741
- }
742
- if (type === "object") {
743
- if (schema.additionalProperties && typeof schema.additionalProperties === "object" && !schema.properties) {
744
- const valueType = convert(schema.additionalProperties, _ctx);
745
- return `Record<string, ${valueType}>`;
746
- }
747
- if (schema.properties && typeof schema.properties === "object") {
748
- const props = schema.properties;
749
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
750
- const parts = [];
751
- for (const [key, propSchema] of Object.entries(props)) {
752
- const propType = convert(propSchema, _ctx);
753
- const optional = required.has(key) ? "" : "?";
754
- parts.push(`${key}${optional}: ${propType}`);
755
- }
756
- return `{ ${parts.join("; ")} }`;
757
- }
758
- return "Record<string, unknown>";
242
+ for (const action of entity.actions) {
243
+ methods.push(action.name);
759
244
  }
760
- return PRIMITIVE_MAP[type] ?? "unknown";
245
+ return methods.length > 0 ? methods.join(", ") : "no operations";
761
246
  }
762
- return "unknown";
763
- }
764
- function refToName(ref) {
765
- const segments = ref.split("/");
766
- return segments[segments.length - 1] ?? "unknown";
767
- }
768
- function toLiteral(value) {
769
- if (typeof value === "string")
770
- return `'${value}'`;
771
- if (typeof value === "number" || typeof value === "boolean")
772
- return String(value);
773
- if (value === null)
774
- return "null";
775
- return "unknown";
776
247
  }
777
248
 
778
- // src/generators/typescript/emit-types.ts
779
- var FILE_HEADER4 = `// Generated by @vertz/codegen — do not edit
249
+ // src/generators/entity-schema-generator.ts
250
+ var FILE_HEADER2 = `// Generated by @vertz/codegen — do not edit
251
+
780
252
  `;
781
- function emitTypeDeclaration(name, tsType) {
782
- if (tsType.startsWith("{")) {
783
- return `export interface ${name} ${tsType}`;
784
- }
785
- return `export type ${name} = ${tsType};`;
786
- }
787
- function emitInterfaceFromSchema(schema) {
788
- const result = jsonSchemaToTS(schema.jsonSchema);
789
- const lines = [];
790
- for (const [name, tsType] of result.extractedTypes) {
791
- lines.push(emitTypeDeclaration(name, tsType));
792
- lines.push("");
793
- }
794
- const jsdocParts = [];
795
- if (schema.annotations.description) {
796
- jsdocParts.push(schema.annotations.description);
797
- }
798
- if (schema.annotations.deprecated) {
799
- jsdocParts.push("@deprecated");
800
- }
801
- if (jsdocParts.length > 0) {
802
- lines.push(`/** ${jsdocParts.join(`
803
- * `)} */`);
804
- }
805
- lines.push(emitTypeDeclaration(schema.name, result.type));
806
- return { content: lines.join(`
807
- `), imports: [] };
253
+ var TYPE_MAP = {
254
+ string: "s.string()",
255
+ number: "s.number()",
256
+ boolean: "s.boolean()",
257
+ date: "s.string()",
258
+ unknown: "s.unknown()"
259
+ };
260
+ function toLowerFirst(s) {
261
+ return s.charAt(0).toLowerCase() + s.slice(1);
808
262
  }
809
- function emitOperationInputType(op) {
810
- const typeName = `${toPascalCase(op.operationId)}Input`;
811
- const imports = [];
812
- const slots = [];
813
- if (op.params) {
814
- const ref = op.schemaRefs.params;
815
- if (ref) {
816
- slots.push(`params: ${ref}`);
817
- imports.push({ from: "", name: ref, isType: true });
818
- } else {
819
- const result = jsonSchemaToTS(op.params);
820
- slots.push(`params: ${result.type}`);
821
- }
822
- }
823
- if (op.query) {
824
- const ref = op.schemaRefs.query;
825
- if (ref) {
826
- slots.push(`query?: ${ref}`);
827
- imports.push({ from: "", name: ref, isType: true });
828
- } else {
829
- const result = jsonSchemaToTS(op.query);
830
- slots.push(`query?: ${result.type}`);
831
- }
832
- }
833
- if (op.body) {
834
- const ref = op.schemaRefs.body;
835
- if (ref) {
836
- slots.push(`body: ${ref}`);
837
- imports.push({ from: "", name: ref, isType: true });
838
- } else {
839
- const result = jsonSchemaToTS(op.body);
840
- slots.push(`body: ${result.type}`);
841
- }
842
- }
843
- if (op.headers) {
844
- const ref = op.schemaRefs.headers;
845
- if (ref) {
846
- slots.push(`headers?: ${ref}`);
847
- imports.push({ from: "", name: ref, isType: true });
848
- } else {
849
- const result = jsonSchemaToTS(op.headers);
850
- slots.push(`headers?: ${result.type}`);
263
+
264
+ class EntitySchemaGenerator {
265
+ name = "entity-schema";
266
+ generate(ir, _config) {
267
+ if (!ir.entities?.length)
268
+ return [];
269
+ const files = [];
270
+ const entitiesWithSchemas = [];
271
+ for (const entity of ir.entities) {
272
+ const schemaOps = entity.operations.filter((op) => (op.kind === "create" || op.kind === "update") && op.resolvedFields && op.resolvedFields.length > 0);
273
+ const schemaActions = entity.actions.filter((a) => a.resolvedInputFields && a.resolvedInputFields.length > 0);
274
+ if (schemaOps.length === 0 && schemaActions.length === 0)
275
+ continue;
276
+ entitiesWithSchemas.push(entity);
277
+ files.push(this.generateEntitySchema(entity, schemaOps, schemaActions));
851
278
  }
852
- }
853
- if (slots.length === 0) {
854
- return { content: "", imports: [] };
855
- }
856
- const lines = [];
857
- lines.push(`/** Input for ${op.operationId} */`);
858
- lines.push(`export interface ${typeName} { ${slots.join("; ")} }`);
859
- return { content: lines.join(`
860
- `), imports };
861
- }
862
- function emitOperationResponseType(op) {
863
- const typeName = `${toPascalCase(op.operationId)}Response`;
864
- const imports = [];
865
- if (!op.response) {
866
- return {
867
- content: `export type ${typeName} = void;`,
868
- imports: []
869
- };
870
- }
871
- const ref = op.schemaRefs.response;
872
- if (ref) {
873
- imports.push({ from: "", name: ref, isType: true });
874
- return {
875
- content: `export type ${typeName} = ${ref};`,
876
- imports
877
- };
878
- }
879
- const result = jsonSchemaToTS(op.response);
880
- return {
881
- content: emitTypeDeclaration(typeName, result.type),
882
- imports: []
883
- };
884
- }
885
- function emitStreamingEventType(op) {
886
- if (!op.streaming) {
887
- return { content: "", imports: [] };
888
- }
889
- const typeName = `${toPascalCase(op.operationId)}Event`;
890
- if (!op.streaming.eventSchema) {
891
- return {
892
- content: `export type ${typeName} = unknown;`,
893
- imports: []
894
- };
895
- }
896
- const result = jsonSchemaToTS(op.streaming.eventSchema);
897
- return {
898
- content: emitTypeDeclaration(typeName, result.type),
899
- imports: []
900
- };
901
- }
902
- function emitModuleTypesFile(module, schemas) {
903
- const sections = [FILE_HEADER4];
904
- for (const schema of schemas) {
905
- const fragment = emitInterfaceFromSchema(schema);
906
- if (fragment.content) {
907
- sections.push(fragment.content);
279
+ if (entitiesWithSchemas.length > 0) {
280
+ files.push(this.generateIndex(entitiesWithSchemas));
908
281
  }
282
+ return files;
909
283
  }
910
- for (const op of module.operations) {
911
- const inputFragment = emitOperationInputType(op);
912
- if (inputFragment.content) {
913
- sections.push(inputFragment.content);
914
- }
915
- const responseFragment = emitOperationResponseType(op);
916
- if (responseFragment.content) {
917
- sections.push(responseFragment.content);
284
+ generateEntitySchema(entity, schemaOps, schemaActions) {
285
+ const lines = [FILE_HEADER2];
286
+ lines.push("import { s } from '@vertz/schema';");
287
+ lines.push("");
288
+ for (const op of schemaOps) {
289
+ if (!op.resolvedFields)
290
+ continue;
291
+ const varName = `${toLowerFirst(op.inputSchema ?? `${op.kind}Input`)}Schema`;
292
+ const fields = op.resolvedFields.map((f) => {
293
+ const baseCall = TYPE_MAP[f.tsType] ?? "s.unknown()";
294
+ const call = f.optional ? `${baseCall}.optional()` : baseCall;
295
+ return ` ${f.name}: ${call}`;
296
+ }).join(`,
297
+ `);
298
+ lines.push(`export const ${varName} = s.object({`);
299
+ lines.push(`${fields},`);
300
+ lines.push("});");
301
+ lines.push("");
918
302
  }
919
- const streamingFragment = emitStreamingEventType(op);
920
- if (streamingFragment.content) {
921
- sections.push(streamingFragment.content);
303
+ for (const action of schemaActions) {
304
+ if (!action.resolvedInputFields)
305
+ continue;
306
+ const varName = `${toLowerFirst(action.inputSchema ?? `${action.name}Input`)}Schema`;
307
+ const fields = action.resolvedInputFields.map((f) => {
308
+ const baseCall = TYPE_MAP[f.tsType] ?? "s.unknown()";
309
+ const call = f.optional ? `${baseCall}.optional()` : baseCall;
310
+ return ` ${f.name}: ${call}`;
311
+ }).join(`,
312
+ `);
313
+ lines.push(`export const ${varName} = s.object({`);
314
+ lines.push(`${fields},`);
315
+ lines.push("});");
316
+ lines.push("");
922
317
  }
923
- }
924
- return {
925
- path: `types/${module.name}.ts`,
926
- content: sections.join(`
927
-
318
+ return {
319
+ path: `schemas/${entity.entityName}.ts`,
320
+ content: lines.join(`
928
321
  `)
929
- };
930
- }
931
- function emitSharedTypesFile(schemas) {
932
- const sections = [FILE_HEADER4];
933
- for (const schema of schemas) {
934
- const fragment = emitInterfaceFromSchema(schema);
935
- if (fragment.content) {
936
- sections.push(fragment.content);
322
+ };
323
+ }
324
+ generateIndex(entities) {
325
+ const lines = [FILE_HEADER2];
326
+ for (const entity of entities) {
327
+ const exports = [];
328
+ const schemaOps = entity.operations.filter((op) => (op.kind === "create" || op.kind === "update") && op.resolvedFields && op.resolvedFields.length > 0);
329
+ for (const op of schemaOps) {
330
+ const varName = `${toLowerFirst(op.inputSchema ?? `${op.kind}Input`)}Schema`;
331
+ exports.push(varName);
332
+ }
333
+ const schemaActions = entity.actions.filter((a) => a.resolvedInputFields && a.resolvedInputFields.length > 0);
334
+ for (const action of schemaActions) {
335
+ const varName = `${toLowerFirst(action.inputSchema ?? `${action.name}Input`)}Schema`;
336
+ exports.push(varName);
337
+ }
338
+ if (exports.length > 0) {
339
+ lines.push(`export { ${exports.join(", ")} } from './${entity.entityName}';`);
340
+ }
937
341
  }
342
+ return { path: "schemas/index.ts", content: lines.join(`
343
+ `) };
938
344
  }
939
- return {
940
- path: "types/shared.ts",
941
- content: sections.join(`
942
-
943
- `)
944
- };
945
345
  }
946
346
 
947
- // src/generators/typescript/emit-routes.ts
948
- var FILE_HEADER5 = `// Generated by @vertz/codegen — do not edit
347
+ // src/generators/entity-sdk-generator.ts
348
+ var FILE_HEADER3 = `// Generated by @vertz/codegen — do not edit
349
+
949
350
  `;
950
- function paramsToTS(op) {
951
- if (!op.params) {
952
- return "Record<string, never>";
953
- }
954
- const ref = op.schemaRefs.params;
955
- if (ref) {
956
- return ref;
957
- }
958
- const result = jsonSchemaToTS(op.params);
959
- return result.type;
960
- }
961
- function queryToTS(op) {
962
- if (!op.query) {
963
- return "Record<string, never>";
964
- }
965
- const ref = op.schemaRefs.query;
966
- if (ref) {
967
- return ref;
968
- }
969
- const result = jsonSchemaToTS(op.query);
970
- return result.type;
351
+ function toPascalCase2(s) {
352
+ return s.split("-").map((w) => w[0]?.toUpperCase() + w.slice(1)).join("");
971
353
  }
972
- function bodyToTS(op) {
973
- if (!op.body) {
974
- return "never";
354
+
355
+ class EntitySdkGenerator {
356
+ name = "entity-sdk";
357
+ generate(ir, _config) {
358
+ if (!ir.entities?.length)
359
+ return [];
360
+ const files = [];
361
+ for (const entity of ir.entities) {
362
+ files.push(this.generateEntitySdk(entity, ir.basePath));
363
+ }
364
+ files.push(this.generateIndex(ir.entities));
365
+ return files;
366
+ }
367
+ generateEntitySdk(entity, _basePath) {
368
+ const pascal = toPascalCase2(entity.entityName);
369
+ const lines = [FILE_HEADER3];
370
+ const createOpsWithMeta = entity.operations.filter((op) => op.kind === "create" && op.resolvedFields && op.resolvedFields.length > 0);
371
+ const hasSchemaImports = createOpsWithMeta.length > 0;
372
+ if (hasSchemaImports) {
373
+ const schemaImports = [];
374
+ for (const op of createOpsWithMeta) {
375
+ const schemaVarName = `${(op.inputSchema ?? "createInput").charAt(0).toLowerCase()}${(op.inputSchema ?? "createInput").slice(1)}Schema`;
376
+ schemaImports.push(schemaVarName);
377
+ }
378
+ lines.push(`import { ${schemaImports.join(", ")} } from '../schemas/${entity.entityName}';`);
379
+ }
380
+ const hasTypes = entity.operations.some((op) => op.outputSchema || op.inputSchema);
381
+ const hasListOp = entity.operations.some((op) => op.kind === "list");
382
+ if (hasTypes) {
383
+ const typeImports = new Set;
384
+ for (const op of entity.operations) {
385
+ if (op.outputSchema)
386
+ typeImports.add(op.outputSchema);
387
+ if (op.inputSchema)
388
+ typeImports.add(op.inputSchema);
389
+ }
390
+ for (const action of entity.actions) {
391
+ if (action.inputSchema)
392
+ typeImports.add(action.inputSchema);
393
+ if (action.outputSchema)
394
+ typeImports.add(action.outputSchema);
395
+ }
396
+ lines.push(`import type { ${[...typeImports].join(", ")} } from '../types/${entity.entityName}';`);
397
+ const fetchImports = hasListOp ? "type FetchClient, type ListResponse, createDescriptor" : "type FetchClient, createDescriptor";
398
+ lines.push(`import { ${fetchImports} } from '@vertz/fetch';`);
399
+ lines.push("");
400
+ }
401
+ lines.push(`export function create${pascal}Sdk(client: FetchClient) {`);
402
+ lines.push(" return {");
403
+ for (const op of entity.operations) {
404
+ const inputType = op.inputSchema ?? "unknown";
405
+ const outputType = op.outputSchema ?? "unknown";
406
+ const listOutput = op.kind === "list" ? `ListResponse<${outputType}>` : outputType;
407
+ switch (op.kind) {
408
+ case "list":
409
+ lines.push(` list: Object.assign(`);
410
+ lines.push(` (query?: Record<string, unknown>) => createDescriptor('GET', '${op.path}', () => client.get<${listOutput}>('${op.path}', { query }), query),`);
411
+ lines.push(` { url: '${op.path}', method: 'GET' as const },`);
412
+ lines.push(` ),`);
413
+ break;
414
+ case "get":
415
+ lines.push(` get: Object.assign(`);
416
+ lines.push(` (id: string) => createDescriptor('GET', \`${op.path.replace(":id", "${id}")}\`, () => client.get<${outputType}>(\`${op.path.replace(":id", "${id}")}\`)),`);
417
+ lines.push(` { url: '${op.path}', method: 'GET' as const },`);
418
+ lines.push(` ),`);
419
+ break;
420
+ case "create":
421
+ if (op.resolvedFields && op.resolvedFields.length > 0) {
422
+ const schemaVarName = `${(op.inputSchema ?? "createInput").charAt(0).toLowerCase()}${(op.inputSchema ?? "createInput").slice(1)}Schema`;
423
+ lines.push(` create: Object.assign(`);
424
+ lines.push(` (body: ${inputType}) => createDescriptor('POST', '${op.path}', () => client.post<${outputType}>('${op.path}', body)),`);
425
+ lines.push(` {`);
426
+ lines.push(` url: '${op.path}',`);
427
+ lines.push(` method: 'POST' as const,`);
428
+ lines.push(` meta: { bodySchema: ${schemaVarName} },`);
429
+ lines.push(` },`);
430
+ lines.push(` ),`);
431
+ } else {
432
+ lines.push(` create: Object.assign(`);
433
+ lines.push(` (body: ${inputType}) => createDescriptor('POST', '${op.path}', () => client.post<${outputType}>('${op.path}', body)),`);
434
+ lines.push(` { url: '${op.path}', method: 'POST' as const },`);
435
+ lines.push(` ),`);
436
+ }
437
+ break;
438
+ case "update":
439
+ lines.push(` update: Object.assign(`);
440
+ lines.push(` (id: string, body: ${inputType}) => createDescriptor('PATCH', \`${op.path.replace(":id", "${id}")}\`, () => client.patch<${outputType}>(\`${op.path.replace(":id", "${id}")}\`, body)),`);
441
+ lines.push(` { url: '${op.path}', method: 'PATCH' as const },`);
442
+ lines.push(` ),`);
443
+ break;
444
+ case "delete":
445
+ lines.push(` delete: Object.assign(`);
446
+ lines.push(` (id: string) => createDescriptor('DELETE', \`${op.path.replace(":id", "${id}")}\`, () => client.delete<${outputType}>(\`${op.path.replace(":id", "${id}")}\`)),`);
447
+ lines.push(` { url: '${op.path}', method: 'DELETE' as const },`);
448
+ lines.push(` ),`);
449
+ break;
450
+ }
451
+ }
452
+ for (const action of entity.actions) {
453
+ const inputType = action.inputSchema ?? "unknown";
454
+ const outputType = action.outputSchema ?? "unknown";
455
+ const method = action.method ?? "POST";
456
+ const methodLower = method.toLowerCase();
457
+ const hasId = action.hasId ?? action.path.includes(":id");
458
+ const pathExpr = hasId ? `\`${action.path.replace(":id", "${id}")}\`` : `'${action.path}'`;
459
+ const hasBody = method === "POST" || method === "PUT" || method === "PATCH";
460
+ const params = [];
461
+ if (hasId)
462
+ params.push("id: string");
463
+ if (hasBody)
464
+ params.push(`body: ${inputType}`);
465
+ const paramStr = params.join(", ");
466
+ const clientArgs = hasBody ? `${pathExpr}, body` : pathExpr;
467
+ const clientCall = `client.${methodLower}<${outputType}>(${clientArgs})`;
468
+ lines.push(` ${action.name}: Object.assign(`);
469
+ lines.push(` (${paramStr}) => createDescriptor('${method}', ${pathExpr}, () => ${clientCall}${hasBody ? ", body" : ""}),`);
470
+ lines.push(` { url: '${action.path}', method: '${method}' as const },`);
471
+ lines.push(" ),");
472
+ }
473
+ lines.push(" };");
474
+ lines.push("}");
475
+ return {
476
+ path: `entities/${entity.entityName}.ts`,
477
+ content: lines.join(`
478
+ `)
479
+ };
975
480
  }
976
- const ref = op.schemaRefs.body;
977
- if (ref) {
978
- return ref;
481
+ generateIndex(entities) {
482
+ const lines = [FILE_HEADER3];
483
+ for (const entity of entities) {
484
+ const pascal = toPascalCase2(entity.entityName);
485
+ lines.push(`export { create${pascal}Sdk } from './${entity.entityName}';`);
486
+ }
487
+ return { path: "entities/index.ts", content: lines.join(`
488
+ `) };
979
489
  }
980
- const result = jsonSchemaToTS(op.body);
981
- return result.type;
982
490
  }
983
- function headersToTS(op) {
984
- if (!op.headers) {
985
- return "Record<string, never>";
986
- }
987
- const ref = op.schemaRefs.headers;
988
- if (ref) {
989
- return ref;
491
+
492
+ // src/generators/entity-types-generator.ts
493
+ var FILE_HEADER4 = `// Generated by @vertz/codegen — do not edit
494
+
495
+ `;
496
+ var TS_TYPE_MAP = {
497
+ string: "string",
498
+ number: "number",
499
+ boolean: "boolean",
500
+ date: "string",
501
+ unknown: "unknown"
502
+ };
503
+
504
+ class EntityTypesGenerator {
505
+ name = "entity-types";
506
+ generate(ir, _config) {
507
+ if (!ir.entities?.length)
508
+ return [];
509
+ const files = [];
510
+ const entitiesWithTypes = [];
511
+ for (const entity of ir.entities) {
512
+ const hasOpTypes = entity.operations.some((op) => op.inputSchema || op.outputSchema);
513
+ const hasActionTypes = entity.actions.some((a) => a.resolvedInputFields?.length || a.resolvedOutputFields?.length);
514
+ if (!hasOpTypes && !hasActionTypes)
515
+ continue;
516
+ entitiesWithTypes.push(entity);
517
+ files.push(this.generateEntityTypes(entity));
518
+ }
519
+ if (entitiesWithTypes.length > 0) {
520
+ files.push(this.generateIndex(entitiesWithTypes));
521
+ }
522
+ return files;
523
+ }
524
+ generateEntityTypes(entity) {
525
+ const lines = [FILE_HEADER4];
526
+ const emitted = new Set;
527
+ for (const op of entity.operations) {
528
+ if (op.inputSchema && !emitted.has(op.inputSchema)) {
529
+ const inputType = this.emitBodyType(op.inputSchema, op.resolvedFields);
530
+ if (inputType) {
531
+ lines.push(inputType);
532
+ lines.push("");
533
+ emitted.add(op.inputSchema);
534
+ }
535
+ }
536
+ if (op.outputSchema && !emitted.has(op.outputSchema)) {
537
+ const outputType = this.emitResponseType(op.outputSchema, op.responseFields);
538
+ if (outputType) {
539
+ lines.push(outputType);
540
+ lines.push("");
541
+ emitted.add(op.outputSchema);
542
+ }
543
+ }
544
+ }
545
+ for (const action of entity.actions) {
546
+ if (action.inputSchema && !emitted.has(action.inputSchema)) {
547
+ const inputType = this.emitBodyType(action.inputSchema, action.resolvedInputFields);
548
+ if (inputType) {
549
+ lines.push(inputType);
550
+ lines.push("");
551
+ emitted.add(action.inputSchema);
552
+ }
553
+ }
554
+ if (action.outputSchema && !emitted.has(action.outputSchema)) {
555
+ const outputType = this.emitResponseType(action.outputSchema, action.resolvedOutputFields);
556
+ if (outputType) {
557
+ lines.push(outputType);
558
+ lines.push("");
559
+ emitted.add(action.outputSchema);
560
+ }
561
+ }
562
+ }
563
+ return {
564
+ path: `types/${entity.entityName}.ts`,
565
+ content: lines.join(`
566
+ `)
567
+ };
990
568
  }
991
- const result = jsonSchemaToTS(op.headers);
992
- return result.type;
993
- }
994
- function responseToTS(op) {
995
- if (!op.response) {
996
- return "void";
569
+ emitBodyType(typeName, fields) {
570
+ if (!fields || fields.length === 0)
571
+ return;
572
+ const props = fields.map((f) => {
573
+ const tsType = TS_TYPE_MAP[f.tsType] ?? "unknown";
574
+ const optional = f.optional ? "?" : "";
575
+ return ` ${f.name}${optional}: ${tsType}`;
576
+ }).join(`;
577
+ `);
578
+ return `export interface ${typeName} {
579
+ ${props};
580
+ }`;
997
581
  }
998
- const ref = op.schemaRefs.response;
999
- if (ref) {
1000
- return ref;
582
+ emitResponseType(typeName, fields) {
583
+ if (!fields || fields.length === 0)
584
+ return;
585
+ const props = fields.map((f) => {
586
+ const tsType = TS_TYPE_MAP[f.tsType] ?? "unknown";
587
+ const optional = f.optional ? "?" : "";
588
+ return ` ${f.name}${optional}: ${tsType}`;
589
+ }).join(`;
590
+ `);
591
+ return `export interface ${typeName} {
592
+ ${props};
593
+ }`;
1001
594
  }
1002
- const result = jsonSchemaToTS(op.response);
1003
- return result.type;
1004
- }
1005
- function emitRouteEntry(op) {
1006
- const routeKey = `${op.method} ${op.path}`;
1007
- const params = paramsToTS(op);
1008
- const query = queryToTS(op);
1009
- const body = bodyToTS(op);
1010
- const headers = headersToTS(op);
1011
- const response = responseToTS(op);
1012
- return ` '${routeKey}': { params: ${params}; query: ${query}; body: ${body}; headers: ${headers}; response: ${response} };`;
1013
- }
1014
- function emitRouteMapType(ir) {
1015
- const sections = [FILE_HEADER5];
1016
- sections.push("export interface AppRouteMap {");
1017
- const operations = ir.modules.flatMap((mod) => mod.operations);
1018
- if (operations.length === 0) {
1019
- sections.push(" [key: string]: never;");
1020
- } else {
1021
- const entries = operations.map((op) => emitRouteEntry(op));
1022
- sections.push(entries.join(`
1023
- `));
595
+ generateIndex(entities) {
596
+ const lines = [FILE_HEADER4];
597
+ for (const entity of entities) {
598
+ lines.push(`export * from './${entity.entityName}';`);
599
+ }
600
+ return { path: "types/index.ts", content: lines.join(`
601
+ `) };
1024
602
  }
1025
- sections.push("}");
1026
- return {
1027
- path: "types/routes.ts",
1028
- content: sections.join(`
1029
- `)
1030
- };
1031
603
  }
1032
604
 
1033
605
  // src/incremental.ts
1034
606
  import { mkdir as mkdir2, readdir, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
1035
- import { dirname as dirname2, join as join2, relative } from "node:path";
607
+ import { dirname as dirname2, join as join2, relative, resolve as resolve2 } from "node:path";
1036
608
 
1037
609
  // src/hasher.ts
1038
610
  import { createHash } from "node:crypto";
@@ -1067,6 +639,10 @@ async function writeIncremental(files, outputDir, options) {
1067
639
  const generatedPaths = new Set(files.map((f) => f.path));
1068
640
  for (const file of files) {
1069
641
  const filePath = join2(outputDir, file.path);
642
+ const resolvedPath = resolve2(filePath);
643
+ if (!resolvedPath.startsWith(resolve2(outputDir))) {
644
+ throw new Error(`Generated file path "${file.path}" escapes output directory`);
645
+ }
1070
646
  const dir = dirname2(filePath);
1071
647
  await mkdir2(dir, { recursive: true });
1072
648
  let existingContent;
@@ -1116,122 +692,107 @@ function adaptIR(appIR) {
1116
692
  }
1117
693
  };
1118
694
  });
1119
- const modules = appIR.modules.map((mod) => ({
1120
- name: mod.name,
1121
- operations: mod.routers.flatMap((router) => router.routes.map((route) => {
1122
- const resolveRef = (ref) => {
1123
- if (!ref || ref.kind !== "named")
1124
- return;
1125
- return renameMap.get(`${mod.name}:${ref.schemaName}`) ?? ref.schemaName;
1126
- };
1127
- const schemaRefs = {
1128
- params: resolveRef(route.params),
1129
- query: resolveRef(route.query),
1130
- body: resolveRef(route.body),
1131
- headers: resolveRef(route.headers),
1132
- response: resolveRef(route.response)
1133
- };
1134
- return {
1135
- operationId: route.operationId,
1136
- method: route.method,
1137
- path: route.fullPath,
1138
- description: route.description,
1139
- tags: route.tags,
1140
- params: route.params?.jsonSchema,
1141
- query: route.query?.jsonSchema,
1142
- body: route.body?.jsonSchema,
1143
- headers: route.headers?.jsonSchema,
1144
- response: route.response?.jsonSchema,
1145
- schemaRefs
1146
- };
1147
- }))
1148
- }));
1149
- const slotNames = {
1150
- params: "Params",
1151
- query: "Query",
1152
- body: "Body",
1153
- headers: "Headers",
1154
- response: "Response"
1155
- };
1156
- const inlineSchemas = [];
1157
- for (const mod of appIR.modules) {
1158
- for (const router of mod.routers) {
1159
- for (const route of router.routes) {
1160
- for (const [slot, suffix] of Object.entries(slotNames)) {
1161
- const ref = route[slot];
1162
- if (ref && typeof ref === "object" && "kind" in ref && ref.kind === "inline" && ref.jsonSchema) {
1163
- const name = `${toPascalCase(route.operationId)}${suffix}`;
1164
- inlineSchemas.push({
1165
- name,
1166
- jsonSchema: ref.jsonSchema,
1167
- annotations: { namingParts: {} }
1168
- });
1169
- }
695
+ const allSchemas = [...schemas].sort((a, b) => a.name.localeCompare(b.name));
696
+ const entities = (appIR.entities ?? []).map((entity) => {
697
+ const entityPascal = toPascalCase(entity.name);
698
+ const operations = [];
699
+ const crudOps = [
700
+ { kind: "list", method: "GET", path: `/${entity.name}`, schema: "response" },
701
+ { kind: "get", method: "GET", path: `/${entity.name}/:id`, schema: "response" },
702
+ { kind: "create", method: "POST", path: `/${entity.name}`, schema: "createInput" },
703
+ { kind: "update", method: "PATCH", path: `/${entity.name}/:id`, schema: "updateInput" },
704
+ { kind: "delete", method: "DELETE", path: `/${entity.name}/:id`, schema: "response" }
705
+ ];
706
+ for (const op of crudOps) {
707
+ const accessKind = entity.access[op.kind];
708
+ if (accessKind === "false")
709
+ continue;
710
+ let resolvedFields;
711
+ if (op.kind === "create" || op.kind === "update") {
712
+ const schemaRef = entity.modelRef.schemaRefs[op.schema];
713
+ if (schemaRef?.kind === "inline") {
714
+ resolvedFields = schemaRef.resolvedFields?.map((f) => ({
715
+ name: f.name,
716
+ tsType: f.tsType,
717
+ optional: f.optional
718
+ }));
1170
719
  }
1171
720
  }
721
+ let responseFields;
722
+ const responseRef = entity.modelRef.schemaRefs.response;
723
+ if (responseRef?.kind === "inline") {
724
+ responseFields = responseRef.resolvedFields?.map((f) => ({
725
+ name: f.name,
726
+ tsType: f.tsType,
727
+ optional: f.optional
728
+ }));
729
+ }
730
+ operations.push({
731
+ kind: op.kind,
732
+ method: op.method,
733
+ path: op.path,
734
+ operationId: `${op.kind}${entityPascal}`,
735
+ outputSchema: entity.modelRef.schemaRefs.resolved ? `${entityPascal}Response` : undefined,
736
+ inputSchema: (op.kind === "create" || op.kind === "update") && entity.modelRef.schemaRefs.resolved ? `${op.kind === "create" ? "Create" : "Update"}${entityPascal}Input` : undefined,
737
+ resolvedFields,
738
+ responseFields
739
+ });
1172
740
  }
1173
- }
1174
- const allSchemas = [...schemas, ...inlineSchemas].sort((a, b) => a.name.localeCompare(b.name));
1175
- const sortedModules = modules.map((m) => ({
1176
- ...m,
1177
- operations: [...m.operations].sort((a, b) => a.operationId.localeCompare(b.operationId))
1178
- })).sort((a, b) => a.name.localeCompare(b.name));
741
+ const actions = entity.actions.filter((a) => entity.access.custom[a.name] !== "false").map((a) => {
742
+ const actionPascal = toPascalCase(a.name);
743
+ const path = a.path ? `/${entity.name}/${a.path}` : `/${entity.name}/:id/${a.name}`;
744
+ let resolvedInputFields;
745
+ if (a.body?.kind === "inline") {
746
+ resolvedInputFields = a.body.resolvedFields?.map((f) => ({
747
+ name: f.name,
748
+ tsType: f.tsType,
749
+ optional: f.optional
750
+ }));
751
+ }
752
+ let resolvedOutputFields;
753
+ if (a.response?.kind === "inline") {
754
+ resolvedOutputFields = a.response.resolvedFields?.map((f) => ({
755
+ name: f.name,
756
+ tsType: f.tsType,
757
+ optional: f.optional
758
+ }));
759
+ }
760
+ return {
761
+ name: a.name,
762
+ method: a.method,
763
+ operationId: `${a.name}${entityPascal}`,
764
+ path,
765
+ hasId: path.includes(":id"),
766
+ inputSchema: a.body ? `${actionPascal}${entityPascal}Input` : undefined,
767
+ outputSchema: a.response ? `${actionPascal}${entityPascal}Output` : undefined,
768
+ resolvedInputFields,
769
+ resolvedOutputFields
770
+ };
771
+ });
772
+ return { entityName: entity.name, operations, actions };
773
+ });
1179
774
  return {
1180
775
  basePath: appIR.app.basePath,
1181
776
  version: appIR.app.version,
1182
- modules: sortedModules,
777
+ modules: [],
1183
778
  schemas: allSchemas,
779
+ entities,
1184
780
  auth: { schemes: [] }
1185
781
  };
1186
782
  }
1187
783
 
1188
784
  // src/generate.ts
1189
- function runTypescriptGenerator(ir, config) {
1190
- const files = [];
1191
- const moduleSchemaNames = new Set;
1192
- for (const mod of ir.modules) {
1193
- for (const op of mod.operations) {
1194
- for (const ref of Object.values(op.schemaRefs)) {
1195
- if (ref)
1196
- moduleSchemaNames.add(ref);
1197
- }
1198
- }
1199
- }
1200
- const sharedSchemas = ir.schemas.filter((s) => !moduleSchemaNames.has(s.name));
1201
- for (const mod of ir.modules) {
1202
- const moduleRefNames = new Set;
1203
- for (const op of mod.operations) {
1204
- for (const ref of Object.values(op.schemaRefs)) {
1205
- if (ref)
1206
- moduleRefNames.add(ref);
1207
- }
1208
- }
1209
- const moduleSchemas = ir.schemas.filter((s) => moduleRefNames.has(s.name));
1210
- files.push(emitModuleTypesFile(mod, moduleSchemas));
1211
- }
1212
- if (sharedSchemas.length > 0) {
1213
- files.push(emitSharedTypesFile(sharedSchemas));
1214
- }
1215
- files.push(emitRouteMapType(ir));
1216
- for (const mod of ir.modules) {
1217
- files.push(emitModuleFile(mod));
1218
- }
1219
- files.push(emitClientFile(ir));
1220
- if (ir.schemas.length > 0) {
1221
- files.push(emitSchemaReExports(ir.schemas));
1222
- }
1223
- files.push(emitBarrelIndex(ir));
1224
- if (config.typescript?.publishable) {
1225
- files.push(emitPackageJson(ir, {
1226
- packageName: config.typescript.publishable.name,
1227
- packageVersion: config.typescript.publishable.version
1228
- }));
1229
- }
1230
- return files;
1231
- }
1232
- function runCLIGenerator(ir) {
785
+ function runTypescriptGenerator(ir, _config) {
1233
786
  const files = [];
1234
- files.push(emitManifestFile(ir));
787
+ const generatorConfig = { outputDir: _config.outputDir, options: {} };
788
+ const entityTypesGen = new EntityTypesGenerator;
789
+ files.push(...entityTypesGen.generate(ir, generatorConfig));
790
+ const entitySchemaGen = new EntitySchemaGenerator;
791
+ files.push(...entitySchemaGen.generate(ir, generatorConfig));
792
+ const entitySdkGen = new EntitySdkGenerator;
793
+ files.push(...entitySdkGen.generate(ir, generatorConfig));
794
+ const clientGen = new ClientGenerator;
795
+ files.push(...clientGen.generate(ir, generatorConfig));
1235
796
  return files;
1236
797
  }
1237
798
  function generateSync(ir, config) {
@@ -1241,9 +802,6 @@ function generateSync(ir, config) {
1241
802
  if (gen === "typescript") {
1242
803
  generators.push("typescript");
1243
804
  files.push(...runTypescriptGenerator(ir, config));
1244
- } else if (gen === "cli") {
1245
- generators.push("cli");
1246
- files.push(...runCLIGenerator(ir));
1247
805
  }
1248
806
  }
1249
807
  return {
@@ -1253,6 +811,42 @@ function generateSync(ir, config) {
1253
811
  generators
1254
812
  };
1255
813
  }
814
+ async function mergeImportsToPackageJson(files, outputDir) {
815
+ const generatedPkg = files.find((f) => f.path === "package.json");
816
+ if (!generatedPkg)
817
+ return false;
818
+ const generated = JSON.parse(generatedPkg.content);
819
+ const imports = generated.imports;
820
+ if (!imports || Object.keys(imports).length === 0)
821
+ return false;
822
+ const projectRoot = await findProjectRoot(resolve3(outputDir));
823
+ if (!projectRoot)
824
+ return false;
825
+ const pkgPath = join3(projectRoot, "package.json");
826
+ const raw = await readFile3(pkgPath, "utf-8");
827
+ const pkg = JSON.parse(raw);
828
+ const existing = pkg.imports;
829
+ if (existing && JSON.stringify(existing) === JSON.stringify(imports)) {
830
+ return false;
831
+ }
832
+ pkg.imports = imports;
833
+ await writeFile3(pkgPath, `${JSON.stringify(pkg, null, 2)}
834
+ `, "utf-8");
835
+ return true;
836
+ }
837
+ async function findProjectRoot(startDir) {
838
+ let dir = startDir;
839
+ const root = dirname3(dir);
840
+ while (dir !== root) {
841
+ try {
842
+ await readFile3(join3(dir, "package.json"), "utf-8");
843
+ return dir;
844
+ } catch {
845
+ dir = dirname3(dir);
846
+ }
847
+ }
848
+ return null;
849
+ }
1256
850
  async function generate(appIR, config) {
1257
851
  const ir = adaptIR(appIR);
1258
852
  const result = generateSync(ir, config);
@@ -1269,11 +863,16 @@ async function generate(appIR, config) {
1269
863
  await mkdir3(config.outputDir, { recursive: true });
1270
864
  for (const file of files) {
1271
865
  const filePath = join3(config.outputDir, file.path);
866
+ const resolvedPath = resolve3(filePath);
867
+ if (!resolvedPath.startsWith(resolve3(config.outputDir))) {
868
+ throw new Error(`Generated file path "${file.path}" escapes output directory`);
869
+ }
1272
870
  const dir = dirname3(filePath);
1273
871
  await mkdir3(dir, { recursive: true });
1274
872
  await writeFile3(filePath, file.content, "utf-8");
1275
873
  }
1276
874
  }
875
+ await mergeImportsToPackageJson(files, config.outputDir);
1277
876
  return {
1278
877
  files,
1279
878
  ir,
@@ -1282,6 +881,106 @@ async function generate(appIR, config) {
1282
881
  incremental: incrementalResult
1283
882
  };
1284
883
  }
884
+ // src/json-schema-converter.ts
885
+ var PRIMITIVE_MAP = {
886
+ string: "string",
887
+ number: "number",
888
+ integer: "number",
889
+ boolean: "boolean",
890
+ null: "null"
891
+ };
892
+ function jsonSchemaToTS(schema, ctx) {
893
+ const context = ctx ?? {
894
+ namedTypes: new Map,
895
+ resolving: new Set
896
+ };
897
+ const type = convert(schema, context);
898
+ return { type, extractedTypes: context.namedTypes };
899
+ }
900
+ function convert(schema, _ctx) {
901
+ if (schema.$defs && typeof schema.$defs === "object") {
902
+ const defs = schema.$defs;
903
+ for (const [name, defSchema] of Object.entries(defs)) {
904
+ if (!_ctx.namedTypes.has(name)) {
905
+ _ctx.resolving.add(name);
906
+ const typeStr = convert(defSchema, _ctx);
907
+ _ctx.resolving.delete(name);
908
+ _ctx.namedTypes.set(name, typeStr);
909
+ }
910
+ }
911
+ }
912
+ if (typeof schema.$ref === "string") {
913
+ if (!schema.$ref.startsWith("#")) {
914
+ throw new Error(`External $ref is not supported: ${schema.$ref}`);
915
+ }
916
+ return refToName(schema.$ref);
917
+ }
918
+ if (schema.const !== undefined) {
919
+ return toLiteral(schema.const);
920
+ }
921
+ if (Array.isArray(schema.enum)) {
922
+ return schema.enum.map((v) => toLiteral(v)).join(" | ");
923
+ }
924
+ if (Array.isArray(schema.oneOf)) {
925
+ return schema.oneOf.map((s) => convert(s, _ctx)).join(" | ");
926
+ }
927
+ if (Array.isArray(schema.anyOf)) {
928
+ return schema.anyOf.map((s) => convert(s, _ctx)).join(" | ");
929
+ }
930
+ if (Array.isArray(schema.allOf)) {
931
+ return schema.allOf.map((s) => convert(s, _ctx)).join(" & ");
932
+ }
933
+ if (Array.isArray(schema.type)) {
934
+ return schema.type.map((t) => PRIMITIVE_MAP[t] ?? t).join(" | ");
935
+ }
936
+ if (typeof schema.type === "string") {
937
+ const type = schema.type;
938
+ if (type === "array") {
939
+ if (Array.isArray(schema.prefixItems)) {
940
+ const items = schema.prefixItems.map((s) => convert(s, _ctx));
941
+ return `[${items.join(", ")}]`;
942
+ }
943
+ if (schema.items && typeof schema.items === "object") {
944
+ const itemType = convert(schema.items, _ctx);
945
+ return itemType.includes(" | ") ? `(${itemType})[]` : `${itemType}[]`;
946
+ }
947
+ return "unknown[]";
948
+ }
949
+ if (type === "object") {
950
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object" && !schema.properties) {
951
+ const valueType = convert(schema.additionalProperties, _ctx);
952
+ return `Record<string, ${valueType}>`;
953
+ }
954
+ if (schema.properties && typeof schema.properties === "object") {
955
+ const props = schema.properties;
956
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
957
+ const parts = [];
958
+ for (const [key, propSchema] of Object.entries(props)) {
959
+ const propType = convert(propSchema, _ctx);
960
+ const optional = required.has(key) ? "" : "?";
961
+ parts.push(`${key}${optional}: ${propType}`);
962
+ }
963
+ return `{ ${parts.join("; ")} }`;
964
+ }
965
+ return "Record<string, unknown>";
966
+ }
967
+ return PRIMITIVE_MAP[type] ?? "unknown";
968
+ }
969
+ return "unknown";
970
+ }
971
+ function refToName(ref) {
972
+ const segments = ref.split("/");
973
+ return segments[segments.length - 1] ?? "unknown";
974
+ }
975
+ function toLiteral(value) {
976
+ if (typeof value === "string")
977
+ return `'${value}'`;
978
+ if (typeof value === "number" || typeof value === "boolean")
979
+ return String(value);
980
+ if (value === null)
981
+ return "null";
982
+ return "unknown";
983
+ }
1285
984
  // src/pipeline.ts
1286
985
  function createCodegenPipeline() {
1287
986
  return {
@@ -1301,6 +1000,49 @@ function createCodegenPipeline() {
1301
1000
  }
1302
1001
  };
1303
1002
  }
1003
+ // src/utils/imports.ts
1004
+ function mergeImports(imports) {
1005
+ const seen = new Map;
1006
+ for (const imp of imports) {
1007
+ const key = `${imp.from}::${imp.name}::${imp.isType}::${imp.alias ?? ""}`;
1008
+ if (!seen.has(key)) {
1009
+ seen.set(key, imp);
1010
+ }
1011
+ }
1012
+ return [...seen.values()].sort((a, b) => {
1013
+ const fromCmp = a.from.localeCompare(b.from);
1014
+ if (fromCmp !== 0)
1015
+ return fromCmp;
1016
+ return a.name.localeCompare(b.name);
1017
+ });
1018
+ }
1019
+ function renderImports(imports) {
1020
+ const grouped = new Map;
1021
+ for (const imp of imports) {
1022
+ let group = grouped.get(imp.from);
1023
+ if (!group) {
1024
+ group = { types: [], values: [] };
1025
+ grouped.set(imp.from, group);
1026
+ }
1027
+ const nameStr = imp.alias ? `${imp.name} as ${imp.alias}` : imp.name;
1028
+ if (imp.isType) {
1029
+ group.types.push(nameStr);
1030
+ } else {
1031
+ group.values.push(nameStr);
1032
+ }
1033
+ }
1034
+ const lines = [];
1035
+ for (const [from, group] of grouped) {
1036
+ if (group.types.length > 0) {
1037
+ lines.push(`import type { ${group.types.join(", ")} } from '${from}';`);
1038
+ }
1039
+ if (group.values.length > 0) {
1040
+ lines.push(`import { ${group.values.join(", ")} } from '${from}';`);
1041
+ }
1042
+ }
1043
+ return lines.join(`
1044
+ `);
1045
+ }
1304
1046
  export {
1305
1047
  writeIncremental,
1306
1048
  validateCodegenConfig,
@@ -1308,36 +1050,19 @@ export {
1308
1050
  toPascalCase,
1309
1051
  toKebabCase,
1310
1052
  toCamelCase,
1311
- scaffoldCLIRootIndex,
1312
- scaffoldCLIPackageJson,
1313
1053
  resolveCodegenConfig,
1314
1054
  renderImports,
1055
+ mergeImportsToPackageJson,
1315
1056
  mergeImports,
1316
1057
  jsonSchemaToTS,
1317
1058
  hashContent,
1318
1059
  generate,
1319
1060
  formatWithBiome,
1320
- emitStreamingMethod,
1321
- emitStreamingEventType,
1322
- emitSharedTypesFile,
1323
- emitSchemaReExports,
1324
- emitSDKConfig,
1325
- emitRouteMapType,
1326
- emitPackageJson,
1327
- emitOperationResponseType,
1328
- emitOperationMethod,
1329
- emitOperationInputType,
1330
- emitModuleTypesFile,
1331
- emitModuleFile,
1332
- emitModuleCommands,
1333
- emitManifestFile,
1334
- emitInterfaceFromSchema,
1335
- emitCommandDefinition,
1336
- emitClientFile,
1337
- emitBinEntryPoint,
1338
- emitBarrelIndex,
1339
- emitAuthStrategyBuilder,
1340
1061
  defineCodegenConfig,
1341
1062
  createCodegenPipeline,
1342
- adaptIR
1063
+ adaptIR,
1064
+ EntityTypesGenerator,
1065
+ EntitySdkGenerator,
1066
+ EntitySchemaGenerator,
1067
+ ClientGenerator
1343
1068
  };