@vertz/codegen 0.2.18 → 0.2.19

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
@@ -168,6 +168,17 @@ interface CodegenRelation {
168
168
  type: "one" | "many";
169
169
  entity: string;
170
170
  }
171
+ interface CodegenExposeField {
172
+ name: string;
173
+ conditional: boolean;
174
+ }
175
+ interface CodegenExposeRelation {
176
+ name: string;
177
+ entity: string;
178
+ type: "one" | "many";
179
+ select?: CodegenExposeField[];
180
+ resolvedFields?: CodegenResolvedField[];
181
+ }
171
182
  interface CodegenEntityModule {
172
183
  entityName: string;
173
184
  operations: CodegenEntityOperation[];
@@ -178,6 +189,8 @@ interface CodegenEntityModule {
178
189
  primaryKey?: string;
179
190
  hiddenFields?: string[];
180
191
  responseFields?: CodegenResolvedField[];
192
+ exposeSelect?: CodegenExposeField[];
193
+ exposeInclude?: CodegenExposeRelation[];
181
194
  relationSelections?: Record<string, "all" | string[]>;
182
195
  relationQueryConfig?: Record<string, {
183
196
  allowWhere?: string[];
package/dist/index.js CHANGED
@@ -575,9 +575,17 @@ class EntitySdkGenerator {
575
575
  switch (op.kind) {
576
576
  case "list": {
577
577
  lines.push(` list: Object.assign(`);
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 });`);
578
+ if (op.outputSchema) {
579
+ lines.push(` <K extends keyof ${outputType} = keyof ${outputType}>(`);
580
+ lines.push(` query?: { select?: Record<K, true> } & Record<string, unknown>,`);
581
+ lines.push(` ) => {`);
582
+ lines.push(` const resolvedQuery = resolveVertzQL(query);`);
583
+ lines.push(` return createDescriptor('GET', '${op.path}', () => client.get<ListResponse<Pick<${outputType}, K>>>('${op.path}', { query: resolvedQuery }), resolvedQuery, { entityType: '${entity.entityName}', kind: 'list' as const });`);
584
+ } else {
585
+ lines.push(` (query?: Record<string, unknown>) => {`);
586
+ lines.push(` const resolvedQuery = resolveVertzQL(query);`);
587
+ lines.push(` return createDescriptor('GET', '${op.path}', () => client.get<${listOutput}>('${op.path}', { query: resolvedQuery }), resolvedQuery, { entityType: '${entity.entityName}', kind: 'list' as const });`);
588
+ }
581
589
  lines.push(` },`);
582
590
  lines.push(` { url: '${op.path}', method: 'GET' as const },`);
583
591
  lines.push(` ),`);
@@ -586,9 +594,17 @@ class EntitySdkGenerator {
586
594
  case "get": {
587
595
  const getPathExpr = `\`${op.path.replace(":id", "${id}")}\``;
588
596
  lines.push(` get: Object.assign(`);
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 });`);
597
+ if (op.outputSchema) {
598
+ lines.push(` <K extends keyof ${outputType} = keyof ${outputType}>(`);
599
+ lines.push(` id: string, options?: { select?: Record<K, true> },`);
600
+ lines.push(` ) => {`);
601
+ lines.push(` const resolvedQuery = resolveVertzQL(options);`);
602
+ lines.push(` return createDescriptor('GET', ${getPathExpr}, () => client.get<Pick<${outputType}, K>>(${getPathExpr}, { query: resolvedQuery }), resolvedQuery, { entityType: '${entity.entityName}', kind: 'get' as const, id });`);
603
+ } else {
604
+ lines.push(` (id: string, options?: { select?: Record<string, true> }) => {`);
605
+ lines.push(` const resolvedQuery = resolveVertzQL(options);`);
606
+ lines.push(` return createDescriptor('GET', ${getPathExpr}, () => client.get<${outputType}>(${getPathExpr}, { query: resolvedQuery }), resolvedQuery, { entityType: '${entity.entityName}', kind: 'get' as const, id });`);
607
+ }
592
608
  lines.push(` },`);
593
609
  lines.push(` { url: '${op.path}', method: 'GET' as const },`);
594
610
  lines.push(` ),`);
@@ -711,7 +727,7 @@ class EntityTypesGenerator {
711
727
  }
712
728
  }
713
729
  if (op.outputSchema && !emitted.has(op.outputSchema)) {
714
- const outputType = this.emitResponseType(op.outputSchema, op.responseFields);
730
+ const outputType = this.emitResponseType(op.outputSchema, op.responseFields, entity);
715
731
  if (outputType) {
716
732
  lines.push(outputType);
717
733
  lines.push("");
@@ -719,6 +735,13 @@ class EntityTypesGenerator {
719
735
  }
720
736
  }
721
737
  }
738
+ const responseSchema = entity.operations.find((op) => (op.kind === "list" || op.kind === "get") && op.outputSchema)?.outputSchema;
739
+ if (responseSchema && !emitted.has(`${responseSchema}Fields`)) {
740
+ const fieldsTypeName = responseSchema.replace(/Response$/, "Fields");
741
+ lines.push(`export type ${fieldsTypeName} = keyof ${responseSchema};`);
742
+ lines.push("");
743
+ emitted.add(`${responseSchema}Fields`);
744
+ }
722
745
  for (const action of entity.actions) {
723
746
  if (action.inputSchema && !emitted.has(action.inputSchema)) {
724
747
  const inputType = this.emitBodyType(action.inputSchema, action.resolvedInputFields);
@@ -729,7 +752,7 @@ class EntityTypesGenerator {
729
752
  }
730
753
  }
731
754
  if (action.outputSchema && !emitted.has(action.outputSchema)) {
732
- const outputType = this.emitResponseType(action.outputSchema, action.resolvedOutputFields);
755
+ const outputType = this.emitBodyType(action.outputSchema, action.resolvedOutputFields);
733
756
  if (outputType) {
734
757
  lines.push(outputType);
735
758
  lines.push("");
@@ -756,17 +779,38 @@ class EntityTypesGenerator {
756
779
  ${props};
757
780
  }`;
758
781
  }
759
- emitResponseType(typeName, fields) {
782
+ emitResponseType(typeName, fields, entity) {
760
783
  if (!fields || fields.length === 0)
761
784
  return;
785
+ const conditionalFields = new Set(entity.exposeSelect?.filter((f) => f.conditional).map((f) => f.name) ?? []);
762
786
  const props = fields.map((f) => {
763
787
  const tsType = TS_TYPE_MAP[f.tsType] ?? "unknown";
764
788
  const optional = f.optional ? "?" : "";
765
- return ` ${f.name}${optional}: ${tsType}`;
789
+ const nullable = conditionalFields.has(f.name) ? " | null" : "";
790
+ return ` ${f.name}${optional}: ${tsType}${nullable}`;
766
791
  }).join(`;
767
792
  `);
793
+ const relationProps = [];
794
+ if (entity.exposeInclude) {
795
+ for (const rel of entity.exposeInclude) {
796
+ if (!rel.resolvedFields || rel.resolvedFields.length === 0)
797
+ continue;
798
+ const relFields = rel.resolvedFields.map((f) => {
799
+ const tsType = TS_TYPE_MAP[f.tsType] ?? "unknown";
800
+ return `${f.name}: ${tsType}`;
801
+ }).join("; ");
802
+ if (rel.type === "many") {
803
+ relationProps.push(` ${rel.name}: Array<{ ${relFields} }>`);
804
+ } else {
805
+ relationProps.push(` ${rel.name}?: { ${relFields} }`);
806
+ }
807
+ }
808
+ }
809
+ const allProps = relationProps.length > 0 ? `${props};
810
+ ${relationProps.join(`;
811
+ `)}` : props;
768
812
  return `export interface ${typeName} {
769
- ${props};
813
+ ${allProps};
770
814
  }`;
771
815
  }
772
816
  generateIndex(entities) {
@@ -1038,6 +1082,19 @@ function adaptIR(appIR) {
1038
1082
  };
1039
1083
  });
1040
1084
  const allSchemas = [...schemas].sort((a, b) => a.name.localeCompare(b.name));
1085
+ const entityResponseFieldsMap = new Map;
1086
+ for (const entity of appIR.entities ?? []) {
1087
+ const respRef = entity.modelRef.schemaRefs.response;
1088
+ if (respRef?.kind === "inline") {
1089
+ const fields = respRef.resolvedFields?.map((f) => ({
1090
+ name: f.name,
1091
+ tsType: f.tsType,
1092
+ optional: f.optional
1093
+ }));
1094
+ if (fields)
1095
+ entityResponseFieldsMap.set(entity.name, fields);
1096
+ }
1097
+ }
1041
1098
  const entities = (appIR.entities ?? []).map((entity) => {
1042
1099
  const entityPascal = toPascalCase(entity.name);
1043
1100
  const operations = [];
@@ -1136,6 +1193,51 @@ function adaptIR(appIR) {
1136
1193
  optional: f.optional
1137
1194
  }));
1138
1195
  }
1196
+ let exposeSelect;
1197
+ let exposeInclude;
1198
+ if (entity.expose) {
1199
+ exposeSelect = entity.expose.select.map((f) => ({
1200
+ name: f.name,
1201
+ conditional: f.conditional
1202
+ }));
1203
+ if (entityResponseFields) {
1204
+ const exposedNames = new Set(entity.expose.select.map((f) => f.name));
1205
+ const hiddenNames = new Set(entity.modelRef.hiddenFields ?? []);
1206
+ entityResponseFields = entityResponseFields.filter((f) => exposedNames.has(f.name) && !hiddenNames.has(f.name));
1207
+ for (const op of operations) {
1208
+ if (op.responseFields) {
1209
+ op.responseFields = op.responseFields.filter((f) => exposedNames.has(f.name) && !hiddenNames.has(f.name));
1210
+ }
1211
+ }
1212
+ }
1213
+ if (entity.expose.include && entity.expose.include.length > 0) {
1214
+ const relationsLookup = new Map(entity.relations.filter((r) => r.type && r.entity).map((r) => [r.name, { type: r.type, entity: r.entity }]));
1215
+ const resolved = [];
1216
+ for (const rel of entity.expose.include) {
1217
+ const relEntity = rel.entity ?? relationsLookup.get(rel.name)?.entity;
1218
+ const relType = rel.type ?? relationsLookup.get(rel.name)?.type;
1219
+ if (!relEntity || !relType)
1220
+ continue;
1221
+ const targetFields = entityResponseFieldsMap.get(relEntity);
1222
+ let resolvedFields;
1223
+ if (targetFields && rel.select) {
1224
+ const selectedNames = new Set(rel.select.map((f) => f.name));
1225
+ resolvedFields = targetFields.filter((f) => selectedNames.has(f.name));
1226
+ } else if (targetFields) {
1227
+ resolvedFields = targetFields;
1228
+ }
1229
+ resolved.push({
1230
+ name: rel.name,
1231
+ entity: relEntity,
1232
+ type: relType,
1233
+ ...rel.select ? { select: rel.select.map((f) => ({ name: f.name, conditional: f.conditional })) } : {},
1234
+ ...resolvedFields ? { resolvedFields } : {}
1235
+ });
1236
+ }
1237
+ if (resolved.length > 0)
1238
+ exposeInclude = resolved;
1239
+ }
1240
+ }
1139
1241
  return {
1140
1242
  entityName: entity.name,
1141
1243
  operations,
@@ -1146,6 +1248,8 @@ function adaptIR(appIR) {
1146
1248
  primaryKey: entity.modelRef.primaryKey,
1147
1249
  hiddenFields: entity.modelRef.hiddenFields,
1148
1250
  responseFields: entityResponseFields,
1251
+ exposeSelect,
1252
+ exposeInclude,
1149
1253
  relationSelections: Object.keys(relationSelections).length > 0 ? relationSelections : undefined,
1150
1254
  relationQueryConfig: Object.keys(relationQueryConfig).length > 0 ? relationQueryConfig : undefined
1151
1255
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/codegen",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
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.17"
30
+ "@vertz/compiler": "^0.2.18"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.3.1",