From b8acf20cf24d8e771e87cac1f7ab5cba71069623 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 15 Mar 2026 01:50:03 +0000 Subject: [PATCH] fix(codegen): include TCondition in React hooks FindManyArgs + add VectorFilter to types.ts Bug 1: queries.ts passed 3 type args to FindManyArgs (TSelect, TWhere, TOrderBy), but the template defines 4 params: FindManyArgs. This caused TOrderBy to land in the TCondition slot, defaulting TOrderBy to never and breaking all hook orderBy params. Fixed by conditionally spreading conditionTypeName between filter and orderBy type args, matching the pattern in model-generator.ts. Bug 2: VectorFilter was generated in ORM input-types-generator.ts but was missing from the React types.ts FILTER_CONFIGS. schema-types.ts imports VectorFilter from types.ts, so the missing export caused build failures. Added 5 regression tests to prevent both bugs from recurring. --- .../react-query-hooks.test.ts.snap | 14 ++-- .../codegen/react-query-hooks.test.ts | 72 +++++++++++++++++++ graphql/codegen/src/core/codegen/index.ts | 4 ++ graphql/codegen/src/core/codegen/queries.ts | 24 +++++-- graphql/codegen/src/core/codegen/types.ts | 6 ++ 5 files changed, 106 insertions(+), 14 deletions(-) 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 89631177b..1ed497d76 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 } from "../../orm/input-types"; +import type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } from "../../orm/input-types"; import type { FindManyArgs, InferSelectResult, ConnectionResult, HookStrictSelect } from "../../orm/select-types"; -export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy } from "../../orm/input-types"; +export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } 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 } from "../../orm/input-types"; +import type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy, PostCondition } from "../../orm/input-types"; import type { FindManyArgs, InferSelectResult, ConnectionResult, HookStrictSelect } from "../../orm/select-types"; -export type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy } from "../../orm/input-types"; +export type { PostSelect, PostWithRelations, PostFilter, PostsOrderBy, PostCondition } 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 } from "../../orm/input-types"; +import type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } from "../../orm/input-types"; import type { FindManyArgs, InferSelectResult, ConnectionResult, HookStrictSelect } from "../../orm/select-types"; -export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy } from "../../orm/input-types"; -export const usersQueryKey = (variables?: FindManyArgs) => ["user", "list", variables] as const; +export type { UserSelect, UserWithRelations, UserFilter, UsersOrderBy, UserCondition } 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/react-query-hooks.test.ts b/graphql/codegen/src/__tests__/codegen/react-query-hooks.test.ts index c5e0f5b99..54b279494 100644 --- a/graphql/codegen/src/__tests__/codegen/react-query-hooks.test.ts +++ b/graphql/codegen/src/__tests__/codegen/react-query-hooks.test.ts @@ -28,6 +28,7 @@ import { generateSingleQueryHook, } from '../../core/codegen/queries'; import { generateSchemaTypesFile } from '../../core/codegen/schema-types-generator'; +import { generateTypesFile } from '../../core/codegen/types'; import type { CleanFieldType, CleanOperation, @@ -335,6 +336,53 @@ describe('Query Hook Generators', () => { }); }); +describe('Regression: FindManyArgs TCondition type arg', () => { + // Bug: queries.ts passed 3 type args to FindManyArgs (TSelect, TWhere, TOrderBy), + // but the template defines 4 params: FindManyArgs. + // This caused TOrderBy to land in the TCondition slot, defaulting TOrderBy to `never` + // and breaking all hook orderBy params. + + it('includes Condition type in imports and re-exports when condition is enabled', () => { + const result = generateListQueryHook(simpleUserTable, { + reactQueryEnabled: true, + useCentralizedKeys: true, + condition: true, + }); + expect(result.content).toContain('UserCondition'); + expect(result.content).toMatch( + /import type \{[^}]*UserCondition[^}]*\} from "\.\.\/\.\.\/orm\/input-types"/, + ); + expect(result.content).toMatch( + /export type \{[^}]*UserCondition[^}]*\} from "\.\.\/\.\.\/orm\/input-types"/, + ); + }); + + it('includes Condition type in FindManyArgs type arguments', () => { + const result = generateListQueryHook(simpleUserTable, { + reactQueryEnabled: true, + useCentralizedKeys: false, + condition: true, + }); + // FindManyArgs should have 4 type args: unknown, UserFilter, UserCondition, UsersOrderBy + expect(result.content).toMatch( + /FindManyArgs/, + ); + }); + + it('omits Condition type when condition is disabled', () => { + const result = generateListQueryHook(simpleUserTable, { + reactQueryEnabled: true, + useCentralizedKeys: false, + condition: false, + }); + // FindManyArgs should have 3 type args: unknown, UserFilter, UsersOrderBy + expect(result.content).toMatch( + /FindManyArgs/, + ); + expect(result.content).not.toContain('UserCondition'); + }); +}); + describe('Mutation Hook Generators', () => { describe('generateCreateMutationHook', () => { it('generates create mutation hook for simple table', () => { @@ -644,3 +692,27 @@ describe('Barrel File Generators', () => { }); }); }); + +describe('Regression: VectorFilter in types.ts', () => { + // Bug: VectorFilter was generated in ORM input-types-generator.ts but was missing + // from the React types.ts FILTER_CONFIGS. schema-types.ts imports VectorFilter + // from types.ts, so the missing export caused build failures. + + it('exports VectorFilter interface from types.ts', () => { + const result = generateTypesFile([simpleUserTable]); + expect(result).toContain('export interface VectorFilter {'); + }); + + it('VectorFilter has equality and distinct operators with number[] type', () => { + const result = generateTypesFile([simpleUserTable]); + expect(result).toContain('export interface VectorFilter {'); + // equality operators + expect(result).toMatch(/isNull\?:\s*boolean/); + // VectorFilter uses number[] type + expect(result).toMatch(/equalTo\?:\s*number\[\]/); + expect(result).toMatch(/notEqualTo\?:\s*number\[\]/); + // distinct operators + expect(result).toMatch(/distinctFrom\?:\s*number\[\]/); + expect(result).toMatch(/notDistinctFrom\?:\s*number\[\]/); + }); +}); diff --git a/graphql/codegen/src/core/codegen/index.ts b/graphql/codegen/src/core/codegen/index.ts index bf641c3e7..a314e3768 100644 --- a/graphql/codegen/src/core/codegen/index.ts +++ b/graphql/codegen/src/core/codegen/index.ts @@ -190,11 +190,15 @@ export function generate(options: GenerateOptions): GenerateResult { hasInvalidation = true; } + // Condition types (PostGraphile simple equality filters) + const conditionEnabled = config.codegen?.condition !== false; + // 4. Generate table-based query hooks (queries/*.ts) const queryHooks = generateAllQueryHooks(tables, { reactQueryEnabled, useCentralizedKeys, hasRelationships, + condition: conditionEnabled, }); for (const hook of queryHooks) { files.push({ diff --git a/graphql/codegen/src/core/codegen/queries.ts b/graphql/codegen/src/core/codegen/queries.ts index c1ebd28bc..5d28d5caa 100644 --- a/graphql/codegen/src/core/codegen/queries.ts +++ b/graphql/codegen/src/core/codegen/queries.ts @@ -52,6 +52,7 @@ import { } from './hooks-ast'; import { getAllRowsQueryName, + getConditionTypeName, getFilterTypeName, getListQueryFileName, getListQueryHookName, @@ -75,6 +76,7 @@ export interface QueryGeneratorOptions { reactQueryEnabled?: boolean; useCentralizedKeys?: boolean; hasRelationships?: boolean; + condition?: boolean; } export function generateListQueryHook( @@ -85,12 +87,14 @@ export function generateListQueryHook( reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, + condition: conditionEnabled = true, } = options; const { typeName, pluralName, singularName } = getTableNames(table); const hookName = getListQueryHookName(table); const queryName = getAllRowsQueryName(table); const filterTypeName = getFilterTypeName(table); const orderByTypeName = getOrderByTypeName(table); + const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined; const keysName = `${lcFirst(typeName)}Keys`; const scopeTypeName = `${typeName}Scope`; const selectTypeName = `${typeName}Select`; @@ -131,10 +135,12 @@ export function generateListQueryHook( } } + const inputTypeImports = [selectTypeName, relationTypeName, filterTypeName, orderByTypeName]; + if (conditionTypeName) inputTypeImports.push(conditionTypeName); statements.push( createImportDeclaration( '../../orm/input-types', - [selectTypeName, relationTypeName, filterTypeName, orderByTypeName], + inputTypeImports, true, ), ); @@ -152,9 +158,11 @@ export function generateListQueryHook( ); // Re-exports + const reExportTypes = [selectTypeName, relationTypeName, filterTypeName, orderByTypeName]; + if (conditionTypeName) reExportTypes.push(conditionTypeName); statements.push( createTypeReExport( - [selectTypeName, relationTypeName, filterTypeName, orderByTypeName], + reExportTypes, '../../orm/input-types', ), ); @@ -174,15 +182,17 @@ export function generateListQueryHook( ]); statements.push(keyDecl); } else { + const findManyKeyTypeArgs: t.TSType[] = [ + t.tsUnknownKeyword(), + typeRef(filterTypeName), + ...(conditionTypeName ? [typeRef(conditionTypeName)] : []), + typeRef(orderByTypeName), + ]; const keyFn = t.arrowFunctionExpression( [ createFunctionParam( 'variables', - typeRef('FindManyArgs', [ - t.tsUnknownKeyword(), - typeRef(filterTypeName), - typeRef(orderByTypeName), - ]), + typeRef('FindManyArgs', findManyKeyTypeArgs), true, ), ], diff --git a/graphql/codegen/src/core/codegen/types.ts b/graphql/codegen/src/core/codegen/types.ts index f9dfcc498..1d8837e79 100644 --- a/graphql/codegen/src/core/codegen/types.ts +++ b/graphql/codegen/src/core/codegen/types.ts @@ -93,6 +93,12 @@ const FILTER_CONFIGS: Array<{ operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'], }, { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] }, + // Vector filter (for pgvector embedding columns) + { + name: 'VectorFilter', + tsType: 'number[]', + operators: ['equality', 'distinct'], + }, // List filters { name: 'StringListFilter',