From 50162aced07f6e4d37340a64510bcee4c197929d Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 28 Mar 2026 02:50:46 +0000 Subject: [PATCH 1/4] fix(codegen): cast parseFindManyArgs/parseFindFirstArgs to any for ORM type compatibility The ORM's findMany/findFirst methods require typed args with a required 'select' property, but the CLI runtime helpers return Record. Adding 'as any' casts in the generated code prevents TS2345 errors when the generated CLI is compiled against the typed ORM client. --- .../__snapshots__/cli-generator.test.ts.snap | 20 +++++++++---------- .../codegen/cli/table-command-generator.ts | 12 +++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap index 3787efdea..5eac1db6d 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap @@ -758,7 +758,7 @@ async function handleList(argv: Partial>, _prompter: Inq }; const findManyArgs = parseFindManyArgs(argv, defaultSelect); const client = getClient(); - const result = await client.car.findMany(findManyArgs).execute(); + const result = await client.car.findMany(findManyArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -780,7 +780,7 @@ async function handleFindFirst(argv: Partial>, _prompter }; const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); const client = getClient(); - const result = await client.car.findFirst(findFirstArgs).execute(); + const result = await client.car.findFirst(findFirstArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -1215,7 +1215,7 @@ async function handleList(argv: Partial>, _prompter: Inq }; const findManyArgs = parseFindManyArgs(argv, defaultSelect); const client = getClient(); - const result = await client.driver.findMany(findManyArgs).execute(); + const result = await client.driver.findMany(findManyArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -1234,7 +1234,7 @@ async function handleFindFirst(argv: Partial>, _prompter }; const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); const client = getClient(); - const result = await client.driver.findFirst(findFirstArgs).execute(); + const result = await client.driver.findFirst(findFirstArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -3246,7 +3246,7 @@ async function handleList(argv: Partial>, _prompter: Inq }; const findManyArgs = parseFindManyArgs(argv, defaultSelect); const client = getClient("auth"); - const result = await client.user.findMany(findManyArgs).execute(); + const result = await client.user.findMany(findManyArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -3265,7 +3265,7 @@ async function handleFindFirst(argv: Partial>, _prompter }; const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); const client = getClient("auth"); - const result = await client.user.findFirst(findFirstArgs).execute(); + const result = await client.user.findFirst(findFirstArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -3473,7 +3473,7 @@ async function handleList(argv: Partial>, _prompter: Inq }; const findManyArgs = parseFindManyArgs(argv, defaultSelect); const client = getClient("members"); - const result = await client.member.findMany(findManyArgs).execute(); + const result = await client.member.findMany(findManyArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -3491,7 +3491,7 @@ async function handleFindFirst(argv: Partial>, _prompter }; const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); const client = getClient("members"); - const result = await client.member.findFirst(findFirstArgs).execute(); + const result = await client.member.findFirst(findFirstArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -3692,7 +3692,7 @@ async function handleList(argv: Partial>, _prompter: Inq }; const findManyArgs = parseFindManyArgs(argv, defaultSelect); const client = getClient("app"); - const result = await client.car.findMany(findManyArgs).execute(); + const result = await client.car.findMany(findManyArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -3714,7 +3714,7 @@ async function handleFindFirst(argv: Partial>, _prompter }; const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); const client = getClient("app"); - const result = await client.car.findFirst(findFirstArgs).execute(); + const result = await client.car.findFirst(findFirstArgs as any).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); diff --git a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts index 9bb158724..ea56301d6 100644 --- a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts @@ -520,7 +520,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: tryBody.push(buildGetClientStatement(targetName)); - // const result = await client..findMany(findManyArgs).execute(); + // const result = await client..findMany(findManyArgs as any).execute(); tryBody.push( t.variableDeclaration('const', [ t.variableDeclarator( @@ -536,7 +536,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: ), t.identifier('findMany'), ), - [t.identifier('findManyArgs')], + [t.tsAsExpression(t.identifier('findManyArgs'), t.tsAnyKeyword())], ), t.identifier('execute'), ), @@ -603,7 +603,7 @@ function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: tryBody.push(buildGetClientStatement(targetName)); - // const result = await client..findFirst(findFirstArgs).execute(); + // const result = await client..findFirst(findFirstArgs as any).execute(); tryBody.push( t.variableDeclaration('const', [ t.variableDeclarator( @@ -616,7 +616,7 @@ function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: t.memberExpression(t.identifier('client'), t.identifier(singularName)), t.identifier('findFirst'), ), - [t.identifier('findFirstArgs')], + [t.tsAsExpression(t.identifier('findFirstArgs'), t.tsAnyKeyword())], ), t.identifier('execute'), ), @@ -824,7 +824,7 @@ function buildSearchHandler( tryBody.push(buildGetClientStatement(targetName)); - // const result = await client..findMany(findManyArgs).execute(); + // const result = await client..findMany(findManyArgs as any).execute(); tryBody.push( t.variableDeclaration('const', [ t.variableDeclarator( @@ -840,7 +840,7 @@ function buildSearchHandler( ), t.identifier('findMany'), ), - [t.identifier('findManyArgs')], + [t.tsAsExpression(t.identifier('findManyArgs'), t.tsAnyKeyword())], ), t.identifier('execute'), ), From 70cfd72821968f5314afa6134a3d7a5bdc490ec5 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 28 Mar 2026 03:06:31 +0000 Subject: [PATCH 2/4] refactor(codegen): replace 'as any' with generic type parameters on parseFindManyArgs/parseFindFirstArgs Instead of casting ORM args to 'any', the helpers are now generic: parseFindManyArgs() / parseFindFirstArgs() The codegen passes specific table types: parseFindManyArgs>(...) This gives proper type safety at the ORM call site without escape hatches. The internal 'as unknown as T' cast in the helpers is the single known bridge between the untyped CLI argv layer and the typed ORM interface. --- .../__snapshots__/cli-generator.test.ts.snap | 55 +++--- .../codegen/cli/table-command-generator.ts | 179 +++++++++++++----- .../src/core/codegen/templates/cli-utils.ts | 12 +- 3 files changed, 165 insertions(+), 81 deletions(-) diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap index 5eac1db6d..4f663bae3 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap @@ -697,7 +697,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../utils"; import type { FieldSchema } from "../utils"; -import type { CreateCarInput, CarPatch } from "../../orm/input-types"; +import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarCondition, CarsOrderBy } from "../../orm/input-types"; +import type { FindManyArgs, FindFirstArgs } from "../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", make: "string", @@ -756,9 +757,9 @@ async function handleList(argv: Partial>, _prompter: Inq isElectric: true, createdAt: true }; - const findManyArgs = parseFindManyArgs(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient(); - const result = await client.car.findMany(findManyArgs as any).execute(); + const result = await client.car.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -778,9 +779,9 @@ async function handleFindFirst(argv: Partial>, _prompter isElectric: true, createdAt: true }; - const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient(); - const result = await client.car.findFirst(findFirstArgs as any).execute(); + const result = await client.car.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -1160,7 +1161,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../utils"; import type { FieldSchema } from "../utils"; -import type { CreateDriverInput, DriverPatch } from "../../orm/input-types"; +import type { CreateDriverInput, DriverPatch, DriverSelect, DriverFilter, DriverCondition, DriversOrderBy } from "../../orm/input-types"; +import type { FindManyArgs, FindFirstArgs } from "../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", name: "string", @@ -1213,9 +1215,9 @@ async function handleList(argv: Partial>, _prompter: Inq name: true, licenseNumber: true }; - const findManyArgs = parseFindManyArgs(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient(); - const result = await client.driver.findMany(findManyArgs as any).execute(); + const result = await client.driver.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -1232,9 +1234,9 @@ async function handleFindFirst(argv: Partial>, _prompter name: true, licenseNumber: true }; - const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient(); - const result = await client.driver.findFirst(findFirstArgs as any).execute(); + const result = await client.driver.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -3191,7 +3193,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils"; import type { FieldSchema } from "../../utils"; -import type { CreateUserInput, UserPatch } from "../../../orm/input-types"; +import type { CreateUserInput, UserPatch, UserSelect, UserFilter, UserCondition, UsersOrderBy } from "../../../orm/input-types"; +import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", email: "string", @@ -3244,9 +3247,9 @@ async function handleList(argv: Partial>, _prompter: Inq email: true, name: true }; - const findManyArgs = parseFindManyArgs(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient("auth"); - const result = await client.user.findMany(findManyArgs as any).execute(); + const result = await client.user.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -3263,9 +3266,9 @@ async function handleFindFirst(argv: Partial>, _prompter email: true, name: true }; - const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient("auth"); - const result = await client.user.findFirst(findFirstArgs as any).execute(); + const result = await client.user.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -3420,7 +3423,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils"; import type { FieldSchema } from "../../utils"; -import type { CreateMemberInput, MemberPatch } from "../../../orm/input-types"; +import type { CreateMemberInput, MemberPatch, MemberSelect, MemberFilter, MemberCondition, MembersOrderBy } from "../../../orm/input-types"; +import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", role: "string" @@ -3471,9 +3475,9 @@ async function handleList(argv: Partial>, _prompter: Inq id: true, role: true }; - const findManyArgs = parseFindManyArgs(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient("members"); - const result = await client.member.findMany(findManyArgs as any).execute(); + const result = await client.member.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -3489,9 +3493,9 @@ async function handleFindFirst(argv: Partial>, _prompter id: true, role: true }; - const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient("members"); - const result = await client.member.findFirst(findFirstArgs as any).execute(); + const result = await client.member.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); @@ -3631,7 +3635,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils"; import type { FieldSchema } from "../../utils"; -import type { CreateCarInput, CarPatch } from "../../../orm/input-types"; +import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarCondition, CarsOrderBy } from "../../../orm/input-types"; +import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", make: "string", @@ -3690,9 +3695,9 @@ async function handleList(argv: Partial>, _prompter: Inq isElectric: true, createdAt: true }; - const findManyArgs = parseFindManyArgs(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient("app"); - const result = await client.car.findMany(findManyArgs as any).execute(); + const result = await client.car.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to list records."); @@ -3712,9 +3717,9 @@ async function handleFindFirst(argv: Partial>, _prompter isElectric: true, createdAt: true }; - const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient("app"); - const result = await client.car.findFirst(findFirstArgs as any).execute(); + const result = await client.car.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error("Failed to find record."); diff --git a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts index ea56301d6..05040047d 100644 --- a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts @@ -15,6 +15,9 @@ import { toPascalCase, getCreateInputTypeName, getPatchTypeName, + getFilterTypeName, + getOrderByTypeName, + getConditionTypeName, } from '../utils'; import type { Table, TypeRegistry } from '../../../types/schema'; import type { GeneratedFile } from './executor-generator'; @@ -480,7 +483,51 @@ function buildAutoEmbedInputBlock( ); } -function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry): t.FunctionDeclaration { +/** + * Build the FindManyArgs type instantiation for a table: + * FindManyArgs + */ +function buildFindManyArgsType(table: Table, conditionEnabled: boolean): t.TSType { + const { typeName } = getTableNames(table); + const selectTypeName = `${typeName}Select`; + const whereTypeName = getFilterTypeName(table); + const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined; + const orderByTypeName = getOrderByTypeName(table); + return t.tsTypeReference( + t.identifier('FindManyArgs'), + t.tsTypeParameterInstantiation([ + t.tsTypeReference(t.identifier(selectTypeName)), + t.tsTypeReference(t.identifier(whereTypeName)), + conditionTypeName + ? t.tsTypeReference(t.identifier(conditionTypeName)) + : t.tsNeverKeyword(), + t.tsTypeReference(t.identifier(orderByTypeName)), + ]), + ); +} + +/** + * Build the FindFirstArgs type instantiation for a table: + * FindFirstArgs + */ +function buildFindFirstArgsType(table: Table, conditionEnabled: boolean): t.TSType { + const { typeName } = getTableNames(table); + const selectTypeName = `${typeName}Select`; + const whereTypeName = getFilterTypeName(table); + const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined; + return t.tsTypeReference( + t.identifier('FindFirstArgs'), + t.tsTypeParameterInstantiation([ + t.tsTypeReference(t.identifier(selectTypeName)), + t.tsTypeReference(t.identifier(whereTypeName)), + conditionTypeName + ? t.tsTypeReference(t.identifier(conditionTypeName)) + : t.tsNeverKeyword(), + ]), + ); +} + +function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = true): t.FunctionDeclaration { const { singularName } = getTableNames(table); const defaultSelectObj = buildSelectObject(table, typeRegistry); @@ -494,18 +541,21 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: ]), ); - // const findManyArgs = parseFindManyArgs(argv, defaultSelect); - tryBody.push( - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('findManyArgs'), - t.callExpression(t.identifier('parseFindManyArgs'), [ - t.identifier('argv'), - t.identifier('defaultSelect'), - ]), - ), - ]), - ); + // const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + { + const callExpr = t.callExpression(t.identifier('parseFindManyArgs'), [ + t.identifier('argv'), + t.identifier('defaultSelect'), + ]); + callExpr.typeParameters = t.tsTypeParameterInstantiation([ + buildFindManyArgsType(table, conditionEnabled), + ]); + tryBody.push( + t.variableDeclaration('const', [ + t.variableDeclarator(t.identifier('findManyArgs'), callExpr), + ]), + ); + } // Auto-embed vector fields in the where clause when --auto-embed is passed if (vectorFieldNames.length > 0) { @@ -520,7 +570,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: tryBody.push(buildGetClientStatement(targetName)); - // const result = await client..findMany(findManyArgs as any).execute(); + // const result = await client..findMany(findManyArgs).execute(); tryBody.push( t.variableDeclaration('const', [ t.variableDeclarator( @@ -536,7 +586,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: ), t.identifier('findMany'), ), - [t.tsAsExpression(t.identifier('findManyArgs'), t.tsAnyKeyword())], + [t.identifier('findManyArgs')], ), t.identifier('execute'), ), @@ -575,7 +625,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: * Accepts --select, --where.., --condition.. flags. * Internally calls findMany with first:1 and returns a single record (or null). */ -function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: TypeRegistry): t.FunctionDeclaration { +function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = true): t.FunctionDeclaration { const { singularName } = getTableNames(table); const defaultSelectObj = buildSelectObject(table, typeRegistry); @@ -588,22 +638,25 @@ function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: ]), ); - // const findFirstArgs = parseFindFirstArgs(argv, defaultSelect); - tryBody.push( - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('findFirstArgs'), - t.callExpression(t.identifier('parseFindFirstArgs'), [ - t.identifier('argv'), - t.identifier('defaultSelect'), - ]), - ), - ]), - ); + // const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + { + const callExpr = t.callExpression(t.identifier('parseFindFirstArgs'), [ + t.identifier('argv'), + t.identifier('defaultSelect'), + ]); + callExpr.typeParameters = t.tsTypeParameterInstantiation([ + buildFindFirstArgsType(table, conditionEnabled), + ]); + tryBody.push( + t.variableDeclaration('const', [ + t.variableDeclarator(t.identifier('findFirstArgs'), callExpr), + ]), + ); + } tryBody.push(buildGetClientStatement(targetName)); - // const result = await client..findFirst(findFirstArgs as any).execute(); + // const result = await client..findFirst(findFirstArgs).execute(); tryBody.push( t.variableDeclaration('const', [ t.variableDeclarator( @@ -616,7 +669,7 @@ function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: t.memberExpression(t.identifier('client'), t.identifier(singularName)), t.identifier('findFirst'), ), - [t.tsAsExpression(t.identifier('findFirstArgs'), t.tsAnyKeyword())], + [t.identifier('findFirstArgs')], ), t.identifier('execute'), ), @@ -662,6 +715,7 @@ function buildSearchHandler( vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, + conditionEnabled = true, ): t.FunctionDeclaration { const { singularName } = getTableNames(table); const defaultSelectObj = buildSelectObject(table, typeRegistry); @@ -808,23 +862,26 @@ function buildSearchHandler( ]), ); - // const findManyArgs = parseFindManyArgs(argv, defaultSelect, searchWhere); - tryBody.push( - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('findManyArgs'), - t.callExpression(t.identifier('parseFindManyArgs'), [ - t.identifier('argv'), - t.identifier('defaultSelect'), - t.identifier('searchWhere'), - ]), - ), - ]), - ); + // const findManyArgs = parseFindManyArgs>(argv, defaultSelect, searchWhere); + { + const callExpr = t.callExpression(t.identifier('parseFindManyArgs'), [ + t.identifier('argv'), + t.identifier('defaultSelect'), + t.identifier('searchWhere'), + ]); + callExpr.typeParameters = t.tsTypeParameterInstantiation([ + buildFindManyArgsType(table, conditionEnabled), + ]); + tryBody.push( + t.variableDeclaration('const', [ + t.variableDeclarator(t.identifier('findManyArgs'), callExpr), + ]), + ); + } tryBody.push(buildGetClientStatement(targetName)); - // const result = await client..findMany(findManyArgs as any).execute(); + // const result = await client..findMany(findManyArgs).execute(); tryBody.push( t.variableDeclaration('const', [ t.variableDeclarator( @@ -840,7 +897,7 @@ function buildSearchHandler( ), t.identifier('findMany'), ), - [t.tsAsExpression(t.identifier('findManyArgs'), t.tsAnyKeyword())], + [t.identifier('findManyArgs')], ), t.identifier('execute'), ), @@ -1250,7 +1307,7 @@ export interface TableCommandOptions { } export function generateTableCommand(table: Table, options?: TableCommandOptions): GeneratedFile { - const { singularName } = getTableNames(table); + const { singularName, typeName } = getTableNames(table); const commandName = toKebabCase(singularName); const statements: t.Statement[] = []; const executorPath = options?.executorImportPath ?? '../executor'; @@ -1286,8 +1343,30 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions const inputTypesPath = options?.targetName ? `../../../orm/input-types` : `../../orm/input-types`; + // Import table-specific ORM types for generic type parameters on parseFindManyArgs/parseFindFirstArgs + const selectTypeName = `${typeName}Select`; + const whereTypeName = getFilterTypeName(table); + const conditionTypeName = getConditionTypeName(table); + const orderByTypeName = getOrderByTypeName(table); + // Condition types are always generated, so we always import them + const conditionEnabled = true; + statements.push( + createImportDeclaration(inputTypesPath, [ + createInputTypeName, + patchTypeName, + selectTypeName, + whereTypeName, + conditionTypeName, + orderByTypeName, + ], true), + ); + + // Import FindManyArgs/FindFirstArgs from select-types for proper generic typing + const selectTypesPath = options?.targetName + ? `../../../orm/select-types` + : `../../orm/select-types`; statements.push( - createImportDeclaration(inputTypesPath, [createInputTypeName, patchTypeName], true), + createImportDeclaration(selectTypesPath, ['FindManyArgs', 'FindFirstArgs'], true), ); // Generate field schema for type coercion @@ -1579,9 +1658,9 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions const tn = options?.targetName; const ormTypes = { createInputTypeName, patchTypeName, innerFieldName }; - statements.push(buildListHandler(table, vectorFieldNames, tn, options?.typeRegistry)); - statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry)); - if (hasSearchFields) statements.push(buildSearchHandler(table, specialGroups, vectorFieldNames, tn, options?.typeRegistry)); + statements.push(buildListHandler(table, vectorFieldNames, tn, options?.typeRegistry, conditionEnabled)); + statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry, conditionEnabled)); + if (hasSearchFields) statements.push(buildSearchHandler(table, specialGroups, vectorFieldNames, tn, options?.typeRegistry, conditionEnabled)); if (hasGet) statements.push(buildGetHandler(table, tn, options?.typeRegistry)); statements.push(buildMutationHandler(table, 'create', vectorFieldNames, tn, options?.typeRegistry, ormTypes)); if (hasUpdate) statements.push(buildMutationHandler(table, 'update', vectorFieldNames, tn, options?.typeRegistry, ormTypes)); diff --git a/graphql/codegen/src/core/codegen/templates/cli-utils.ts b/graphql/codegen/src/core/codegen/templates/cli-utils.ts index 3a8cac99f..fd0170d43 100644 --- a/graphql/codegen/src/core/codegen/templates/cli-utils.ts +++ b/graphql/codegen/src/core/codegen/templates/cli-utils.ts @@ -252,11 +252,11 @@ export function parseSelectFlag( * const findManyArgs = parseFindManyArgs(argv, { id: true, name: true }); * const result = await client.user.findMany(findManyArgs).execute(); */ -export function parseFindManyArgs( +export function parseFindManyArgs>( argv: Record, defaultSelect: Record, extraWhere?: Record, -): Record { +): T { const limit = parseIntFlag(argv, 'limit'); const last = parseIntFlag(argv, 'last'); const offset = parseIntFlag(argv, 'offset'); @@ -280,7 +280,7 @@ export function parseFindManyArgs( ...(where !== undefined ? { where } : {}), ...(condition !== undefined ? { condition } : {}), ...(orderBy !== undefined ? { orderBy } : {}), - }; + } as unknown as T; } /** @@ -288,10 +288,10 @@ export function parseFindManyArgs( * Like parseFindManyArgs but only includes select, where, and condition * (no pagination flags — findFirst returns the first matching record). */ -export function parseFindFirstArgs( +export function parseFindFirstArgs>( argv: Record, defaultSelect: Record, -): Record { +): T { const select = parseSelectFlag(argv, defaultSelect); const parsed = unflattenDotNotation(argv); const where = parsed.where; @@ -301,7 +301,7 @@ export function parseFindFirstArgs( select, ...(where !== undefined ? { where } : {}), ...(condition !== undefined ? { condition } : {}), - }; + } as unknown as T; } export function buildSelectFromPaths( From 42c733b895ef03fde2e1d754764f711d6a08b34d Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 28 Mar 2026 03:21:47 +0000 Subject: [PATCH 3/4] =?UTF-8?q?refactor(codegen):=20flip=20condition=20def?= =?UTF-8?q?ault=20to=20false=20=E2=80=94=20where/filter=20is=20first-class?= =?UTF-8?q?,=20condition=20is=20opt-in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change all condition checks from `!== false` to `=== true` (8 locations) - Flip function parameter defaults from `= true` to `= false` - Thread config.codegen.condition through CLI codegen TableCommandOptions - Update tests that explicitly test condition types to pass condition: true - Update snapshots to reflect condition-disabled output --- .../__snapshots__/cli-generator.test.ts.snap | 30 +++---- .../input-types-generator.test.ts.snap | 85 ------------------- .../model-generator.test.ts.snap | 42 ++++----- .../react-query-hooks.test.ts.snap | 14 +-- .../codegen/input-types-generator.test.ts | 8 +- .../__tests__/codegen/model-generator.test.ts | 2 +- graphql/codegen/src/core/codegen/cli/index.ts | 7 ++ .../codegen/cli/table-command-generator.ts | 15 ++-- graphql/codegen/src/core/codegen/index.ts | 2 +- graphql/codegen/src/core/codegen/orm/index.ts | 2 +- .../core/codegen/orm/input-types-generator.ts | 2 +- .../src/core/codegen/orm/model-generator.ts | 2 +- graphql/codegen/src/core/codegen/queries.ts | 2 +- graphql/codegen/src/core/generate.ts | 2 + 14 files changed, 67 insertions(+), 148 deletions(-) diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap index 4f663bae3..b86b932b8 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap @@ -697,7 +697,7 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../utils"; import type { FieldSchema } from "../utils"; -import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarCondition, CarsOrderBy } from "../../orm/input-types"; +import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarsOrderBy } from "../../orm/input-types"; import type { FindManyArgs, FindFirstArgs } from "../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", @@ -757,7 +757,7 @@ async function handleList(argv: Partial>, _prompter: Inq isElectric: true, createdAt: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient(); const result = await client.car.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -779,7 +779,7 @@ async function handleFindFirst(argv: Partial>, _prompter isElectric: true, createdAt: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient(); const result = await client.car.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -1161,7 +1161,7 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../utils"; import type { FieldSchema } from "../utils"; -import type { CreateDriverInput, DriverPatch, DriverSelect, DriverFilter, DriverCondition, DriversOrderBy } from "../../orm/input-types"; +import type { CreateDriverInput, DriverPatch, DriverSelect, DriverFilter, DriversOrderBy } from "../../orm/input-types"; import type { FindManyArgs, FindFirstArgs } from "../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", @@ -1215,7 +1215,7 @@ async function handleList(argv: Partial>, _prompter: Inq name: true, licenseNumber: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient(); const result = await client.driver.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -1234,7 +1234,7 @@ async function handleFindFirst(argv: Partial>, _prompter name: true, licenseNumber: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient(); const result = await client.driver.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3193,7 +3193,7 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils"; import type { FieldSchema } from "../../utils"; -import type { CreateUserInput, UserPatch, UserSelect, UserFilter, UserCondition, UsersOrderBy } from "../../../orm/input-types"; +import type { CreateUserInput, UserPatch, UserSelect, UserFilter, UsersOrderBy } from "../../../orm/input-types"; import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", @@ -3247,7 +3247,7 @@ async function handleList(argv: Partial>, _prompter: Inq email: true, name: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient("auth"); const result = await client.user.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3266,7 +3266,7 @@ async function handleFindFirst(argv: Partial>, _prompter email: true, name: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient("auth"); const result = await client.user.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3423,7 +3423,7 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils"; import type { FieldSchema } from "../../utils"; -import type { CreateMemberInput, MemberPatch, MemberSelect, MemberFilter, MemberCondition, MembersOrderBy } from "../../../orm/input-types"; +import type { CreateMemberInput, MemberPatch, MemberSelect, MemberFilter, MembersOrderBy } from "../../../orm/input-types"; import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", @@ -3475,7 +3475,7 @@ async function handleList(argv: Partial>, _prompter: Inq id: true, role: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient("members"); const result = await client.member.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3493,7 +3493,7 @@ async function handleFindFirst(argv: Partial>, _prompter id: true, role: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient("members"); const result = await client.member.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3635,7 +3635,7 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer"; import { getClient } from "../../executor"; import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils"; import type { FieldSchema } from "../../utils"; -import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarCondition, CarsOrderBy } from "../../../orm/input-types"; +import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarsOrderBy } from "../../../orm/input-types"; import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types"; const fieldSchema: FieldSchema = { id: "uuid", @@ -3695,7 +3695,7 @@ async function handleList(argv: Partial>, _prompter: Inq isElectric: true, createdAt: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs>(argv, defaultSelect); const client = getClient("app"); const result = await client.car.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3717,7 +3717,7 @@ async function handleFindFirst(argv: Partial>, _prompter isElectric: true, createdAt: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); const client = getClient("app"); const result = await client.car.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap index c90ff15df..dca52f449 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/input-types-generator.test.ts.snap @@ -373,31 +373,6 @@ export interface CommentFilter { or?: CommentFilter[]; not?: CommentFilter; } -// ============ Table Condition Types ============ -export interface UserCondition { - id?: string | null; - email?: string | null; - name?: string | null; - age?: number | null; - isActive?: boolean | null; - createdAt?: string | null; - metadata?: unknown | null; -} -export interface PostCondition { - id?: string | null; - title?: string | null; - content?: string | null; - authorId?: string | null; - publishedAt?: string | null; - tags?: string | null; -} -export interface CommentCondition { - id?: string | null; - body?: string | null; - postId?: string | null; - authorId?: string | null; - createdAt?: string | null; -} // ============ OrderBy Types ============ export type UsersOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "AGE_ASC" | "AGE_DESC" | "IS_ACTIVE_ASC" | "IS_ACTIVE_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "METADATA_ASC" | "METADATA_DESC"; export type PostsOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "TITLE_ASC" | "TITLE_DESC" | "CONTENT_ASC" | "CONTENT_DESC" | "AUTHOR_ID_ASC" | "AUTHOR_ID_DESC" | "PUBLISHED_AT_ASC" | "PUBLISHED_AT_DESC" | "TAGS_ASC" | "TAGS_DESC"; @@ -771,16 +746,6 @@ export interface UserFilter { or?: UserFilter[]; not?: UserFilter; } -// ============ Table Condition Types ============ -export interface UserCondition { - id?: string | null; - email?: string | null; - name?: string | null; - age?: number | null; - isActive?: boolean | null; - createdAt?: string | null; - metadata?: unknown | null; -} // ============ OrderBy Types ============ export type UsersOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "AGE_ASC" | "AGE_DESC" | "IS_ACTIVE_ASC" | "IS_ACTIVE_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "METADATA_ASC" | "METADATA_DESC"; // ============ CRUD Input Types ============ @@ -1096,16 +1061,6 @@ export interface UserFilter { or?: UserFilter[]; not?: UserFilter; } -// ============ Table Condition Types ============ -export interface UserCondition { - id?: string | null; - email?: string | null; - name?: string | null; - age?: number | null; - isActive?: boolean | null; - createdAt?: string | null; - metadata?: unknown | null; -} // ============ OrderBy Types ============ export type UsersOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "AGE_ASC" | "AGE_DESC" | "IS_ACTIVE_ASC" | "IS_ACTIVE_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "METADATA_ASC" | "METADATA_DESC"; // ============ CRUD Input Types ============ @@ -1433,16 +1388,6 @@ export interface UserFilter { or?: UserFilter[]; not?: UserFilter; } -// ============ Table Condition Types ============ -export interface UserCondition { - id?: string | null; - email?: string | null; - name?: string | null; - age?: number | null; - isActive?: boolean | null; - createdAt?: string | null; - metadata?: unknown | null; -} // ============ OrderBy Types ============ export type UsersOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "AGE_ASC" | "AGE_DESC" | "IS_ACTIVE_ASC" | "IS_ACTIVE_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "METADATA_ASC" | "METADATA_DESC"; // ============ CRUD Input Types ============ @@ -1815,22 +1760,6 @@ export interface ProfileFilter { or?: ProfileFilter[]; not?: ProfileFilter; } -// ============ Table Condition Types ============ -export interface UserCondition { - id?: string | null; - email?: string | null; - name?: string | null; - age?: number | null; - isActive?: boolean | null; - createdAt?: string | null; - metadata?: unknown | null; -} -export interface ProfileCondition { - id?: string | null; - bio?: string | null; - userId?: string | null; - avatarUrl?: string | null; -} // ============ OrderBy Types ============ export type UsersOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "AGE_ASC" | "AGE_DESC" | "IS_ACTIVE_ASC" | "IS_ACTIVE_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "METADATA_ASC" | "METADATA_DESC"; export type ProfilesOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "BIO_ASC" | "BIO_DESC" | "USER_ID_ASC" | "USER_ID_DESC" | "AVATAR_URL_ASC" | "AVATAR_URL_DESC"; @@ -2210,20 +2139,6 @@ export interface CategoryFilter { or?: CategoryFilter[]; not?: CategoryFilter; } -// ============ Table Condition Types ============ -export interface PostCondition { - id?: string | null; - title?: string | null; - content?: string | null; - authorId?: string | null; - publishedAt?: string | null; - tags?: string | null; -} -export interface CategoryCondition { - id?: string | null; - name?: string | null; - slug?: string | null; -} // ============ OrderBy Types ============ export type PostsOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "TITLE_ASC" | "TITLE_DESC" | "CONTENT_ASC" | "CONTENT_DESC" | "AUTHOR_ID_ASC" | "AUTHOR_ID_DESC" | "PUBLISHED_AT_ASC" | "PUBLISHED_AT_DESC" | "TAGS_ASC" | "TAGS_DESC"; export type CategoriesOrderBy = "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC" | "NATURAL" | "ID_ASC" | "ID_DESC" | "NAME_ASC" | "NAME_DESC" | "SLUG_ASC" | "SLUG_DESC"; diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/model-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/model-generator.test.ts.snap index 4153840aa..18824756c 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/model-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/model-generator.test.ts.snap @@ -9,11 +9,11 @@ exports[`model-generator generates model with all CRUD methods 1`] = ` import { OrmClient } from "../client"; import { QueryBuilder, buildFindManyDocument, buildFindFirstDocument, buildFindOneDocument, buildCreateDocument, buildUpdateByPkDocument, buildDeleteByPkDocument } from "../query-builder"; import type { ConnectionResult, FindManyArgs, FindFirstArgs, CreateArgs, UpdateArgs, DeleteArgs, InferSelectResult, StrictSelect } from "../select-types"; -import type { User, UserWithRelations, UserSelect, UserFilter, UserCondition, UsersOrderBy, CreateUserInput, UpdateUserInput, UserPatch } from "../input-types"; +import type { User, UserWithRelations, UserSelect, UserFilter, UsersOrderBy, CreateUserInput, UpdateUserInput, UserPatch } from "../input-types"; import { connectionFieldsMap } from "../input-types"; export class UserModel { constructor(private client: OrmClient) {} - findMany(args: FindManyArgs & { + findMany(args: FindManyArgs & { select: S; } & StrictSelect): QueryBuilder<{ users: ConnectionResult>; @@ -23,14 +23,13 @@ export class UserModel { variables } = buildFindManyDocument("User", "users", args.select, { where: args?.where, - condition: args?.condition, orderBy: args?.orderBy as string[] | undefined, first: args?.first, last: args?.last, after: args?.after, before: args?.before, offset: args?.offset - }, "UserFilter", "UsersOrderBy", connectionFieldsMap, "UserCondition"); + }, "UserFilter", "UsersOrderBy", connectionFieldsMap); return new QueryBuilder({ client: this.client, operation: "query", @@ -40,7 +39,7 @@ export class UserModel { variables }); } - findFirst(args: FindFirstArgs & { + findFirst(args: FindFirstArgs & { select: S; } & StrictSelect): QueryBuilder<{ users: { @@ -51,9 +50,8 @@ export class UserModel { document, variables } = buildFindFirstDocument("User", "users", args.select, { - where: args?.where, - condition: args?.condition - }, "UserFilter", connectionFieldsMap, "UserCondition"); + where: args?.where + }, "UserFilter", connectionFieldsMap); return new QueryBuilder({ client: this.client, operation: "query", @@ -160,11 +158,11 @@ exports[`model-generator generates model without update/delete when not availabl import { OrmClient } from "../client"; import { QueryBuilder, buildFindManyDocument, buildFindFirstDocument, buildFindOneDocument, buildCreateDocument, buildUpdateByPkDocument, buildDeleteByPkDocument } from "../query-builder"; import type { ConnectionResult, FindManyArgs, FindFirstArgs, CreateArgs, UpdateArgs, DeleteArgs, InferSelectResult, StrictSelect } from "../select-types"; -import type { AuditLog, AuditLogWithRelations, AuditLogSelect, AuditLogFilter, AuditLogCondition, AuditLogsOrderBy, CreateAuditLogInput, UpdateAuditLogInput, AuditLogPatch } from "../input-types"; +import type { AuditLog, AuditLogWithRelations, AuditLogSelect, AuditLogFilter, AuditLogsOrderBy, CreateAuditLogInput, UpdateAuditLogInput, AuditLogPatch } from "../input-types"; import { connectionFieldsMap } from "../input-types"; export class AuditLogModel { constructor(private client: OrmClient) {} - findMany(args: FindManyArgs & { + findMany(args: FindManyArgs & { select: S; } & StrictSelect): QueryBuilder<{ auditLogs: ConnectionResult>; @@ -174,14 +172,13 @@ export class AuditLogModel { variables } = buildFindManyDocument("AuditLog", "auditLogs", args.select, { where: args?.where, - condition: args?.condition, orderBy: args?.orderBy as string[] | undefined, first: args?.first, last: args?.last, after: args?.after, before: args?.before, offset: args?.offset - }, "AuditLogFilter", "AuditLogsOrderBy", connectionFieldsMap, "AuditLogCondition"); + }, "AuditLogFilter", "AuditLogsOrderBy", connectionFieldsMap); return new QueryBuilder({ client: this.client, operation: "query", @@ -191,7 +188,7 @@ export class AuditLogModel { variables }); } - findFirst(args: FindFirstArgs & { + findFirst(args: FindFirstArgs & { select: S; } & StrictSelect): QueryBuilder<{ auditLogs: { @@ -202,9 +199,8 @@ export class AuditLogModel { document, variables } = buildFindFirstDocument("AuditLog", "auditLogs", args.select, { - where: args?.where, - condition: args?.condition - }, "AuditLogFilter", connectionFieldsMap, "AuditLogCondition"); + where: args?.where + }, "AuditLogFilter", connectionFieldsMap); return new QueryBuilder({ client: this.client, operation: "query", @@ -265,11 +261,11 @@ exports[`model-generator handles custom query/mutation names 1`] = ` import { OrmClient } from "../client"; import { QueryBuilder, buildFindManyDocument, buildFindFirstDocument, buildFindOneDocument, buildCreateDocument, buildUpdateByPkDocument, buildDeleteByPkDocument } from "../query-builder"; import type { ConnectionResult, FindManyArgs, FindFirstArgs, CreateArgs, UpdateArgs, DeleteArgs, InferSelectResult, StrictSelect } from "../select-types"; -import type { Organization, OrganizationWithRelations, OrganizationSelect, OrganizationFilter, OrganizationCondition, OrganizationsOrderBy, CreateOrganizationInput, UpdateOrganizationInput, OrganizationPatch } from "../input-types"; +import type { Organization, OrganizationWithRelations, OrganizationSelect, OrganizationFilter, OrganizationsOrderBy, CreateOrganizationInput, UpdateOrganizationInput, OrganizationPatch } from "../input-types"; import { connectionFieldsMap } from "../input-types"; export class OrganizationModel { constructor(private client: OrmClient) {} - findMany(args: FindManyArgs & { + findMany(args: FindManyArgs & { select: S; } & StrictSelect): QueryBuilder<{ allOrganizations: ConnectionResult>; @@ -279,14 +275,13 @@ export class OrganizationModel { variables } = buildFindManyDocument("Organization", "allOrganizations", args.select, { where: args?.where, - condition: args?.condition, orderBy: args?.orderBy as string[] | undefined, first: args?.first, last: args?.last, after: args?.after, before: args?.before, offset: args?.offset - }, "OrganizationFilter", "OrganizationsOrderBy", connectionFieldsMap, "OrganizationCondition"); + }, "OrganizationFilter", "OrganizationsOrderBy", connectionFieldsMap); return new QueryBuilder({ client: this.client, operation: "query", @@ -296,7 +291,7 @@ export class OrganizationModel { variables }); } - findFirst(args: FindFirstArgs & { + findFirst(args: FindFirstArgs & { select: S; } & StrictSelect): QueryBuilder<{ allOrganizations: { @@ -307,9 +302,8 @@ export class OrganizationModel { document, variables } = buildFindFirstDocument("Organization", "allOrganizations", args.select, { - where: args?.where, - condition: args?.condition - }, "OrganizationFilter", connectionFieldsMap, "OrganizationCondition"); + where: args?.where + }, "OrganizationFilter", connectionFieldsMap); return new QueryBuilder({ client: this.client, operation: "query", diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap index 1ed497d76..16ed2c736 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/react-query-hooks.test.ts.snap @@ -1436,9 +1436,9 @@ import { getClient } from "../client"; import { buildListSelectionArgs } from "../selection"; import type { ListSelectionConfig } from "../selection"; import { userKeys } from "../query-keys"; -import type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } from "../../orm/input-types"; +import type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy } from "../../orm/input-types"; import type { FindManyArgs, InferSelectResult, ConnectionResult, HookStrictSelect } from "../../orm/select-types"; -export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } from "../../orm/input-types"; +export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy } from "../../orm/input-types"; /** Query key factory - re-exported from query-keys.ts */ export const usersQueryKey = userKeys.list; /** @@ -1545,9 +1545,9 @@ import { buildListSelectionArgs } from "../selection"; import type { ListSelectionConfig } from "../selection"; import { postKeys } from "../query-keys"; import type { PostScope } from "../query-keys"; -import type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy, PostCondition } from "../../orm/input-types"; +import type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy } from "../../orm/input-types"; import type { FindManyArgs, InferSelectResult, ConnectionResult, HookStrictSelect } from "../../orm/select-types"; -export type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy, PostCondition } from "../../orm/input-types"; +export type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy } from "../../orm/input-types"; /** Query key factory - re-exported from query-keys.ts */ export const postsQueryKey = postKeys.list; /** @@ -1669,10 +1669,10 @@ import type { UseQueryOptions, UseQueryResult, QueryClient } from "@tanstack/rea import { getClient } from "../client"; import { buildListSelectionArgs } from "../selection"; import type { ListSelectionConfig } from "../selection"; -import type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } from "../../orm/input-types"; +import type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy } from "../../orm/input-types"; import type { FindManyArgs, InferSelectResult, ConnectionResult, HookStrictSelect } from "../../orm/select-types"; -export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } from "../../orm/input-types"; -export const usersQueryKey = (variables?: FindManyArgs) => ["user", "list", variables] as const; +export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy } from "../../orm/input-types"; +export const usersQueryKey = (variables?: FindManyArgs) => ["user", "list", variables] as const; /** * Query hook for fetching User list * diff --git a/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts b/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts index 89cc8fed4..995b80767 100644 --- a/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts +++ b/graphql/codegen/src/__tests__/codegen/input-types-generator.test.ts @@ -965,7 +965,7 @@ describe('plugin-injected condition fields', () => { }, }); - const result = generateInputTypesFile(registry, new Set(), [contactTable]); + const result = generateInputTypesFile(registry, new Set(), [contactTable], undefined, true, { condition: true }); // Regular table column fields should still be present expect(result.content).toContain('export interface ContactCondition {'); @@ -1025,7 +1025,7 @@ describe('plugin-injected condition fields', () => { }, }); - const result = generateInputTypesFile(registry, new Set(), [contactTable]); + const result = generateInputTypesFile(registry, new Set(), [contactTable], undefined, true, { condition: true }); // VectorNearbyInput should be generated (follows *Input pattern) expect(result.content).toContain('export interface VectorNearbyInput {'); @@ -1051,7 +1051,7 @@ describe('plugin-injected condition fields', () => { }, }); - const result = generateInputTypesFile(registry, new Set(), [contactTable]); + const result = generateInputTypesFile(registry, new Set(), [contactTable], undefined, true, { condition: true }); // Count occurrences of 'id?' in the ContactCondition interface const conditionMatch = result.content.match( @@ -1102,7 +1102,7 @@ describe('plugin-injected condition fields', () => { it('works without typeRegistry (backwards compatible)', () => { // When no typeRegistry has the condition type, only table columns are used - const result = generateInputTypesFile(new Map(), new Set(), [contactTable]); + const result = generateInputTypesFile(new Map(), new Set(), [contactTable], undefined, true, { condition: true }); expect(result.content).toContain('export interface ContactCondition {'); expect(result.content).toContain('id?: string | null;'); diff --git a/graphql/codegen/src/__tests__/codegen/model-generator.test.ts b/graphql/codegen/src/__tests__/codegen/model-generator.test.ts index b2efd99c8..8b111ebad 100644 --- a/graphql/codegen/src/__tests__/codegen/model-generator.test.ts +++ b/graphql/codegen/src/__tests__/codegen/model-generator.test.ts @@ -249,7 +249,7 @@ describe('model-generator', () => { }, }); - const result = generateModelFile(table, false); + const result = generateModelFile(table, false, { condition: true }); // Condition type should be imported expect(result.content).toContain('ContactCondition'); diff --git a/graphql/codegen/src/core/codegen/cli/index.ts b/graphql/codegen/src/core/codegen/cli/index.ts index 9e107db1a..fb47040a4 100644 --- a/graphql/codegen/src/core/codegen/cli/index.ts +++ b/graphql/codegen/src/core/codegen/cli/index.ts @@ -78,9 +78,12 @@ export function generateCli(options: GenerateCliOptions): GenerateCliResult { const authFile = generateAuthCommand(toolName); files.push(authFile); + const conditionEnabled = config.codegen?.condition === true; + for (const table of tables) { const tableFile = generateTableCommand(table, { typeRegistry: options.typeRegistry, + condition: conditionEnabled, }); files.push(tableFile); } @@ -143,6 +146,8 @@ export interface GenerateMultiTargetCliOptions { nodeHttpAdapter?: boolean; /** Generate a runnable index.ts entry point */ entryPoint?: boolean; + /** Whether PostGraphile condition types are enabled (default: true) */ + condition?: boolean; } export function resolveBuiltinNames( @@ -170,6 +175,7 @@ export function generateMultiTargetCli( options: GenerateMultiTargetCliOptions, ): GenerateCliResult { const { toolName, targets } = options; + const conditionEnabled = options.condition === true; const files: GeneratedFile[] = []; const targetNames = targets.map((t) => t.name); @@ -244,6 +250,7 @@ export function generateMultiTargetCli( targetName: target.name, executorImportPath: '../../executor', typeRegistry: target.typeRegistry, + condition: conditionEnabled, }); files.push(tableFile); } diff --git a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts index 05040047d..b21df8177 100644 --- a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts @@ -527,7 +527,7 @@ function buildFindFirstArgsType(table: Table, conditionEnabled: boolean): t.TSTy ); } -function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = true): t.FunctionDeclaration { +function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = false): t.FunctionDeclaration { const { singularName } = getTableNames(table); const defaultSelectObj = buildSelectObject(table, typeRegistry); @@ -625,7 +625,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: * Accepts --select, --where.., --condition.. flags. * Internally calls findMany with first:1 and returns a single record (or null). */ -function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = true): t.FunctionDeclaration { +function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = false): t.FunctionDeclaration { const { singularName } = getTableNames(table); const defaultSelectObj = buildSelectObject(table, typeRegistry); @@ -715,7 +715,7 @@ function buildSearchHandler( vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, - conditionEnabled = true, + conditionEnabled = false, ): t.FunctionDeclaration { const { singularName } = getTableNames(table); const defaultSelectObj = buildSelectObject(table, typeRegistry); @@ -1304,6 +1304,8 @@ export interface TableCommandOptions { executorImportPath?: string; /** TypeRegistry from introspection, used to check field defaults */ typeRegistry?: TypeRegistry; + /** Whether PostGraphile condition types are enabled (default: true) */ + condition?: boolean; } export function generateTableCommand(table: Table, options?: TableCommandOptions): GeneratedFile { @@ -1346,17 +1348,16 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions // Import table-specific ORM types for generic type parameters on parseFindManyArgs/parseFindFirstArgs const selectTypeName = `${typeName}Select`; const whereTypeName = getFilterTypeName(table); - const conditionTypeName = getConditionTypeName(table); const orderByTypeName = getOrderByTypeName(table); - // Condition types are always generated, so we always import them - const conditionEnabled = true; + const conditionEnabled = options?.condition === true; + const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined; statements.push( createImportDeclaration(inputTypesPath, [ createInputTypeName, patchTypeName, selectTypeName, whereTypeName, - conditionTypeName, + ...(conditionTypeName ? [conditionTypeName] : []), orderByTypeName, ], true), ); diff --git a/graphql/codegen/src/core/codegen/index.ts b/graphql/codegen/src/core/codegen/index.ts index d8960c2b5..d51a0260d 100644 --- a/graphql/codegen/src/core/codegen/index.ts +++ b/graphql/codegen/src/core/codegen/index.ts @@ -191,7 +191,7 @@ export function generate(options: GenerateOptions): GenerateResult { } // Condition types (PostGraphile simple equality filters) - const conditionEnabled = config.codegen?.condition !== false; + const conditionEnabled = config.codegen?.condition === true; // 4. Generate table-based query hooks (queries/*.ts) const queryHooks = generateAllQueryHooks(tables, { diff --git a/graphql/codegen/src/core/codegen/orm/index.ts b/graphql/codegen/src/core/codegen/orm/index.ts index aa0d1b87e..b46f28f60 100644 --- a/graphql/codegen/src/core/codegen/orm/index.ts +++ b/graphql/codegen/src/core/codegen/orm/index.ts @@ -66,7 +66,7 @@ export interface GenerateOrmResult { export function generateOrm(options: GenerateOrmOptions): GenerateOrmResult { const { tables, customOperations, sharedTypesPath } = options; const commentsEnabled = options.config.codegen?.comments !== false; - const conditionEnabled = options.config.codegen?.condition !== false; + const conditionEnabled = options.config.codegen?.condition === true; const files: GeneratedFile[] = []; // Use shared types when a sharedTypesPath is provided (unified output mode) diff --git a/graphql/codegen/src/core/codegen/orm/input-types-generator.ts b/graphql/codegen/src/core/codegen/orm/input-types-generator.ts index 071a8ad15..84d6c2c3e 100644 --- a/graphql/codegen/src/core/codegen/orm/input-types-generator.ts +++ b/graphql/codegen/src/core/codegen/orm/input-types-generator.ts @@ -2093,7 +2093,7 @@ export function generateInputTypesFile( comments: boolean = true, options?: { condition?: boolean }, ): GeneratedInputTypesFile { - const conditionEnabled = options?.condition !== false; + const conditionEnabled = options?.condition === true; const statements: t.Statement[] = []; const tablesList = tables ?? []; const hasTables = tablesList.length > 0; diff --git a/graphql/codegen/src/core/codegen/orm/model-generator.ts b/graphql/codegen/src/core/codegen/orm/model-generator.ts index d50fdcd7c..8d971b58d 100644 --- a/graphql/codegen/src/core/codegen/orm/model-generator.ts +++ b/graphql/codegen/src/core/codegen/orm/model-generator.ts @@ -174,7 +174,7 @@ export function generateModelFile( options?: { condition?: boolean }, allTables?: Table[], ): GeneratedModelFile { - const conditionEnabled = options?.condition !== false; + const conditionEnabled = options?.condition === true; const { typeName, singularName, pluralName } = getTableNames(table); const modelName = `${typeName}Model`; const baseFileName = lcFirst(typeName); diff --git a/graphql/codegen/src/core/codegen/queries.ts b/graphql/codegen/src/core/codegen/queries.ts index 1b43251c2..326ba4519 100644 --- a/graphql/codegen/src/core/codegen/queries.ts +++ b/graphql/codegen/src/core/codegen/queries.ts @@ -87,7 +87,7 @@ export function generateListQueryHook( reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, - condition: conditionEnabled = true, + condition: conditionEnabled = false, } = options; const { typeName, pluralName, singularName } = getTableNames(table); const hookName = getListQueryHookName(table); diff --git a/graphql/codegen/src/core/generate.ts b/graphql/codegen/src/core/generate.ts index f2f760991..cf4e4be5e 100644 --- a/graphql/codegen/src/core/generate.ts +++ b/graphql/codegen/src/core/generate.ts @@ -714,12 +714,14 @@ export async function generateMulti( firstTargetConfig?.nodeHttpAdapter === true || (firstTargetConfig?.nodeHttpAdapter !== false); + const multiConditionEnabled = firstTargetConfig?.codegen?.condition === true; const { files } = generateMultiTargetCli({ toolName, builtinNames: cliConfig.builtinNames, targets: cliTargets, nodeHttpAdapter: multiNodeHttpAdapter, entryPoint: cliConfig.entryPoint, + condition: multiConditionEnabled, }); const cliFilesToWrite = files.map((file) => ({ From 63068e096234feba73153988445ec816ebbdaa62 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 28 Mar 2026 03:37:42 +0000 Subject: [PATCH 4/4] fix(codegen): make select required in FindManyArgs/FindFirstArgs type params The ORM's findMany/findFirst methods require select to be present (via intersection & { select: SelectType }). parseFindManyArgs always sets select at runtime, but the generic type parameter only had FindManyArgs<...> where select is optional. Fix: generate FindManyArgs<...> & { select: SelectType } as the type parameter, making the type match what the ORM expects. --- .../__snapshots__/cli-generator.test.ts.snap | 40 +++++++++++++----- .../codegen/cli/table-command-generator.ts | 41 +++++++++++++++++-- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap index b86b932b8..b6a64b76e 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap @@ -757,7 +757,9 @@ async function handleList(argv: Partial>, _prompter: Inq isElectric: true, createdAt: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs & { + select: CarSelect; + }>(argv, defaultSelect); const client = getClient(); const result = await client.car.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -779,7 +781,9 @@ async function handleFindFirst(argv: Partial>, _prompter isElectric: true, createdAt: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs & { + select: CarSelect; + }>(argv, defaultSelect); const client = getClient(); const result = await client.car.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -1215,7 +1219,9 @@ async function handleList(argv: Partial>, _prompter: Inq name: true, licenseNumber: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs & { + select: DriverSelect; + }>(argv, defaultSelect); const client = getClient(); const result = await client.driver.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -1234,7 +1240,9 @@ async function handleFindFirst(argv: Partial>, _prompter name: true, licenseNumber: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs & { + select: DriverSelect; + }>(argv, defaultSelect); const client = getClient(); const result = await client.driver.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3247,7 +3255,9 @@ async function handleList(argv: Partial>, _prompter: Inq email: true, name: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs & { + select: UserSelect; + }>(argv, defaultSelect); const client = getClient("auth"); const result = await client.user.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3266,7 +3276,9 @@ async function handleFindFirst(argv: Partial>, _prompter email: true, name: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs & { + select: UserSelect; + }>(argv, defaultSelect); const client = getClient("auth"); const result = await client.user.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3475,7 +3487,9 @@ async function handleList(argv: Partial>, _prompter: Inq id: true, role: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs & { + select: MemberSelect; + }>(argv, defaultSelect); const client = getClient("members"); const result = await client.member.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3493,7 +3507,9 @@ async function handleFindFirst(argv: Partial>, _prompter id: true, role: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs & { + select: MemberSelect; + }>(argv, defaultSelect); const client = getClient("members"); const result = await client.member.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3695,7 +3711,9 @@ async function handleList(argv: Partial>, _prompter: Inq isElectric: true, createdAt: true }; - const findManyArgs = parseFindManyArgs>(argv, defaultSelect); + const findManyArgs = parseFindManyArgs & { + select: CarSelect; + }>(argv, defaultSelect); const client = getClient("app"); const result = await client.car.findMany(findManyArgs).execute(); console.log(JSON.stringify(result, null, 2)); @@ -3717,7 +3735,9 @@ async function handleFindFirst(argv: Partial>, _prompter isElectric: true, createdAt: true }; - const findFirstArgs = parseFindFirstArgs>(argv, defaultSelect); + const findFirstArgs = parseFindFirstArgs & { + select: CarSelect; + }>(argv, defaultSelect); const client = getClient("app"); const result = await client.car.findFirst(findFirstArgs).execute(); console.log(JSON.stringify(result, null, 2)); diff --git a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts index b21df8177..fa159a11a 100644 --- a/graphql/codegen/src/core/codegen/cli/table-command-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/table-command-generator.ts @@ -485,7 +485,11 @@ function buildAutoEmbedInputBlock( /** * Build the FindManyArgs type instantiation for a table: - * FindManyArgs + * FindManyArgs & { select: SelectType } + * + * The intersection with { select: SelectType } makes select required, + * matching what the ORM's findMany method expects. parseFindManyArgs + * always sets select at runtime (from defaultSelect or --select flag). */ function buildFindManyArgsType(table: Table, conditionEnabled: boolean): t.TSType { const { typeName } = getTableNames(table); @@ -493,7 +497,7 @@ function buildFindManyArgsType(table: Table, conditionEnabled: boolean): t.TSTyp const whereTypeName = getFilterTypeName(table); const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined; const orderByTypeName = getOrderByTypeName(table); - return t.tsTypeReference( + const findManyType = t.tsTypeReference( t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([ t.tsTypeReference(t.identifier(selectTypeName)), @@ -504,18 +508,34 @@ function buildFindManyArgsType(table: Table, conditionEnabled: boolean): t.TSTyp t.tsTypeReference(t.identifier(orderByTypeName)), ]), ); + // Intersect with { select: SelectType } to make select required + return t.tsIntersectionType([ + findManyType, + t.tsTypeLiteral([ + Object.assign( + t.tsPropertySignature( + t.identifier('select'), + t.tsTypeAnnotation(t.tsTypeReference(t.identifier(selectTypeName))), + ), + { optional: false }, + ), + ]), + ]); } /** * Build the FindFirstArgs type instantiation for a table: - * FindFirstArgs + * FindFirstArgs & { select: SelectType } + * + * The intersection with { select: SelectType } makes select required, + * matching what the ORM's findFirst method expects. */ function buildFindFirstArgsType(table: Table, conditionEnabled: boolean): t.TSType { const { typeName } = getTableNames(table); const selectTypeName = `${typeName}Select`; const whereTypeName = getFilterTypeName(table); const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined; - return t.tsTypeReference( + const findFirstType = t.tsTypeReference( t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([ t.tsTypeReference(t.identifier(selectTypeName)), @@ -525,6 +545,19 @@ function buildFindFirstArgsType(table: Table, conditionEnabled: boolean): t.TSTy : t.tsNeverKeyword(), ]), ); + // Intersect with { select: SelectType } to make select required + return t.tsIntersectionType([ + findFirstType, + t.tsTypeLiteral([ + Object.assign( + t.tsPropertySignature( + t.identifier('select'), + t.tsTypeAnnotation(t.tsTypeReference(t.identifier(selectTypeName))), + ), + { optional: false }, + ), + ]), + ]); } function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = false): t.FunctionDeclaration {