@constructive-io/graphql-codegen 2.27.0 → 2.27.2
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/cli/codegen/orm/client-generator.d.ts +3 -0
- package/cli/codegen/orm/client-generator.js +35 -614
- package/cli/codegen/orm/client.d.ts +44 -0
- package/cli/codegen/orm/client.js +73 -0
- package/cli/codegen/orm/query-builder.d.ts +85 -0
- package/cli/codegen/orm/query-builder.js +485 -0
- package/esm/cli/codegen/orm/client-generator.d.ts +3 -0
- package/esm/cli/codegen/orm/client-generator.js +35 -614
- package/esm/cli/codegen/orm/client.d.ts +44 -0
- package/esm/cli/codegen/orm/client.js +68 -0
- package/esm/cli/codegen/orm/query-builder.d.ts +85 -0
- package/esm/cli/codegen/orm/query-builder.js +441 -0
- package/package.json +7 -7
|
@@ -15,6 +15,9 @@ export interface GeneratedClientFile {
|
|
|
15
15
|
export declare function generateOrmClientFile(): GeneratedClientFile;
|
|
16
16
|
/**
|
|
17
17
|
* Generate the query-builder.ts file (runtime query builder)
|
|
18
|
+
*
|
|
19
|
+
* Reads from the actual TypeScript file in the source directory,
|
|
20
|
+
* which enables proper type checking and testability.
|
|
18
21
|
*/
|
|
19
22
|
export declare function generateQueryBuilderFile(): GeneratedClientFile;
|
|
20
23
|
/**
|
|
@@ -40,6 +40,8 @@ exports.generateCreateClientFile = generateCreateClientFile;
|
|
|
40
40
|
const t = __importStar(require("@babel/types"));
|
|
41
41
|
const babel_ast_1 = require("../babel-ast");
|
|
42
42
|
const utils_1 = require("../utils");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
43
45
|
/**
|
|
44
46
|
* Generate the main client.ts file (OrmClient class)
|
|
45
47
|
* This is the runtime client that handles GraphQL execution
|
|
@@ -167,627 +169,46 @@ export class OrmClient {
|
|
|
167
169
|
}
|
|
168
170
|
/**
|
|
169
171
|
* Generate the query-builder.ts file (runtime query builder)
|
|
172
|
+
*
|
|
173
|
+
* Reads from the actual TypeScript file in the source directory,
|
|
174
|
+
* which enables proper type checking and testability.
|
|
170
175
|
*/
|
|
171
176
|
function generateQueryBuilderFile() {
|
|
172
|
-
|
|
177
|
+
// Read the query-builder.ts source file
|
|
178
|
+
// Handle both development (src/) and production (dist/) scenarios
|
|
179
|
+
let sourceFilePath = path.join(__dirname, 'query-builder.ts');
|
|
180
|
+
// If running from dist/, look for the source in src/ instead
|
|
181
|
+
if (!fs.existsSync(sourceFilePath)) {
|
|
182
|
+
// Navigate from dist/cli/codegen/orm/ to src/cli/codegen/orm/
|
|
183
|
+
sourceFilePath = path.resolve(__dirname, '../../../../src/cli/codegen/orm/query-builder.ts');
|
|
184
|
+
}
|
|
185
|
+
// If still not found, try relative to package root
|
|
186
|
+
if (!fs.existsSync(sourceFilePath)) {
|
|
187
|
+
// For installed packages, the file should be adjacent in the same dir
|
|
188
|
+
throw new Error(`Could not find query-builder.ts source file. ` +
|
|
189
|
+
`Searched in: ${path.join(__dirname, 'query-builder.ts')} and ` +
|
|
190
|
+
`${path.resolve(__dirname, '../../../../src/cli/codegen/orm/query-builder.ts')}`);
|
|
191
|
+
}
|
|
192
|
+
let sourceContent = fs.readFileSync(sourceFilePath, 'utf-8');
|
|
193
|
+
// Replace the source file header comment with the generated file header
|
|
194
|
+
const headerComment = `/**
|
|
195
|
+
* Query Builder - Builds and executes GraphQL operations
|
|
196
|
+
*
|
|
197
|
+
* This is the RUNTIME code that gets copied to generated output.
|
|
198
|
+
* It uses gql-ast to build GraphQL documents programmatically.
|
|
199
|
+
*
|
|
200
|
+
* NOTE: This file is read at codegen time and written to output.
|
|
201
|
+
* Any changes here will affect all generated ORM clients.
|
|
202
|
+
*/`;
|
|
203
|
+
const generatedHeader = `/**
|
|
173
204
|
* Query Builder - Builds and executes GraphQL operations
|
|
174
205
|
* @generated by @constructive-io/graphql-codegen
|
|
175
206
|
* DO NOT EDIT - changes will be overwritten
|
|
176
|
-
|
|
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';
|
|
186
|
-
import { OrmClient, QueryResult, GraphQLRequestError } from './client';
|
|
187
|
-
|
|
188
|
-
export interface QueryBuilderConfig {
|
|
189
|
-
client: OrmClient;
|
|
190
|
-
operation: 'query' | 'mutation';
|
|
191
|
-
operationName: string;
|
|
192
|
-
fieldName: string;
|
|
193
|
-
document: string;
|
|
194
|
-
variables?: Record<string, unknown>;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export class QueryBuilder<TResult> {
|
|
198
|
-
private config: QueryBuilderConfig;
|
|
199
|
-
|
|
200
|
-
constructor(config: QueryBuilderConfig) {
|
|
201
|
-
this.config = config;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Execute the query and return a discriminated union result
|
|
206
|
-
* Use result.ok to check success, or .unwrap() to throw on error
|
|
207
|
-
*/
|
|
208
|
-
async execute(): Promise<QueryResult<TResult>> {
|
|
209
|
-
return this.config.client.execute<TResult>(
|
|
210
|
-
this.config.document,
|
|
211
|
-
this.config.variables
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Execute and unwrap the result, throwing GraphQLRequestError on failure
|
|
217
|
-
* @throws {GraphQLRequestError} If the query returns errors
|
|
218
|
-
*/
|
|
219
|
-
async unwrap(): Promise<TResult> {
|
|
220
|
-
const result = await this.execute();
|
|
221
|
-
if (!result.ok) {
|
|
222
|
-
throw new GraphQLRequestError(result.errors, result.data);
|
|
223
|
-
}
|
|
224
|
-
return result.data;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Execute and unwrap, returning defaultValue on error instead of throwing
|
|
229
|
-
*/
|
|
230
|
-
async unwrapOr<D>(defaultValue: D): Promise<TResult | D> {
|
|
231
|
-
const result = await this.execute();
|
|
232
|
-
if (!result.ok) {
|
|
233
|
-
return defaultValue;
|
|
234
|
-
}
|
|
235
|
-
return result.data;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Execute and unwrap, calling onError callback on failure
|
|
240
|
-
*/
|
|
241
|
-
async unwrapOrElse<D>(onError: (errors: import('./client').GraphQLError[]) => D): Promise<TResult | D> {
|
|
242
|
-
const result = await this.execute();
|
|
243
|
-
if (!result.ok) {
|
|
244
|
-
return onError(result.errors);
|
|
245
|
-
}
|
|
246
|
-
return result.data;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
toGraphQL(): string {
|
|
250
|
-
return this.config.document;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
getVariables(): Record<string, unknown> | undefined {
|
|
254
|
-
return this.config.variables;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ============================================================================
|
|
259
|
-
// Selection Builders
|
|
260
|
-
// ============================================================================
|
|
261
|
-
|
|
262
|
-
export function buildSelections(
|
|
263
|
-
select: Record<string, unknown> | undefined
|
|
264
|
-
): FieldNode[] {
|
|
265
|
-
if (!select) {
|
|
266
|
-
return [];
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const fields: FieldNode[] = [];
|
|
270
|
-
|
|
271
|
-
for (const [key, value] of Object.entries(select)) {
|
|
272
|
-
if (value === false || value === undefined) {
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (value === true) {
|
|
277
|
-
fields.push(t.field({ name: key }));
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (typeof value === 'object' && value !== null) {
|
|
282
|
-
const nested = value as {
|
|
283
|
-
select?: Record<string, unknown>;
|
|
284
|
-
first?: number;
|
|
285
|
-
filter?: Record<string, unknown>;
|
|
286
|
-
orderBy?: string[];
|
|
287
|
-
connection?: boolean;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
if (nested.select) {
|
|
291
|
-
const nestedSelections = buildSelections(nested.select);
|
|
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
|
-
|
|
304
|
-
if (isConnection) {
|
|
305
|
-
fields.push(
|
|
306
|
-
t.field({
|
|
307
|
-
name: key,
|
|
308
|
-
args,
|
|
309
|
-
selectionSet: t.selectionSet({
|
|
310
|
-
selections: buildConnectionSelections(nestedSelections),
|
|
311
|
-
}),
|
|
312
|
-
})
|
|
313
|
-
);
|
|
314
|
-
} else {
|
|
315
|
-
fields.push(
|
|
316
|
-
t.field({
|
|
317
|
-
name: key,
|
|
318
|
-
args,
|
|
319
|
-
selectionSet: t.selectionSet({ selections: nestedSelections }),
|
|
320
|
-
})
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return fields;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ============================================================================
|
|
331
|
-
// Document Builders
|
|
332
|
-
// ============================================================================
|
|
333
|
-
|
|
334
|
-
export function buildFindManyDocument<TSelect, TWhere>(
|
|
335
|
-
operationName: string,
|
|
336
|
-
queryField: string,
|
|
337
|
-
select: TSelect,
|
|
338
|
-
args: {
|
|
339
|
-
where?: TWhere;
|
|
340
|
-
orderBy?: string[];
|
|
341
|
-
first?: number;
|
|
342
|
-
last?: number;
|
|
343
|
-
after?: string;
|
|
344
|
-
before?: string;
|
|
345
|
-
offset?: number;
|
|
346
|
-
},
|
|
347
|
-
filterTypeName: string,
|
|
348
|
-
orderByTypeName: string
|
|
349
|
-
): { document: string; variables: Record<string, unknown> } {
|
|
350
|
-
const selections = select
|
|
351
|
-
? buildSelections(select as Record<string, unknown>)
|
|
352
|
-
: [t.field({ name: 'id' })];
|
|
353
|
-
|
|
354
|
-
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
355
|
-
const queryArgs: ArgumentNode[] = [];
|
|
356
|
-
const variables: Record<string, unknown> = {};
|
|
357
|
-
|
|
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
|
-
});
|
|
386
|
-
|
|
387
|
-
return { document: print(document), variables };
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export function buildFindFirstDocument<TSelect, TWhere>(
|
|
391
|
-
operationName: string,
|
|
392
|
-
queryField: string,
|
|
393
|
-
select: TSelect,
|
|
394
|
-
args: { where?: TWhere },
|
|
395
|
-
filterTypeName: string
|
|
396
|
-
): { document: string; variables: Record<string, unknown> } {
|
|
397
|
-
const selections = select
|
|
398
|
-
? buildSelections(select as Record<string, unknown>)
|
|
399
|
-
: [t.field({ name: 'id' })];
|
|
400
|
-
|
|
401
|
-
const variableDefinitions: VariableDefinitionNode[] = [];
|
|
402
|
-
const queryArgs: ArgumentNode[] = [];
|
|
403
|
-
const variables: Record<string, unknown> = {};
|
|
404
|
-
|
|
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
|
-
});
|
|
434
|
-
|
|
435
|
-
return { document: print(document), variables };
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
export function buildCreateDocument<TSelect, TData>(
|
|
439
|
-
operationName: string,
|
|
440
|
-
mutationField: string,
|
|
441
|
-
entityField: string,
|
|
442
|
-
select: TSelect,
|
|
443
|
-
data: TData,
|
|
444
|
-
inputTypeName: string
|
|
445
|
-
): { document: string; variables: Record<string, unknown> } {
|
|
446
|
-
const selections = select
|
|
447
|
-
? buildSelections(select as Record<string, unknown>)
|
|
448
|
-
: [t.field({ name: 'id' })];
|
|
449
|
-
|
|
450
|
-
return {
|
|
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
|
-
},
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
export function buildUpdateDocument<TSelect, TWhere extends { id: string }, TData>(
|
|
471
|
-
operationName: string,
|
|
472
|
-
mutationField: string,
|
|
473
|
-
entityField: string,
|
|
474
|
-
select: TSelect,
|
|
475
|
-
where: TWhere,
|
|
476
|
-
data: TData,
|
|
477
|
-
inputTypeName: string
|
|
478
|
-
): { document: string; variables: Record<string, unknown> } {
|
|
479
|
-
const selections = select
|
|
480
|
-
? buildSelections(select as Record<string, unknown>)
|
|
481
|
-
: [t.field({ name: 'id' })];
|
|
482
|
-
|
|
483
|
-
return {
|
|
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
|
-
},
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
export function buildDeleteDocument<TWhere extends { id: string }>(
|
|
505
|
-
operationName: string,
|
|
506
|
-
mutationField: string,
|
|
507
|
-
entityField: string,
|
|
508
|
-
where: TWhere,
|
|
509
|
-
inputTypeName: string
|
|
510
|
-
): { document: string; variables: Record<string, unknown> } {
|
|
511
|
-
return {
|
|
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
|
-
},
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
export function buildCustomDocument<TSelect, TArgs>(
|
|
534
|
-
operationType: 'query' | 'mutation',
|
|
535
|
-
operationName: string,
|
|
536
|
-
fieldName: string,
|
|
537
|
-
select: TSelect,
|
|
538
|
-
args: TArgs,
|
|
539
|
-
variableDefinitions: Array<{ name: string; type: string }>
|
|
540
|
-
): { document: string; variables: Record<string, unknown> } {
|
|
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);
|
|
786
|
-
}
|
|
787
|
-
`;
|
|
207
|
+
*/`;
|
|
208
|
+
sourceContent = sourceContent.replace(headerComment, generatedHeader);
|
|
788
209
|
return {
|
|
789
210
|
fileName: 'query-builder.ts',
|
|
790
|
-
content,
|
|
211
|
+
content: sourceContent,
|
|
791
212
|
};
|
|
792
213
|
}
|
|
793
214
|
/**
|