@constructive-io/graphql-codegen 2.28.2 → 2.28.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Builder - Builds and executes GraphQL operations
|
|
3
|
+
*
|
|
4
|
+
* This is the RUNTIME code that gets copied to generated output.
|
|
5
|
+
* It uses gql-ast to build GraphQL documents programmatically.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This file is read at codegen time and written to output.
|
|
8
|
+
* Any changes here will affect all generated ORM clients.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as t from 'gql-ast';
|
|
12
|
+
import { parseType, print } from 'graphql';
|
|
13
|
+
import type {
|
|
14
|
+
ArgumentNode,
|
|
15
|
+
FieldNode,
|
|
16
|
+
VariableDefinitionNode,
|
|
17
|
+
EnumValueNode,
|
|
18
|
+
} from 'graphql';
|
|
19
|
+
import { OrmClient, QueryResult, GraphQLRequestError } from './client';
|
|
20
|
+
|
|
21
|
+
export interface QueryBuilderConfig {
|
|
22
|
+
client: OrmClient;
|
|
23
|
+
operation: 'query' | 'mutation';
|
|
24
|
+
operationName: string;
|
|
25
|
+
fieldName: string;
|
|
26
|
+
document: string;
|
|
27
|
+
variables?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class QueryBuilder<TResult> {
|
|
31
|
+
private config: QueryBuilderConfig;
|
|
32
|
+
|
|
33
|
+
constructor(config: QueryBuilderConfig) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute the query and return a discriminated union result
|
|
39
|
+
* Use result.ok to check success, or .unwrap() to throw on error
|
|
40
|
+
*/
|
|
41
|
+
async execute(): Promise<QueryResult<TResult>> {
|
|
42
|
+
return this.config.client.execute<TResult>(
|
|
43
|
+
this.config.document,
|
|
44
|
+
this.config.variables
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execute and unwrap the result, throwing GraphQLRequestError on failure
|
|
50
|
+
* @throws {GraphQLRequestError} If the query returns errors
|
|
51
|
+
*/
|
|
52
|
+
async unwrap(): Promise<TResult> {
|
|
53
|
+
const result = await this.execute();
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
throw new GraphQLRequestError(result.errors, result.data);
|
|
56
|
+
}
|
|
57
|
+
return result.data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Execute and unwrap, returning defaultValue on error instead of throwing
|
|
62
|
+
*/
|
|
63
|
+
async unwrapOr<D>(defaultValue: D): Promise<TResult | D> {
|
|
64
|
+
const result = await this.execute();
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
return defaultValue;
|
|
67
|
+
}
|
|
68
|
+
return result.data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execute and unwrap, calling onError callback on failure
|
|
73
|
+
*/
|
|
74
|
+
async unwrapOrElse<D>(
|
|
75
|
+
onError: (errors: import('./client').GraphQLError[]) => D
|
|
76
|
+
): Promise<TResult | D> {
|
|
77
|
+
const result = await this.execute();
|
|
78
|
+
if (!result.ok) {
|
|
79
|
+
return onError(result.errors);
|
|
80
|
+
}
|
|
81
|
+
return result.data;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
toGraphQL(): string {
|
|
85
|
+
return this.config.document;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getVariables(): Record<string, unknown> | undefined {
|
|
89
|
+
return this.config.variables;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Selection Builders
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
export function buildSelections(
|
|
98
|
+
select: Record<string, unknown> | undefined
|
|
99
|
+
): FieldNode[] {
|
|
100
|
+
if (!select) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const fields: FieldNode[] = [];
|
|
105
|
+
|
|
106
|
+
for (const [key, value] of Object.entries(select)) {
|
|
107
|
+
if (value === false || value === undefined) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (value === true) {
|
|
112
|
+
fields.push(t.field({ name: key }));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof value === 'object' && value !== null) {
|
|
117
|
+
const nested = value as {
|
|
118
|
+
select?: Record<string, unknown>;
|
|
119
|
+
first?: number;
|
|
120
|
+
filter?: Record<string, unknown>;
|
|
121
|
+
orderBy?: string[];
|
|
122
|
+
connection?: boolean;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (nested.select) {
|
|
126
|
+
const nestedSelections = buildSelections(nested.select);
|
|
127
|
+
const isConnection =
|
|
128
|
+
nested.connection === true ||
|
|
129
|
+
nested.first !== undefined ||
|
|
130
|
+
nested.filter !== undefined;
|
|
131
|
+
const args = buildArgs([
|
|
132
|
+
buildOptionalArg('first', nested.first),
|
|
133
|
+
nested.filter
|
|
134
|
+
? t.argument({ name: 'filter', value: buildValueAst(nested.filter) })
|
|
135
|
+
: null,
|
|
136
|
+
buildEnumListArg('orderBy', nested.orderBy),
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
if (isConnection) {
|
|
140
|
+
fields.push(
|
|
141
|
+
t.field({
|
|
142
|
+
name: key,
|
|
143
|
+
args,
|
|
144
|
+
selectionSet: t.selectionSet({
|
|
145
|
+
selections: buildConnectionSelections(nestedSelections),
|
|
146
|
+
}),
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
fields.push(
|
|
151
|
+
t.field({
|
|
152
|
+
name: key,
|
|
153
|
+
args,
|
|
154
|
+
selectionSet: t.selectionSet({ selections: nestedSelections }),
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return fields;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ============================================================================
|
|
166
|
+
// Document Builders
|
|
167
|
+
// ============================================================================
|
|
168
|
+
|
|
169
|
+
export function buildFindManyDocument<TSelect, TWhere>(
|
|
170
|
+
operationName: string,
|
|
171
|
+
queryField: string,
|
|
172
|
+
select: TSelect,
|
|
173
|
+
args: {
|
|
174
|
+
where?: TWhere;
|
|
175
|
+
orderBy?: string[];
|
|
176
|
+
first?: number;
|
|
177
|
+
last?: number;
|
|
178
|
+
after?: string;
|
|
179
|
+
before?: string;
|
|
180
|
+
offset?: number;
|
|
181
|
+
},
|
|
182
|
+
filterTypeName: string,
|
|
183
|
+
orderByTypeName: string
|
|
184
|
+
): { document: string; variables: Record<string, unknown> } {
|
|
185
|
+
const selections = select
|
|
186
|
+
? buildSelections(select as Record<string, unknown>)
|
|
187
|
+
: [t.field({ name: 'id' })];
|
|
188
|
+
|
|
189
|
+
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
190
|
+
const queryArgs: ArgumentNode[] = [];
|
|
191
|
+
const variables: Record<string, unknown> = {};
|
|
192
|
+
|
|
193
|
+
addVariable(
|
|
194
|
+
{ varName: 'where', argName: 'filter', typeName: filterTypeName, value: args.where },
|
|
195
|
+
variableDefinitions,
|
|
196
|
+
queryArgs,
|
|
197
|
+
variables
|
|
198
|
+
);
|
|
199
|
+
addVariable(
|
|
200
|
+
{
|
|
201
|
+
varName: 'orderBy',
|
|
202
|
+
typeName: '[' + orderByTypeName + '!]',
|
|
203
|
+
value: args.orderBy?.length ? args.orderBy : undefined,
|
|
204
|
+
},
|
|
205
|
+
variableDefinitions,
|
|
206
|
+
queryArgs,
|
|
207
|
+
variables
|
|
208
|
+
);
|
|
209
|
+
addVariable(
|
|
210
|
+
{ varName: 'first', typeName: 'Int', value: args.first },
|
|
211
|
+
variableDefinitions,
|
|
212
|
+
queryArgs,
|
|
213
|
+
variables
|
|
214
|
+
);
|
|
215
|
+
addVariable(
|
|
216
|
+
{ varName: 'last', typeName: 'Int', value: args.last },
|
|
217
|
+
variableDefinitions,
|
|
218
|
+
queryArgs,
|
|
219
|
+
variables
|
|
220
|
+
);
|
|
221
|
+
addVariable(
|
|
222
|
+
{ varName: 'after', typeName: 'Cursor', value: args.after },
|
|
223
|
+
variableDefinitions,
|
|
224
|
+
queryArgs,
|
|
225
|
+
variables
|
|
226
|
+
);
|
|
227
|
+
addVariable(
|
|
228
|
+
{ varName: 'before', typeName: 'Cursor', value: args.before },
|
|
229
|
+
variableDefinitions,
|
|
230
|
+
queryArgs,
|
|
231
|
+
variables
|
|
232
|
+
);
|
|
233
|
+
addVariable(
|
|
234
|
+
{ varName: 'offset', typeName: 'Int', value: args.offset },
|
|
235
|
+
variableDefinitions,
|
|
236
|
+
queryArgs,
|
|
237
|
+
variables
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const document = t.document({
|
|
241
|
+
definitions: [
|
|
242
|
+
t.operationDefinition({
|
|
243
|
+
operation: 'query',
|
|
244
|
+
name: operationName + 'Query',
|
|
245
|
+
variableDefinitions: variableDefinitions.length ? variableDefinitions : undefined,
|
|
246
|
+
selectionSet: t.selectionSet({
|
|
247
|
+
selections: [
|
|
248
|
+
t.field({
|
|
249
|
+
name: queryField,
|
|
250
|
+
args: queryArgs.length ? queryArgs : undefined,
|
|
251
|
+
selectionSet: t.selectionSet({
|
|
252
|
+
selections: buildConnectionSelections(selections),
|
|
253
|
+
}),
|
|
254
|
+
}),
|
|
255
|
+
],
|
|
256
|
+
}),
|
|
257
|
+
}),
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return { document: print(document), variables };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function buildFindFirstDocument<TSelect, TWhere>(
|
|
265
|
+
operationName: string,
|
|
266
|
+
queryField: string,
|
|
267
|
+
select: TSelect,
|
|
268
|
+
args: { where?: TWhere },
|
|
269
|
+
filterTypeName: string
|
|
270
|
+
): { document: string; variables: Record<string, unknown> } {
|
|
271
|
+
const selections = select
|
|
272
|
+
? buildSelections(select as Record<string, unknown>)
|
|
273
|
+
: [t.field({ name: 'id' })];
|
|
274
|
+
|
|
275
|
+
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
276
|
+
const queryArgs: ArgumentNode[] = [];
|
|
277
|
+
const variables: Record<string, unknown> = {};
|
|
278
|
+
|
|
279
|
+
// Always add first: 1 for findFirst
|
|
280
|
+
addVariable(
|
|
281
|
+
{ varName: 'first', typeName: 'Int', value: 1 },
|
|
282
|
+
variableDefinitions,
|
|
283
|
+
queryArgs,
|
|
284
|
+
variables
|
|
285
|
+
);
|
|
286
|
+
addVariable(
|
|
287
|
+
{ varName: 'where', argName: 'filter', typeName: filterTypeName, value: args.where },
|
|
288
|
+
variableDefinitions,
|
|
289
|
+
queryArgs,
|
|
290
|
+
variables
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const document = t.document({
|
|
294
|
+
definitions: [
|
|
295
|
+
t.operationDefinition({
|
|
296
|
+
operation: 'query',
|
|
297
|
+
name: operationName + 'Query',
|
|
298
|
+
variableDefinitions,
|
|
299
|
+
selectionSet: t.selectionSet({
|
|
300
|
+
selections: [
|
|
301
|
+
t.field({
|
|
302
|
+
name: queryField,
|
|
303
|
+
args: queryArgs,
|
|
304
|
+
selectionSet: t.selectionSet({
|
|
305
|
+
selections: [
|
|
306
|
+
t.field({
|
|
307
|
+
name: 'nodes',
|
|
308
|
+
selectionSet: t.selectionSet({ selections }),
|
|
309
|
+
}),
|
|
310
|
+
],
|
|
311
|
+
}),
|
|
312
|
+
}),
|
|
313
|
+
],
|
|
314
|
+
}),
|
|
315
|
+
}),
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return { document: print(document), variables };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function buildCreateDocument<TSelect, TData>(
|
|
323
|
+
operationName: string,
|
|
324
|
+
mutationField: string,
|
|
325
|
+
entityField: string,
|
|
326
|
+
select: TSelect,
|
|
327
|
+
data: TData,
|
|
328
|
+
inputTypeName: string
|
|
329
|
+
): { document: string; variables: Record<string, unknown> } {
|
|
330
|
+
const selections = select
|
|
331
|
+
? buildSelections(select as Record<string, unknown>)
|
|
332
|
+
: [t.field({ name: 'id' })];
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
document: buildInputMutationDocument({
|
|
336
|
+
operationName,
|
|
337
|
+
mutationField,
|
|
338
|
+
inputTypeName,
|
|
339
|
+
resultSelections: [
|
|
340
|
+
t.field({
|
|
341
|
+
name: entityField,
|
|
342
|
+
selectionSet: t.selectionSet({ selections }),
|
|
343
|
+
}),
|
|
344
|
+
],
|
|
345
|
+
}),
|
|
346
|
+
variables: {
|
|
347
|
+
input: {
|
|
348
|
+
[entityField]: data,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function buildUpdateDocument<TSelect, TWhere extends { id: string }, TData>(
|
|
355
|
+
operationName: string,
|
|
356
|
+
mutationField: string,
|
|
357
|
+
entityField: string,
|
|
358
|
+
select: TSelect,
|
|
359
|
+
where: TWhere,
|
|
360
|
+
data: TData,
|
|
361
|
+
inputTypeName: string
|
|
362
|
+
): { document: string; variables: Record<string, unknown> } {
|
|
363
|
+
const selections = select
|
|
364
|
+
? buildSelections(select as Record<string, unknown>)
|
|
365
|
+
: [t.field({ name: 'id' })];
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
document: buildInputMutationDocument({
|
|
369
|
+
operationName,
|
|
370
|
+
mutationField,
|
|
371
|
+
inputTypeName,
|
|
372
|
+
resultSelections: [
|
|
373
|
+
t.field({
|
|
374
|
+
name: entityField,
|
|
375
|
+
selectionSet: t.selectionSet({ selections }),
|
|
376
|
+
}),
|
|
377
|
+
],
|
|
378
|
+
}),
|
|
379
|
+
variables: {
|
|
380
|
+
input: {
|
|
381
|
+
id: where.id,
|
|
382
|
+
patch: data,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function buildDeleteDocument<TWhere extends { id: string }>(
|
|
389
|
+
operationName: string,
|
|
390
|
+
mutationField: string,
|
|
391
|
+
entityField: string,
|
|
392
|
+
where: TWhere,
|
|
393
|
+
inputTypeName: string
|
|
394
|
+
): { document: string; variables: Record<string, unknown> } {
|
|
395
|
+
return {
|
|
396
|
+
document: buildInputMutationDocument({
|
|
397
|
+
operationName,
|
|
398
|
+
mutationField,
|
|
399
|
+
inputTypeName,
|
|
400
|
+
resultSelections: [
|
|
401
|
+
t.field({
|
|
402
|
+
name: entityField,
|
|
403
|
+
selectionSet: t.selectionSet({
|
|
404
|
+
selections: [t.field({ name: 'id' })],
|
|
405
|
+
}),
|
|
406
|
+
}),
|
|
407
|
+
],
|
|
408
|
+
}),
|
|
409
|
+
variables: {
|
|
410
|
+
input: {
|
|
411
|
+
id: where.id,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function buildCustomDocument<TSelect, TArgs>(
|
|
418
|
+
operationType: 'query' | 'mutation',
|
|
419
|
+
operationName: string,
|
|
420
|
+
fieldName: string,
|
|
421
|
+
select: TSelect,
|
|
422
|
+
args: TArgs,
|
|
423
|
+
variableDefinitions: Array<{ name: string; type: string }>
|
|
424
|
+
): { document: string; variables: Record<string, unknown> } {
|
|
425
|
+
let actualSelect = select;
|
|
426
|
+
let isConnection = false;
|
|
427
|
+
|
|
428
|
+
if (select && typeof select === 'object' && 'select' in select) {
|
|
429
|
+
const wrapper = select as { select?: TSelect; connection?: boolean };
|
|
430
|
+
if (wrapper.select) {
|
|
431
|
+
actualSelect = wrapper.select;
|
|
432
|
+
isConnection = wrapper.connection === true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const selections = actualSelect
|
|
437
|
+
? buildSelections(actualSelect as Record<string, unknown>)
|
|
438
|
+
: [];
|
|
439
|
+
|
|
440
|
+
const variableDefs = variableDefinitions.map((definition) =>
|
|
441
|
+
t.variableDefinition({
|
|
442
|
+
variable: t.variable({ name: definition.name }),
|
|
443
|
+
type: parseType(definition.type),
|
|
444
|
+
})
|
|
445
|
+
);
|
|
446
|
+
const fieldArgs = variableDefinitions.map((definition) =>
|
|
447
|
+
t.argument({
|
|
448
|
+
name: definition.name,
|
|
449
|
+
value: t.variable({ name: definition.name }),
|
|
450
|
+
})
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const fieldSelections = isConnection
|
|
454
|
+
? buildConnectionSelections(selections)
|
|
455
|
+
: selections;
|
|
456
|
+
|
|
457
|
+
const document = t.document({
|
|
458
|
+
definitions: [
|
|
459
|
+
t.operationDefinition({
|
|
460
|
+
operation: operationType,
|
|
461
|
+
name: operationName,
|
|
462
|
+
variableDefinitions: variableDefs.length ? variableDefs : undefined,
|
|
463
|
+
selectionSet: t.selectionSet({
|
|
464
|
+
selections: [
|
|
465
|
+
t.field({
|
|
466
|
+
name: fieldName,
|
|
467
|
+
args: fieldArgs.length ? fieldArgs : undefined,
|
|
468
|
+
selectionSet: fieldSelections.length
|
|
469
|
+
? t.selectionSet({ selections: fieldSelections })
|
|
470
|
+
: undefined,
|
|
471
|
+
}),
|
|
472
|
+
],
|
|
473
|
+
}),
|
|
474
|
+
}),
|
|
475
|
+
],
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
document: print(document),
|
|
480
|
+
variables: (args ?? {}) as Record<string, unknown>,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ============================================================================
|
|
485
|
+
// Helper Functions
|
|
486
|
+
// ============================================================================
|
|
487
|
+
|
|
488
|
+
function buildArgs(args: Array<ArgumentNode | null>): ArgumentNode[] {
|
|
489
|
+
return args.filter((arg): arg is ArgumentNode => arg !== null);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function buildOptionalArg(
|
|
493
|
+
name: string,
|
|
494
|
+
value: number | string | undefined
|
|
495
|
+
): ArgumentNode | null {
|
|
496
|
+
if (value === undefined) {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
const valueNode =
|
|
500
|
+
typeof value === 'number'
|
|
501
|
+
? t.intValue({ value: value.toString() })
|
|
502
|
+
: t.stringValue({ value });
|
|
503
|
+
return t.argument({ name, value: valueNode });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function buildEnumListArg(
|
|
507
|
+
name: string,
|
|
508
|
+
values: string[] | undefined
|
|
509
|
+
): ArgumentNode | null {
|
|
510
|
+
if (!values || values.length === 0) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
return t.argument({
|
|
514
|
+
name,
|
|
515
|
+
value: t.listValue({
|
|
516
|
+
values: values.map((value) => buildEnumValue(value)),
|
|
517
|
+
}),
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function buildEnumValue(value: string): EnumValueNode {
|
|
522
|
+
return {
|
|
523
|
+
kind: 'EnumValue',
|
|
524
|
+
value,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function buildPageInfoSelections(): FieldNode[] {
|
|
529
|
+
return [
|
|
530
|
+
t.field({ name: 'hasNextPage' }),
|
|
531
|
+
t.field({ name: 'hasPreviousPage' }),
|
|
532
|
+
t.field({ name: 'startCursor' }),
|
|
533
|
+
t.field({ name: 'endCursor' }),
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function buildConnectionSelections(nodeSelections: FieldNode[]): FieldNode[] {
|
|
538
|
+
return [
|
|
539
|
+
t.field({
|
|
540
|
+
name: 'nodes',
|
|
541
|
+
selectionSet: t.selectionSet({ selections: nodeSelections }),
|
|
542
|
+
}),
|
|
543
|
+
t.field({ name: 'totalCount' }),
|
|
544
|
+
t.field({
|
|
545
|
+
name: 'pageInfo',
|
|
546
|
+
selectionSet: t.selectionSet({ selections: buildPageInfoSelections() }),
|
|
547
|
+
}),
|
|
548
|
+
];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
interface VariableSpec {
|
|
552
|
+
varName: string;
|
|
553
|
+
argName?: string;
|
|
554
|
+
typeName: string;
|
|
555
|
+
value: unknown;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
interface InputMutationConfig {
|
|
559
|
+
operationName: string;
|
|
560
|
+
mutationField: string;
|
|
561
|
+
inputTypeName: string;
|
|
562
|
+
resultSelections: FieldNode[];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function buildInputMutationDocument(config: InputMutationConfig): string {
|
|
566
|
+
const document = t.document({
|
|
567
|
+
definitions: [
|
|
568
|
+
t.operationDefinition({
|
|
569
|
+
operation: 'mutation',
|
|
570
|
+
name: config.operationName + 'Mutation',
|
|
571
|
+
variableDefinitions: [
|
|
572
|
+
t.variableDefinition({
|
|
573
|
+
variable: t.variable({ name: 'input' }),
|
|
574
|
+
type: parseType(config.inputTypeName + '!'),
|
|
575
|
+
}),
|
|
576
|
+
],
|
|
577
|
+
selectionSet: t.selectionSet({
|
|
578
|
+
selections: [
|
|
579
|
+
t.field({
|
|
580
|
+
name: config.mutationField,
|
|
581
|
+
args: [
|
|
582
|
+
t.argument({
|
|
583
|
+
name: 'input',
|
|
584
|
+
value: t.variable({ name: 'input' }),
|
|
585
|
+
}),
|
|
586
|
+
],
|
|
587
|
+
selectionSet: t.selectionSet({
|
|
588
|
+
selections: config.resultSelections,
|
|
589
|
+
}),
|
|
590
|
+
}),
|
|
591
|
+
],
|
|
592
|
+
}),
|
|
593
|
+
}),
|
|
594
|
+
],
|
|
595
|
+
});
|
|
596
|
+
return print(document);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function addVariable(
|
|
600
|
+
spec: VariableSpec,
|
|
601
|
+
definitions: VariableDefinitionNode[],
|
|
602
|
+
args: ArgumentNode[],
|
|
603
|
+
variables: Record<string, unknown>
|
|
604
|
+
): void {
|
|
605
|
+
if (spec.value === undefined) return;
|
|
606
|
+
|
|
607
|
+
definitions.push(
|
|
608
|
+
t.variableDefinition({
|
|
609
|
+
variable: t.variable({ name: spec.varName }),
|
|
610
|
+
type: parseType(spec.typeName),
|
|
611
|
+
})
|
|
612
|
+
);
|
|
613
|
+
args.push(
|
|
614
|
+
t.argument({
|
|
615
|
+
name: spec.argName ?? spec.varName,
|
|
616
|
+
value: t.variable({ name: spec.varName }),
|
|
617
|
+
})
|
|
618
|
+
);
|
|
619
|
+
variables[spec.varName] = spec.value;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function buildValueAst(
|
|
623
|
+
value: unknown
|
|
624
|
+
):
|
|
625
|
+
| ReturnType<typeof t.stringValue>
|
|
626
|
+
| ReturnType<typeof t.intValue>
|
|
627
|
+
| ReturnType<typeof t.floatValue>
|
|
628
|
+
| ReturnType<typeof t.booleanValue>
|
|
629
|
+
| ReturnType<typeof t.listValue>
|
|
630
|
+
| ReturnType<typeof t.objectValue>
|
|
631
|
+
| ReturnType<typeof t.nullValue>
|
|
632
|
+
| EnumValueNode {
|
|
633
|
+
if (value === null) {
|
|
634
|
+
return t.nullValue();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (typeof value === 'boolean') {
|
|
638
|
+
return t.booleanValue({ value });
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (typeof value === 'number') {
|
|
642
|
+
return Number.isInteger(value)
|
|
643
|
+
? t.intValue({ value: value.toString() })
|
|
644
|
+
: t.floatValue({ value: value.toString() });
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (typeof value === 'string') {
|
|
648
|
+
return t.stringValue({ value });
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (Array.isArray(value)) {
|
|
652
|
+
return t.listValue({
|
|
653
|
+
values: value.map((item) => buildValueAst(item)),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (typeof value === 'object' && value !== null) {
|
|
658
|
+
const obj = value as Record<string, unknown>;
|
|
659
|
+
return t.objectValue({
|
|
660
|
+
fields: Object.entries(obj).map(([key, val]) =>
|
|
661
|
+
t.objectField({
|
|
662
|
+
name: key,
|
|
663
|
+
value: buildValueAst(val),
|
|
664
|
+
})
|
|
665
|
+
),
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
throw new Error('Unsupported value type: ' + typeof value);
|
|
670
|
+
}
|
|
@@ -59,7 +59,6 @@ export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: s
|
|
|
59
59
|
/**
|
|
60
60
|
* Format generated files using prettier
|
|
61
61
|
* Runs prettier on the output directory after all files are written
|
|
62
|
-
* Uses bundled config with sensible defaults (singleQuote, trailingComma, etc.)
|
|
63
62
|
*/
|
|
64
63
|
export declare function formatOutput(outputDir: string): {
|
|
65
64
|
success: boolean;
|
package/cli/commands/generate.js
CHANGED
|
@@ -414,27 +414,17 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
|
|
|
414
414
|
/**
|
|
415
415
|
* Format generated files using prettier
|
|
416
416
|
* Runs prettier on the output directory after all files are written
|
|
417
|
-
* Uses bundled config with sensible defaults (singleQuote, trailingComma, etc.)
|
|
418
417
|
*/
|
|
419
418
|
function formatOutput(outputDir) {
|
|
420
|
-
// Resolve to absolute path for reliable execution
|
|
421
419
|
const absoluteOutputDir = path.resolve(outputDir);
|
|
422
420
|
try {
|
|
423
|
-
|
|
424
|
-
// prettier is a dependency of @constructive-io/graphql-codegen
|
|
425
|
-
const prettierPkgPath = require.resolve('prettier/package.json');
|
|
426
|
-
const prettierDir = path.dirname(prettierPkgPath);
|
|
427
|
-
const prettierBin = path.join(prettierDir, 'bin', 'prettier.cjs');
|
|
428
|
-
// Use bundled config with sensible defaults
|
|
429
|
-
const configPath = path.join(__dirname, 'codegen-prettier.json');
|
|
430
|
-
(0, node_child_process_1.execSync)(`"${prettierBin}" --write --config "${configPath}" "${absoluteOutputDir}"`, {
|
|
421
|
+
(0, node_child_process_1.execSync)(`npx prettier --write --single-quote --trailing-comma all --tab-width 2 --semi "${absoluteOutputDir}"`, {
|
|
431
422
|
stdio: 'pipe',
|
|
432
423
|
encoding: 'utf-8',
|
|
433
424
|
});
|
|
434
425
|
return { success: true };
|
|
435
426
|
}
|
|
436
427
|
catch (err) {
|
|
437
|
-
// prettier may fail if files have syntax errors or if not installed
|
|
438
428
|
const message = err instanceof Error ? err.message : String(err);
|
|
439
429
|
return { success: false, error: message };
|
|
440
430
|
}
|
|
@@ -59,7 +59,6 @@ export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: s
|
|
|
59
59
|
/**
|
|
60
60
|
* Format generated files using prettier
|
|
61
61
|
* Runs prettier on the output directory after all files are written
|
|
62
|
-
* Uses bundled config with sensible defaults (singleQuote, trailingComma, etc.)
|
|
63
62
|
*/
|
|
64
63
|
export declare function formatOutput(outputDir: string): {
|
|
65
64
|
success: boolean;
|
|
@@ -376,27 +376,17 @@ export async function writeGeneratedFiles(files, outputDir, subdirs, options = {
|
|
|
376
376
|
/**
|
|
377
377
|
* Format generated files using prettier
|
|
378
378
|
* Runs prettier on the output directory after all files are written
|
|
379
|
-
* Uses bundled config with sensible defaults (singleQuote, trailingComma, etc.)
|
|
380
379
|
*/
|
|
381
380
|
export function formatOutput(outputDir) {
|
|
382
|
-
// Resolve to absolute path for reliable execution
|
|
383
381
|
const absoluteOutputDir = path.resolve(outputDir);
|
|
384
382
|
try {
|
|
385
|
-
|
|
386
|
-
// prettier is a dependency of @constructive-io/graphql-codegen
|
|
387
|
-
const prettierPkgPath = require.resolve('prettier/package.json');
|
|
388
|
-
const prettierDir = path.dirname(prettierPkgPath);
|
|
389
|
-
const prettierBin = path.join(prettierDir, 'bin', 'prettier.cjs');
|
|
390
|
-
// Use bundled config with sensible defaults
|
|
391
|
-
const configPath = path.join(__dirname, 'codegen-prettier.json');
|
|
392
|
-
execSync(`"${prettierBin}" --write --config "${configPath}" "${absoluteOutputDir}"`, {
|
|
383
|
+
execSync(`npx prettier --write --single-quote --trailing-comma all --tab-width 2 --semi "${absoluteOutputDir}"`, {
|
|
393
384
|
stdio: 'pipe',
|
|
394
385
|
encoding: 'utf-8',
|
|
395
386
|
});
|
|
396
387
|
return { success: true };
|
|
397
388
|
}
|
|
398
389
|
catch (err) {
|
|
399
|
-
// prettier may fail if files have syntax errors or if not installed
|
|
400
390
|
const message = err instanceof Error ? err.message : String(err);
|
|
401
391
|
return { success: false, error: message };
|
|
402
392
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-codegen",
|
|
3
|
-
"version": "2.28.
|
|
3
|
+
"version": "2.28.4",
|
|
4
4
|
"description": "CLI-based GraphQL SDK generator for PostGraphile endpoints with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"clean": "makage clean",
|
|
37
37
|
"prepack": "npm run build",
|
|
38
|
-
"
|
|
39
|
-
"build
|
|
38
|
+
"copy:ts": "makage copy src/cli/codegen/orm/query-builder.ts dist/cli/codegen/orm --flat",
|
|
39
|
+
"build": "makage build && npm run copy:ts",
|
|
40
|
+
"build:dev": "makage build --dev && npm run copy:ts",
|
|
40
41
|
"dev": "ts-node ./src/index.ts",
|
|
41
42
|
"lint": "eslint . --fix",
|
|
42
43
|
"fmt": "prettier --write .",
|
|
@@ -88,5 +89,5 @@
|
|
|
88
89
|
"tsx": "^4.21.0",
|
|
89
90
|
"typescript": "^5.9.3"
|
|
90
91
|
},
|
|
91
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "13c6cd90521b5521a7219ce270e1b4dd979652cf"
|
|
92
93
|
}
|