diff --git a/graphql/codegen/examples/orm-sdk.ts b/graphql/codegen/examples/orm-sdk.ts index d36de4fe8..27a216780 100644 --- a/graphql/codegen/examples/orm-sdk.ts +++ b/graphql/codegen/examples/orm-sdk.ts @@ -5,7 +5,7 @@ import { createClient, GraphQLRequestError, -} from '../examples/output/generated-orm'; +} from './output/generated-orm'; const ENDPOINT = 'http://api.localhost:3000/graphql'; let db = createClient({ endpoint: ENDPOINT }); @@ -17,23 +17,23 @@ async function main() { console.log('ORM SDK Demo\n'); // ───────────────────────────────────────────────────────────────────────────── - // 1. Login & Auth + // 1. SignIn & Auth // ───────────────────────────────────────────────────────────────────────────── - section('1. Login Mutation'); - const loginResult = await db.mutation - .login( + section('1. SignIn Mutation'); + const signInResult = await db.mutation + .signIn( { input: { email: 'admin@gmail.com', password: 'password1111!@#$' } }, { select: { apiToken: { select: { accessToken: true } } } } ) .execute(); - const token = loginResult.data?.login?.apiToken?.accessToken; + const token = signInResult.data?.signIn?.apiToken?.accessToken; if (token) { db = createClient({ endpoint: ENDPOINT, headers: { Authorization: `Bearer ${token}` }, }); - console.log('✓ Logged in, token:', token.slice(0, 30) + '...'); + console.log('✓ Signed in, token:', token.slice(0, 30) + '...'); } // ───────────────────────────────────────────────────────────────────────────── @@ -169,11 +169,11 @@ async function main() { // ───────────────────────────────────────────────────────────────────────────── section('6. Custom Queries'); const currentUser = await db.query - .getCurrentUser({ + .currentUser({ select: { id: true, username: true, displayName: true }, }) .execute(); - console.log('Current user:', currentUser.data?.getCurrentUser?.username); + console.log('Current user:', currentUser.data?.currentUser?.username); // ───────────────────────────────────────────────────────────────────────────── // 7. Error Handling: execute(), unwrap(), unwrapOr() @@ -223,7 +223,7 @@ async function main() { select: { id: true, username: true, - userProfile: { select: { displayName: true } }, + roleTypeByType: { select: { name: true } }, }, first: 5, where: { username: { isNull: false } }, diff --git a/graphql/codegen/examples/react-hooks-test.tsx b/graphql/codegen/examples/react-hooks-test.tsx new file mode 100644 index 000000000..379cd5b28 --- /dev/null +++ b/graphql/codegen/examples/react-hooks-test.tsx @@ -0,0 +1,398 @@ +/** + * React Query Hooks Test File + * Tests that the generated hooks work correctly with React + TypeScript + * + * This file is for type-checking purposes. Run: npx tsc --noEmit examples/react-hooks-test.tsx + */ +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +// Import generated hooks +import { + useUsersQuery, + usersQueryKey, + fetchUsersQuery, + prefetchUsersQuery, +} from './output/generated-sdk/queries/useUsersQuery'; +import { + useUserQuery, + userQueryKey, +} from './output/generated-sdk/queries/useUserQuery'; +import { + useDatabasesQuery, + databasesQueryKey, +} from './output/generated-sdk/queries/useDatabasesQuery'; +import { + useTablesQuery, + tablesQueryKey, +} from './output/generated-sdk/queries/useTablesQuery'; +import { + useCurrentUserQuery, + currentUserQueryKey, +} from './output/generated-sdk/queries/useCurrentUserQuery'; + +// Import mutation hooks +import { useCreateUserMutation } from './output/generated-sdk/mutations/useCreateUserMutation'; +import { useUpdateUserMutation } from './output/generated-sdk/mutations/useUpdateUserMutation'; +import { useDeleteUserMutation } from './output/generated-sdk/mutations/useDeleteUserMutation'; +import { useSignInMutation } from './output/generated-sdk/mutations/useSignInMutation'; +import { useSignOutMutation } from './output/generated-sdk/mutations/useSignOutMutation'; +import { useSignUpMutation } from './output/generated-sdk/mutations/useSignUpMutation'; + +// Import types +import type { UserFilter, DatabaseFilter, TableFilter } from './output/generated-sdk/schema-types'; +import type { User, Database, Table } from './output/generated-sdk/types'; + +// Import client configuration +import { configure } from './output/generated-sdk/client'; + +// Import query/mutation keys for cache invalidation +import { queryKeys, userKeys, customQueryKeys } from './output/generated-sdk/query-keys'; +import { mutationKeys, customMutationKeys } from './output/generated-sdk/mutation-keys'; +import { invalidate } from './output/generated-sdk/invalidation'; + +const queryClient = new QueryClient(); + +/** + * Test: List query hook with pagination and filtering + */ +function UsersListComponent() { + const filter: UserFilter = { + username: { isNull: false }, + and: [{ type: { equalTo: 0 } }, { type: { greaterThan: 5 } }], + }; + + const { data, isLoading, error, refetch } = useUsersQuery({ + first: 10, + orderBy: ['USERNAME_ASC'], + filter, + }); + + // Type assertions to verify return types + const users: User[] | undefined = data?.users?.nodes; + const totalCount: number | undefined = data?.users?.totalCount; + const hasNextPage: boolean | undefined = data?.users?.pageInfo.hasNextPage; + const hasPreviousPage: boolean | undefined = data?.users?.pageInfo.hasPreviousPage; + const startCursor: string | null | undefined = data?.users?.pageInfo.startCursor; + const endCursor: string | null | undefined = data?.users?.pageInfo.endCursor; + + if (isLoading) return
Loading...
; + if (error) return
Error: {error.message}
; + + return ( +
+

Users ({totalCount})

+ + +

+ Page: {hasNextPage ? 'Has more' : 'Last page'} |{' '} + {hasPreviousPage ? 'Has previous' : 'First page'} +

+
+ ); +} + +/** + * Test: Single item query hook + */ +function UserDetailComponent({ userId }: { userId: string }) { + const { data, isLoading, error } = useUserQuery({ id: userId }); + + // Type assertion + const user: User | null | undefined = data?.user; + + if (isLoading) return
Loading...
; + if (error) return
Error: {error.message}
; + if (!user) return
User not found
; + + return ( +
+

{user.username}

+

Display name: {user.displayName}

+

Type: {user.type}

+

Created: {user.createdAt}

+
+ ); +} + +/** + * Test: Custom query hook (currentUser) + */ +function CurrentUserComponent() { + const { data, isLoading, error } = useCurrentUserQuery(); + + const currentUser = data?.currentUser; + + if (isLoading) return
Loading...
; + if (error) return
Error: {error.message}
; + if (!currentUser) return
Not logged in
; + + return ( +
+

Welcome, {currentUser.username}

+

ID: {currentUser.id}

+
+ ); +} + +/** + * Test: Create mutation hook + */ +function CreateUserForm() { + const createMutation = useCreateUserMutation({ + onSuccess: (data) => { + console.log('Created user:', data.createUser?.user?.id); + }, + onError: (error) => { + console.error('Failed to create user:', error.message); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + createMutation.mutate({ + input: { + user: { + username: 'newuser', + displayName: 'New User', + }, + }, + }); + }; + + return ( +
+ + {createMutation.isError &&

Error: {createMutation.error.message}

} + {createMutation.isSuccess &&

User created!

} +
+ ); +} + +/** + * Test: Update mutation hook + */ +function UpdateUserForm({ userId }: { userId: string }) { + const updateMutation = useUpdateUserMutation({ + onSuccess: (data) => { + console.log('Updated user:', data.updateUser?.user?.username); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + updateMutation.mutate({ + input: { + id: userId, + patch: { + displayName: 'Updated Display Name', + }, + }, + }); + }; + + return ( +
+ +
+ ); +} + +/** + * Test: Delete mutation hook + */ +function DeleteUserButton({ userId }: { userId: string }) { + const deleteMutation = useDeleteUserMutation({ + onSuccess: () => { + console.log('User deleted'); + }, + }); + + return ( + + ); +} + +/** + * Test: Auth mutations (signIn, signOut, signUp) + */ +function AuthComponent() { + const signInMutation = useSignInMutation({ + onSuccess: (data) => { + const token = data.signIn?.apiToken?.accessToken; + if (token) { + console.log('Signed in with token:', token); + } + }, + }); + + const signOutMutation = useSignOutMutation({ + onSuccess: () => { + console.log('Signed out'); + }, + }); + + const signUpMutation = useSignUpMutation({ + onSuccess: (data) => { + console.log('Signed up:', data.signUp?.apiToken?.accessToken); + }, + }); + + const handleSignIn = () => { + signInMutation.mutate({ + input: { + email: 'test@example.com', + password: 'password123', + }, + }); + }; + + const handleSignOut = () => { + signOutMutation.mutate({ input: {} }); + }; + + const handleSignUp = () => { + signUpMutation.mutate({ + input: { + email: 'newuser@example.com', + password: 'password123', + }, + }); + }; + + return ( +
+ + + +
+ ); +} + +/** + * Test: Query keys and cache invalidation + */ +function CacheInvalidationTest() { + // Test query key factories + const usersKey = userKeys.list({ first: 10 }); + const userKey = userKeys.detail('123'); + const currentUserKey = customQueryKeys.currentUser(); + + // Test centralized query keys + const allUserKeys = queryKeys.user; + const allDatabaseKeys = queryKeys.database; + + // Test invalidation helper + const handleInvalidate = async () => { + await invalidate.user.all(queryClient); + }; + + return ( +
+

Users key: {JSON.stringify(usersKey)}

+

User key: {JSON.stringify(userKey)}

+ +
+ ); +} + +/** + * Test: Prefetch and fetch functions + */ +async function testPrefetchAndFetch() { + // Configure the client + configure({ + endpoint: 'http://api.localhost:3000/graphql', + headers: { Authorization: 'Bearer token' }, + }); + + // Test fetch function (for SSR/server components) + const users = await fetchUsersQuery({ first: 10 }); + console.log('Fetched users:', users.users?.nodes?.length); + + // Test prefetch function (for cache warming) + await prefetchUsersQuery(queryClient, { first: 10 }); + console.log('Prefetched users query'); +} + +/** + * Test: Relation queries with filters + */ +function RelationQueriesComponent() { + const { data: dbData } = useDatabasesQuery({ first: 1 }); + const databaseId = dbData?.databases?.nodes?.[0]?.id; + + // Filter tables by database ID (foreign key filter) + const tableFilter: TableFilter | undefined = databaseId + ? { databaseId: { equalTo: databaseId } } + : undefined; + + const { data: tablesData } = useTablesQuery( + { + first: 10, + filter: tableFilter, + orderBy: ['NAME_ASC'], + }, + { + enabled: !!databaseId, + } + ); + + const tables: Table[] | undefined = tablesData?.tables?.nodes; + + return ( +
+

Tables in Database

+ +
+ ); +} + +/** + * Main App component to wrap everything + */ +export function App() { + return ( + +
+

React Query Hooks Test

+ + + + + + + + + +
+
+ ); +} + +export default App; diff --git a/graphql/codegen/examples/react-query-sdk.ts b/graphql/codegen/examples/react-query-sdk.ts index e13a7ee07..4c1d265d8 100644 --- a/graphql/codegen/examples/react-query-sdk.ts +++ b/graphql/codegen/examples/react-query-sdk.ts @@ -8,45 +8,45 @@ import { execute, executeWithErrors, GraphQLClientError, -} from '../examples/output/generated-sdk/client'; +} from './output/generated-sdk/client'; import { usersQueryDocument, type UsersQueryResult, type UsersQueryVariables, -} from '../examples/output/generated-sdk/queries/useUsersQuery'; +} from './output/generated-sdk/queries/useUsersQuery'; import { userQueryDocument, type UserQueryResult, type UserQueryVariables, -} from '../examples/output/generated-sdk/queries/useUserQuery'; +} from './output/generated-sdk/queries/useUserQuery'; import { databasesQueryDocument, type DatabasesQueryResult, type DatabasesQueryVariables, -} from '../examples/output/generated-sdk/queries/useDatabasesQuery'; +} from './output/generated-sdk/queries/useDatabasesQuery'; import { tablesQueryDocument, type TablesQueryResult, type TablesQueryVariables, -} from '../examples/output/generated-sdk/queries/useTablesQuery'; +} from './output/generated-sdk/queries/useTablesQuery'; import { - getCurrentUserQueryDocument, - type GetCurrentUserQueryResult, -} from '../examples/output/generated-sdk/queries/useGetCurrentUserQuery'; + currentUserQueryDocument, + type CurrentUserQueryResult, +} from './output/generated-sdk/queries/useCurrentUserQuery'; import { userByUsernameQueryDocument, type UserByUsernameQueryResult, type UserByUsernameQueryVariables, -} from '../examples/output/generated-sdk/queries/useUserByUsernameQuery'; +} from './output/generated-sdk/queries/useUserByUsernameQuery'; import { - loginMutationDocument, - type LoginMutationResult, - type LoginMutationVariables, -} from '../examples/output/generated-sdk/mutations/useLoginMutation'; + signInMutationDocument, + type SignInMutationResult, + type SignInMutationVariables, +} from './output/generated-sdk/mutations/useSignInMutation'; import type { UserFilter, TableFilter, -} from '../examples/output/generated-sdk/schema-types'; +} from './output/generated-sdk/schema-types'; const ENDPOINT = 'http://api.localhost:3000/graphql'; const section = (title: string) => @@ -66,25 +66,25 @@ async function main() { console.log('✓ Client configured'); // ───────────────────────────────────────────────────────────────────────────── - // 2. Login Mutation + // 2. SignIn Mutation // ───────────────────────────────────────────────────────────────────────────── - section('2. Login Mutation'); + section('2. SignIn Mutation'); try { - const loginResult = await execute< - LoginMutationResult, - LoginMutationVariables - >(loginMutationDocument, { + const signInResult = await execute< + SignInMutationResult, + SignInMutationVariables + >(signInMutationDocument, { input: { email: 'admin@gmail.com', password: 'password1111!@#$' }, }); - const token = loginResult.login?.apiToken?.accessToken; + const token = signInResult.signIn?.apiToken?.accessToken; if (token) { // Use setHeader() to update auth without re-configuring setHeader('Authorization', `Bearer ${token}`); - console.log('✓ Logged in, token:', token.slice(0, 30) + '...'); + console.log('✓ Signed in, token:', token.slice(0, 30) + '...'); } } catch (e) { if (e instanceof GraphQLClientError) - console.log('Login failed:', e.errors[0]?.message); + console.log('SignIn failed:', e.errors[0]?.message); else throw e; } @@ -162,11 +162,10 @@ async function main() { // 6. Custom Queries // ───────────────────────────────────────────────────────────────────────────── section('6. Custom Queries'); - const { data: currentUser } = - await executeWithErrors( - getCurrentUserQueryDocument - ); - console.log('Current user:', currentUser?.getCurrentUser?.username); + const { data: currentUser } = await executeWithErrors( + currentUserQueryDocument + ); + console.log('Current user:', currentUser?.currentUser?.username); // ───────────────────────────────────────────────────────────────────────────── // 7. Relation Queries (Foreign Key Filter) @@ -212,8 +211,8 @@ async function main() { // execute - throws on error try { - await execute( - loginMutationDocument, + await execute( + signInMutationDocument, { input: { email: 'invalid@x.com', password: 'wrong' } } ); } catch (e) { 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 f0d630e95..9fa5733d5 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 @@ -166,6 +166,66 @@ export interface InternetAddressFilter { export interface FullTextFilter { matches?: string; } +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} // ============ Entity Types ============ export interface User { id: string; @@ -578,6 +638,66 @@ export interface InternetAddressFilter { export interface FullTextFilter { matches?: string; } +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} // ============ Entity Types ============ export interface User { id: string; @@ -834,6 +954,66 @@ export interface InternetAddressFilter { export interface FullTextFilter { matches?: string; } +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} // ============ Entity Types ============ export interface User { id: string; @@ -1102,6 +1282,66 @@ export interface InternetAddressFilter { export interface FullTextFilter { matches?: string; } +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} // ============ Entity Types ============ export interface User { id: string; @@ -1375,6 +1615,66 @@ export interface InternetAddressFilter { export interface FullTextFilter { matches?: string; } +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} // ============ Entity Types ============ export interface User { id: string; @@ -1700,6 +2000,66 @@ export interface InternetAddressFilter { export interface FullTextFilter { matches?: string; } +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} // ============ Entity Types ============ export interface Post { id: string; @@ -2017,5 +2377,65 @@ export interface InternetAddressFilter { } export interface FullTextFilter { matches?: string; +} +export interface StringListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; +} +export interface IntListFilter { + isNull?: boolean; + equalTo?: number[]; + notEqualTo?: number[]; + distinctFrom?: number[]; + notDistinctFrom?: number[]; + lessThan?: number[]; + lessThanOrEqualTo?: number[]; + greaterThan?: number[]; + greaterThanOrEqualTo?: number[]; + contains?: number[]; + containedBy?: number[]; + overlaps?: number[]; + anyEqualTo?: number; + anyNotEqualTo?: number; + anyLessThan?: number; + anyLessThanOrEqualTo?: number; + anyGreaterThan?: number; + anyGreaterThanOrEqualTo?: number; +} +export interface UUIDListFilter { + isNull?: boolean; + equalTo?: string[]; + notEqualTo?: string[]; + distinctFrom?: string[]; + notDistinctFrom?: string[]; + lessThan?: string[]; + lessThanOrEqualTo?: string[]; + greaterThan?: string[]; + greaterThanOrEqualTo?: string[]; + contains?: string[]; + containedBy?: string[]; + overlaps?: string[]; + anyEqualTo?: string; + anyNotEqualTo?: string; + anyLessThan?: string; + anyLessThanOrEqualTo?: string; + anyGreaterThan?: string; + anyGreaterThanOrEqualTo?: string; }" `; 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 b5b055b2d..1fa921534 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 @@ -215,7 +215,7 @@ export interface LoginMutationResult { export function useLoginMutation(options?: Omit, 'mutationFn'>) { return useMutation({ mutationKey: customMutationKeys.login(), - mutationFn: (variables: LoginMutationVariables) => execute(loginMutationDocument, variables), + mutationFn: (variables: LoginMutationVariables) => execute(loginMutationDocument, variables), ...options }); }" @@ -250,7 +250,7 @@ export interface RegisterMutationResult { export function useRegisterMutation(options?: Omit, 'mutationFn'>) { return useMutation({ mutationKey: customMutationKeys.register(), - mutationFn: (variables: RegisterMutationVariables) => execute(registerMutationDocument, variables), + mutationFn: (variables: RegisterMutationVariables) => execute(registerMutationDocument, variables), ...options }); }" @@ -282,7 +282,7 @@ export interface LogoutMutationResult { export function useLogoutMutation(options?: Omit, 'mutationFn'>) { return useMutation({ mutationKey: customMutationKeys.logout(), - mutationFn: () => execute(logoutMutationDocument), + mutationFn: () => execute(logoutMutationDocument), ...options }); }" @@ -316,7 +316,7 @@ export interface LoginMutationResult { } export function useLoginMutation(options?: Omit, 'mutationFn'>) { return useMutation({ - mutationFn: (variables: LoginMutationVariables) => execute(loginMutationDocument, variables), + mutationFn: (variables: LoginMutationVariables) => execute(loginMutationDocument, variables), ...options }); }" @@ -365,7 +365,7 @@ export const searchUsersQueryKey = customQueryKeys.searchUsers; export function useSearchUsersQuery(variables: SearchUsersQueryVariables, options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: searchUsersQueryKey(variables), - queryFn: () => execute(searchUsersQueryDocument, variables), + queryFn: () => execute(searchUsersQueryDocument, variables), enabled: !!variables && options?.enabled !== false, ...options }); @@ -379,7 +379,7 @@ export function useSearchUsersQuery(variables: SearchUsersQueryVariables, option * \`\`\` */ export async function fetchSearchUsersQuery(variables: SearchUsersQueryVariables, options?: ExecuteOptions): Promise { - return execute(searchUsersQueryDocument, variables, options); + return execute(searchUsersQueryDocument, variables, options); } /** * Prefetch searchUsers for SSR or cache warming @@ -392,7 +392,7 @@ export async function fetchSearchUsersQuery(variables: SearchUsersQueryVariables export async function prefetchSearchUsersQuery(queryClient: QueryClient, variables: SearchUsersQueryVariables, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: searchUsersQueryKey(variables), - queryFn: () => execute(searchUsersQueryDocument, variables, options) + queryFn: () => execute(searchUsersQueryDocument, variables, options) }); }" `; @@ -436,7 +436,7 @@ export const currentUserQueryKey = customQueryKeys.currentUser; export function useCurrentUserQuery(options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: currentUserQueryKey(), - queryFn: () => execute(currentUserQueryDocument), + queryFn: () => execute(currentUserQueryDocument), ...options }); } @@ -449,7 +449,7 @@ export function useCurrentUserQuery(options?: Omit { - return execute(currentUserQueryDocument, undefined, options); + return execute(currentUserQueryDocument, undefined, options); } /** * Prefetch currentUser for SSR or cache warming @@ -462,7 +462,7 @@ export async function fetchCurrentUserQuery(options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: currentUserQueryKey(), - queryFn: () => execute(currentUserQueryDocument, undefined, options) + queryFn: () => execute(currentUserQueryDocument, undefined, options) }); }" `; @@ -505,7 +505,7 @@ export const currentUserQueryKey = () => ["currentUser"] as const; export function useCurrentUserQuery(options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: currentUserQueryKey(), - queryFn: () => execute(currentUserQueryDocument), + queryFn: () => execute(currentUserQueryDocument), ...options }); } @@ -518,7 +518,7 @@ export function useCurrentUserQuery(options?: Omit { - return execute(currentUserQueryDocument, undefined, options); + return execute(currentUserQueryDocument, undefined, options); } /** * Prefetch currentUser for SSR or cache warming @@ -531,7 +531,7 @@ export async function fetchCurrentUserQuery(options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: currentUserQueryKey(), - queryFn: () => execute(currentUserQueryDocument, undefined, options) + queryFn: () => execute(currentUserQueryDocument, undefined, options) }); }" `; @@ -597,7 +597,7 @@ export function useCreateUserMutation(options?: Omit execute(createUserMutationDocument, variables), + mutationFn: (variables: CreateUserMutationVariables) => execute(createUserMutationDocument, variables), onSuccess: () => { queryClient.invalidateQueries({ queryKey: userKeys.lists() @@ -674,7 +674,7 @@ export function useCreatePostMutation(options?: Omit execute(createPostMutationDocument, variables), + mutationFn: (variables: CreatePostMutationVariables) => execute(createPostMutationDocument, variables), onSuccess: () => { queryClient.invalidateQueries({ queryKey: postKeys.lists() @@ -743,7 +743,7 @@ export interface CreateUserMutationResult { export function useCreateUserMutation(options?: Omit, 'mutationFn'>) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (variables: CreateUserMutationVariables) => execute(createUserMutationDocument, variables), + mutationFn: (variables: CreateUserMutationVariables) => execute(createUserMutationDocument, variables), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user", "list"] @@ -803,7 +803,7 @@ export function useDeleteUserMutation(options?: Omit execute(deleteUserMutationDocument, variables), + mutationFn: (variables: DeleteUserMutationVariables) => execute(deleteUserMutationDocument, variables), onSuccess: (_, variables) => { queryClient.removeQueries({ queryKey: userKeys.detail(variables.input.id) @@ -867,7 +867,7 @@ export function useDeletePostMutation(options?: Omit execute(deletePostMutationDocument, variables), + mutationFn: (variables: DeletePostMutationVariables) => execute(deletePostMutationDocument, variables), onSuccess: (_, variables) => { queryClient.removeQueries({ queryKey: postKeys.detail(variables.input.id) @@ -927,7 +927,7 @@ export interface DeleteUserMutationResult { export function useDeleteUserMutation(options?: Omit, 'mutationFn'>) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (variables: DeleteUserMutationVariables) => execute(deleteUserMutationDocument, variables), + mutationFn: (variables: DeleteUserMutationVariables) => execute(deleteUserMutationDocument, variables), onSuccess: (_, variables) => { queryClient.removeQueries({ queryKey: ["user", "detail", variables.input.id] @@ -1005,7 +1005,7 @@ export function useUpdateUserMutation(options?: Omit execute(updateUserMutationDocument, variables), + mutationFn: (variables: UpdateUserMutationVariables) => execute(updateUserMutationDocument, variables), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.input.id) @@ -1088,7 +1088,7 @@ export function useUpdatePostMutation(options?: Omit execute(updatePostMutationDocument, variables), + mutationFn: (variables: UpdatePostMutationVariables) => execute(updatePostMutationDocument, variables), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: postKeys.detail(variables.input.id) @@ -1163,7 +1163,7 @@ export interface UpdateUserMutationResult { export function useUpdateUserMutation(options?: Omit, 'mutationFn'>) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (variables: UpdateUserMutationVariables) => execute(updateUserMutationDocument, variables), + mutationFn: (variables: UpdateUserMutationVariables) => execute(updateUserMutationDocument, variables), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["user", "detail", variables.input.id] @@ -1192,8 +1192,17 @@ import type { User, UUIDFilter, StringFilter, DatetimeFilter } from "../types"; import { userKeys } from "../query-keys"; export type { User } from "../types"; export const usersQueryDocument = \` -query UsersQuery($first: Int, $offset: Int, $filter: UserFilter, $orderBy: [UsersOrderBy!]) { - users(first: $first, offset: $offset, filter: $filter, orderBy: $orderBy) { +query UsersQuery($first: Int, $last: Int, $offset: Int, $before: Cursor, $after: Cursor, $filter: UserFilter, $condition: UserCondition, $orderBy: [UsersOrderBy!]) { + users( + first: $first + last: $last + offset: $offset + before: $before + after: $after + filter: $filter + condition: $condition + orderBy: $orderBy + ) { totalCount nodes { id @@ -1219,11 +1228,21 @@ interface UserFilter { or?: UserFilter[]; not?: UserFilter; } +interface UserCondition { + id?: string; + email?: string; + name?: string; + createdAt?: string; +} type UsersOrderBy = "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "NATURAL" | "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC"; export interface UsersQueryVariables { first?: number; + last?: number; offset?: number; + before?: string; + after?: string; filter?: UserFilter; + condition?: UserCondition; orderBy?: UsersOrderBy[]; } export interface UsersQueryResult { @@ -1255,7 +1274,7 @@ export const usersQueryKey = userKeys.list; export function useUsersQuery(variables?: UsersQueryVariables, options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: userKeys.list(variables), - queryFn: () => execute(usersQueryDocument, variables), + queryFn: () => execute(usersQueryDocument, variables), ...options }); } @@ -1275,7 +1294,7 @@ export function useUsersQuery(variables?: UsersQueryVariables, options?: Omit { - return execute(usersQueryDocument, variables, options); + return execute(usersQueryDocument, variables, options); } /** * Prefetch User list for SSR or cache warming @@ -1288,7 +1307,7 @@ export async function fetchUsersQuery(variables?: UsersQueryVariables, options?: export async function prefetchUsersQuery(queryClient: QueryClient, variables?: UsersQueryVariables, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: userKeys.list(variables), - queryFn: () => execute(usersQueryDocument, variables, options) + queryFn: () => execute(usersQueryDocument, variables, options) }); }" `; @@ -1309,8 +1328,17 @@ import { postKeys } from "../query-keys"; import type { PostScope } from "../query-keys"; export type { Post } from "../types"; export const postsQueryDocument = \` -query PostsQuery($first: Int, $offset: Int, $filter: PostFilter, $orderBy: [PostsOrderBy!]) { - posts(first: $first, offset: $offset, filter: $filter, orderBy: $orderBy) { +query PostsQuery($first: Int, $last: Int, $offset: Int, $before: Cursor, $after: Cursor, $filter: PostFilter, $condition: PostCondition, $orderBy: [PostsOrderBy!]) { + posts( + first: $first + last: $last + offset: $offset + before: $before + after: $after + filter: $filter + condition: $condition + orderBy: $orderBy + ) { totalCount nodes { id @@ -1340,11 +1368,23 @@ interface PostFilter { or?: PostFilter[]; not?: PostFilter; } +interface PostCondition { + id?: string; + title?: string; + content?: string; + authorId?: string; + published?: boolean; + createdAt?: string; +} type PostsOrderBy = "ID_ASC" | "ID_DESC" | "TITLE_ASC" | "TITLE_DESC" | "CONTENT_ASC" | "CONTENT_DESC" | "AUTHOR_ID_ASC" | "AUTHOR_ID_DESC" | "PUBLISHED_ASC" | "PUBLISHED_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "NATURAL" | "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC"; export interface PostsQueryVariables { first?: number; + last?: number; offset?: number; + before?: string; + after?: string; filter?: PostFilter; + condition?: PostCondition; orderBy?: PostsOrderBy[]; } export interface PostsQueryResult { @@ -1388,7 +1428,7 @@ export function usePostsQuery(variables?: PostsQueryVariables, options?: Omit execute(postsQueryDocument, variables), + queryFn: () => execute(postsQueryDocument, variables), ...queryOptions }); } @@ -1408,7 +1448,7 @@ export function usePostsQuery(variables?: PostsQueryVariables, options?: Omit { - return execute(postsQueryDocument, variables, options); + return execute(postsQueryDocument, variables, options); } /** * Prefetch Post list for SSR or cache warming @@ -1421,7 +1461,7 @@ export async function fetchPostsQuery(variables?: PostsQueryVariables, options?: export async function prefetchPostsQuery(queryClient: QueryClient, variables?: PostsQueryVariables, scope?: PostScope, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: postKeys.list(variables, scope), - queryFn: () => execute(postsQueryDocument, variables, options) + queryFn: () => execute(postsQueryDocument, variables, options) }); }" `; @@ -1440,8 +1480,17 @@ import type { ExecuteOptions } from "../client"; import type { User, UUIDFilter, StringFilter, DatetimeFilter } from "../types"; export type { User } from "../types"; export const usersQueryDocument = \` -query UsersQuery($first: Int, $offset: Int, $filter: UserFilter, $orderBy: [UsersOrderBy!]) { - users(first: $first, offset: $offset, filter: $filter, orderBy: $orderBy) { +query UsersQuery($first: Int, $last: Int, $offset: Int, $before: Cursor, $after: Cursor, $filter: UserFilter, $condition: UserCondition, $orderBy: [UsersOrderBy!]) { + users( + first: $first + last: $last + offset: $offset + before: $before + after: $after + filter: $filter + condition: $condition + orderBy: $orderBy + ) { totalCount nodes { id @@ -1467,11 +1516,21 @@ interface UserFilter { or?: UserFilter[]; not?: UserFilter; } +interface UserCondition { + id?: string; + email?: string; + name?: string; + createdAt?: string; +} type UsersOrderBy = "ID_ASC" | "ID_DESC" | "EMAIL_ASC" | "EMAIL_DESC" | "NAME_ASC" | "NAME_DESC" | "CREATED_AT_ASC" | "CREATED_AT_DESC" | "NATURAL" | "PRIMARY_KEY_ASC" | "PRIMARY_KEY_DESC"; export interface UsersQueryVariables { first?: number; + last?: number; offset?: number; + before?: string; + after?: string; filter?: UserFilter; + condition?: UserCondition; orderBy?: UsersOrderBy[]; } export interface UsersQueryResult { @@ -1502,7 +1561,7 @@ export const usersQueryKey = (variables?: UsersQueryVariables) => ["user", "list export function useUsersQuery(variables?: UsersQueryVariables, options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: usersQueryKey(variables), - queryFn: () => execute(usersQueryDocument, variables), + queryFn: () => execute(usersQueryDocument, variables), ...options }); } @@ -1522,7 +1581,7 @@ export function useUsersQuery(variables?: UsersQueryVariables, options?: Omit { - return execute(usersQueryDocument, variables, options); + return execute(usersQueryDocument, variables, options); } /** * Prefetch User list for SSR or cache warming @@ -1535,7 +1594,7 @@ export async function fetchUsersQuery(variables?: UsersQueryVariables, options?: export async function prefetchUsersQuery(queryClient: QueryClient, variables?: UsersQueryVariables, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: usersQueryKey(variables), - queryFn: () => execute(usersQueryDocument, variables, options) + queryFn: () => execute(usersQueryDocument, variables, options) }); }" `; @@ -1583,7 +1642,7 @@ export const userQueryKey = userKeys.detail; export function useUserQuery(variables: UserQueryVariables, options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: userKeys.detail(variables.id), - queryFn: () => execute(userQueryDocument, variables), + queryFn: () => execute(userQueryDocument, variables), ...options }); } @@ -1596,7 +1655,7 @@ export function useUserQuery(variables: UserQueryVariables, options?: Omit { - return execute(userQueryDocument, variables, options); + return execute(userQueryDocument, variables, options); } /** * Prefetch a single User for SSR or cache warming @@ -1609,7 +1668,7 @@ export async function fetchUserQuery(variables: UserQueryVariables, options?: Ex export async function prefetchUserQuery(queryClient: QueryClient, variables: UserQueryVariables, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: userKeys.detail(variables.id), - queryFn: () => execute(userQueryDocument, variables, options) + queryFn: () => execute(userQueryDocument, variables, options) }); }" `; @@ -1672,7 +1731,7 @@ export function usePostQuery(variables: PostQueryVariables, options?: Omit execute(postQueryDocument, variables), + queryFn: () => execute(postQueryDocument, variables), ...queryOptions }); } @@ -1685,7 +1744,7 @@ export function usePostQuery(variables: PostQueryVariables, options?: Omit { - return execute(postQueryDocument, variables, options); + return execute(postQueryDocument, variables, options); } /** * Prefetch a single Post for SSR or cache warming @@ -1698,7 +1757,7 @@ export async function fetchPostQuery(variables: PostQueryVariables, options?: Ex export async function prefetchPostQuery(queryClient: QueryClient, variables: PostQueryVariables, scope?: PostScope, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: postKeys.detail(variables.id, scope), - queryFn: () => execute(postQueryDocument, variables, options) + queryFn: () => execute(postQueryDocument, variables, options) }); }" `; @@ -1744,7 +1803,7 @@ export const userQueryKey = (id: string) => ["user", "detail", id] as const; export function useUserQuery(variables: UserQueryVariables, options?: Omit, 'queryKey' | 'queryFn'>) { return useQuery({ queryKey: userQueryKey(variables.id), - queryFn: () => execute(userQueryDocument, variables), + queryFn: () => execute(userQueryDocument, variables), ...options }); } @@ -1757,7 +1816,7 @@ export function useUserQuery(variables: UserQueryVariables, options?: Omit { - return execute(userQueryDocument, variables, options); + return execute(userQueryDocument, variables, options); } /** * Prefetch a single User for SSR or cache warming @@ -1770,7 +1829,7 @@ export async function fetchUserQuery(variables: UserQueryVariables, options?: Ex export async function prefetchUserQuery(queryClient: QueryClient, variables: UserQueryVariables, options?: ExecuteOptions): Promise { await queryClient.prefetchQuery({ queryKey: userQueryKey(variables.id), - queryFn: () => execute(userQueryDocument, variables, options) + queryFn: () => execute(userQueryDocument, variables, options) }); }" `; diff --git a/graphql/codegen/src/cli/codegen/babel-ast.ts b/graphql/codegen/src/cli/codegen/babel-ast.ts index ccdae6ede..9a189b2f7 100644 --- a/graphql/codegen/src/cli/codegen/babel-ast.ts +++ b/graphql/codegen/src/cli/codegen/babel-ast.ts @@ -109,9 +109,28 @@ export function typedParam( export function keyofTypeof(name: string): t.TSTypeOperator { const typeofOp = t.tsTypeOperator(t.tsTypeReference(t.identifier(name))); typeofOp.operator = 'typeof'; - + const keyofOp = t.tsTypeOperator(typeofOp); keyofOp.operator = 'keyof'; - + return keyofOp; } + +/** + * Create a call expression with TypeScript type parameters + * + * This is used to generate typed function calls like: + * execute(document, variables) + */ +export function createTypedCallExpression( + callee: t.Expression, + args: (t.Expression | t.SpreadElement)[], + typeParams: t.TSType[] +): t.CallExpression { + const call = t.callExpression(callee, args); + if (typeParams.length > 0) { + // @ts-ignore - Babel types support typeParameters on CallExpression for TS + call.typeParameters = t.tsTypeParameterInstantiation(typeParams); + } + return call; +} diff --git a/graphql/codegen/src/cli/codegen/barrel.ts b/graphql/codegen/src/cli/codegen/barrel.ts index b5d89b591..90a722b1a 100644 --- a/graphql/codegen/src/cli/codegen/barrel.ts +++ b/graphql/codegen/src/cli/codegen/barrel.ts @@ -12,6 +12,7 @@ import { getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, + hasValidPrimaryKey, } from './utils'; import { getOperationHookName } from './type-resolver'; @@ -31,10 +32,13 @@ export function generateQueriesBarrel(tables: CleanTable[]): string { // Export all query hooks for (const table of tables) { const listHookName = getListQueryHookName(table); - const singleHookName = getSingleQueryHookName(table); - statements.push(exportAllFrom(`./${listHookName}`)); - statements.push(exportAllFrom(`./${singleHookName}`)); + + // Only export single query hook if table has valid primary key + if (hasValidPrimaryKey(table)) { + const singleHookName = getSingleQueryHookName(table); + statements.push(exportAllFrom(`./${singleHookName}`)); + } } // Add file header as leading comment on first statement @@ -204,20 +208,33 @@ export function generateCustomQueriesBarrel( customQueryNames: string[] ): string { const statements: t.Statement[] = []; + const exportedHooks = new Set(); // Export all table query hooks for (const table of tables) { const listHookName = getListQueryHookName(table); - const singleHookName = getSingleQueryHookName(table); + if (!exportedHooks.has(listHookName)) { + statements.push(exportAllFrom(`./${listHookName}`)); + exportedHooks.add(listHookName); + } - statements.push(exportAllFrom(`./${listHookName}`)); - statements.push(exportAllFrom(`./${singleHookName}`)); + // Only export single query hook if table has valid primary key + if (hasValidPrimaryKey(table)) { + const singleHookName = getSingleQueryHookName(table); + if (!exportedHooks.has(singleHookName)) { + statements.push(exportAllFrom(`./${singleHookName}`)); + exportedHooks.add(singleHookName); + } + } } - // Add custom query hooks + // Add custom query hooks (skip if already exported from table hooks) for (const name of customQueryNames) { const hookName = getOperationHookName(name, 'query'); - statements.push(exportAllFrom(`./${hookName}`)); + if (!exportedHooks.has(hookName)) { + statements.push(exportAllFrom(`./${hookName}`)); + exportedHooks.add(hookName); + } } // Add file header as leading comment on first statement @@ -240,28 +257,40 @@ export function generateCustomMutationsBarrel( customMutationNames: string[] ): string { const statements: t.Statement[] = []; + const exportedHooks = new Set(); // Export all table mutation hooks for (const table of tables) { const createHookName = getCreateMutationHookName(table); - const updateHookName = getUpdateMutationHookName(table); - const deleteHookName = getDeleteMutationHookName(table); - - statements.push(exportAllFrom(`./${createHookName}`)); + if (!exportedHooks.has(createHookName)) { + statements.push(exportAllFrom(`./${createHookName}`)); + exportedHooks.add(createHookName); + } // Only add update/delete if they exist if (table.query?.update !== null) { - statements.push(exportAllFrom(`./${updateHookName}`)); + const updateHookName = getUpdateMutationHookName(table); + if (!exportedHooks.has(updateHookName)) { + statements.push(exportAllFrom(`./${updateHookName}`)); + exportedHooks.add(updateHookName); + } } if (table.query?.delete !== null) { - statements.push(exportAllFrom(`./${deleteHookName}`)); + const deleteHookName = getDeleteMutationHookName(table); + if (!exportedHooks.has(deleteHookName)) { + statements.push(exportAllFrom(`./${deleteHookName}`)); + exportedHooks.add(deleteHookName); + } } } - // Add custom mutation hooks + // Add custom mutation hooks (skip if already exported from table hooks) for (const name of customMutationNames) { const hookName = getOperationHookName(name, 'mutation'); - statements.push(exportAllFrom(`./${hookName}`)); + if (!exportedHooks.has(hookName)) { + statements.push(exportAllFrom(`./${hookName}`)); + exportedHooks.add(hookName); + } } // Add file header as leading comment on first statement diff --git a/graphql/codegen/src/cli/codegen/custom-mutations.ts b/graphql/codegen/src/cli/codegen/custom-mutations.ts index f9156cadb..2d9a10aef 100644 --- a/graphql/codegen/src/cli/codegen/custom-mutations.ts +++ b/graphql/codegen/src/cli/codegen/custom-mutations.ts @@ -16,7 +16,7 @@ import type { TypeRegistry, } from '../../types/schema'; import * as t from '@babel/types'; -import { generateCode, addJSDocComment, typedParam } from './babel-ast'; +import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast'; import { buildCustomMutationString } from './schema-gql-ast'; import { typeRefToTsType, @@ -236,10 +236,14 @@ function generateCustomMutationHookInternal( t.identifier('mutationFn'), t.arrowFunctionExpression( [typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)))], - t.callExpression(t.identifier('execute'), [ - t.identifier(documentConstName), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(resultTypeName)), + t.tsTypeReference(t.identifier(variablesTypeName)), + ] + ) ) ) ); @@ -249,7 +253,11 @@ function generateCustomMutationHookInternal( t.identifier('mutationFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [t.identifier(documentConstName)]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName)], + [t.tsTypeReference(t.identifier(resultTypeName))] + ) ) ) ); diff --git a/graphql/codegen/src/cli/codegen/custom-queries.ts b/graphql/codegen/src/cli/codegen/custom-queries.ts index 4bcbd2d9f..daefb0eb0 100644 --- a/graphql/codegen/src/cli/codegen/custom-queries.ts +++ b/graphql/codegen/src/cli/codegen/custom-queries.ts @@ -16,7 +16,7 @@ import type { TypeRegistry, } from '../../types/schema'; import * as t from '@babel/types'; -import { generateCode, addJSDocComment, typedParam } from './babel-ast'; +import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast'; import { buildCustomQueryString } from './schema-gql-ast'; import { typeRefToTsType, @@ -267,10 +267,14 @@ export function generateCustomQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(documentConstName), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(resultTypeName)), + t.tsTypeReference(t.identifier(variablesTypeName)), + ] + ) ) ) ); @@ -307,7 +311,11 @@ export function generateCustomQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [t.identifier(documentConstName)]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName)], + [t.tsTypeReference(t.identifier(resultTypeName))] + ) ) ) ); @@ -365,21 +373,24 @@ export function generateCustomQueryHook( if (hasArgs) { fetchBodyStatements.push( t.returnStatement( - t.callExpression(t.identifier('execute'), [ - t.identifier(documentConstName), - t.identifier('variables'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName), t.identifier('variables'), t.identifier('options')], + [ + t.tsTypeReference(t.identifier(resultTypeName)), + t.tsTypeReference(t.identifier(variablesTypeName)), + ] + ) ) ); } else { fetchBodyStatements.push( t.returnStatement( - t.callExpression(t.identifier('execute'), [ - t.identifier(documentConstName), - t.identifier('undefined'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName), t.identifier('undefined'), t.identifier('options')], + [t.tsTypeReference(t.identifier(resultTypeName))] + ) ) ); } @@ -436,11 +447,14 @@ export function generateCustomQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(documentConstName), - t.identifier('variables'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName), t.identifier('variables'), t.identifier('options')], + [ + t.tsTypeReference(t.identifier(resultTypeName)), + t.tsTypeReference(t.identifier(variablesTypeName)), + ] + ) ) ) ); @@ -456,11 +470,11 @@ export function generateCustomQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(documentConstName), - t.identifier('undefined'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(documentConstName), t.identifier('undefined'), t.identifier('options')], + [t.tsTypeReference(t.identifier(resultTypeName))] + ) ) ) ); diff --git a/graphql/codegen/src/cli/codegen/gql-ast.ts b/graphql/codegen/src/cli/codegen/gql-ast.ts index 7e1353ce3..ec2c13aac 100644 --- a/graphql/codegen/src/cli/codegen/gql-ast.ts +++ b/graphql/codegen/src/cli/codegen/gql-ast.ts @@ -21,6 +21,7 @@ import { getUpdateMutationName, getDeleteMutationName, getFilterTypeName, + getConditionTypeName, getOrderByTypeName, getScalarFields, getPrimaryKeyInfo, @@ -72,23 +73,40 @@ export function buildListQueryAST(config: ListQueryConfig): DocumentNode { const { table } = config; const queryName = getAllRowsQueryName(table); const filterType = getFilterTypeName(table); + const conditionType = getConditionTypeName(table); const orderByType = getOrderByTypeName(table); const scalarFields = getScalarFields(table); - // Variable definitions + // Variable definitions - all pagination arguments from PostGraphile const variableDefinitions: VariableDefinitionNode[] = [ t.variableDefinition({ variable: t.variable({ name: 'first' }), type: t.namedType({ type: 'Int' }), }), + t.variableDefinition({ + variable: t.variable({ name: 'last' }), + type: t.namedType({ type: 'Int' }), + }), t.variableDefinition({ variable: t.variable({ name: 'offset' }), type: t.namedType({ type: 'Int' }), }), + t.variableDefinition({ + variable: t.variable({ name: 'before' }), + type: t.namedType({ type: 'Cursor' }), + }), + t.variableDefinition({ + variable: t.variable({ name: 'after' }), + type: t.namedType({ type: 'Cursor' }), + }), t.variableDefinition({ variable: t.variable({ name: 'filter' }), type: t.namedType({ type: filterType }), }), + t.variableDefinition({ + variable: t.variable({ name: 'condition' }), + type: t.namedType({ type: conditionType }), + }), t.variableDefinition({ variable: t.variable({ name: 'orderBy' }), type: t.listType({ @@ -100,8 +118,12 @@ export function buildListQueryAST(config: ListQueryConfig): DocumentNode { // Query arguments const args: ArgumentNode[] = [ t.argument({ name: 'first', value: t.variable({ name: 'first' }) }), + t.argument({ name: 'last', value: t.variable({ name: 'last' }) }), t.argument({ name: 'offset', value: t.variable({ name: 'offset' }) }), + t.argument({ name: 'before', value: t.variable({ name: 'before' }) }), + t.argument({ name: 'after', value: t.variable({ name: 'after' }) }), t.argument({ name: 'filter', value: t.variable({ name: 'filter' }) }), + t.argument({ name: 'condition', value: t.variable({ name: 'condition' }) }), t.argument({ name: 'orderBy', value: t.variable({ name: 'orderBy' }) }), ]; diff --git a/graphql/codegen/src/cli/codegen/index.ts b/graphql/codegen/src/cli/codegen/index.ts index 163a5acd8..8778b7406 100644 --- a/graphql/codegen/src/cli/codegen/index.ts +++ b/graphql/codegen/src/cli/codegen/index.ts @@ -255,6 +255,7 @@ export function generate(options: GenerateOptions): GenerateResult { enumsFromSchemaTypes: generatedEnumNames, useCentralizedKeys, hasRelationships, + tableTypeNames, }); for (const hook of mutationHooks) { files.push({ diff --git a/graphql/codegen/src/cli/codegen/mutations.ts b/graphql/codegen/src/cli/codegen/mutations.ts index d0f5f4fce..11596b3fe 100644 --- a/graphql/codegen/src/cli/codegen/mutations.ts +++ b/graphql/codegen/src/cli/codegen/mutations.ts @@ -9,7 +9,7 @@ */ import type { CleanTable } from '../../types/schema'; import * as t from '@babel/types'; -import { generateCode, addJSDocComment, typedParam } from './babel-ast'; +import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast'; import { buildCreateMutationAST, buildUpdateMutationAST, @@ -56,6 +56,8 @@ export interface MutationGeneratorOptions { enumsFromSchemaTypes?: string[]; useCentralizedKeys?: boolean; hasRelationships?: boolean; + /** All table type names for determining which types to import from types.ts vs schema-types.ts */ + tableTypeNames?: Set; } export function generateCreateMutationHook( @@ -67,6 +69,7 @@ export function generateCreateMutationHook( enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, + tableTypeNames = new Set(), } = options; if (!reactQueryEnabled) { @@ -85,10 +88,14 @@ export function generateCreateMutationHook( const pkFieldNames = new Set(getPrimaryKeyInfo(table).map((pk) => pk.name)); const usedEnums = new Set(); + const usedTableTypes = new Set(); for (const field of scalarFields) { const cleanType = field.type.gqlType.replace(/!/g, ''); if (enumSet.has(cleanType)) { usedEnums.add(cleanType); + } else if (tableTypeNames.has(cleanType) && cleanType !== typeName) { + // Track table types used in scalar fields (excluding the main type which is already imported) + usedTableTypes.add(cleanType); } } @@ -119,8 +126,10 @@ export function generateCreateMutationHook( ); statements.push(clientImport); + // Import the main type and any other table types used in scalar fields + const allTypesToImport = [typeName, ...Array.from(usedTableTypes)].sort(); const typesImport = t.importDeclaration( - [t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], + allTypesToImport.map((t_) => t.importSpecifier(t.identifier(t_), t.identifier(t_))), t.stringLiteral('../types') ); typesImport.importKind = 'type'; @@ -269,10 +278,14 @@ export function generateCreateMutationHook( t.identifier('mutationFn'), t.arrowFunctionExpression( [typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${mutationName}MutationDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)), + ] + ) ) ) ); @@ -352,6 +365,7 @@ export function generateUpdateMutationHook( enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, + tableTypeNames = new Set(), } = options; if (!reactQueryEnabled) { @@ -376,10 +390,13 @@ export function generateUpdateMutationHook( const pkFieldNames = new Set(pkFields.map((pk) => pk.name)); const usedEnums = new Set(); + const usedTableTypes = new Set(); for (const field of scalarFields) { const cleanType = field.type.gqlType.replace(/!/g, ''); if (enumSet.has(cleanType)) { usedEnums.add(cleanType); + } else if (tableTypeNames.has(cleanType) && cleanType !== typeName) { + usedTableTypes.add(cleanType); } } @@ -410,8 +427,10 @@ export function generateUpdateMutationHook( ); statements.push(clientImport); + // Import the main type and any other table types used in scalar fields + const allTypesToImportUpdate = [typeName, ...Array.from(usedTableTypes)].sort(); const typesImport = t.importDeclaration( - [t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], + allTypesToImportUpdate.map((t_) => t.importSpecifier(t.identifier(t_), t.identifier(t_))), t.stringLiteral('../types') ); typesImport.importKind = 'type'; @@ -565,10 +584,14 @@ export function generateUpdateMutationHook( t.identifier('mutationFn'), t.arrowFunctionExpression( [typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${mutationName}MutationDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)), + ] + ) ) ) ); @@ -816,10 +839,14 @@ export function generateDeleteMutationHook( t.identifier('mutationFn'), t.arrowFunctionExpression( [typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${mutationName}MutationDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)), + ] + ) ) ) ); diff --git a/graphql/codegen/src/cli/codegen/orm/input-types-generator.ts b/graphql/codegen/src/cli/codegen/orm/input-types-generator.ts index f5d0246c3..ca7154c57 100644 --- a/graphql/codegen/src/cli/codegen/orm/input-types-generator.ts +++ b/graphql/codegen/src/cli/codegen/orm/input-types-generator.ts @@ -242,7 +242,8 @@ type FilterOperators = | 'string' | 'json' | 'inet' - | 'fulltext'; + | 'fulltext' + | 'listArray'; interface ScalarFilterConfig { name: string; @@ -305,6 +306,22 @@ const SCALAR_FILTER_CONFIGS: ScalarFilterConfig[] = [ operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'], }, { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] }, + // List filters (for array fields like string[], int[], uuid[]) + { + name: 'StringListFilter', + tsType: 'string[]', + operators: ['equality', 'distinct', 'comparison', 'listArray'], + }, + { + name: 'IntListFilter', + tsType: 'number[]', + operators: ['equality', 'distinct', 'comparison', 'listArray'], + }, + { + name: 'UUIDListFilter', + tsType: 'string[]', + operators: ['equality', 'distinct', 'comparison', 'listArray'], + }, ]; /** @@ -400,6 +417,23 @@ function buildScalarFilterProperties( props.push({ name: 'matches', type: 'string', optional: true }); } + // List/Array operators (contains, overlaps, anyEqualTo, etc.) + if (operators.includes('listArray')) { + // Extract base type from array type (e.g., 'string[]' -> 'string') + const baseType = tsType.replace('[]', ''); + props.push( + { name: 'contains', type: tsType, optional: true }, + { name: 'containedBy', type: tsType, optional: true }, + { name: 'overlaps', type: tsType, optional: true }, + { name: 'anyEqualTo', type: baseType, optional: true }, + { name: 'anyNotEqualTo', type: baseType, optional: true }, + { name: 'anyLessThan', type: baseType, optional: true }, + { name: 'anyLessThanOrEqualTo', type: baseType, optional: true }, + { name: 'anyGreaterThan', type: baseType, optional: true }, + { name: 'anyGreaterThanOrEqualTo', type: baseType, optional: true } + ); + } + return props; } diff --git a/graphql/codegen/src/cli/codegen/queries.ts b/graphql/codegen/src/cli/codegen/queries.ts index 3e4ae2775..55852c339 100644 --- a/graphql/codegen/src/cli/codegen/queries.ts +++ b/graphql/codegen/src/cli/codegen/queries.ts @@ -8,7 +8,7 @@ */ import type { CleanTable } from '../../types/schema'; import * as t from '@babel/types'; -import { generateCode, addJSDocComment, typedParam } from './babel-ast'; +import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast'; import { buildListQueryAST, buildSingleQueryAST, @@ -23,10 +23,13 @@ import { getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, + getConditionTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, + hasValidPrimaryKey, + fieldTypeToTs, toScreamingSnake, ucFirst, lcFirst, @@ -106,6 +109,7 @@ export function generateListQueryHook( const hookName = getListQueryHookName(table); const queryName = getAllRowsQueryName(table); const filterTypeName = getFilterTypeName(table); + const conditionTypeName = getConditionTypeName(table); const orderByTypeName = getOrderByTypeName(table); const scalarFields = getScalarFields(table); const keysName = `${lcFirst(typeName)}Keys`; @@ -232,6 +236,80 @@ export function generateListQueryHook( createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false) ); + // Generate Condition interface (simple equality filter with scalar types) + // Track non-primitive types (enums) that need to be imported + const enumTypesUsed = new Set(); + const conditionProperties: t.TSPropertySignature[] = scalarFields.map( + (field) => { + const tsType = fieldTypeToTs(field.type); + const isPrimitive = + tsType === 'string' || + tsType === 'number' || + tsType === 'boolean' || + tsType === 'unknown' || + tsType.endsWith('[]'); + let typeAnnotation: t.TSType; + if (field.type.isArray) { + const baseType = tsType.replace('[]', ''); + const isBasePrimitive = + baseType === 'string' || + baseType === 'number' || + baseType === 'boolean' || + baseType === 'unknown'; + if (!isBasePrimitive) { + enumTypesUsed.add(baseType); + } + typeAnnotation = t.tsArrayType( + baseType === 'string' + ? t.tsStringKeyword() + : baseType === 'number' + ? t.tsNumberKeyword() + : baseType === 'boolean' + ? t.tsBooleanKeyword() + : t.tsTypeReference(t.identifier(baseType)) + ); + } else { + if (!isPrimitive) { + enumTypesUsed.add(tsType); + } + typeAnnotation = + tsType === 'string' + ? t.tsStringKeyword() + : tsType === 'number' + ? t.tsNumberKeyword() + : tsType === 'boolean' + ? t.tsBooleanKeyword() + : t.tsTypeReference(t.identifier(tsType)); + } + const prop = t.tsPropertySignature( + t.identifier(field.name), + t.tsTypeAnnotation(typeAnnotation) + ); + prop.optional = true; + return prop; + } + ); + + // Add import for enum types if any are used + if (enumTypesUsed.size > 0) { + const schemaTypesImport = t.importDeclaration( + Array.from(enumTypesUsed).map((et) => + t.importSpecifier(t.identifier(et), t.identifier(et)) + ), + t.stringLiteral('../schema-types') + ); + schemaTypesImport.importKind = 'type'; + statements.push(schemaTypesImport); + } + + const conditionInterface = t.tsInterfaceDeclaration( + t.identifier(conditionTypeName), + null, + null, + t.tsInterfaceBody(conditionProperties) + ); + statements.push(conditionInterface); + const orderByValues = [ ...scalarFields.flatMap((f) => [ `${toScreamingSnake(f.name)}_ASC`, @@ -257,6 +335,14 @@ export function generateListQueryHook( p.optional = true; return p; })(), + (() => { + const p = t.tsPropertySignature( + t.identifier('last'), + t.tsTypeAnnotation(t.tsNumberKeyword()) + ); + p.optional = true; + return p; + })(), (() => { const p = t.tsPropertySignature( t.identifier('offset'), @@ -265,6 +351,22 @@ export function generateListQueryHook( p.optional = true; return p; })(), + (() => { + const p = t.tsPropertySignature( + t.identifier('before'), + t.tsTypeAnnotation(t.tsStringKeyword()) + ); + p.optional = true; + return p; + })(), + (() => { + const p = t.tsPropertySignature( + t.identifier('after'), + t.tsTypeAnnotation(t.tsStringKeyword()) + ); + p.optional = true; + return p; + })(), (() => { const p = t.tsPropertySignature( t.identifier('filter'), @@ -273,6 +375,14 @@ export function generateListQueryHook( p.optional = true; return p; })(), + (() => { + const p = t.tsPropertySignature( + t.identifier('condition'), + t.tsTypeAnnotation(t.tsTypeReference(t.identifier(conditionTypeName))) + ); + p.optional = true; + return p; + })(), (() => { const p = t.tsPropertySignature( t.identifier('orderBy'), @@ -426,10 +536,14 @@ export function generateListQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), + ] + ) ) ), t.spreadElement(t.identifier('queryOptions')), @@ -456,10 +570,14 @@ export function generateListQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), + ] + ) ) ), t.spreadElement(t.identifier('options')), @@ -482,10 +600,14 @@ export function generateListQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), + ] + ) ) ), t.spreadElement(t.identifier('options')), @@ -549,11 +671,14 @@ export function generateListQueryHook( const fetchFuncBody = t.blockStatement([ t.returnStatement( - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), + ] + ) ), ]); const fetchFunc = t.functionDeclaration( @@ -664,11 +789,14 @@ export function generateListQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), + ] + ) ) ), ]), @@ -717,7 +845,12 @@ export function generateListQueryHook( export function generateSingleQueryHook( table: CleanTable, options: QueryGeneratorOptions = {} -): GeneratedQueryFile { +): GeneratedQueryFile | null { + // Skip tables with composite keys - they are handled as custom queries + if (!hasValidPrimaryKey(table)) { + return null; + } + const { reactQueryEnabled = true, useCentralizedKeys = true, @@ -951,10 +1084,14 @@ export function generateSingleQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)), + ] + ) ) ), t.spreadElement(t.identifier('queryOptions')), @@ -986,10 +1123,14 @@ export function generateSingleQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)), + ] + ) ) ), t.spreadElement(t.identifier('options')), @@ -1015,10 +1156,14 @@ export function generateSingleQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)), + ] + ) ) ), t.spreadElement(t.identifier('options')), @@ -1077,11 +1222,14 @@ export function generateSingleQueryHook( const fetchFuncBody = t.blockStatement([ t.returnStatement( - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)), + ] + ) ), ]); const fetchFunc = t.functionDeclaration( @@ -1186,11 +1334,14 @@ export function generateSingleQueryHook( t.identifier('queryFn'), t.arrowFunctionExpression( [], - t.callExpression(t.identifier('execute'), [ - t.identifier(`${queryName}QueryDocument`), - t.identifier('variables'), - t.identifier('options'), - ]) + createTypedCallExpression( + t.identifier('execute'), + [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], + [ + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)), + t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)), + ] + ) ) ), ]), @@ -1243,7 +1394,10 @@ export function generateAllQueryHooks( const files: GeneratedQueryFile[] = []; for (const table of tables) { files.push(generateListQueryHook(table, options)); - files.push(generateSingleQueryHook(table, options)); + const singleHook = generateSingleQueryHook(table, options); + if (singleHook) { + files.push(singleHook); + } } return files; } diff --git a/graphql/codegen/src/cli/codegen/utils.ts b/graphql/codegen/src/cli/codegen/utils.ts index 075e0bad2..dc46b8703 100644 --- a/graphql/codegen/src/cli/codegen/utils.ts +++ b/graphql/codegen/src/cli/codegen/utils.ts @@ -381,6 +381,25 @@ export function getPrimaryKeyFields(table: CleanTable): string[] { return getPrimaryKeyInfo(table).map((pk) => pk.name); } +/** + * Check if table has a valid single-field primary key + * Used to determine if a single query hook can be generated + * Tables with composite keys return false (handled as custom queries) + */ +export function hasValidPrimaryKey(table: CleanTable): boolean { + // Check for explicit primary key constraint with single field + const pk = table.constraints?.primaryKey?.[0]; + if (pk && pk.fields.length === 1) { + return true; + } + // Check for 'id' field as fallback + const idField = table.fields.find((f) => f.name.toLowerCase() === 'id'); + if (idField) { + return true; + } + return false; +} + // ============================================================================ // Query key generation // ============================================================================