Skip to content

Commit 2e2cbb1

Browse files
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<T>() / parseFindFirstArgs<T>() The codegen passes specific table types: parseFindManyArgs<FindManyArgs<CarSelect, CarFilter, CarCondition, CarsOrderBy>>(...) 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.
1 parent f1423ec commit 2e2cbb1

3 files changed

Lines changed: 149 additions & 65 deletions

File tree

graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer";
697697
import { getClient } from "../executor";
698698
import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../utils";
699699
import type { FieldSchema } from "../utils";
700-
import type { CreateCarInput, CarPatch } from "../../orm/input-types";
700+
import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarCondition, CarsOrderBy } from "../../orm/input-types";
701+
import type { FindManyArgs, FindFirstArgs } from "../../orm/select-types";
701702
const fieldSchema: FieldSchema = {
702703
id: "uuid",
703704
make: "string",
@@ -756,7 +757,7 @@ async function handleList(argv: Partial<Record<string, unknown>>, _prompter: Inq
756757
isElectric: true,
757758
createdAt: true
758759
};
759-
const findManyArgs = parseFindManyArgs(argv, defaultSelect);
760+
const findManyArgs = parseFindManyArgs<FindManyArgs<CarSelect, CarFilter, CarCondition, CarsOrderBy>>(argv, defaultSelect);
760761
const client = getClient();
761762
const result = await client.car.findMany(findManyArgs).execute();
762763
console.log(JSON.stringify(result, null, 2));
@@ -778,7 +779,7 @@ async function handleFindFirst(argv: Partial<Record<string, unknown>>, _prompter
778779
isElectric: true,
779780
createdAt: true
780781
};
781-
const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
782+
const findFirstArgs = parseFindFirstArgs<FindFirstArgs<CarSelect, CarFilter, CarCondition>>(argv, defaultSelect);
782783
const client = getClient();
783784
const result = await client.car.findFirst(findFirstArgs).execute();
784785
console.log(JSON.stringify(result, null, 2));
@@ -1160,7 +1161,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer";
11601161
import { getClient } from "../executor";
11611162
import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../utils";
11621163
import type { FieldSchema } from "../utils";
1163-
import type { CreateDriverInput, DriverPatch } from "../../orm/input-types";
1164+
import type { CreateDriverInput, DriverPatch, DriverSelect, DriverFilter, DriverCondition, DriversOrderBy } from "../../orm/input-types";
1165+
import type { FindManyArgs, FindFirstArgs } from "../../orm/select-types";
11641166
const fieldSchema: FieldSchema = {
11651167
id: "uuid",
11661168
name: "string",
@@ -1213,7 +1215,7 @@ async function handleList(argv: Partial<Record<string, unknown>>, _prompter: Inq
12131215
name: true,
12141216
licenseNumber: true
12151217
};
1216-
const findManyArgs = parseFindManyArgs(argv, defaultSelect);
1218+
const findManyArgs = parseFindManyArgs<FindManyArgs<DriverSelect, DriverFilter, DriverCondition, DriversOrderBy>>(argv, defaultSelect);
12171219
const client = getClient();
12181220
const result = await client.driver.findMany(findManyArgs).execute();
12191221
console.log(JSON.stringify(result, null, 2));
@@ -1232,7 +1234,7 @@ async function handleFindFirst(argv: Partial<Record<string, unknown>>, _prompter
12321234
name: true,
12331235
licenseNumber: true
12341236
};
1235-
const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
1237+
const findFirstArgs = parseFindFirstArgs<FindFirstArgs<DriverSelect, DriverFilter, DriverCondition>>(argv, defaultSelect);
12361238
const client = getClient();
12371239
const result = await client.driver.findFirst(findFirstArgs).execute();
12381240
console.log(JSON.stringify(result, null, 2));
@@ -3191,7 +3193,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer";
31913193
import { getClient } from "../../executor";
31923194
import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils";
31933195
import type { FieldSchema } from "../../utils";
3194-
import type { CreateUserInput, UserPatch } from "../../../orm/input-types";
3196+
import type { CreateUserInput, UserPatch, UserSelect, UserFilter, UserCondition, UsersOrderBy } from "../../../orm/input-types";
3197+
import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types";
31953198
const fieldSchema: FieldSchema = {
31963199
id: "uuid",
31973200
email: "string",
@@ -3244,7 +3247,7 @@ async function handleList(argv: Partial<Record<string, unknown>>, _prompter: Inq
32443247
email: true,
32453248
name: true
32463249
};
3247-
const findManyArgs = parseFindManyArgs(argv, defaultSelect);
3250+
const findManyArgs = parseFindManyArgs<FindManyArgs<UserSelect, UserFilter, UserCondition, UsersOrderBy>>(argv, defaultSelect);
32483251
const client = getClient("auth");
32493252
const result = await client.user.findMany(findManyArgs).execute();
32503253
console.log(JSON.stringify(result, null, 2));
@@ -3263,7 +3266,7 @@ async function handleFindFirst(argv: Partial<Record<string, unknown>>, _prompter
32633266
email: true,
32643267
name: true
32653268
};
3266-
const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
3269+
const findFirstArgs = parseFindFirstArgs<FindFirstArgs<UserSelect, UserFilter, UserCondition>>(argv, defaultSelect);
32673270
const client = getClient("auth");
32683271
const result = await client.user.findFirst(findFirstArgs).execute();
32693272
console.log(JSON.stringify(result, null, 2));
@@ -3420,7 +3423,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer";
34203423
import { getClient } from "../../executor";
34213424
import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils";
34223425
import type { FieldSchema } from "../../utils";
3423-
import type { CreateMemberInput, MemberPatch } from "../../../orm/input-types";
3426+
import type { CreateMemberInput, MemberPatch, MemberSelect, MemberFilter, MemberCondition, MembersOrderBy } from "../../../orm/input-types";
3427+
import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types";
34243428
const fieldSchema: FieldSchema = {
34253429
id: "uuid",
34263430
role: "string"
@@ -3471,7 +3475,7 @@ async function handleList(argv: Partial<Record<string, unknown>>, _prompter: Inq
34713475
id: true,
34723476
role: true
34733477
};
3474-
const findManyArgs = parseFindManyArgs(argv, defaultSelect);
3478+
const findManyArgs = parseFindManyArgs<FindManyArgs<MemberSelect, MemberFilter, MemberCondition, MembersOrderBy>>(argv, defaultSelect);
34753479
const client = getClient("members");
34763480
const result = await client.member.findMany(findManyArgs).execute();
34773481
console.log(JSON.stringify(result, null, 2));
@@ -3489,7 +3493,7 @@ async function handleFindFirst(argv: Partial<Record<string, unknown>>, _prompter
34893493
id: true,
34903494
role: true
34913495
};
3492-
const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
3496+
const findFirstArgs = parseFindFirstArgs<FindFirstArgs<MemberSelect, MemberFilter, MemberCondition>>(argv, defaultSelect);
34933497
const client = getClient("members");
34943498
const result = await client.member.findFirst(findFirstArgs).execute();
34953499
console.log(JSON.stringify(result, null, 2));
@@ -3631,7 +3635,8 @@ import { CLIOptions, Inquirerer, extractFirst } from "inquirerer";
36313635
import { getClient } from "../../executor";
36323636
import { coerceAnswers, parseFindFirstArgs, parseFindManyArgs, stripUndefined } from "../../utils";
36333637
import type { FieldSchema } from "../../utils";
3634-
import type { CreateCarInput, CarPatch } from "../../../orm/input-types";
3638+
import type { CreateCarInput, CarPatch, CarSelect, CarFilter, CarCondition, CarsOrderBy } from "../../../orm/input-types";
3639+
import type { FindManyArgs, FindFirstArgs } from "../../../orm/select-types";
36353640
const fieldSchema: FieldSchema = {
36363641
id: "uuid",
36373642
make: "string",
@@ -3690,7 +3695,7 @@ async function handleList(argv: Partial<Record<string, unknown>>, _prompter: Inq
36903695
isElectric: true,
36913696
createdAt: true
36923697
};
3693-
const findManyArgs = parseFindManyArgs(argv, defaultSelect);
3698+
const findManyArgs = parseFindManyArgs<FindManyArgs<CarSelect, CarFilter, CarCondition, CarsOrderBy>>(argv, defaultSelect);
36943699
const client = getClient("app");
36953700
const result = await client.car.findMany(findManyArgs).execute();
36963701
console.log(JSON.stringify(result, null, 2));
@@ -3712,7 +3717,7 @@ async function handleFindFirst(argv: Partial<Record<string, unknown>>, _prompter
37123717
isElectric: true,
37133718
createdAt: true
37143719
};
3715-
const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
3720+
const findFirstArgs = parseFindFirstArgs<FindFirstArgs<CarSelect, CarFilter, CarCondition>>(argv, defaultSelect);
37163721
const client = getClient("app");
37173722
const result = await client.car.findFirst(findFirstArgs).execute();
37183723
console.log(JSON.stringify(result, null, 2));

graphql/codegen/src/core/codegen/cli/table-command-generator.ts

Lines changed: 123 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import {
1515
toPascalCase,
1616
getCreateInputTypeName,
1717
getPatchTypeName,
18+
getFilterTypeName,
19+
getOrderByTypeName,
20+
getConditionTypeName,
1821
} from '../utils';
1922
import type { Table, TypeRegistry } from '../../../types/schema';
2023
import type { GeneratedFile } from './executor-generator';
@@ -480,7 +483,51 @@ function buildAutoEmbedInputBlock(
480483
);
481484
}
482485

483-
function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry): t.FunctionDeclaration {
486+
/**
487+
* Build the FindManyArgs type instantiation for a table:
488+
* FindManyArgs<SelectType, FilterType, ConditionType, OrderByType>
489+
*/
490+
function buildFindManyArgsType(table: Table, conditionEnabled: boolean): t.TSType {
491+
const { typeName } = getTableNames(table);
492+
const selectTypeName = `${typeName}Select`;
493+
const whereTypeName = getFilterTypeName(table);
494+
const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined;
495+
const orderByTypeName = getOrderByTypeName(table);
496+
return t.tsTypeReference(
497+
t.identifier('FindManyArgs'),
498+
t.tsTypeParameterInstantiation([
499+
t.tsTypeReference(t.identifier(selectTypeName)),
500+
t.tsTypeReference(t.identifier(whereTypeName)),
501+
conditionTypeName
502+
? t.tsTypeReference(t.identifier(conditionTypeName))
503+
: t.tsNeverKeyword(),
504+
t.tsTypeReference(t.identifier(orderByTypeName)),
505+
]),
506+
);
507+
}
508+
509+
/**
510+
* Build the FindFirstArgs type instantiation for a table:
511+
* FindFirstArgs<SelectType, FilterType, ConditionType>
512+
*/
513+
function buildFindFirstArgsType(table: Table, conditionEnabled: boolean): t.TSType {
514+
const { typeName } = getTableNames(table);
515+
const selectTypeName = `${typeName}Select`;
516+
const whereTypeName = getFilterTypeName(table);
517+
const conditionTypeName = conditionEnabled ? getConditionTypeName(table) : undefined;
518+
return t.tsTypeReference(
519+
t.identifier('FindFirstArgs'),
520+
t.tsTypeParameterInstantiation([
521+
t.tsTypeReference(t.identifier(selectTypeName)),
522+
t.tsTypeReference(t.identifier(whereTypeName)),
523+
conditionTypeName
524+
? t.tsTypeReference(t.identifier(conditionTypeName))
525+
: t.tsNeverKeyword(),
526+
]),
527+
);
528+
}
529+
530+
function buildListHandler(table: Table, vectorFieldNames: string[], targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = true): t.FunctionDeclaration {
484531
const { singularName } = getTableNames(table);
485532
const defaultSelectObj = buildSelectObject(table, typeRegistry);
486533

@@ -494,18 +541,21 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?:
494541
]),
495542
);
496543

497-
// const findManyArgs = parseFindManyArgs(argv, defaultSelect);
498-
tryBody.push(
499-
t.variableDeclaration('const', [
500-
t.variableDeclarator(
501-
t.identifier('findManyArgs'),
502-
t.callExpression(t.identifier('parseFindManyArgs'), [
503-
t.identifier('argv'),
504-
t.identifier('defaultSelect'),
505-
]),
506-
),
507-
]),
508-
);
544+
// const findManyArgs = parseFindManyArgs<FindManyArgs<...>>(argv, defaultSelect);
545+
{
546+
const callExpr = t.callExpression(t.identifier('parseFindManyArgs'), [
547+
t.identifier('argv'),
548+
t.identifier('defaultSelect'),
549+
]);
550+
callExpr.typeParameters = t.tsTypeParameterInstantiation([
551+
buildFindManyArgsType(table, conditionEnabled),
552+
]);
553+
tryBody.push(
554+
t.variableDeclaration('const', [
555+
t.variableDeclarator(t.identifier('findManyArgs'), callExpr),
556+
]),
557+
);
558+
}
509559

510560
// Auto-embed vector fields in the where clause when --auto-embed is passed
511561
if (vectorFieldNames.length > 0) {
@@ -575,7 +625,7 @@ function buildListHandler(table: Table, vectorFieldNames: string[], targetName?:
575625
* Accepts --select, --where.<field>.<op>, --condition.<field>.<op> flags.
576626
* Internally calls findMany with first:1 and returns a single record (or null).
577627
*/
578-
function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: TypeRegistry): t.FunctionDeclaration {
628+
function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?: TypeRegistry, conditionEnabled = true): t.FunctionDeclaration {
579629
const { singularName } = getTableNames(table);
580630
const defaultSelectObj = buildSelectObject(table, typeRegistry);
581631

@@ -588,18 +638,21 @@ function buildFindFirstHandler(table: Table, targetName?: string, typeRegistry?:
588638
]),
589639
);
590640

591-
// const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
592-
tryBody.push(
593-
t.variableDeclaration('const', [
594-
t.variableDeclarator(
595-
t.identifier('findFirstArgs'),
596-
t.callExpression(t.identifier('parseFindFirstArgs'), [
597-
t.identifier('argv'),
598-
t.identifier('defaultSelect'),
599-
]),
600-
),
601-
]),
602-
);
641+
// const findFirstArgs = parseFindFirstArgs<FindFirstArgs<...>>(argv, defaultSelect);
642+
{
643+
const callExpr = t.callExpression(t.identifier('parseFindFirstArgs'), [
644+
t.identifier('argv'),
645+
t.identifier('defaultSelect'),
646+
]);
647+
callExpr.typeParameters = t.tsTypeParameterInstantiation([
648+
buildFindFirstArgsType(table, conditionEnabled),
649+
]);
650+
tryBody.push(
651+
t.variableDeclaration('const', [
652+
t.variableDeclarator(t.identifier('findFirstArgs'), callExpr),
653+
]),
654+
);
655+
}
603656

604657
tryBody.push(buildGetClientStatement(targetName));
605658

@@ -662,6 +715,7 @@ function buildSearchHandler(
662715
vectorFieldNames: string[],
663716
targetName?: string,
664717
typeRegistry?: TypeRegistry,
718+
conditionEnabled = true,
665719
): t.FunctionDeclaration {
666720
const { singularName } = getTableNames(table);
667721
const defaultSelectObj = buildSelectObject(table, typeRegistry);
@@ -808,19 +862,22 @@ function buildSearchHandler(
808862
]),
809863
);
810864

811-
// const findManyArgs = parseFindManyArgs(argv, defaultSelect, searchWhere);
812-
tryBody.push(
813-
t.variableDeclaration('const', [
814-
t.variableDeclarator(
815-
t.identifier('findManyArgs'),
816-
t.callExpression(t.identifier('parseFindManyArgs'), [
817-
t.identifier('argv'),
818-
t.identifier('defaultSelect'),
819-
t.identifier('searchWhere'),
820-
]),
821-
),
822-
]),
823-
);
865+
// const findManyArgs = parseFindManyArgs<FindManyArgs<...>>(argv, defaultSelect, searchWhere);
866+
{
867+
const callExpr = t.callExpression(t.identifier('parseFindManyArgs'), [
868+
t.identifier('argv'),
869+
t.identifier('defaultSelect'),
870+
t.identifier('searchWhere'),
871+
]);
872+
callExpr.typeParameters = t.tsTypeParameterInstantiation([
873+
buildFindManyArgsType(table, conditionEnabled),
874+
]);
875+
tryBody.push(
876+
t.variableDeclaration('const', [
877+
t.variableDeclarator(t.identifier('findManyArgs'), callExpr),
878+
]),
879+
);
880+
}
824881

825882
tryBody.push(buildGetClientStatement(targetName));
826883

@@ -1250,7 +1307,7 @@ export interface TableCommandOptions {
12501307
}
12511308

12521309
export function generateTableCommand(table: Table, options?: TableCommandOptions): GeneratedFile {
1253-
const { singularName } = getTableNames(table);
1310+
const { singularName, typeName } = getTableNames(table);
12541311
const commandName = toKebabCase(singularName);
12551312
const statements: t.Statement[] = [];
12561313
const executorPath = options?.executorImportPath ?? '../executor';
@@ -1286,8 +1343,30 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions
12861343
const inputTypesPath = options?.targetName
12871344
? `../../../orm/input-types`
12881345
: `../../orm/input-types`;
1346+
// Import table-specific ORM types for generic type parameters on parseFindManyArgs/parseFindFirstArgs
1347+
const selectTypeName = `${typeName}Select`;
1348+
const whereTypeName = getFilterTypeName(table);
1349+
const conditionTypeName = getConditionTypeName(table);
1350+
const orderByTypeName = getOrderByTypeName(table);
1351+
// Condition types are always generated, so we always import them
1352+
const conditionEnabled = true;
1353+
statements.push(
1354+
createImportDeclaration(inputTypesPath, [
1355+
createInputTypeName,
1356+
patchTypeName,
1357+
selectTypeName,
1358+
whereTypeName,
1359+
conditionTypeName,
1360+
orderByTypeName,
1361+
], true),
1362+
);
1363+
1364+
// Import FindManyArgs/FindFirstArgs from select-types for proper generic typing
1365+
const selectTypesPath = options?.targetName
1366+
? `../../../orm/select-types`
1367+
: `../../orm/select-types`;
12891368
statements.push(
1290-
createImportDeclaration(inputTypesPath, [createInputTypeName, patchTypeName], true),
1369+
createImportDeclaration(selectTypesPath, ['FindManyArgs', 'FindFirstArgs'], true),
12911370
);
12921371

12931372
// Generate field schema for type coercion
@@ -1579,9 +1658,9 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions
15791658

15801659
const tn = options?.targetName;
15811660
const ormTypes = { createInputTypeName, patchTypeName, innerFieldName };
1582-
statements.push(buildListHandler(table, vectorFieldNames, tn, options?.typeRegistry));
1583-
statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry));
1584-
if (hasSearchFields) statements.push(buildSearchHandler(table, specialGroups, vectorFieldNames, tn, options?.typeRegistry));
1661+
statements.push(buildListHandler(table, vectorFieldNames, tn, options?.typeRegistry, conditionEnabled));
1662+
statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry, conditionEnabled));
1663+
if (hasSearchFields) statements.push(buildSearchHandler(table, specialGroups, vectorFieldNames, tn, options?.typeRegistry, conditionEnabled));
15851664
if (hasGet) statements.push(buildGetHandler(table, tn, options?.typeRegistry));
15861665
statements.push(buildMutationHandler(table, 'create', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
15871666
if (hasUpdate) statements.push(buildMutationHandler(table, 'update', vectorFieldNames, tn, options?.typeRegistry, ormTypes));

0 commit comments

Comments
 (0)