@vertz/codegen 0.2.15 → 0.2.17

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
@@ -16,6 +16,8 @@ interface CodegenTypescriptConfig {
16
16
  publishable?: CodegenPublishableConfig;
17
17
  /** Augmentable types for customer-specific type narrowing */
18
18
  augmentableTypes?: string[];
19
+ /** Generate PostgreSQL RLS policies from rules.where() conditions. Default: false */
20
+ rls?: boolean;
19
21
  }
20
22
  interface CodegenConfig {
21
23
  /** Generators to run. Default: ['typescript'] */
@@ -48,7 +50,30 @@ interface CodegenIR {
48
50
  schemas: CodegenSchema[];
49
51
  entities: CodegenEntityModule[];
50
52
  auth: CodegenAuth;
53
+ access?: CodegenAccess;
51
54
  }
55
+ interface CodegenAccess {
56
+ entities: CodegenAccessEntity[];
57
+ entitlements: string[];
58
+ whereClauses: CodegenWhereClause[];
59
+ }
60
+ interface CodegenAccessEntity {
61
+ name: string;
62
+ roles: string[];
63
+ }
64
+ interface CodegenWhereClause {
65
+ entitlement: string;
66
+ conditions: CodegenWhereCondition[];
67
+ }
68
+ type CodegenWhereCondition = {
69
+ kind: "marker";
70
+ column: string;
71
+ marker: "user.id" | "user.tenantId";
72
+ } | {
73
+ kind: "literal";
74
+ column: string;
75
+ value: string | number | boolean;
76
+ };
52
77
  interface CodegenModule {
53
78
  name: string;
54
79
  operations: CodegenOperation[];
@@ -148,6 +173,17 @@ interface CodegenEntityModule {
148
173
  operations: CodegenEntityOperation[];
149
174
  actions: CodegenEntityAction[];
150
175
  relations?: CodegenRelation[];
176
+ tenantScoped?: boolean;
177
+ table?: string;
178
+ primaryKey?: string;
179
+ hiddenFields?: string[];
180
+ responseFields?: CodegenResolvedField[];
181
+ relationSelections?: Record<string, "all" | string[]>;
182
+ relationQueryConfig?: Record<string, {
183
+ allowWhere?: string[];
184
+ allowOrderBy?: string[];
185
+ maxLimit?: number;
186
+ }>;
151
187
  }
152
188
  interface CodegenEntityOperation {
153
189
  kind: "list" | "get" | "create" | "update" | "delete";
@@ -265,6 +301,23 @@ declare class EntitySchemaGenerator implements Generator {
265
301
  private generateEntitySchema;
266
302
  private generateIndex;
267
303
  }
304
+ interface EntitySchemaRelation {
305
+ type: "one" | "many";
306
+ entity: string;
307
+ selection: "all" | string[];
308
+ allowWhere: string[];
309
+ allowOrderBy: string[];
310
+ maxLimit?: number;
311
+ }
312
+ interface EntitySchemaManifestEntry {
313
+ table?: string;
314
+ primaryKey?: string;
315
+ tenantScoped: boolean;
316
+ hiddenFields: string[];
317
+ fields: string[];
318
+ relations: Record<string, EntitySchemaRelation>;
319
+ }
320
+ type EntitySchemaManifest = Record<string, EntitySchemaManifestEntry>;
268
321
  declare class EntitySdkGenerator implements Generator {
269
322
  readonly name = "entity-sdk";
270
323
  generate(ir: CodegenIR, _config: GeneratorConfig): GeneratedFile[];
@@ -316,4 +369,4 @@ declare function toPascalCase(input: string): string;
316
369
  declare function toCamelCase(input: string): string;
317
370
  declare function toKebabCase(input: string): string;
318
371
  declare function toSnakeCase(input: string): string;
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 };
372
+ 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, EntitySchemaRelation, EntitySchemaManifestEntry, EntitySchemaManifest, EntitySchemaGenerator, ConversionResult, ConversionContext, CodegenTypescriptConfig, CodegenSchema, CodegenResolvedField, CodegenRelation, CodegenPublishableConfig, CodegenPipeline, CodegenOperation, CodegenModule, CodegenIR, CodegenEntityOperation, CodegenEntityModule, CodegenEntityAction, CodegenConfig, CodegenAuthScheme, CodegenAuth, ClientGenerator };
package/dist/index.js CHANGED
@@ -118,6 +118,62 @@ async function formatWithBiome(files) {
118
118
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
119
119
  import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
120
120
 
121
+ // src/generators/access-types-generator.ts
122
+ var FILE_HEADER = `// Generated by @vertz/codegen — do not edit
123
+
124
+ `;
125
+
126
+ class AccessTypesGenerator {
127
+ name = "access-types";
128
+ generate(ir, _config) {
129
+ if (!ir.access || ir.access.entitlements.length === 0) {
130
+ return [];
131
+ }
132
+ return [
133
+ {
134
+ path: "access.d.ts",
135
+ content: renderAccessTypes(ir.access)
136
+ }
137
+ ];
138
+ }
139
+ }
140
+ function escapeStringLiteral(s) {
141
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
142
+ }
143
+ function renderAccessTypes(access) {
144
+ const lines = [FILE_HEADER];
145
+ const registryEntries = access.entitlements.map((e) => ` '${escapeStringLiteral(e)}': true;`).join(`
146
+ `);
147
+ lines.push("declare module '@vertz/server' {");
148
+ lines.push(" interface EntitlementRegistry {");
149
+ lines.push(registryEntries);
150
+ lines.push(" }");
151
+ lines.push("}");
152
+ lines.push("");
153
+ lines.push("declare module '@vertz/ui/auth' {");
154
+ lines.push(" interface EntitlementRegistry {");
155
+ lines.push(registryEntries);
156
+ lines.push(" }");
157
+ lines.push("}");
158
+ lines.push("");
159
+ const entitiesWithRoles = access.entities.filter((e) => e.roles.length > 0);
160
+ if (entitiesWithRoles.length > 0) {
161
+ const resourceUnion = entitiesWithRoles.map((e) => `'${escapeStringLiteral(e.name)}'`).join(" | ");
162
+ lines.push(`type ResourceType = ${resourceUnion};`);
163
+ lines.push("");
164
+ lines.push("type Role<T extends ResourceType> =");
165
+ entitiesWithRoles.forEach((entity, i) => {
166
+ const rolesUnion = entity.roles.map((r) => `'${escapeStringLiteral(r)}'`).join(" | ");
167
+ const prefix = i === 0 ? " " : " : ";
168
+ lines.push(`${prefix}T extends '${escapeStringLiteral(entity.name)}' ? ${rolesUnion}`);
169
+ });
170
+ lines.push(" : never;");
171
+ lines.push("");
172
+ }
173
+ return lines.join(`
174
+ `);
175
+ }
176
+
121
177
  // src/generators/client-generator.ts
122
178
  import { posix } from "node:path";
123
179
 
@@ -151,7 +207,7 @@ function generateRelationManifest(entities) {
151
207
  }
152
208
 
153
209
  // src/generators/client-generator.ts
154
- var FILE_HEADER = `// Generated by @vertz/codegen — do not edit
210
+ var FILE_HEADER2 = `// Generated by @vertz/codegen — do not edit
155
211
 
156
212
  `;
157
213
 
@@ -166,10 +222,10 @@ class ClientGenerator {
166
222
  }
167
223
  generateClient(ir) {
168
224
  const entities = ir.entities ?? [];
169
- const hasMutations = entities.some((e) => e.operations.some((op) => op.kind === "update" || op.kind === "delete"));
225
+ const hasMutations = entities.some((e) => e.operations.some((op) => op.kind === "create" || op.kind === "update" || op.kind === "delete"));
170
226
  const manifest = generateRelationManifest(entities);
171
227
  const hasRelations = manifest.some((entry) => Object.keys(entry.schema).length > 0);
172
- const lines = [FILE_HEADER];
228
+ const lines = [FILE_HEADER2];
173
229
  if (entities.length > 0) {
174
230
  if (hasMutations) {
175
231
  lines.push("import { FetchClient, type OptimisticHandler } from '@vertz/fetch';");
@@ -224,7 +280,7 @@ class ClientGenerator {
224
280
  for (const entity of entities) {
225
281
  const pascal = toPascalCase(entity.entityName);
226
282
  const camel = toCamelCase(entity.entityName);
227
- const entityHasMutations = entity.operations.some((op) => op.kind === "update" || op.kind === "delete");
283
+ const entityHasMutations = entity.operations.some((op) => op.kind === "create" || op.kind === "update" || op.kind === "delete");
228
284
  if (entityHasMutations) {
229
285
  lines.push(` ${camel}: create${pascal}Sdk(client, optimistic),`);
230
286
  } else {
@@ -299,7 +355,7 @@ class ClientGenerator {
299
355
  }
300
356
 
301
357
  // src/generators/entity-schema-generator.ts
302
- var FILE_HEADER2 = `// Generated by @vertz/codegen — do not edit
358
+ var FILE_HEADER3 = `// Generated by @vertz/codegen — do not edit
303
359
 
304
360
  `;
305
361
  var TYPE_MAP = {
@@ -334,7 +390,7 @@ class EntitySchemaGenerator {
334
390
  return files;
335
391
  }
336
392
  generateEntitySchema(entity, schemaOps, schemaActions) {
337
- const lines = [FILE_HEADER2];
393
+ const lines = [FILE_HEADER3];
338
394
  lines.push("import { s } from '@vertz/schema';");
339
395
  lines.push("");
340
396
  for (const op of schemaOps) {
@@ -374,7 +430,7 @@ class EntitySchemaGenerator {
374
430
  };
375
431
  }
376
432
  generateIndex(entities) {
377
- const lines = [FILE_HEADER2];
433
+ const lines = [FILE_HEADER3];
378
434
  for (const entity of entities) {
379
435
  const exports = [];
380
436
  const schemaOps = entity.operations.filter((op) => (op.kind === "create" || op.kind === "update") && op.resolvedFields && op.resolvedFields.length > 0);
@@ -396,8 +452,50 @@ class EntitySchemaGenerator {
396
452
  }
397
453
  }
398
454
 
455
+ // src/generators/entity-schema-manifest-generator.ts
456
+ function buildManifestEntry(entity) {
457
+ const fields = (entity.responseFields ?? []).map((f) => f.name);
458
+ const relations = {};
459
+ for (const rel of entity.relations ?? []) {
460
+ const selection = entity.relationSelections?.[rel.name] ?? "all";
461
+ const qc = entity.relationQueryConfig?.[rel.name];
462
+ relations[rel.name] = {
463
+ type: rel.type,
464
+ entity: rel.entity,
465
+ selection,
466
+ allowWhere: qc?.allowWhere ?? [],
467
+ allowOrderBy: qc?.allowOrderBy ?? [],
468
+ ...qc?.maxLimit !== undefined ? { maxLimit: qc.maxLimit } : {}
469
+ };
470
+ }
471
+ return {
472
+ ...entity.table !== undefined ? { table: entity.table } : {},
473
+ ...entity.primaryKey !== undefined ? { primaryKey: entity.primaryKey } : {},
474
+ tenantScoped: entity.tenantScoped ?? false,
475
+ hiddenFields: entity.hiddenFields ?? [],
476
+ fields,
477
+ relations
478
+ };
479
+ }
480
+
481
+ class EntitySchemaManifestGenerator {
482
+ name = "entity-schema-manifest";
483
+ generate(ir, _config) {
484
+ const manifest = {};
485
+ for (const entity of ir.entities) {
486
+ manifest[entity.entityName] = buildManifestEntry(entity);
487
+ }
488
+ return [
489
+ {
490
+ path: "entity-schema.json",
491
+ content: JSON.stringify(manifest, null, 2)
492
+ }
493
+ ];
494
+ }
495
+ }
496
+
399
497
  // src/generators/entity-sdk-generator.ts
400
- var FILE_HEADER3 = `// Generated by @vertz/codegen — do not edit
498
+ var FILE_HEADER4 = `// Generated by @vertz/codegen — do not edit
401
499
 
402
500
  `;
403
501
  function toPascalCase2(s) {
@@ -418,7 +516,7 @@ class EntitySdkGenerator {
418
516
  }
419
517
  generateEntitySdk(entity, _basePath) {
420
518
  const pascal = toPascalCase2(entity.entityName);
421
- const lines = [FILE_HEADER3];
519
+ const lines = [FILE_HEADER4];
422
520
  const createOpsWithMeta = entity.operations.filter((op) => op.kind === "create" && op.resolvedFields && op.resolvedFields.length > 0);
423
521
  const hasSchemaImports = createOpsWithMeta.length > 0;
424
522
  if (hasSchemaImports) {
@@ -431,7 +529,7 @@ class EntitySdkGenerator {
431
529
  }
432
530
  const hasTypes = entity.operations.some((op) => op.outputSchema || op.inputSchema);
433
531
  const hasListOp = entity.operations.some((op) => op.kind === "list");
434
- const hasMutationOp = entity.operations.some((op) => op.kind === "update" || op.kind === "delete");
532
+ const hasMutationOp = entity.operations.some((op) => op.kind === "create" || op.kind === "update" || op.kind === "delete");
435
533
  if (hasTypes) {
436
534
  const typeImports = new Set;
437
535
  for (const op of entity.operations) {
@@ -447,6 +545,10 @@ class EntitySdkGenerator {
447
545
  typeImports.add(action.outputSchema);
448
546
  }
449
547
  lines.push(`import type { ${[...typeImports].join(", ")} } from '../types/${entity.entityName}';`);
548
+ }
549
+ const hasReadOp = entity.operations.some((op) => op.kind === "list" || op.kind === "get");
550
+ const needsFetchImport = hasTypes || hasMutationOp || hasListOp;
551
+ if (needsFetchImport) {
450
552
  const fetchImportParts = ["type FetchClient"];
451
553
  if (hasListOp)
452
554
  fetchImportParts.push("type ListResponse");
@@ -455,6 +557,8 @@ class EntitySdkGenerator {
455
557
  fetchImportParts.push("createDescriptor");
456
558
  if (hasMutationOp)
457
559
  fetchImportParts.push("createMutationDescriptor");
560
+ if (hasReadOp)
561
+ fetchImportParts.push("resolveVertzQL");
458
562
  lines.push(`import { ${fetchImportParts.join(", ")} } from '@vertz/fetch';`);
459
563
  lines.push("");
460
564
  }
@@ -469,23 +573,32 @@ class EntitySdkGenerator {
469
573
  const outputType = op.outputSchema ?? "unknown";
470
574
  const listOutput = op.kind === "list" ? `ListResponse<${outputType}>` : outputType;
471
575
  switch (op.kind) {
472
- case "list":
576
+ case "list": {
473
577
  lines.push(` list: Object.assign(`);
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 }),`);
578
+ lines.push(` (query?: Record<string, unknown>) => {`);
579
+ lines.push(` const resolvedQuery = resolveVertzQL(query);`);
580
+ lines.push(` return createDescriptor('GET', '${op.path}', () => client.get<${listOutput}>('${op.path}', { query: resolvedQuery }), resolvedQuery, { entityType: '${entity.entityName}', kind: 'list' as const });`);
581
+ lines.push(` },`);
475
582
  lines.push(` { url: '${op.path}', method: 'GET' as const },`);
476
583
  lines.push(` ),`);
477
584
  break;
478
- case "get":
585
+ }
586
+ case "get": {
587
+ const getPathExpr = `\`${op.path.replace(":id", "${id}")}\``;
479
588
  lines.push(` get: Object.assign(`);
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 }),`);
589
+ lines.push(` (id: string, options?: { select?: Record<string, true> }) => {`);
590
+ lines.push(` const resolvedQuery = resolveVertzQL(options);`);
591
+ lines.push(` return createDescriptor('GET', ${getPathExpr}, () => client.get<${outputType}>(${getPathExpr}, { query: resolvedQuery }), resolvedQuery, { entityType: '${entity.entityName}', kind: 'get' as const, id });`);
592
+ lines.push(` },`);
481
593
  lines.push(` { url: '${op.path}', method: 'GET' as const },`);
482
594
  lines.push(` ),`);
483
595
  break;
596
+ }
484
597
  case "create":
485
598
  if (op.resolvedFields && op.resolvedFields.length > 0) {
486
599
  const schemaVarName = `${(op.inputSchema ?? "createInput").charAt(0).toLowerCase()}${(op.inputSchema ?? "createInput").slice(1)}Schema`;
487
600
  lines.push(` create: Object.assign(`);
488
- lines.push(` (body: ${inputType}) => createDescriptor('POST', '${op.path}', () => client.post<${outputType}>('${op.path}', body)),`);
601
+ lines.push(` (body: ${inputType}) => createMutationDescriptor('POST', '${op.path}', () => client.post<${outputType}>('${op.path}', body), { entityType: '${entity.entityName}', kind: 'create' as const, body }, optimistic),`);
489
602
  lines.push(` {`);
490
603
  lines.push(` url: '${op.path}',`);
491
604
  lines.push(` method: 'POST' as const,`);
@@ -494,7 +607,7 @@ class EntitySdkGenerator {
494
607
  lines.push(` ),`);
495
608
  } else {
496
609
  lines.push(` create: Object.assign(`);
497
- lines.push(` (body: ${inputType}) => createDescriptor('POST', '${op.path}', () => client.post<${outputType}>('${op.path}', body)),`);
610
+ lines.push(` (body: ${inputType}) => createMutationDescriptor('POST', '${op.path}', () => client.post<${outputType}>('${op.path}', body), { entityType: '${entity.entityName}', kind: 'create' as const, body }, optimistic),`);
498
611
  lines.push(` { url: '${op.path}', method: 'POST' as const },`);
499
612
  lines.push(` ),`);
500
613
  }
@@ -543,7 +656,7 @@ class EntitySdkGenerator {
543
656
  };
544
657
  }
545
658
  generateIndex(entities) {
546
- const lines = [FILE_HEADER3];
659
+ const lines = [FILE_HEADER4];
547
660
  for (const entity of entities) {
548
661
  const pascal = toPascalCase2(entity.entityName);
549
662
  lines.push(`export { create${pascal}Sdk } from './${entity.entityName}';`);
@@ -554,7 +667,7 @@ class EntitySdkGenerator {
554
667
  }
555
668
 
556
669
  // src/generators/entity-types-generator.ts
557
- var FILE_HEADER4 = `// Generated by @vertz/codegen — do not edit
670
+ var FILE_HEADER5 = `// Generated by @vertz/codegen — do not edit
558
671
 
559
672
  `;
560
673
  var TS_TYPE_MAP = {
@@ -586,7 +699,7 @@ class EntityTypesGenerator {
586
699
  return files;
587
700
  }
588
701
  generateEntityTypes(entity) {
589
- const lines = [FILE_HEADER4];
702
+ const lines = [FILE_HEADER5];
590
703
  const emitted = new Set;
591
704
  for (const op of entity.operations) {
592
705
  if (op.inputSchema && !emitted.has(op.inputSchema)) {
@@ -657,7 +770,7 @@ ${props};
657
770
  }`;
658
771
  }
659
772
  generateIndex(entities) {
660
- const lines = [FILE_HEADER4];
773
+ const lines = [FILE_HEADER5];
661
774
  for (const entity of entities) {
662
775
  lines.push(`export * from './${entity.entityName}';`);
663
776
  }
@@ -666,10 +779,68 @@ ${props};
666
779
  }
667
780
  }
668
781
 
782
+ // src/generators/rls-policy-generator.ts
783
+ var FILE_HEADER6 = `-- Generated by @vertz/codegen — do not edit
784
+
785
+ `;
786
+ var VALID_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
787
+
788
+ class RlsPolicyGenerator {
789
+ name = "rls-policies";
790
+ generate(ir, _config) {
791
+ if (!ir.access || ir.access.whereClauses.length === 0) {
792
+ return [];
793
+ }
794
+ return [
795
+ {
796
+ path: "rls-policies.sql",
797
+ content: renderPolicies(ir.access)
798
+ }
799
+ ];
800
+ }
801
+ }
802
+ function quoteIdentifier(name) {
803
+ if (!VALID_IDENTIFIER.test(name)) {
804
+ return `"${name.replace(/"/g, '""')}"`;
805
+ }
806
+ return name;
807
+ }
808
+ function renderCondition(condition) {
809
+ const column = quoteIdentifier(toSnakeCase(condition.column));
810
+ if (condition.kind === "marker") {
811
+ if (condition.marker === "user.id") {
812
+ return `${column} = current_setting('app.user_id')::UUID`;
813
+ }
814
+ return `${column} = current_setting('app.tenant_id')::UUID`;
815
+ }
816
+ if (typeof condition.value === "string") {
817
+ const escaped = condition.value.replace(/'/g, "''");
818
+ return `${column} = '${escaped}'`;
819
+ }
820
+ return `${column} = ${condition.value}`;
821
+ }
822
+ function renderPolicies(access) {
823
+ const lines = [FILE_HEADER6];
824
+ for (const clause of access.whereClauses) {
825
+ const colonIdx = clause.entitlement.indexOf(":");
826
+ const entity = colonIdx >= 0 ? clause.entitlement.slice(0, colonIdx) : clause.entitlement;
827
+ const action = colonIdx >= 0 ? clause.entitlement.slice(colonIdx + 1) : "";
828
+ const tableName = quoteIdentifier(`${toSnakeCase(entity)}s`);
829
+ const policyName = quoteIdentifier(`${toSnakeCase(entity)}_${toSnakeCase(action)}`.replace(/^_+|_+$/g, ""));
830
+ lines.push(`-- Entitlement: ${clause.entitlement}`);
831
+ const using = clause.conditions.map(renderCondition).join(" AND ");
832
+ lines.push(`CREATE POLICY ${policyName} ON ${tableName} FOR ALL`);
833
+ lines.push(` USING (${using});`);
834
+ lines.push("");
835
+ }
836
+ return lines.join(`
837
+ `);
838
+ }
839
+
669
840
  // src/generators/router-augmentation-generator.ts
670
841
  import { existsSync, readdirSync, readFileSync } from "node:fs";
671
842
  import { dirname as dirname2, extname, join as join2, relative, resolve as resolve2, sep } from "node:path";
672
- var FILE_HEADER5 = `// Generated by @vertz/codegen — do not edit
843
+ var FILE_HEADER7 = `// Generated by @vertz/codegen — do not edit
673
844
 
674
845
  `;
675
846
  var CANDIDATE_ROUTE_FILES = [
@@ -753,7 +924,7 @@ function fileExportsRoutes(filePath) {
753
924
  function renderRouterAugmentation(routeModulePath, outputDir) {
754
925
  const importPath = toImportPath(relative(resolve2(outputDir), routeModulePath));
755
926
  return [
756
- FILE_HEADER5,
927
+ FILE_HEADER7,
757
928
  "import type { InferRouteMap, TypedRouter, UnwrapSignals } from '@vertz/ui';",
758
929
  `import type { routes } from '${importPath}';`,
759
930
  "",
@@ -944,20 +1115,57 @@ function adaptIR(appIR) {
944
1115
  };
945
1116
  });
946
1117
  const resolvedRelations = entity.relations.filter((r) => !!r.type && !!r.entity).map((r) => ({ name: r.name, type: r.type, entity: r.entity }));
1118
+ const relationSelections = {};
1119
+ const relationQueryConfig = {};
1120
+ for (const rel of entity.relations) {
1121
+ relationSelections[rel.name] = rel.selection;
1122
+ if (rel.allowWhere || rel.allowOrderBy || rel.maxLimit !== undefined) {
1123
+ relationQueryConfig[rel.name] = {
1124
+ ...rel.allowWhere ? { allowWhere: rel.allowWhere } : {},
1125
+ ...rel.allowOrderBy ? { allowOrderBy: rel.allowOrderBy } : {},
1126
+ ...rel.maxLimit !== undefined ? { maxLimit: rel.maxLimit } : {}
1127
+ };
1128
+ }
1129
+ }
1130
+ let entityResponseFields;
1131
+ const respRef = entity.modelRef.schemaRefs.response;
1132
+ if (respRef?.kind === "inline") {
1133
+ entityResponseFields = respRef.resolvedFields?.map((f) => ({
1134
+ name: f.name,
1135
+ tsType: f.tsType,
1136
+ optional: f.optional
1137
+ }));
1138
+ }
947
1139
  return {
948
1140
  entityName: entity.name,
949
1141
  operations,
950
1142
  actions,
951
- relations: resolvedRelations.length > 0 ? resolvedRelations : undefined
1143
+ relations: resolvedRelations.length > 0 ? resolvedRelations : undefined,
1144
+ tenantScoped: entity.tenantScoped,
1145
+ table: entity.table,
1146
+ primaryKey: entity.modelRef.primaryKey,
1147
+ hiddenFields: entity.modelRef.hiddenFields,
1148
+ responseFields: entityResponseFields,
1149
+ relationSelections: Object.keys(relationSelections).length > 0 ? relationSelections : undefined,
1150
+ relationQueryConfig: Object.keys(relationQueryConfig).length > 0 ? relationQueryConfig : undefined
952
1151
  };
953
1152
  });
1153
+ const access = appIR.access ? {
1154
+ entities: appIR.access.entities.map((e) => ({ name: e.name, roles: e.roles })),
1155
+ entitlements: appIR.access.entitlements,
1156
+ whereClauses: (appIR.access.whereClauses ?? []).map((wc) => ({
1157
+ entitlement: wc.entitlement,
1158
+ conditions: wc.conditions.map((c) => ({ ...c }))
1159
+ }))
1160
+ } : undefined;
954
1161
  return {
955
1162
  basePath: appIR.app.basePath,
956
1163
  version: appIR.app.version,
957
1164
  modules: [],
958
1165
  schemas: allSchemas,
959
1166
  entities,
960
- auth: { schemes: [] }
1167
+ auth: { schemes: [] },
1168
+ access
961
1169
  };
962
1170
  }
963
1171
 
@@ -975,6 +1183,14 @@ function runTypescriptGenerator(ir, _config) {
975
1183
  files.push(...clientGen.generate(ir, generatorConfig));
976
1184
  const routerAugmentationGen = new RouterAugmentationGenerator;
977
1185
  files.push(...routerAugmentationGen.generate(ir, generatorConfig));
1186
+ const accessTypesGen = new AccessTypesGenerator;
1187
+ files.push(...accessTypesGen.generate(ir, generatorConfig));
1188
+ const entitySchemaManifestGen = new EntitySchemaManifestGenerator;
1189
+ files.push(...entitySchemaManifestGen.generate(ir, generatorConfig));
1190
+ if (_config.typescript?.rls) {
1191
+ const rlsPolicyGen = new RlsPolicyGenerator;
1192
+ files.push(...rlsPolicyGen.generate(ir, generatorConfig));
1193
+ }
978
1194
  return files;
979
1195
  }
980
1196
  function generateSync(ir, config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/codegen",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
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.14"
30
+ "@vertz/compiler": "^0.2.16"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.3.1",