@vertz/codegen 0.2.11 → 0.2.13

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.d.ts CHANGED
@@ -138,10 +138,16 @@ interface SchemaNamingParts {
138
138
  entity?: string;
139
139
  part?: string;
140
140
  }
141
+ interface CodegenRelation {
142
+ name: string;
143
+ type: "one" | "many";
144
+ entity: string;
145
+ }
141
146
  interface CodegenEntityModule {
142
147
  entityName: string;
143
148
  operations: CodegenEntityOperation[];
144
149
  actions: CodegenEntityAction[];
150
+ relations?: CodegenRelation[];
145
151
  }
146
152
  interface CodegenEntityOperation {
147
153
  kind: "list" | "get" | "create" | "update" | "delete";
@@ -273,6 +279,14 @@ declare class EntityTypesGenerator implements Generator {
273
279
  private emitResponseType;
274
280
  private generateIndex;
275
281
  }
282
+ interface RelationManifestEntry {
283
+ entityType: string;
284
+ schema: Record<string, {
285
+ type: "one" | "many";
286
+ entity: string;
287
+ }>;
288
+ }
289
+ declare function generateRelationManifest(entities: CodegenEntityModule[]): RelationManifestEntry[];
276
290
  /**
277
291
  * Returns a SHA-256 hex hash of the given content string.
278
292
  * Used for comparing generated file content against what is already on disk.
@@ -302,4 +316,4 @@ declare function toPascalCase(input: string): string;
302
316
  declare function toCamelCase(input: string): string;
303
317
  declare function toKebabCase(input: string): string;
304
318
  declare function toSnakeCase(input: string): string;
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 };
319
+ export { writeIncremental, validateCodegenConfig, toSnakeCase, toPascalCase, toKebabCase, toCamelCase, resolveCodegenConfig, renderImports, mergeImportsToPackageJson, mergeImports, jsonSchemaToTS, hashContent, generateRelationManifest, generate, formatWithBiome, defineCodegenConfig, createCodegenPipeline, adaptIR, StreamingConfig, SchemaNamingParts, SchemaAnnotations, ResolvedCodegenConfig, RelationManifestEntry, OperationSchemaRefs, OperationAuth, OAuthFlows, JsonSchema, IncrementalResult, IncrementalOptions, Import, HttpMethod, GeneratorName, GeneratorConfig, Generator, GeneratedFile, GenerateResult, FileFragment, EntityTypesGenerator, EntitySdkGenerator, EntitySchemaGenerator, ConversionResult, ConversionContext, CodegenTypescriptConfig, CodegenSchema, CodegenResolvedField, CodegenRelation, CodegenPublishableConfig, CodegenPipeline, CodegenOperation, CodegenModule, CodegenIR, CodegenEntityOperation, CodegenEntityModule, CodegenEntityAction, CodegenConfig, CodegenAuthScheme, CodegenAuth, ClientGenerator };
package/dist/index.js CHANGED
@@ -139,6 +139,17 @@ function toSnakeCase(input) {
139
139
  return splitWords(input).map((w) => w.toLowerCase()).join("_");
140
140
  }
141
141
 
142
+ // src/generators/relation-manifest-generator.ts
143
+ function generateRelationManifest(entities) {
144
+ return entities.map((entity) => {
145
+ const schema = {};
146
+ for (const rel of entity.relations ?? []) {
147
+ schema[rel.name] = { type: rel.type, entity: rel.entity };
148
+ }
149
+ return { entityType: entity.entityName, schema };
150
+ });
151
+ }
152
+
142
153
  // src/generators/client-generator.ts
143
154
  var FILE_HEADER = `// Generated by @vertz/codegen — do not edit
144
155
 
@@ -155,29 +166,70 @@ class ClientGenerator {
155
166
  }
156
167
  generateClient(ir) {
157
168
  const entities = ir.entities ?? [];
169
+ const hasMutations = entities.some((e) => e.operations.some((op) => op.kind === "update" || op.kind === "delete"));
170
+ const manifest = generateRelationManifest(entities);
171
+ const hasRelations = manifest.some((entry) => Object.keys(entry.schema).length > 0);
158
172
  const lines = [FILE_HEADER];
159
173
  if (entities.length > 0) {
160
- lines.push("import { FetchClient } from '@vertz/fetch';");
174
+ if (hasMutations) {
175
+ lines.push("import { FetchClient, type OptimisticHandler } from '@vertz/fetch';");
176
+ if (hasRelations) {
177
+ lines.push("import { createOptimisticHandler, getEntityStore, registerRelationSchema } from '@vertz/ui';");
178
+ } else {
179
+ lines.push("import { createOptimisticHandler, getEntityStore } from '@vertz/ui';");
180
+ }
181
+ } else {
182
+ lines.push("import { FetchClient } from '@vertz/fetch';");
183
+ if (hasRelations) {
184
+ lines.push("import { registerRelationSchema } from '@vertz/ui';");
185
+ }
186
+ }
161
187
  for (const entity of entities) {
162
188
  const pascal = toPascalCase(entity.entityName);
163
189
  lines.push(`import { create${pascal}Sdk } from './entities/${entity.entityName}';`);
164
190
  }
165
191
  lines.push("");
192
+ if (hasRelations) {
193
+ for (const entry of manifest) {
194
+ const schemaEntries = Object.entries(entry.schema);
195
+ if (schemaEntries.length === 0) {
196
+ lines.push(`registerRelationSchema('${entry.entityType}', {});`);
197
+ } else {
198
+ lines.push(`registerRelationSchema('${entry.entityType}', {`);
199
+ for (const [field, def] of schemaEntries) {
200
+ lines.push(` ${field}: { type: '${def.type}', entity: '${def.entity}' },`);
201
+ }
202
+ lines.push("});");
203
+ }
204
+ }
205
+ lines.push("");
206
+ }
166
207
  }
167
208
  lines.push("export interface ClientOptions {");
168
209
  lines.push(" baseURL?: string;");
169
210
  lines.push(" headers?: Record<string, string>;");
170
211
  lines.push(" timeoutMs?: number;");
212
+ if (hasMutations) {
213
+ lines.push(" optimistic?: OptimisticHandler | false;");
214
+ }
171
215
  lines.push("}");
172
216
  lines.push("");
173
217
  lines.push("export function createClient(options: ClientOptions = {}) {");
174
218
  if (entities.length > 0) {
175
219
  lines.push(` const client = new FetchClient({ baseURL: options.baseURL ?? '/api', headers: options.headers, timeoutMs: options.timeoutMs });`);
220
+ if (hasMutations) {
221
+ lines.push(" const optimistic = options.optimistic !== false ? (options.optimistic ?? createOptimisticHandler(getEntityStore())) : undefined;");
222
+ }
176
223
  lines.push(" return {");
177
224
  for (const entity of entities) {
178
225
  const pascal = toPascalCase(entity.entityName);
179
226
  const camel = toCamelCase(entity.entityName);
180
- lines.push(` ${camel}: create${pascal}Sdk(client),`);
227
+ const entityHasMutations = entity.operations.some((op) => op.kind === "update" || op.kind === "delete");
228
+ if (entityHasMutations) {
229
+ lines.push(` ${camel}: create${pascal}Sdk(client, optimistic),`);
230
+ } else {
231
+ lines.push(` ${camel}: create${pascal}Sdk(client),`);
232
+ }
181
233
  }
182
234
  lines.push(" };");
183
235
  } else {
@@ -379,6 +431,7 @@ class EntitySdkGenerator {
379
431
  }
380
432
  const hasTypes = entity.operations.some((op) => op.outputSchema || op.inputSchema);
381
433
  const hasListOp = entity.operations.some((op) => op.kind === "list");
434
+ const hasMutationOp = entity.operations.some((op) => op.kind === "update" || op.kind === "delete");
382
435
  if (hasTypes) {
383
436
  const typeImports = new Set;
384
437
  for (const op of entity.operations) {
@@ -394,11 +447,22 @@ class EntitySdkGenerator {
394
447
  typeImports.add(action.outputSchema);
395
448
  }
396
449
  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';`);
450
+ const fetchImportParts = ["type FetchClient"];
451
+ if (hasListOp)
452
+ fetchImportParts.push("type ListResponse");
453
+ if (hasMutationOp)
454
+ fetchImportParts.push("type OptimisticHandler");
455
+ fetchImportParts.push("createDescriptor");
456
+ if (hasMutationOp)
457
+ fetchImportParts.push("createMutationDescriptor");
458
+ lines.push(`import { ${fetchImportParts.join(", ")} } from '@vertz/fetch';`);
399
459
  lines.push("");
400
460
  }
401
- lines.push(`export function create${pascal}Sdk(client: FetchClient) {`);
461
+ if (hasMutationOp) {
462
+ lines.push(`export function create${pascal}Sdk(client: FetchClient, optimistic?: OptimisticHandler) {`);
463
+ } else {
464
+ lines.push(`export function create${pascal}Sdk(client: FetchClient) {`);
465
+ }
402
466
  lines.push(" return {");
403
467
  for (const op of entity.operations) {
404
468
  const inputType = op.inputSchema ?? "unknown";
@@ -407,13 +471,13 @@ class EntitySdkGenerator {
407
471
  switch (op.kind) {
408
472
  case "list":
409
473
  lines.push(` list: Object.assign(`);
410
- lines.push(` (query?: Record<string, unknown>) => createDescriptor('GET', '${op.path}', () => client.get<${listOutput}>('${op.path}', { query }), query),`);
474
+ lines.push(` (query?: Record<string, unknown>) => createDescriptor('GET', '${op.path}', () => client.get<${listOutput}>('${op.path}', { query }), query, { entityType: '${entity.entityName}', kind: 'list' as const }),`);
411
475
  lines.push(` { url: '${op.path}', method: 'GET' as const },`);
412
476
  lines.push(` ),`);
413
477
  break;
414
478
  case "get":
415
479
  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}")}\`)),`);
480
+ lines.push(` (id: string) => createDescriptor('GET', \`${op.path.replace(":id", "${id}")}\`, () => client.get<${outputType}>(\`${op.path.replace(":id", "${id}")}\`), undefined, { entityType: '${entity.entityName}', kind: 'get' as const, id }),`);
417
481
  lines.push(` { url: '${op.path}', method: 'GET' as const },`);
418
482
  lines.push(` ),`);
419
483
  break;
@@ -437,13 +501,13 @@ class EntitySdkGenerator {
437
501
  break;
438
502
  case "update":
439
503
  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)),`);
504
+ lines.push(` (id: string, body: ${inputType}) => createMutationDescriptor('PATCH', \`${op.path.replace(":id", "${id}")}\`, () => client.patch<${outputType}>(\`${op.path.replace(":id", "${id}")}\`, body), { entityType: '${entity.entityName}', kind: 'update' as const, id, body }, optimistic),`);
441
505
  lines.push(` { url: '${op.path}', method: 'PATCH' as const },`);
442
506
  lines.push(` ),`);
443
507
  break;
444
508
  case "delete":
445
509
  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}")}\`)),`);
510
+ lines.push(` (id: string) => createMutationDescriptor('DELETE', \`${op.path.replace(":id", "${id}")}\`, () => client.delete<${outputType}>(\`${op.path.replace(":id", "${id}")}\`), { entityType: '${entity.entityName}', kind: 'delete' as const, id }, optimistic),`);
447
511
  lines.push(` { url: '${op.path}', method: 'DELETE' as const },`);
448
512
  lines.push(` ),`);
449
513
  break;
@@ -769,7 +833,13 @@ function adaptIR(appIR) {
769
833
  resolvedOutputFields
770
834
  };
771
835
  });
772
- return { entityName: entity.name, operations, actions };
836
+ const resolvedRelations = entity.relations.filter((r) => !!r.type && !!r.entity).map((r) => ({ name: r.name, type: r.type, entity: r.entity }));
837
+ return {
838
+ entityName: entity.name,
839
+ operations,
840
+ actions,
841
+ relations: resolvedRelations.length > 0 ? resolvedRelations : undefined
842
+ };
773
843
  });
774
844
  return {
775
845
  basePath: appIR.app.basePath,
@@ -1056,6 +1126,7 @@ export {
1056
1126
  mergeImports,
1057
1127
  jsonSchemaToTS,
1058
1128
  hashContent,
1129
+ generateRelationManifest,
1059
1130
  generate,
1060
1131
  formatWithBiome,
1061
1132
  defineCodegenConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/codegen",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz code generation — internal, no stability guarantee",
@@ -27,7 +27,7 @@
27
27
  "typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
28
28
  },
29
29
  "dependencies": {
30
- "@vertz/compiler": "^0.2.11"
30
+ "@vertz/compiler": "^0.2.12"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.3.1",