@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
|
@@ -175,6 +175,14 @@ function generateQueryBuilderFile() {
|
|
|
175
175
|
* DO NOT EDIT - changes will be overwritten
|
|
176
176
|
*/
|
|
177
177
|
|
|
178
|
+
import * as t from 'gql-ast';
|
|
179
|
+
import { parseType, print } from 'graphql';
|
|
180
|
+
import type {
|
|
181
|
+
ArgumentNode,
|
|
182
|
+
FieldNode,
|
|
183
|
+
VariableDefinitionNode,
|
|
184
|
+
EnumValueNode,
|
|
185
|
+
} from 'graphql';
|
|
178
186
|
import { OrmClient, QueryResult, GraphQLRequestError } from './client';
|
|
179
187
|
|
|
180
188
|
export interface QueryBuilderConfig {
|
|
@@ -248,19 +256,25 @@ export class QueryBuilder<TResult> {
|
|
|
248
256
|
}
|
|
249
257
|
|
|
250
258
|
// ============================================================================
|
|
251
|
-
//
|
|
259
|
+
// Selection Builders
|
|
252
260
|
// ============================================================================
|
|
253
261
|
|
|
254
|
-
export function buildSelections
|
|
255
|
-
|
|
262
|
+
export function buildSelections(
|
|
263
|
+
select: Record<string, unknown> | undefined
|
|
264
|
+
): FieldNode[] {
|
|
265
|
+
if (!select) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
256
268
|
|
|
257
|
-
const fields:
|
|
269
|
+
const fields: FieldNode[] = [];
|
|
258
270
|
|
|
259
271
|
for (const [key, value] of Object.entries(select)) {
|
|
260
|
-
if (value === false || value === undefined)
|
|
272
|
+
if (value === false || value === undefined) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
261
275
|
|
|
262
276
|
if (value === true) {
|
|
263
|
-
fields.push(key);
|
|
277
|
+
fields.push(t.field({ name: key }));
|
|
264
278
|
continue;
|
|
265
279
|
}
|
|
266
280
|
|
|
@@ -270,39 +284,53 @@ export function buildSelections<T>(select: T): string {
|
|
|
270
284
|
first?: number;
|
|
271
285
|
filter?: Record<string, unknown>;
|
|
272
286
|
orderBy?: string[];
|
|
273
|
-
// New: connection flag to differentiate connection types from regular objects
|
|
274
287
|
connection?: boolean;
|
|
275
288
|
};
|
|
276
289
|
|
|
277
290
|
if (nested.select) {
|
|
278
291
|
const nestedSelections = buildSelections(nested.select);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
292
|
+
const isConnection =
|
|
293
|
+
nested.connection === true ||
|
|
294
|
+
nested.first !== undefined ||
|
|
295
|
+
nested.filter !== undefined;
|
|
296
|
+
const args = buildArgs([
|
|
297
|
+
buildOptionalArg('first', nested.first),
|
|
298
|
+
nested.filter
|
|
299
|
+
? t.argument({ name: 'filter', value: buildValueAst(nested.filter) })
|
|
300
|
+
: null,
|
|
301
|
+
buildEnumListArg('orderBy', nested.orderBy),
|
|
302
|
+
]);
|
|
303
|
+
|
|
283
304
|
if (isConnection) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
|
|
294
|
-
}\`);
|
|
305
|
+
fields.push(
|
|
306
|
+
t.field({
|
|
307
|
+
name: key,
|
|
308
|
+
args,
|
|
309
|
+
selectionSet: t.selectionSet({
|
|
310
|
+
selections: buildConnectionSelections(nestedSelections),
|
|
311
|
+
}),
|
|
312
|
+
})
|
|
313
|
+
);
|
|
295
314
|
} else {
|
|
296
|
-
|
|
297
|
-
|
|
315
|
+
fields.push(
|
|
316
|
+
t.field({
|
|
317
|
+
name: key,
|
|
318
|
+
args,
|
|
319
|
+
selectionSet: t.selectionSet({ selections: nestedSelections }),
|
|
320
|
+
})
|
|
321
|
+
);
|
|
298
322
|
}
|
|
299
323
|
}
|
|
300
324
|
}
|
|
301
325
|
}
|
|
302
326
|
|
|
303
|
-
return fields
|
|
327
|
+
return fields;
|
|
304
328
|
}
|
|
305
329
|
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Document Builders
|
|
332
|
+
// ============================================================================
|
|
333
|
+
|
|
306
334
|
export function buildFindManyDocument<TSelect, TWhere>(
|
|
307
335
|
operationName: string,
|
|
308
336
|
queryField: string,
|
|
@@ -319,60 +347,44 @@ export function buildFindManyDocument<TSelect, TWhere>(
|
|
|
319
347
|
filterTypeName: string,
|
|
320
348
|
orderByTypeName: string
|
|
321
349
|
): { document: string; variables: Record<string, unknown> } {
|
|
322
|
-
const selections = select
|
|
350
|
+
const selections = select
|
|
351
|
+
? buildSelections(select as Record<string, unknown>)
|
|
352
|
+
: [t.field({ name: 'id' })];
|
|
323
353
|
|
|
324
|
-
const
|
|
325
|
-
const queryArgs:
|
|
354
|
+
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
355
|
+
const queryArgs: ArgumentNode[] = [];
|
|
326
356
|
const variables: Record<string, unknown> = {};
|
|
327
357
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
variables.before = args.before;
|
|
357
|
-
}
|
|
358
|
-
if (args.offset !== undefined) {
|
|
359
|
-
varDefs.push('$offset: Int');
|
|
360
|
-
queryArgs.push('offset: $offset');
|
|
361
|
-
variables.offset = args.offset;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const varDefsStr = varDefs.length > 0 ? \`(\${varDefs.join(', ')})\` : '';
|
|
365
|
-
const queryArgsStr = queryArgs.length > 0 ? \`(\${queryArgs.join(', ')})\` : '';
|
|
366
|
-
|
|
367
|
-
const document = \`query \${operationName}Query\${varDefsStr} {
|
|
368
|
-
\${queryField}\${queryArgsStr} {
|
|
369
|
-
nodes { \${selections} }
|
|
370
|
-
totalCount
|
|
371
|
-
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
|
|
372
|
-
}
|
|
373
|
-
}\`;
|
|
358
|
+
addVariable({ varName: 'where', argName: 'filter', typeName: filterTypeName, value: args.where }, variableDefinitions, queryArgs, variables);
|
|
359
|
+
addVariable({ varName: 'orderBy', typeName: '[' + orderByTypeName + '!]', value: args.orderBy?.length ? args.orderBy : undefined }, variableDefinitions, queryArgs, variables);
|
|
360
|
+
addVariable({ varName: 'first', typeName: 'Int', value: args.first }, variableDefinitions, queryArgs, variables);
|
|
361
|
+
addVariable({ varName: 'last', typeName: 'Int', value: args.last }, variableDefinitions, queryArgs, variables);
|
|
362
|
+
addVariable({ varName: 'after', typeName: 'Cursor', value: args.after }, variableDefinitions, queryArgs, variables);
|
|
363
|
+
addVariable({ varName: 'before', typeName: 'Cursor', value: args.before }, variableDefinitions, queryArgs, variables);
|
|
364
|
+
addVariable({ varName: 'offset', typeName: 'Int', value: args.offset }, variableDefinitions, queryArgs, variables);
|
|
365
|
+
|
|
366
|
+
const document = t.document({
|
|
367
|
+
definitions: [
|
|
368
|
+
t.operationDefinition({
|
|
369
|
+
operation: 'query',
|
|
370
|
+
name: operationName + 'Query',
|
|
371
|
+
variableDefinitions: variableDefinitions.length ? variableDefinitions : undefined,
|
|
372
|
+
selectionSet: t.selectionSet({
|
|
373
|
+
selections: [
|
|
374
|
+
t.field({
|
|
375
|
+
name: queryField,
|
|
376
|
+
args: queryArgs.length ? queryArgs : undefined,
|
|
377
|
+
selectionSet: t.selectionSet({
|
|
378
|
+
selections: buildConnectionSelections(selections),
|
|
379
|
+
}),
|
|
380
|
+
}),
|
|
381
|
+
],
|
|
382
|
+
}),
|
|
383
|
+
}),
|
|
384
|
+
],
|
|
385
|
+
});
|
|
374
386
|
|
|
375
|
-
return { document, variables };
|
|
387
|
+
return { document: print(document), variables };
|
|
376
388
|
}
|
|
377
389
|
|
|
378
390
|
export function buildFindFirstDocument<TSelect, TWhere>(
|
|
@@ -382,25 +394,45 @@ export function buildFindFirstDocument<TSelect, TWhere>(
|
|
|
382
394
|
args: { where?: TWhere },
|
|
383
395
|
filterTypeName: string
|
|
384
396
|
): { document: string; variables: Record<string, unknown> } {
|
|
385
|
-
const selections = select
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const queryArgs: string[] = ['first: $first'];
|
|
389
|
-
const variables: Record<string, unknown> = { first: 1 };
|
|
397
|
+
const selections = select
|
|
398
|
+
? buildSelections(select as Record<string, unknown>)
|
|
399
|
+
: [t.field({ name: 'id' })];
|
|
390
400
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
variables.where = args.where;
|
|
395
|
-
}
|
|
401
|
+
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
402
|
+
const queryArgs: ArgumentNode[] = [];
|
|
403
|
+
const variables: Record<string, unknown> = {};
|
|
396
404
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
405
|
+
// Always add first: 1 for findFirst
|
|
406
|
+
addVariable({ varName: 'first', typeName: 'Int', value: 1 }, variableDefinitions, queryArgs, variables);
|
|
407
|
+
addVariable({ varName: 'where', argName: 'filter', typeName: filterTypeName, value: args.where }, variableDefinitions, queryArgs, variables);
|
|
408
|
+
|
|
409
|
+
const document = t.document({
|
|
410
|
+
definitions: [
|
|
411
|
+
t.operationDefinition({
|
|
412
|
+
operation: 'query',
|
|
413
|
+
name: operationName + 'Query',
|
|
414
|
+
variableDefinitions,
|
|
415
|
+
selectionSet: t.selectionSet({
|
|
416
|
+
selections: [
|
|
417
|
+
t.field({
|
|
418
|
+
name: queryField,
|
|
419
|
+
args: queryArgs,
|
|
420
|
+
selectionSet: t.selectionSet({
|
|
421
|
+
selections: [
|
|
422
|
+
t.field({
|
|
423
|
+
name: 'nodes',
|
|
424
|
+
selectionSet: t.selectionSet({ selections }),
|
|
425
|
+
}),
|
|
426
|
+
],
|
|
427
|
+
}),
|
|
428
|
+
}),
|
|
429
|
+
],
|
|
430
|
+
}),
|
|
431
|
+
}),
|
|
432
|
+
],
|
|
433
|
+
});
|
|
402
434
|
|
|
403
|
-
return { document, variables };
|
|
435
|
+
return { document: print(document), variables };
|
|
404
436
|
}
|
|
405
437
|
|
|
406
438
|
export function buildCreateDocument<TSelect, TData>(
|
|
@@ -411,17 +443,27 @@ export function buildCreateDocument<TSelect, TData>(
|
|
|
411
443
|
data: TData,
|
|
412
444
|
inputTypeName: string
|
|
413
445
|
): { document: string; variables: Record<string, unknown> } {
|
|
414
|
-
const selections = select
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
\${mutationField}(input: $input) {
|
|
418
|
-
\${entityField} { \${selections} }
|
|
419
|
-
}
|
|
420
|
-
}\`;
|
|
446
|
+
const selections = select
|
|
447
|
+
? buildSelections(select as Record<string, unknown>)
|
|
448
|
+
: [t.field({ name: 'id' })];
|
|
421
449
|
|
|
422
450
|
return {
|
|
423
|
-
document
|
|
424
|
-
|
|
451
|
+
document: buildInputMutationDocument({
|
|
452
|
+
operationName,
|
|
453
|
+
mutationField,
|
|
454
|
+
inputTypeName,
|
|
455
|
+
resultSelections: [
|
|
456
|
+
t.field({
|
|
457
|
+
name: entityField,
|
|
458
|
+
selectionSet: t.selectionSet({ selections }),
|
|
459
|
+
}),
|
|
460
|
+
],
|
|
461
|
+
}),
|
|
462
|
+
variables: {
|
|
463
|
+
input: {
|
|
464
|
+
[entityField]: data,
|
|
465
|
+
},
|
|
466
|
+
},
|
|
425
467
|
};
|
|
426
468
|
}
|
|
427
469
|
|
|
@@ -434,17 +476,28 @@ export function buildUpdateDocument<TSelect, TWhere extends { id: string }, TDat
|
|
|
434
476
|
data: TData,
|
|
435
477
|
inputTypeName: string
|
|
436
478
|
): { document: string; variables: Record<string, unknown> } {
|
|
437
|
-
const selections = select
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
\${mutationField}(input: $input) {
|
|
441
|
-
\${entityField} { \${selections} }
|
|
442
|
-
}
|
|
443
|
-
}\`;
|
|
479
|
+
const selections = select
|
|
480
|
+
? buildSelections(select as Record<string, unknown>)
|
|
481
|
+
: [t.field({ name: 'id' })];
|
|
444
482
|
|
|
445
483
|
return {
|
|
446
|
-
document
|
|
447
|
-
|
|
484
|
+
document: buildInputMutationDocument({
|
|
485
|
+
operationName,
|
|
486
|
+
mutationField,
|
|
487
|
+
inputTypeName,
|
|
488
|
+
resultSelections: [
|
|
489
|
+
t.field({
|
|
490
|
+
name: entityField,
|
|
491
|
+
selectionSet: t.selectionSet({ selections }),
|
|
492
|
+
}),
|
|
493
|
+
],
|
|
494
|
+
}),
|
|
495
|
+
variables: {
|
|
496
|
+
input: {
|
|
497
|
+
id: where.id,
|
|
498
|
+
patch: data,
|
|
499
|
+
},
|
|
500
|
+
},
|
|
448
501
|
};
|
|
449
502
|
}
|
|
450
503
|
|
|
@@ -455,15 +508,25 @@ export function buildDeleteDocument<TWhere extends { id: string }>(
|
|
|
455
508
|
where: TWhere,
|
|
456
509
|
inputTypeName: string
|
|
457
510
|
): { document: string; variables: Record<string, unknown> } {
|
|
458
|
-
const document = \`mutation \${operationName}Mutation($input: \${inputTypeName}!) {
|
|
459
|
-
\${mutationField}(input: $input) {
|
|
460
|
-
\${entityField} { id }
|
|
461
|
-
}
|
|
462
|
-
}\`;
|
|
463
|
-
|
|
464
511
|
return {
|
|
465
|
-
document
|
|
466
|
-
|
|
512
|
+
document: buildInputMutationDocument({
|
|
513
|
+
operationName,
|
|
514
|
+
mutationField,
|
|
515
|
+
inputTypeName,
|
|
516
|
+
resultSelections: [
|
|
517
|
+
t.field({
|
|
518
|
+
name: entityField,
|
|
519
|
+
selectionSet: t.selectionSet({
|
|
520
|
+
selections: [t.field({ name: 'id' })],
|
|
521
|
+
}),
|
|
522
|
+
}),
|
|
523
|
+
],
|
|
524
|
+
}),
|
|
525
|
+
variables: {
|
|
526
|
+
input: {
|
|
527
|
+
id: where.id,
|
|
528
|
+
},
|
|
529
|
+
},
|
|
467
530
|
};
|
|
468
531
|
}
|
|
469
532
|
|
|
@@ -475,21 +538,251 @@ export function buildCustomDocument<TSelect, TArgs>(
|
|
|
475
538
|
args: TArgs,
|
|
476
539
|
variableDefinitions: Array<{ name: string; type: string }>
|
|
477
540
|
): { document: string; variables: Record<string, unknown> } {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
541
|
+
let actualSelect = select;
|
|
542
|
+
let isConnection = false;
|
|
543
|
+
|
|
544
|
+
if (select && typeof select === 'object' && 'select' in select) {
|
|
545
|
+
const wrapper = select as { select?: TSelect; connection?: boolean };
|
|
546
|
+
if (wrapper.select) {
|
|
547
|
+
actualSelect = wrapper.select;
|
|
548
|
+
isConnection = wrapper.connection === true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const selections = actualSelect
|
|
553
|
+
? buildSelections(actualSelect as Record<string, unknown>)
|
|
554
|
+
: [];
|
|
555
|
+
|
|
556
|
+
const variableDefs = variableDefinitions.map((definition) =>
|
|
557
|
+
t.variableDefinition({
|
|
558
|
+
variable: t.variable({ name: definition.name }),
|
|
559
|
+
type: parseType(definition.type),
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
const fieldArgs = variableDefinitions.map((definition) =>
|
|
563
|
+
t.argument({
|
|
564
|
+
name: definition.name,
|
|
565
|
+
value: t.variable({ name: definition.name }),
|
|
566
|
+
})
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
const fieldSelections = isConnection
|
|
570
|
+
? buildConnectionSelections(selections)
|
|
571
|
+
: selections;
|
|
572
|
+
|
|
573
|
+
const document = t.document({
|
|
574
|
+
definitions: [
|
|
575
|
+
t.operationDefinition({
|
|
576
|
+
operation: operationType,
|
|
577
|
+
name: operationName,
|
|
578
|
+
variableDefinitions: variableDefs.length ? variableDefs : undefined,
|
|
579
|
+
selectionSet: t.selectionSet({
|
|
580
|
+
selections: [
|
|
581
|
+
t.field({
|
|
582
|
+
name: fieldName,
|
|
583
|
+
args: fieldArgs.length ? fieldArgs : undefined,
|
|
584
|
+
selectionSet: fieldSelections.length
|
|
585
|
+
? t.selectionSet({ selections: fieldSelections })
|
|
586
|
+
: undefined,
|
|
587
|
+
}),
|
|
588
|
+
],
|
|
589
|
+
}),
|
|
590
|
+
}),
|
|
591
|
+
],
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
document: print(document),
|
|
596
|
+
variables: (args ?? {}) as Record<string, unknown>,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// Helper Functions
|
|
602
|
+
// ============================================================================
|
|
603
|
+
|
|
604
|
+
function buildArgs(args: Array<ArgumentNode | null>): ArgumentNode[] {
|
|
605
|
+
return args.filter((arg): arg is ArgumentNode => arg !== null);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function buildOptionalArg(
|
|
609
|
+
name: string,
|
|
610
|
+
value: number | string | undefined
|
|
611
|
+
): ArgumentNode | null {
|
|
612
|
+
if (value === undefined) {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
const valueNode =
|
|
616
|
+
typeof value === 'number'
|
|
617
|
+
? t.intValue({ value: value.toString() })
|
|
618
|
+
: t.stringValue({ value });
|
|
619
|
+
return t.argument({ name, value: valueNode });
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function buildEnumListArg(
|
|
623
|
+
name: string,
|
|
624
|
+
values: string[] | undefined
|
|
625
|
+
): ArgumentNode | null {
|
|
626
|
+
if (!values || values.length === 0) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
return t.argument({
|
|
630
|
+
name,
|
|
631
|
+
value: t.listValue({
|
|
632
|
+
values: values.map((value) => buildEnumValue(value)),
|
|
633
|
+
}),
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function buildEnumValue(value: string): EnumValueNode {
|
|
638
|
+
return {
|
|
639
|
+
kind: 'EnumValue',
|
|
640
|
+
value,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function buildPageInfoSelections(): FieldNode[] {
|
|
645
|
+
return [
|
|
646
|
+
t.field({ name: 'hasNextPage' }),
|
|
647
|
+
t.field({ name: 'hasPreviousPage' }),
|
|
648
|
+
t.field({ name: 'startCursor' }),
|
|
649
|
+
t.field({ name: 'endCursor' }),
|
|
650
|
+
];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function buildConnectionSelections(nodeSelections: FieldNode[]): FieldNode[] {
|
|
654
|
+
return [
|
|
655
|
+
t.field({
|
|
656
|
+
name: 'nodes',
|
|
657
|
+
selectionSet: t.selectionSet({ selections: nodeSelections }),
|
|
658
|
+
}),
|
|
659
|
+
t.field({ name: 'totalCount' }),
|
|
660
|
+
t.field({
|
|
661
|
+
name: 'pageInfo',
|
|
662
|
+
selectionSet: t.selectionSet({ selections: buildPageInfoSelections() }),
|
|
663
|
+
}),
|
|
664
|
+
];
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
interface VariableSpec {
|
|
668
|
+
varName: string;
|
|
669
|
+
argName?: string;
|
|
670
|
+
typeName: string;
|
|
671
|
+
value: unknown;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
interface InputMutationConfig {
|
|
675
|
+
operationName: string;
|
|
676
|
+
mutationField: string;
|
|
677
|
+
inputTypeName: string;
|
|
678
|
+
resultSelections: FieldNode[];
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function buildInputMutationDocument(config: InputMutationConfig): string {
|
|
682
|
+
const document = t.document({
|
|
683
|
+
definitions: [
|
|
684
|
+
t.operationDefinition({
|
|
685
|
+
operation: 'mutation',
|
|
686
|
+
name: config.operationName + 'Mutation',
|
|
687
|
+
variableDefinitions: [
|
|
688
|
+
t.variableDefinition({
|
|
689
|
+
variable: t.variable({ name: 'input' }),
|
|
690
|
+
type: parseType(config.inputTypeName + '!'),
|
|
691
|
+
}),
|
|
692
|
+
],
|
|
693
|
+
selectionSet: t.selectionSet({
|
|
694
|
+
selections: [
|
|
695
|
+
t.field({
|
|
696
|
+
name: config.mutationField,
|
|
697
|
+
args: [
|
|
698
|
+
t.argument({
|
|
699
|
+
name: 'input',
|
|
700
|
+
value: t.variable({ name: 'input' }),
|
|
701
|
+
}),
|
|
702
|
+
],
|
|
703
|
+
selectionSet: t.selectionSet({
|
|
704
|
+
selections: config.resultSelections,
|
|
705
|
+
}),
|
|
706
|
+
}),
|
|
707
|
+
],
|
|
708
|
+
}),
|
|
709
|
+
}),
|
|
710
|
+
],
|
|
711
|
+
});
|
|
712
|
+
return print(document);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function addVariable(
|
|
716
|
+
spec: VariableSpec,
|
|
717
|
+
definitions: VariableDefinitionNode[],
|
|
718
|
+
args: ArgumentNode[],
|
|
719
|
+
variables: Record<string, unknown>
|
|
720
|
+
): void {
|
|
721
|
+
if (spec.value === undefined) return;
|
|
722
|
+
|
|
723
|
+
definitions.push(
|
|
724
|
+
t.variableDefinition({
|
|
725
|
+
variable: t.variable({ name: spec.varName }),
|
|
726
|
+
type: parseType(spec.typeName),
|
|
727
|
+
})
|
|
728
|
+
);
|
|
729
|
+
args.push(
|
|
730
|
+
t.argument({
|
|
731
|
+
name: spec.argName ?? spec.varName,
|
|
732
|
+
value: t.variable({ name: spec.varName }),
|
|
733
|
+
})
|
|
734
|
+
);
|
|
735
|
+
variables[spec.varName] = spec.value;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function buildValueAst(
|
|
739
|
+
value: unknown
|
|
740
|
+
):
|
|
741
|
+
| ReturnType<typeof t.stringValue>
|
|
742
|
+
| ReturnType<typeof t.intValue>
|
|
743
|
+
| ReturnType<typeof t.floatValue>
|
|
744
|
+
| ReturnType<typeof t.booleanValue>
|
|
745
|
+
| ReturnType<typeof t.listValue>
|
|
746
|
+
| ReturnType<typeof t.objectValue>
|
|
747
|
+
| ReturnType<typeof t.nullValue>
|
|
748
|
+
| EnumValueNode {
|
|
749
|
+
if (value === null) {
|
|
750
|
+
return t.nullValue();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (typeof value === 'boolean') {
|
|
754
|
+
return t.booleanValue({ value });
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (typeof value === 'number') {
|
|
758
|
+
return Number.isInteger(value)
|
|
759
|
+
? t.intValue({ value: value.toString() })
|
|
760
|
+
: t.floatValue({ value: value.toString() });
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (typeof value === 'string') {
|
|
764
|
+
return t.stringValue({ value });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (Array.isArray(value)) {
|
|
768
|
+
return t.listValue({
|
|
769
|
+
values: value.map((item) => buildValueAst(item)),
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (typeof value === 'object' && value !== null) {
|
|
774
|
+
const obj = value as Record<string, unknown>;
|
|
775
|
+
return t.objectValue({
|
|
776
|
+
fields: Object.entries(obj).map(([key, val]) =>
|
|
777
|
+
t.objectField({
|
|
778
|
+
name: key,
|
|
779
|
+
value: buildValueAst(val),
|
|
780
|
+
})
|
|
781
|
+
),
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
throw new Error('Unsupported value type: ' + typeof value);
|
|
493
786
|
}
|
|
494
787
|
`;
|
|
495
788
|
return {
|
|
@@ -551,6 +844,44 @@ export interface DeleteArgs<TWhere> {
|
|
|
551
844
|
where: TWhere;
|
|
552
845
|
}
|
|
553
846
|
|
|
847
|
+
/**
|
|
848
|
+
* Recursively validates select objects, rejecting unknown keys.
|
|
849
|
+
*
|
|
850
|
+
* This type ensures that users can only select fields that actually exist
|
|
851
|
+
* in the GraphQL schema. It returns \`never\` if any excess keys are found
|
|
852
|
+
* at any nesting level, causing a TypeScript compile error.
|
|
853
|
+
*
|
|
854
|
+
* Why this is needed:
|
|
855
|
+
* TypeScript's excess property checking has a quirk where it only catches
|
|
856
|
+
* invalid fields when they are the ONLY fields. When mixed with valid fields
|
|
857
|
+
* (e.g., \`{ id: true, invalidField: true }\`), the structural typing allows
|
|
858
|
+
* the excess property through. This type explicitly checks for and rejects
|
|
859
|
+
* such cases.
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* // This will cause a type error because 'invalid' doesn't exist:
|
|
863
|
+
* type Result = DeepExact<{ id: true, invalid: true }, { id?: boolean }>;
|
|
864
|
+
* // Result = never (causes assignment error)
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* // This works because all fields are valid:
|
|
868
|
+
* type Result = DeepExact<{ id: true }, { id?: boolean; name?: boolean }>;
|
|
869
|
+
* // Result = { id: true }
|
|
870
|
+
*/
|
|
871
|
+
export type DeepExact<T, Shape> = T extends Shape
|
|
872
|
+
? Exclude<keyof T, keyof Shape> extends never
|
|
873
|
+
? {
|
|
874
|
+
[K in keyof T]: K extends keyof Shape
|
|
875
|
+
? T[K] extends { select: infer NS }
|
|
876
|
+
? Shape[K] extends { select?: infer ShapeNS }
|
|
877
|
+
? { select: DeepExact<NS, NonNullable<ShapeNS>> }
|
|
878
|
+
: T[K]
|
|
879
|
+
: T[K]
|
|
880
|
+
: never;
|
|
881
|
+
}
|
|
882
|
+
: never
|
|
883
|
+
: never;
|
|
884
|
+
|
|
554
885
|
/**
|
|
555
886
|
* Infer result type from select configuration
|
|
556
887
|
*/
|
|
@@ -616,9 +947,13 @@ function generateCreateClientFile(tables, hasCustomQueries, hasCustomMutations)
|
|
|
616
947
|
typeExportDecl.exportKind = 'type';
|
|
617
948
|
statements.push(typeExportDecl);
|
|
618
949
|
// export { GraphQLRequestError } from './client';
|
|
619
|
-
statements.push(t.exportNamedDeclaration(null, [
|
|
950
|
+
statements.push(t.exportNamedDeclaration(null, [
|
|
951
|
+
t.exportSpecifier(t.identifier('GraphQLRequestError'), t.identifier('GraphQLRequestError')),
|
|
952
|
+
], t.stringLiteral('./client')));
|
|
620
953
|
// export { QueryBuilder } from './query-builder';
|
|
621
|
-
statements.push(t.exportNamedDeclaration(null, [
|
|
954
|
+
statements.push(t.exportNamedDeclaration(null, [
|
|
955
|
+
t.exportSpecifier(t.identifier('QueryBuilder'), t.identifier('QueryBuilder')),
|
|
956
|
+
], t.stringLiteral('./query-builder')));
|
|
622
957
|
// export * from './select-types';
|
|
623
958
|
statements.push(t.exportAllDeclaration(t.stringLiteral('./select-types')));
|
|
624
959
|
// Build the return object properties
|
|
@@ -629,10 +964,14 @@ function generateCreateClientFile(tables, hasCustomQueries, hasCustomMutations)
|
|
|
629
964
|
returnProperties.push(t.objectProperty(t.identifier(singularName), t.newExpression(t.identifier(modelName), [t.identifier('client')])));
|
|
630
965
|
}
|
|
631
966
|
if (hasCustomQueries) {
|
|
632
|
-
returnProperties.push(t.objectProperty(t.identifier('query'), t.callExpression(t.identifier('createQueryOperations'), [
|
|
967
|
+
returnProperties.push(t.objectProperty(t.identifier('query'), t.callExpression(t.identifier('createQueryOperations'), [
|
|
968
|
+
t.identifier('client'),
|
|
969
|
+
])));
|
|
633
970
|
}
|
|
634
971
|
if (hasCustomMutations) {
|
|
635
|
-
returnProperties.push(t.objectProperty(t.identifier('mutation'), t.callExpression(t.identifier('createMutationOperations'), [
|
|
972
|
+
returnProperties.push(t.objectProperty(t.identifier('mutation'), t.callExpression(t.identifier('createMutationOperations'), [
|
|
973
|
+
t.identifier('client'),
|
|
974
|
+
])));
|
|
636
975
|
}
|
|
637
976
|
// Build the createClient function body
|
|
638
977
|
const clientDecl = t.variableDeclaration('const', [
|