Skip to content

Commit 1588296

Browse files
committed
feat(codegen): wire condition arg through ORM query builder for findMany/findFirst
- Add TCondition generic and condition?: TCondition to FindManyArgs and FindFirstArgs - Add conditionTypeName parameter and addVariable() call in buildFindManyDocument/buildFindFirstDocument - Wire ${TypeName}Condition into model-generator for findMany/findFirst methods - Add unit tests for condition wiring in model-generator and query-builder - Update snapshots
1 parent 7aa49cb commit 1588296

8 files changed

Lines changed: 187 additions & 33 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,10 @@ export interface PageInfo {
276276
endCursor?: string | null;
277277
}
278278
279-
export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
279+
export interface FindManyArgs<TSelect, TWhere, TCondition, TOrderBy> {
280280
select?: TSelect;
281281
where?: TWhere;
282+
condition?: TCondition;
282283
orderBy?: TOrderBy[];
283284
first?: number;
284285
last?: number;
@@ -287,9 +288,10 @@ export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
287288
offset?: number;
288289
}
289290
290-
export interface FindFirstArgs<TSelect, TWhere> {
291+
export interface FindFirstArgs<TSelect, TWhere, TCondition> {
291292
select?: TSelect;
292293
where?: TWhere;
294+
condition?: TCondition;
293295
}
294296
295297
export interface CreateArgs<TSelect, TData> {

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

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ exports[`model-generator generates model with all CRUD methods 1`] = `
99
import { OrmClient } from "../client";
1010
import { QueryBuilder, buildFindManyDocument, buildFindFirstDocument, buildFindOneDocument, buildCreateDocument, buildUpdateByPkDocument, buildDeleteByPkDocument } from "../query-builder";
1111
import type { ConnectionResult, FindManyArgs, FindFirstArgs, CreateArgs, UpdateArgs, DeleteArgs, InferSelectResult, StrictSelect } from "../select-types";
12-
import type { User, UserWithRelations, UserSelect, UserFilter, UsersOrderBy, CreateUserInput, UpdateUserInput, UserPatch } from "../input-types";
12+
import type { User, UserWithRelations, UserSelect, UserFilter, UserCondition, UsersOrderBy, CreateUserInput, UpdateUserInput, UserPatch } from "../input-types";
1313
import { connectionFieldsMap } from "../input-types";
1414
export class UserModel {
1515
constructor(private client: OrmClient) {}
16-
findMany<S extends UserSelect>(args: FindManyArgs<S, UserFilter, UsersOrderBy> & {
16+
findMany<S extends UserSelect>(args: FindManyArgs<S, UserFilter, UserCondition, UsersOrderBy> & {
1717
select: S;
1818
} & StrictSelect<S, UserSelect>): QueryBuilder<{
1919
users: ConnectionResult<InferSelectResult<UserWithRelations, S>>;
@@ -23,13 +23,14 @@ export class UserModel {
2323
variables
2424
} = buildFindManyDocument("User", "users", args.select, {
2525
where: args?.where,
26+
condition: args?.condition,
2627
orderBy: args?.orderBy as string[] | undefined,
2728
first: args?.first,
2829
last: args?.last,
2930
after: args?.after,
3031
before: args?.before,
3132
offset: args?.offset
32-
}, "UserFilter", "UsersOrderBy", connectionFieldsMap);
33+
}, "UserFilter", "UsersOrderBy", connectionFieldsMap, "UserCondition");
3334
return new QueryBuilder({
3435
client: this.client,
3536
operation: "query",
@@ -39,7 +40,7 @@ export class UserModel {
3940
variables
4041
});
4142
}
42-
findFirst<S extends UserSelect>(args: FindFirstArgs<S, UserFilter> & {
43+
findFirst<S extends UserSelect>(args: FindFirstArgs<S, UserFilter, UserCondition> & {
4344
select: S;
4445
} & StrictSelect<S, UserSelect>): QueryBuilder<{
4546
users: {
@@ -50,8 +51,9 @@ export class UserModel {
5051
document,
5152
variables
5253
} = buildFindFirstDocument("User", "users", args.select, {
53-
where: args?.where
54-
}, "UserFilter", connectionFieldsMap);
54+
where: args?.where,
55+
condition: args?.condition
56+
}, "UserFilter", connectionFieldsMap, "UserCondition");
5557
return new QueryBuilder({
5658
client: this.client,
5759
operation: "query",
@@ -156,11 +158,11 @@ exports[`model-generator generates model without update/delete when not availabl
156158
import { OrmClient } from "../client";
157159
import { QueryBuilder, buildFindManyDocument, buildFindFirstDocument, buildFindOneDocument, buildCreateDocument, buildUpdateByPkDocument, buildDeleteByPkDocument } from "../query-builder";
158160
import type { ConnectionResult, FindManyArgs, FindFirstArgs, CreateArgs, UpdateArgs, DeleteArgs, InferSelectResult, StrictSelect } from "../select-types";
159-
import type { AuditLog, AuditLogWithRelations, AuditLogSelect, AuditLogFilter, AuditLogsOrderBy, CreateAuditLogInput, UpdateAuditLogInput, AuditLogPatch } from "../input-types";
161+
import type { AuditLog, AuditLogWithRelations, AuditLogSelect, AuditLogFilter, AuditLogCondition, AuditLogsOrderBy, CreateAuditLogInput, UpdateAuditLogInput, AuditLogPatch } from "../input-types";
160162
import { connectionFieldsMap } from "../input-types";
161163
export class AuditLogModel {
162164
constructor(private client: OrmClient) {}
163-
findMany<S extends AuditLogSelect>(args: FindManyArgs<S, AuditLogFilter, AuditLogsOrderBy> & {
165+
findMany<S extends AuditLogSelect>(args: FindManyArgs<S, AuditLogFilter, AuditLogCondition, AuditLogsOrderBy> & {
164166
select: S;
165167
} & StrictSelect<S, AuditLogSelect>): QueryBuilder<{
166168
auditLogs: ConnectionResult<InferSelectResult<AuditLogWithRelations, S>>;
@@ -170,13 +172,14 @@ export class AuditLogModel {
170172
variables
171173
} = buildFindManyDocument("AuditLog", "auditLogs", args.select, {
172174
where: args?.where,
175+
condition: args?.condition,
173176
orderBy: args?.orderBy as string[] | undefined,
174177
first: args?.first,
175178
last: args?.last,
176179
after: args?.after,
177180
before: args?.before,
178181
offset: args?.offset
179-
}, "AuditLogFilter", "AuditLogsOrderBy", connectionFieldsMap);
182+
}, "AuditLogFilter", "AuditLogsOrderBy", connectionFieldsMap, "AuditLogCondition");
180183
return new QueryBuilder({
181184
client: this.client,
182185
operation: "query",
@@ -186,7 +189,7 @@ export class AuditLogModel {
186189
variables
187190
});
188191
}
189-
findFirst<S extends AuditLogSelect>(args: FindFirstArgs<S, AuditLogFilter> & {
192+
findFirst<S extends AuditLogSelect>(args: FindFirstArgs<S, AuditLogFilter, AuditLogCondition> & {
190193
select: S;
191194
} & StrictSelect<S, AuditLogSelect>): QueryBuilder<{
192195
auditLogs: {
@@ -197,8 +200,9 @@ export class AuditLogModel {
197200
document,
198201
variables
199202
} = buildFindFirstDocument("AuditLog", "auditLogs", args.select, {
200-
where: args?.where
201-
}, "AuditLogFilter", connectionFieldsMap);
203+
where: args?.where,
204+
condition: args?.condition
205+
}, "AuditLogFilter", connectionFieldsMap, "AuditLogCondition");
202206
return new QueryBuilder({
203207
client: this.client,
204208
operation: "query",
@@ -259,11 +263,11 @@ exports[`model-generator handles custom query/mutation names 1`] = `
259263
import { OrmClient } from "../client";
260264
import { QueryBuilder, buildFindManyDocument, buildFindFirstDocument, buildFindOneDocument, buildCreateDocument, buildUpdateByPkDocument, buildDeleteByPkDocument } from "../query-builder";
261265
import type { ConnectionResult, FindManyArgs, FindFirstArgs, CreateArgs, UpdateArgs, DeleteArgs, InferSelectResult, StrictSelect } from "../select-types";
262-
import type { Organization, OrganizationWithRelations, OrganizationSelect, OrganizationFilter, OrganizationsOrderBy, CreateOrganizationInput, UpdateOrganizationInput, OrganizationPatch } from "../input-types";
266+
import type { Organization, OrganizationWithRelations, OrganizationSelect, OrganizationFilter, OrganizationCondition, OrganizationsOrderBy, CreateOrganizationInput, UpdateOrganizationInput, OrganizationPatch } from "../input-types";
263267
import { connectionFieldsMap } from "../input-types";
264268
export class OrganizationModel {
265269
constructor(private client: OrmClient) {}
266-
findMany<S extends OrganizationSelect>(args: FindManyArgs<S, OrganizationFilter, OrganizationsOrderBy> & {
270+
findMany<S extends OrganizationSelect>(args: FindManyArgs<S, OrganizationFilter, OrganizationCondition, OrganizationsOrderBy> & {
267271
select: S;
268272
} & StrictSelect<S, OrganizationSelect>): QueryBuilder<{
269273
allOrganizations: ConnectionResult<InferSelectResult<OrganizationWithRelations, S>>;
@@ -273,13 +277,14 @@ export class OrganizationModel {
273277
variables
274278
} = buildFindManyDocument("Organization", "allOrganizations", args.select, {
275279
where: args?.where,
280+
condition: args?.condition,
276281
orderBy: args?.orderBy as string[] | undefined,
277282
first: args?.first,
278283
last: args?.last,
279284
after: args?.after,
280285
before: args?.before,
281286
offset: args?.offset
282-
}, "OrganizationFilter", "OrganizationsOrderBy", connectionFieldsMap);
287+
}, "OrganizationFilter", "OrganizationsOrderBy", connectionFieldsMap, "OrganizationCondition");
283288
return new QueryBuilder({
284289
client: this.client,
285290
operation: "query",
@@ -289,7 +294,7 @@ export class OrganizationModel {
289294
variables
290295
});
291296
}
292-
findFirst<S extends OrganizationSelect>(args: FindFirstArgs<S, OrganizationFilter> & {
297+
findFirst<S extends OrganizationSelect>(args: FindFirstArgs<S, OrganizationFilter, OrganizationCondition> & {
293298
select: S;
294299
} & StrictSelect<S, OrganizationSelect>): QueryBuilder<{
295300
allOrganizations: {
@@ -300,8 +305,9 @@ export class OrganizationModel {
300305
document,
301306
variables
302307
} = buildFindFirstDocument("Organization", "allOrganizations", args.select, {
303-
where: args?.where
304-
}, "OrganizationFilter", connectionFieldsMap);
308+
where: args?.where,
309+
condition: args?.condition
310+
}, "OrganizationFilter", connectionFieldsMap, "OrganizationCondition");
305311
return new QueryBuilder({
306312
client: this.client,
307313
operation: "query",

graphql/codegen/src/__tests__/codegen/model-generator.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,42 @@ describe('model-generator', () => {
232232
expect(result.content).toContain('UpdateProductInput');
233233
expect(result.content).toContain('ProductPatch');
234234
});
235+
236+
it('imports and wires Condition type for findMany and findFirst', () => {
237+
const table = createTable({
238+
name: 'Contact',
239+
fields: [
240+
{ name: 'id', type: fieldTypes.uuid },
241+
{ name: 'name', type: fieldTypes.string },
242+
],
243+
query: {
244+
all: 'contacts',
245+
one: 'contact',
246+
create: 'createContact',
247+
update: 'updateContact',
248+
delete: 'deleteContact',
249+
},
250+
});
251+
252+
const result = generateModelFile(table, false);
253+
254+
// Condition type should be imported
255+
expect(result.content).toContain('ContactCondition');
256+
257+
// findMany should include condition in its args type
258+
expect(result.content).toContain(
259+
'FindManyArgs<S, ContactFilter, ContactCondition, ContactsOrderBy>',
260+
);
261+
262+
// findFirst should include condition in its args type
263+
expect(result.content).toContain(
264+
'FindFirstArgs<S, ContactFilter, ContactCondition>',
265+
);
266+
267+
// condition should be forwarded in the body args object
268+
expect(result.content).toContain('condition: args?.condition');
269+
270+
// conditionTypeName should be passed as a string literal to the document builder
271+
expect(result.content).toContain('"ContactCondition"');
272+
});
235273
});

graphql/codegen/src/__tests__/codegen/query-builder.test.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ function buildConnectionSelections(nodeSelections: FieldNode[]): FieldNode[] {
3838
}
3939

4040
function addVariable(
41-
spec: { varName: string; argName?: string; typeName: string; value: unknown },
41+
spec: { varName: string; argName?: string; typeName?: string; value: unknown },
4242
definitions: VariableDefinitionNode[],
4343
args: ArgumentNode[],
4444
variables: Record<string, unknown>,
4545
): void {
46-
if (spec.value === undefined) return;
46+
if (spec.value === undefined || !spec.typeName) return;
4747
definitions.push(
4848
t.variableDefinition({
4949
variable: t.variable({ name: spec.varName }),
@@ -123,14 +123,15 @@ function buildSelections(
123123
return fields;
124124
}
125125

126-
function buildFindManyDocument<TSelect, TWhere>(
126+
function buildFindManyDocument<TSelect, TWhere, TCondition>(
127127
operationName: string,
128128
queryField: string,
129129
select: TSelect,
130-
args: { where?: TWhere; first?: number; orderBy?: string[] },
130+
args: { where?: TWhere; condition?: TCondition; first?: number; orderBy?: string[] },
131131
filterTypeName: string,
132132
orderByTypeName: string,
133133
connectionFieldsMap?: Record<string, Record<string, string>>,
134+
conditionTypeName?: string,
134135
): { document: string; variables: Record<string, unknown> } {
135136
const selections = select
136137
? buildSelections(
@@ -143,6 +144,16 @@ function buildFindManyDocument<TSelect, TWhere>(
143144
const queryArgs: ArgumentNode[] = [];
144145
const variables: Record<string, unknown> = {};
145146

147+
addVariable(
148+
{
149+
varName: 'condition',
150+
typeName: conditionTypeName,
151+
value: args.condition,
152+
},
153+
variableDefinitions,
154+
queryArgs,
155+
variables,
156+
);
146157
addVariable(
147158
{
148159
varName: 'where',
@@ -557,6 +568,52 @@ describe('query-builder', () => {
557568
orderBy: ['NAME_ASC'],
558569
});
559570
});
571+
572+
it('includes condition variable when conditionTypeName is provided', () => {
573+
const { document, variables } = buildFindManyDocument(
574+
'Contacts',
575+
'contacts',
576+
{ id: true, name: true },
577+
{
578+
condition: { embeddingNearby: { vector: [0.1, 0.2], metric: 'COSINE' } },
579+
where: { name: { equalTo: 'test' } },
580+
first: 5,
581+
},
582+
'ContactFilter',
583+
'ContactsOrderBy',
584+
undefined,
585+
'ContactCondition',
586+
);
587+
588+
// condition variable should appear in the query
589+
expect(document).toContain('$condition: ContactCondition');
590+
expect(document).toContain('condition: $condition');
591+
// filter should still work alongside condition
592+
expect(document).toContain('$where: ContactFilter');
593+
expect(document).toContain('filter: $where');
594+
// variables should include both
595+
expect(variables.condition).toEqual({
596+
embeddingNearby: { vector: [0.1, 0.2], metric: 'COSINE' },
597+
});
598+
expect(variables.where).toEqual({ name: { equalTo: 'test' } });
599+
});
600+
601+
it('omits condition variable when not provided', () => {
602+
const { document } = buildFindManyDocument(
603+
'Users',
604+
'users',
605+
{ id: true },
606+
{ first: 10 },
607+
'UserFilter',
608+
'UsersOrderBy',
609+
undefined,
610+
'UserCondition',
611+
);
612+
613+
// condition should NOT appear since no value was provided
614+
expect(document).not.toContain('$condition');
615+
expect(document).not.toContain('condition:');
616+
});
560617
});
561618

562619
describe('buildMutationDocument', () => {

graphql/codegen/src/core/codegen/orm/model-generator.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ export function generateModelFile(
175175
const selectTypeName = `${typeName}Select`;
176176
const relationTypeName = `${typeName}WithRelations`;
177177
const whereTypeName = getFilterTypeName(table);
178+
const conditionTypeName = `${typeName}Condition`;
178179
const orderByTypeName = getOrderByTypeName(table);
179180
const createInputTypeName = `Create${typeName}Input`;
180181
const updateInputTypeName = `Update${typeName}Input`;
@@ -228,6 +229,7 @@ export function generateModelFile(
228229
relationTypeName,
229230
selectTypeName,
230231
whereTypeName,
232+
conditionTypeName,
231233
orderByTypeName,
232234
createInputTypeName,
233235
updateInputTypeName,
@@ -270,6 +272,7 @@ export function generateModelFile(
270272
t.tsTypeParameterInstantiation([
271273
sel,
272274
t.tsTypeReference(t.identifier(whereTypeName)),
275+
t.tsTypeReference(t.identifier(conditionTypeName)),
273276
t.tsTypeReference(t.identifier(orderByTypeName)),
274277
]),
275278
);
@@ -327,6 +330,15 @@ export function generateModelFile(
327330
true,
328331
),
329332
),
333+
t.objectProperty(
334+
t.identifier('condition'),
335+
t.optionalMemberExpression(
336+
t.identifier('args'),
337+
t.identifier('condition'),
338+
false,
339+
true,
340+
),
341+
),
330342
t.objectProperty(
331343
t.identifier('orderBy'),
332344
t.tsAsExpression(
@@ -391,6 +403,7 @@ export function generateModelFile(
391403
t.stringLiteral(whereTypeName),
392404
t.stringLiteral(orderByTypeName),
393405
t.identifier('connectionFieldsMap'),
406+
t.stringLiteral(conditionTypeName),
394407
];
395408
classBody.push(
396409
createClassMethod(
@@ -417,6 +430,7 @@ export function generateModelFile(
417430
t.tsTypeParameterInstantiation([
418431
sel,
419432
t.tsTypeReference(t.identifier(whereTypeName)),
433+
t.tsTypeReference(t.identifier(conditionTypeName)),
420434
]),
421435
);
422436
const retType = (sel: t.TSType) =>
@@ -477,9 +491,19 @@ export function generateModelFile(
477491
true,
478492
),
479493
),
494+
t.objectProperty(
495+
t.identifier('condition'),
496+
t.optionalMemberExpression(
497+
t.identifier('args'),
498+
t.identifier('condition'),
499+
false,
500+
true,
501+
),
502+
),
480503
]),
481504
t.stringLiteral(whereTypeName),
482505
t.identifier('connectionFieldsMap'),
506+
t.stringLiteral(conditionTypeName),
483507
];
484508
classBody.push(
485509
createClassMethod(

0 commit comments

Comments
 (0)