@constructive-io/graphql-codegen 2.24.0 → 2.26.0
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.
- package/README.md +403 -279
- package/cli/codegen/babel-ast.d.ts +7 -0
- package/cli/codegen/babel-ast.js +15 -0
- package/cli/codegen/barrel.js +43 -14
- package/cli/codegen/custom-mutations.js +4 -4
- package/cli/codegen/custom-queries.js +12 -22
- package/cli/codegen/gql-ast.js +22 -1
- package/cli/codegen/index.js +1 -0
- package/cli/codegen/mutations.d.ts +2 -0
- package/cli/codegen/mutations.js +26 -13
- package/cli/codegen/orm/client-generator.js +475 -136
- package/cli/codegen/orm/custom-ops-generator.js +8 -3
- package/cli/codegen/orm/input-types-generator.js +22 -0
- package/cli/codegen/orm/model-generator.js +18 -5
- package/cli/codegen/orm/select-types.d.ts +33 -0
- package/cli/codegen/queries.d.ts +1 -1
- package/cli/codegen/queries.js +112 -35
- package/cli/codegen/utils.d.ts +6 -0
- package/cli/codegen/utils.js +19 -0
- package/cli/commands/generate-orm.d.ts +14 -0
- package/cli/commands/generate-orm.js +160 -44
- package/cli/commands/generate.d.ts +22 -0
- package/cli/commands/generate.js +195 -55
- package/cli/commands/init.js +29 -9
- package/cli/index.js +133 -28
- package/cli/watch/orchestrator.d.ts +4 -0
- package/cli/watch/orchestrator.js +4 -0
- package/esm/cli/codegen/babel-ast.d.ts +7 -0
- package/esm/cli/codegen/babel-ast.js +14 -0
- package/esm/cli/codegen/barrel.js +44 -15
- package/esm/cli/codegen/custom-mutations.js +5 -5
- package/esm/cli/codegen/custom-queries.js +13 -23
- package/esm/cli/codegen/gql-ast.js +23 -2
- package/esm/cli/codegen/index.js +1 -0
- package/esm/cli/codegen/mutations.d.ts +2 -0
- package/esm/cli/codegen/mutations.js +27 -14
- package/esm/cli/codegen/orm/client-generator.js +475 -136
- package/esm/cli/codegen/orm/custom-ops-generator.js +8 -3
- package/esm/cli/codegen/orm/input-types-generator.js +22 -0
- package/esm/cli/codegen/orm/model-generator.js +18 -5
- package/esm/cli/codegen/orm/select-types.d.ts +33 -0
- package/esm/cli/codegen/queries.d.ts +1 -1
- package/esm/cli/codegen/queries.js +114 -37
- package/esm/cli/codegen/utils.d.ts +6 -0
- package/esm/cli/codegen/utils.js +18 -0
- package/esm/cli/commands/generate-orm.d.ts +14 -0
- package/esm/cli/commands/generate-orm.js +161 -45
- package/esm/cli/commands/generate.d.ts +22 -0
- package/esm/cli/commands/generate.js +195 -56
- package/esm/cli/commands/init.js +29 -9
- package/esm/cli/index.js +134 -29
- package/esm/cli/watch/orchestrator.d.ts +4 -0
- package/esm/cli/watch/orchestrator.js +5 -1
- package/esm/types/config.d.ts +39 -2
- package/esm/types/config.js +88 -4
- package/esm/types/index.d.ts +2 -2
- package/esm/types/index.js +1 -1
- package/package.json +10 -7
- package/types/config.d.ts +39 -2
- package/types/config.js +91 -4
- package/types/index.d.ts +2 -2
- package/types/index.js +2 -1
- package/cli/codegen/orm/query-builder.d.ts +0 -161
- package/cli/codegen/orm/query-builder.js +0 -366
- package/esm/cli/codegen/orm/query-builder.d.ts +0 -161
- package/esm/cli/codegen/orm/query-builder.js +0 -353
|
@@ -136,6 +136,14 @@ export function generateQueryBuilderFile() {
|
|
|
136
136
|
* DO NOT EDIT - changes will be overwritten
|
|
137
137
|
*/
|
|
138
138
|
|
|
139
|
+
import * as t from 'gql-ast';
|
|
140
|
+
import { parseType, print } from 'graphql';
|
|
141
|
+
import type {
|
|
142
|
+
ArgumentNode,
|
|
143
|
+
FieldNode,
|
|
144
|
+
VariableDefinitionNode,
|
|
145
|
+
EnumValueNode,
|
|
146
|
+
} from 'graphql';
|
|
139
147
|
import { OrmClient, QueryResult, GraphQLRequestError } from './client';
|
|
140
148
|
|
|
141
149
|
export interface QueryBuilderConfig {
|
|
@@ -209,19 +217,25 @@ export class QueryBuilder<TResult> {
|
|
|
209
217
|
}
|
|
210
218
|
|
|
211
219
|
// ============================================================================
|
|
212
|
-
//
|
|
220
|
+
// Selection Builders
|
|
213
221
|
// ============================================================================
|
|
214
222
|
|
|
215
|
-
export function buildSelections
|
|
216
|
-
|
|
223
|
+
export function buildSelections(
|
|
224
|
+
select: Record<string, unknown> | undefined
|
|
225
|
+
): FieldNode[] {
|
|
226
|
+
if (!select) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
217
229
|
|
|
218
|
-
const fields:
|
|
230
|
+
const fields: FieldNode[] = [];
|
|
219
231
|
|
|
220
232
|
for (const [key, value] of Object.entries(select)) {
|
|
221
|
-
if (value === false || value === undefined)
|
|
233
|
+
if (value === false || value === undefined) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
222
236
|
|
|
223
237
|
if (value === true) {
|
|
224
|
-
fields.push(key);
|
|
238
|
+
fields.push(t.field({ name: key }));
|
|
225
239
|
continue;
|
|
226
240
|
}
|
|
227
241
|
|
|
@@ -231,39 +245,53 @@ export function buildSelections<T>(select: T): string {
|
|
|
231
245
|
first?: number;
|
|
232
246
|
filter?: Record<string, unknown>;
|
|
233
247
|
orderBy?: string[];
|
|
234
|
-
// New: connection flag to differentiate connection types from regular objects
|
|
235
248
|
connection?: boolean;
|
|
236
249
|
};
|
|
237
250
|
|
|
238
251
|
if (nested.select) {
|
|
239
252
|
const nestedSelections = buildSelections(nested.select);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
253
|
+
const isConnection =
|
|
254
|
+
nested.connection === true ||
|
|
255
|
+
nested.first !== undefined ||
|
|
256
|
+
nested.filter !== undefined;
|
|
257
|
+
const args = buildArgs([
|
|
258
|
+
buildOptionalArg('first', nested.first),
|
|
259
|
+
nested.filter
|
|
260
|
+
? t.argument({ name: 'filter', value: buildValueAst(nested.filter) })
|
|
261
|
+
: null,
|
|
262
|
+
buildEnumListArg('orderBy', nested.orderBy),
|
|
263
|
+
]);
|
|
264
|
+
|
|
244
265
|
if (isConnection) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
|
|
255
|
-
}\`);
|
|
266
|
+
fields.push(
|
|
267
|
+
t.field({
|
|
268
|
+
name: key,
|
|
269
|
+
args,
|
|
270
|
+
selectionSet: t.selectionSet({
|
|
271
|
+
selections: buildConnectionSelections(nestedSelections),
|
|
272
|
+
}),
|
|
273
|
+
})
|
|
274
|
+
);
|
|
256
275
|
} else {
|
|
257
|
-
|
|
258
|
-
|
|
276
|
+
fields.push(
|
|
277
|
+
t.field({
|
|
278
|
+
name: key,
|
|
279
|
+
args,
|
|
280
|
+
selectionSet: t.selectionSet({ selections: nestedSelections }),
|
|
281
|
+
})
|
|
282
|
+
);
|
|
259
283
|
}
|
|
260
284
|
}
|
|
261
285
|
}
|
|
262
286
|
}
|
|
263
287
|
|
|
264
|
-
return fields
|
|
288
|
+
return fields;
|
|
265
289
|
}
|
|
266
290
|
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Document Builders
|
|
293
|
+
// ============================================================================
|
|
294
|
+
|
|
267
295
|
export function buildFindManyDocument<TSelect, TWhere>(
|
|
268
296
|
operationName: string,
|
|
269
297
|
queryField: string,
|
|
@@ -280,60 +308,44 @@ export function buildFindManyDocument<TSelect, TWhere>(
|
|
|
280
308
|
filterTypeName: string,
|
|
281
309
|
orderByTypeName: string
|
|
282
310
|
): { document: string; variables: Record<string, unknown> } {
|
|
283
|
-
const selections = select
|
|
311
|
+
const selections = select
|
|
312
|
+
? buildSelections(select as Record<string, unknown>)
|
|
313
|
+
: [t.field({ name: 'id' })];
|
|
284
314
|
|
|
285
|
-
const
|
|
286
|
-
const queryArgs:
|
|
315
|
+
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
316
|
+
const queryArgs: ArgumentNode[] = [];
|
|
287
317
|
const variables: Record<string, unknown> = {};
|
|
288
318
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
variables.before = args.before;
|
|
318
|
-
}
|
|
319
|
-
if (args.offset !== undefined) {
|
|
320
|
-
varDefs.push('$offset: Int');
|
|
321
|
-
queryArgs.push('offset: $offset');
|
|
322
|
-
variables.offset = args.offset;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const varDefsStr = varDefs.length > 0 ? \`(\${varDefs.join(', ')})\` : '';
|
|
326
|
-
const queryArgsStr = queryArgs.length > 0 ? \`(\${queryArgs.join(', ')})\` : '';
|
|
327
|
-
|
|
328
|
-
const document = \`query \${operationName}Query\${varDefsStr} {
|
|
329
|
-
\${queryField}\${queryArgsStr} {
|
|
330
|
-
nodes { \${selections} }
|
|
331
|
-
totalCount
|
|
332
|
-
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
|
|
333
|
-
}
|
|
334
|
-
}\`;
|
|
319
|
+
addVariable({ varName: 'where', argName: 'filter', typeName: filterTypeName, value: args.where }, variableDefinitions, queryArgs, variables);
|
|
320
|
+
addVariable({ varName: 'orderBy', typeName: '[' + orderByTypeName + '!]', value: args.orderBy?.length ? args.orderBy : undefined }, variableDefinitions, queryArgs, variables);
|
|
321
|
+
addVariable({ varName: 'first', typeName: 'Int', value: args.first }, variableDefinitions, queryArgs, variables);
|
|
322
|
+
addVariable({ varName: 'last', typeName: 'Int', value: args.last }, variableDefinitions, queryArgs, variables);
|
|
323
|
+
addVariable({ varName: 'after', typeName: 'Cursor', value: args.after }, variableDefinitions, queryArgs, variables);
|
|
324
|
+
addVariable({ varName: 'before', typeName: 'Cursor', value: args.before }, variableDefinitions, queryArgs, variables);
|
|
325
|
+
addVariable({ varName: 'offset', typeName: 'Int', value: args.offset }, variableDefinitions, queryArgs, variables);
|
|
326
|
+
|
|
327
|
+
const document = t.document({
|
|
328
|
+
definitions: [
|
|
329
|
+
t.operationDefinition({
|
|
330
|
+
operation: 'query',
|
|
331
|
+
name: operationName + 'Query',
|
|
332
|
+
variableDefinitions: variableDefinitions.length ? variableDefinitions : undefined,
|
|
333
|
+
selectionSet: t.selectionSet({
|
|
334
|
+
selections: [
|
|
335
|
+
t.field({
|
|
336
|
+
name: queryField,
|
|
337
|
+
args: queryArgs.length ? queryArgs : undefined,
|
|
338
|
+
selectionSet: t.selectionSet({
|
|
339
|
+
selections: buildConnectionSelections(selections),
|
|
340
|
+
}),
|
|
341
|
+
}),
|
|
342
|
+
],
|
|
343
|
+
}),
|
|
344
|
+
}),
|
|
345
|
+
],
|
|
346
|
+
});
|
|
335
347
|
|
|
336
|
-
return { document, variables };
|
|
348
|
+
return { document: print(document), variables };
|
|
337
349
|
}
|
|
338
350
|
|
|
339
351
|
export function buildFindFirstDocument<TSelect, TWhere>(
|
|
@@ -343,25 +355,45 @@ export function buildFindFirstDocument<TSelect, TWhere>(
|
|
|
343
355
|
args: { where?: TWhere },
|
|
344
356
|
filterTypeName: string
|
|
345
357
|
): { document: string; variables: Record<string, unknown> } {
|
|
346
|
-
const selections = select
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const queryArgs: string[] = ['first: $first'];
|
|
350
|
-
const variables: Record<string, unknown> = { first: 1 };
|
|
358
|
+
const selections = select
|
|
359
|
+
? buildSelections(select as Record<string, unknown>)
|
|
360
|
+
: [t.field({ name: 'id' })];
|
|
351
361
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
variables.where = args.where;
|
|
356
|
-
}
|
|
362
|
+
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
363
|
+
const queryArgs: ArgumentNode[] = [];
|
|
364
|
+
const variables: Record<string, unknown> = {};
|
|
357
365
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
366
|
+
// Always add first: 1 for findFirst
|
|
367
|
+
addVariable({ varName: 'first', typeName: 'Int', value: 1 }, variableDefinitions, queryArgs, variables);
|
|
368
|
+
addVariable({ varName: 'where', argName: 'filter', typeName: filterTypeName, value: args.where }, variableDefinitions, queryArgs, variables);
|
|
369
|
+
|
|
370
|
+
const document = t.document({
|
|
371
|
+
definitions: [
|
|
372
|
+
t.operationDefinition({
|
|
373
|
+
operation: 'query',
|
|
374
|
+
name: operationName + 'Query',
|
|
375
|
+
variableDefinitions,
|
|
376
|
+
selectionSet: t.selectionSet({
|
|
377
|
+
selections: [
|
|
378
|
+
t.field({
|
|
379
|
+
name: queryField,
|
|
380
|
+
args: queryArgs,
|
|
381
|
+
selectionSet: t.selectionSet({
|
|
382
|
+
selections: [
|
|
383
|
+
t.field({
|
|
384
|
+
name: 'nodes',
|
|
385
|
+
selectionSet: t.selectionSet({ selections }),
|
|
386
|
+
}),
|
|
387
|
+
],
|
|
388
|
+
}),
|
|
389
|
+
}),
|
|
390
|
+
],
|
|
391
|
+
}),
|
|
392
|
+
}),
|
|
393
|
+
],
|
|
394
|
+
});
|
|
363
395
|
|
|
364
|
-
return { document, variables };
|
|
396
|
+
return { document: print(document), variables };
|
|
365
397
|
}
|
|
366
398
|
|
|
367
399
|
export function buildCreateDocument<TSelect, TData>(
|
|
@@ -372,17 +404,27 @@ export function buildCreateDocument<TSelect, TData>(
|
|
|
372
404
|
data: TData,
|
|
373
405
|
inputTypeName: string
|
|
374
406
|
): { document: string; variables: Record<string, unknown> } {
|
|
375
|
-
const selections = select
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
\${mutationField}(input: $input) {
|
|
379
|
-
\${entityField} { \${selections} }
|
|
380
|
-
}
|
|
381
|
-
}\`;
|
|
407
|
+
const selections = select
|
|
408
|
+
? buildSelections(select as Record<string, unknown>)
|
|
409
|
+
: [t.field({ name: 'id' })];
|
|
382
410
|
|
|
383
411
|
return {
|
|
384
|
-
document
|
|
385
|
-
|
|
412
|
+
document: buildInputMutationDocument({
|
|
413
|
+
operationName,
|
|
414
|
+
mutationField,
|
|
415
|
+
inputTypeName,
|
|
416
|
+
resultSelections: [
|
|
417
|
+
t.field({
|
|
418
|
+
name: entityField,
|
|
419
|
+
selectionSet: t.selectionSet({ selections }),
|
|
420
|
+
}),
|
|
421
|
+
],
|
|
422
|
+
}),
|
|
423
|
+
variables: {
|
|
424
|
+
input: {
|
|
425
|
+
[entityField]: data,
|
|
426
|
+
},
|
|
427
|
+
},
|
|
386
428
|
};
|
|
387
429
|
}
|
|
388
430
|
|
|
@@ -395,17 +437,28 @@ export function buildUpdateDocument<TSelect, TWhere extends { id: string }, TDat
|
|
|
395
437
|
data: TData,
|
|
396
438
|
inputTypeName: string
|
|
397
439
|
): { document: string; variables: Record<string, unknown> } {
|
|
398
|
-
const selections = select
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
\${mutationField}(input: $input) {
|
|
402
|
-
\${entityField} { \${selections} }
|
|
403
|
-
}
|
|
404
|
-
}\`;
|
|
440
|
+
const selections = select
|
|
441
|
+
? buildSelections(select as Record<string, unknown>)
|
|
442
|
+
: [t.field({ name: 'id' })];
|
|
405
443
|
|
|
406
444
|
return {
|
|
407
|
-
document
|
|
408
|
-
|
|
445
|
+
document: buildInputMutationDocument({
|
|
446
|
+
operationName,
|
|
447
|
+
mutationField,
|
|
448
|
+
inputTypeName,
|
|
449
|
+
resultSelections: [
|
|
450
|
+
t.field({
|
|
451
|
+
name: entityField,
|
|
452
|
+
selectionSet: t.selectionSet({ selections }),
|
|
453
|
+
}),
|
|
454
|
+
],
|
|
455
|
+
}),
|
|
456
|
+
variables: {
|
|
457
|
+
input: {
|
|
458
|
+
id: where.id,
|
|
459
|
+
patch: data,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
409
462
|
};
|
|
410
463
|
}
|
|
411
464
|
|
|
@@ -416,15 +469,25 @@ export function buildDeleteDocument<TWhere extends { id: string }>(
|
|
|
416
469
|
where: TWhere,
|
|
417
470
|
inputTypeName: string
|
|
418
471
|
): { document: string; variables: Record<string, unknown> } {
|
|
419
|
-
const document = \`mutation \${operationName}Mutation($input: \${inputTypeName}!) {
|
|
420
|
-
\${mutationField}(input: $input) {
|
|
421
|
-
\${entityField} { id }
|
|
422
|
-
}
|
|
423
|
-
}\`;
|
|
424
|
-
|
|
425
472
|
return {
|
|
426
|
-
document
|
|
427
|
-
|
|
473
|
+
document: buildInputMutationDocument({
|
|
474
|
+
operationName,
|
|
475
|
+
mutationField,
|
|
476
|
+
inputTypeName,
|
|
477
|
+
resultSelections: [
|
|
478
|
+
t.field({
|
|
479
|
+
name: entityField,
|
|
480
|
+
selectionSet: t.selectionSet({
|
|
481
|
+
selections: [t.field({ name: 'id' })],
|
|
482
|
+
}),
|
|
483
|
+
}),
|
|
484
|
+
],
|
|
485
|
+
}),
|
|
486
|
+
variables: {
|
|
487
|
+
input: {
|
|
488
|
+
id: where.id,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
428
491
|
};
|
|
429
492
|
}
|
|
430
493
|
|
|
@@ -436,21 +499,251 @@ export function buildCustomDocument<TSelect, TArgs>(
|
|
|
436
499
|
args: TArgs,
|
|
437
500
|
variableDefinitions: Array<{ name: string; type: string }>
|
|
438
501
|
): { document: string; variables: Record<string, unknown> } {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
502
|
+
let actualSelect = select;
|
|
503
|
+
let isConnection = false;
|
|
504
|
+
|
|
505
|
+
if (select && typeof select === 'object' && 'select' in select) {
|
|
506
|
+
const wrapper = select as { select?: TSelect; connection?: boolean };
|
|
507
|
+
if (wrapper.select) {
|
|
508
|
+
actualSelect = wrapper.select;
|
|
509
|
+
isConnection = wrapper.connection === true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const selections = actualSelect
|
|
514
|
+
? buildSelections(actualSelect as Record<string, unknown>)
|
|
515
|
+
: [];
|
|
516
|
+
|
|
517
|
+
const variableDefs = variableDefinitions.map((definition) =>
|
|
518
|
+
t.variableDefinition({
|
|
519
|
+
variable: t.variable({ name: definition.name }),
|
|
520
|
+
type: parseType(definition.type),
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
const fieldArgs = variableDefinitions.map((definition) =>
|
|
524
|
+
t.argument({
|
|
525
|
+
name: definition.name,
|
|
526
|
+
value: t.variable({ name: definition.name }),
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const fieldSelections = isConnection
|
|
531
|
+
? buildConnectionSelections(selections)
|
|
532
|
+
: selections;
|
|
533
|
+
|
|
534
|
+
const document = t.document({
|
|
535
|
+
definitions: [
|
|
536
|
+
t.operationDefinition({
|
|
537
|
+
operation: operationType,
|
|
538
|
+
name: operationName,
|
|
539
|
+
variableDefinitions: variableDefs.length ? variableDefs : undefined,
|
|
540
|
+
selectionSet: t.selectionSet({
|
|
541
|
+
selections: [
|
|
542
|
+
t.field({
|
|
543
|
+
name: fieldName,
|
|
544
|
+
args: fieldArgs.length ? fieldArgs : undefined,
|
|
545
|
+
selectionSet: fieldSelections.length
|
|
546
|
+
? t.selectionSet({ selections: fieldSelections })
|
|
547
|
+
: undefined,
|
|
548
|
+
}),
|
|
549
|
+
],
|
|
550
|
+
}),
|
|
551
|
+
}),
|
|
552
|
+
],
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
document: print(document),
|
|
557
|
+
variables: (args ?? {}) as Record<string, unknown>,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ============================================================================
|
|
562
|
+
// Helper Functions
|
|
563
|
+
// ============================================================================
|
|
564
|
+
|
|
565
|
+
function buildArgs(args: Array<ArgumentNode | null>): ArgumentNode[] {
|
|
566
|
+
return args.filter((arg): arg is ArgumentNode => arg !== null);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function buildOptionalArg(
|
|
570
|
+
name: string,
|
|
571
|
+
value: number | string | undefined
|
|
572
|
+
): ArgumentNode | null {
|
|
573
|
+
if (value === undefined) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
const valueNode =
|
|
577
|
+
typeof value === 'number'
|
|
578
|
+
? t.intValue({ value: value.toString() })
|
|
579
|
+
: t.stringValue({ value });
|
|
580
|
+
return t.argument({ name, value: valueNode });
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function buildEnumListArg(
|
|
584
|
+
name: string,
|
|
585
|
+
values: string[] | undefined
|
|
586
|
+
): ArgumentNode | null {
|
|
587
|
+
if (!values || values.length === 0) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return t.argument({
|
|
591
|
+
name,
|
|
592
|
+
value: t.listValue({
|
|
593
|
+
values: values.map((value) => buildEnumValue(value)),
|
|
594
|
+
}),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function buildEnumValue(value: string): EnumValueNode {
|
|
599
|
+
return {
|
|
600
|
+
kind: 'EnumValue',
|
|
601
|
+
value,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function buildPageInfoSelections(): FieldNode[] {
|
|
606
|
+
return [
|
|
607
|
+
t.field({ name: 'hasNextPage' }),
|
|
608
|
+
t.field({ name: 'hasPreviousPage' }),
|
|
609
|
+
t.field({ name: 'startCursor' }),
|
|
610
|
+
t.field({ name: 'endCursor' }),
|
|
611
|
+
];
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function buildConnectionSelections(nodeSelections: FieldNode[]): FieldNode[] {
|
|
615
|
+
return [
|
|
616
|
+
t.field({
|
|
617
|
+
name: 'nodes',
|
|
618
|
+
selectionSet: t.selectionSet({ selections: nodeSelections }),
|
|
619
|
+
}),
|
|
620
|
+
t.field({ name: 'totalCount' }),
|
|
621
|
+
t.field({
|
|
622
|
+
name: 'pageInfo',
|
|
623
|
+
selectionSet: t.selectionSet({ selections: buildPageInfoSelections() }),
|
|
624
|
+
}),
|
|
625
|
+
];
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
interface VariableSpec {
|
|
629
|
+
varName: string;
|
|
630
|
+
argName?: string;
|
|
631
|
+
typeName: string;
|
|
632
|
+
value: unknown;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
interface InputMutationConfig {
|
|
636
|
+
operationName: string;
|
|
637
|
+
mutationField: string;
|
|
638
|
+
inputTypeName: string;
|
|
639
|
+
resultSelections: FieldNode[];
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function buildInputMutationDocument(config: InputMutationConfig): string {
|
|
643
|
+
const document = t.document({
|
|
644
|
+
definitions: [
|
|
645
|
+
t.operationDefinition({
|
|
646
|
+
operation: 'mutation',
|
|
647
|
+
name: config.operationName + 'Mutation',
|
|
648
|
+
variableDefinitions: [
|
|
649
|
+
t.variableDefinition({
|
|
650
|
+
variable: t.variable({ name: 'input' }),
|
|
651
|
+
type: parseType(config.inputTypeName + '!'),
|
|
652
|
+
}),
|
|
653
|
+
],
|
|
654
|
+
selectionSet: t.selectionSet({
|
|
655
|
+
selections: [
|
|
656
|
+
t.field({
|
|
657
|
+
name: config.mutationField,
|
|
658
|
+
args: [
|
|
659
|
+
t.argument({
|
|
660
|
+
name: 'input',
|
|
661
|
+
value: t.variable({ name: 'input' }),
|
|
662
|
+
}),
|
|
663
|
+
],
|
|
664
|
+
selectionSet: t.selectionSet({
|
|
665
|
+
selections: config.resultSelections,
|
|
666
|
+
}),
|
|
667
|
+
}),
|
|
668
|
+
],
|
|
669
|
+
}),
|
|
670
|
+
}),
|
|
671
|
+
],
|
|
672
|
+
});
|
|
673
|
+
return print(document);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function addVariable(
|
|
677
|
+
spec: VariableSpec,
|
|
678
|
+
definitions: VariableDefinitionNode[],
|
|
679
|
+
args: ArgumentNode[],
|
|
680
|
+
variables: Record<string, unknown>
|
|
681
|
+
): void {
|
|
682
|
+
if (spec.value === undefined) return;
|
|
683
|
+
|
|
684
|
+
definitions.push(
|
|
685
|
+
t.variableDefinition({
|
|
686
|
+
variable: t.variable({ name: spec.varName }),
|
|
687
|
+
type: parseType(spec.typeName),
|
|
688
|
+
})
|
|
689
|
+
);
|
|
690
|
+
args.push(
|
|
691
|
+
t.argument({
|
|
692
|
+
name: spec.argName ?? spec.varName,
|
|
693
|
+
value: t.variable({ name: spec.varName }),
|
|
694
|
+
})
|
|
695
|
+
);
|
|
696
|
+
variables[spec.varName] = spec.value;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function buildValueAst(
|
|
700
|
+
value: unknown
|
|
701
|
+
):
|
|
702
|
+
| ReturnType<typeof t.stringValue>
|
|
703
|
+
| ReturnType<typeof t.intValue>
|
|
704
|
+
| ReturnType<typeof t.floatValue>
|
|
705
|
+
| ReturnType<typeof t.booleanValue>
|
|
706
|
+
| ReturnType<typeof t.listValue>
|
|
707
|
+
| ReturnType<typeof t.objectValue>
|
|
708
|
+
| ReturnType<typeof t.nullValue>
|
|
709
|
+
| EnumValueNode {
|
|
710
|
+
if (value === null) {
|
|
711
|
+
return t.nullValue();
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (typeof value === 'boolean') {
|
|
715
|
+
return t.booleanValue({ value });
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (typeof value === 'number') {
|
|
719
|
+
return Number.isInteger(value)
|
|
720
|
+
? t.intValue({ value: value.toString() })
|
|
721
|
+
: t.floatValue({ value: value.toString() });
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (typeof value === 'string') {
|
|
725
|
+
return t.stringValue({ value });
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (Array.isArray(value)) {
|
|
729
|
+
return t.listValue({
|
|
730
|
+
values: value.map((item) => buildValueAst(item)),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (typeof value === 'object' && value !== null) {
|
|
735
|
+
const obj = value as Record<string, unknown>;
|
|
736
|
+
return t.objectValue({
|
|
737
|
+
fields: Object.entries(obj).map(([key, val]) =>
|
|
738
|
+
t.objectField({
|
|
739
|
+
name: key,
|
|
740
|
+
value: buildValueAst(val),
|
|
741
|
+
})
|
|
742
|
+
),
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
throw new Error('Unsupported value type: ' + typeof value);
|
|
454
747
|
}
|
|
455
748
|
`;
|
|
456
749
|
return {
|
|
@@ -512,6 +805,44 @@ export interface DeleteArgs<TWhere> {
|
|
|
512
805
|
where: TWhere;
|
|
513
806
|
}
|
|
514
807
|
|
|
808
|
+
/**
|
|
809
|
+
* Recursively validates select objects, rejecting unknown keys.
|
|
810
|
+
*
|
|
811
|
+
* This type ensures that users can only select fields that actually exist
|
|
812
|
+
* in the GraphQL schema. It returns \`never\` if any excess keys are found
|
|
813
|
+
* at any nesting level, causing a TypeScript compile error.
|
|
814
|
+
*
|
|
815
|
+
* Why this is needed:
|
|
816
|
+
* TypeScript's excess property checking has a quirk where it only catches
|
|
817
|
+
* invalid fields when they are the ONLY fields. When mixed with valid fields
|
|
818
|
+
* (e.g., \`{ id: true, invalidField: true }\`), the structural typing allows
|
|
819
|
+
* the excess property through. This type explicitly checks for and rejects
|
|
820
|
+
* such cases.
|
|
821
|
+
*
|
|
822
|
+
* @example
|
|
823
|
+
* // This will cause a type error because 'invalid' doesn't exist:
|
|
824
|
+
* type Result = DeepExact<{ id: true, invalid: true }, { id?: boolean }>;
|
|
825
|
+
* // Result = never (causes assignment error)
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* // This works because all fields are valid:
|
|
829
|
+
* type Result = DeepExact<{ id: true }, { id?: boolean; name?: boolean }>;
|
|
830
|
+
* // Result = { id: true }
|
|
831
|
+
*/
|
|
832
|
+
export type DeepExact<T, Shape> = T extends Shape
|
|
833
|
+
? Exclude<keyof T, keyof Shape> extends never
|
|
834
|
+
? {
|
|
835
|
+
[K in keyof T]: K extends keyof Shape
|
|
836
|
+
? T[K] extends { select: infer NS }
|
|
837
|
+
? Shape[K] extends { select?: infer ShapeNS }
|
|
838
|
+
? { select: DeepExact<NS, NonNullable<ShapeNS>> }
|
|
839
|
+
: T[K]
|
|
840
|
+
: T[K]
|
|
841
|
+
: never;
|
|
842
|
+
}
|
|
843
|
+
: never
|
|
844
|
+
: never;
|
|
845
|
+
|
|
515
846
|
/**
|
|
516
847
|
* Infer result type from select configuration
|
|
517
848
|
*/
|
|
@@ -577,9 +908,13 @@ export function generateCreateClientFile(tables, hasCustomQueries, hasCustomMuta
|
|
|
577
908
|
typeExportDecl.exportKind = 'type';
|
|
578
909
|
statements.push(typeExportDecl);
|
|
579
910
|
// export { GraphQLRequestError } from './client';
|
|
580
|
-
statements.push(t.exportNamedDeclaration(null, [
|
|
911
|
+
statements.push(t.exportNamedDeclaration(null, [
|
|
912
|
+
t.exportSpecifier(t.identifier('GraphQLRequestError'), t.identifier('GraphQLRequestError')),
|
|
913
|
+
], t.stringLiteral('./client')));
|
|
581
914
|
// export { QueryBuilder } from './query-builder';
|
|
582
|
-
statements.push(t.exportNamedDeclaration(null, [
|
|
915
|
+
statements.push(t.exportNamedDeclaration(null, [
|
|
916
|
+
t.exportSpecifier(t.identifier('QueryBuilder'), t.identifier('QueryBuilder')),
|
|
917
|
+
], t.stringLiteral('./query-builder')));
|
|
583
918
|
// export * from './select-types';
|
|
584
919
|
statements.push(t.exportAllDeclaration(t.stringLiteral('./select-types')));
|
|
585
920
|
// Build the return object properties
|
|
@@ -590,10 +925,14 @@ export function generateCreateClientFile(tables, hasCustomQueries, hasCustomMuta
|
|
|
590
925
|
returnProperties.push(t.objectProperty(t.identifier(singularName), t.newExpression(t.identifier(modelName), [t.identifier('client')])));
|
|
591
926
|
}
|
|
592
927
|
if (hasCustomQueries) {
|
|
593
|
-
returnProperties.push(t.objectProperty(t.identifier('query'), t.callExpression(t.identifier('createQueryOperations'), [
|
|
928
|
+
returnProperties.push(t.objectProperty(t.identifier('query'), t.callExpression(t.identifier('createQueryOperations'), [
|
|
929
|
+
t.identifier('client'),
|
|
930
|
+
])));
|
|
594
931
|
}
|
|
595
932
|
if (hasCustomMutations) {
|
|
596
|
-
returnProperties.push(t.objectProperty(t.identifier('mutation'), t.callExpression(t.identifier('createMutationOperations'), [
|
|
933
|
+
returnProperties.push(t.objectProperty(t.identifier('mutation'), t.callExpression(t.identifier('createMutationOperations'), [
|
|
934
|
+
t.identifier('client'),
|
|
935
|
+
])));
|
|
597
936
|
}
|
|
598
937
|
// Build the createClient function body
|
|
599
938
|
const clientDecl = t.variableDeclaration('const', [
|