@constructive-io/graphql-codegen 4.40.6 → 4.41.1

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.
Files changed (45) hide show
  1. package/core/codegen/cli/executor-generator.d.ts +2 -6
  2. package/core/codegen/cli/executor-generator.js +12 -48
  3. package/core/codegen/cli/index.d.ts +0 -2
  4. package/core/codegen/cli/index.js +2 -16
  5. package/core/codegen/cli/table-command-generator.js +141 -2
  6. package/core/codegen/cli/utils-generator.d.ts +0 -8
  7. package/core/codegen/cli/utils-generator.js +0 -14
  8. package/core/codegen/mutation-keys.js +18 -0
  9. package/core/codegen/mutations.js +187 -0
  10. package/core/codegen/orm/client-generator.d.ts +1 -3
  11. package/core/codegen/orm/client-generator.js +1 -7
  12. package/core/codegen/orm/index.js +1 -1
  13. package/core/codegen/orm/model-generator.js +167 -5
  14. package/core/codegen/orm/select-types.d.ts +2 -1
  15. package/core/codegen/queries.js +1 -1
  16. package/core/codegen/templates/cli-utils.ts +4 -2
  17. package/core/codegen/templates/query-builder.ts +170 -1
  18. package/core/codegen/templates/select-types.ts +30 -1
  19. package/core/codegen/utils.d.ts +8 -0
  20. package/core/codegen/utils.js +39 -0
  21. package/core/generate.js +2 -19
  22. package/esm/core/codegen/cli/executor-generator.d.ts +2 -6
  23. package/esm/core/codegen/cli/executor-generator.js +12 -48
  24. package/esm/core/codegen/cli/index.d.ts +0 -2
  25. package/esm/core/codegen/cli/index.js +3 -17
  26. package/esm/core/codegen/cli/table-command-generator.js +141 -2
  27. package/esm/core/codegen/cli/utils-generator.d.ts +0 -8
  28. package/esm/core/codegen/cli/utils-generator.js +0 -13
  29. package/esm/core/codegen/mutation-keys.js +18 -0
  30. package/esm/core/codegen/mutations.js +188 -1
  31. package/esm/core/codegen/orm/client-generator.d.ts +1 -3
  32. package/esm/core/codegen/orm/client-generator.js +1 -7
  33. package/esm/core/codegen/orm/index.js +1 -1
  34. package/esm/core/codegen/orm/model-generator.js +168 -6
  35. package/esm/core/codegen/orm/select-types.d.ts +2 -1
  36. package/esm/core/codegen/queries.js +1 -1
  37. package/esm/core/codegen/utils.d.ts +8 -0
  38. package/esm/core/codegen/utils.js +31 -0
  39. package/esm/core/generate.js +2 -19
  40. package/esm/types/config.d.ts +0 -18
  41. package/esm/types/schema.d.ts +8 -0
  42. package/package.json +3 -3
  43. package/types/config.d.ts +0 -18
  44. package/types/schema.d.ts +8 -0
  45. package/core/codegen/templates/node-fetch.ts +0 -198
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import * as t from '@babel/types';
11
11
  import { addJSDocComment, buildSelectionArgsCall, callExpr, constDecl, createFunctionParam, createImportDeclaration, createSTypeParam, createTypeReExport, destructureParamsWithSelection, exportDeclareFunction, exportFunction, generateHookFileCode, getClientCallUnwrap, inferSelectResultType, objectProp, omitType, returnUseMutation, selectionConfigType, shorthandProp, spreadObj, sRef, typeRef, typeLiteralWithProps, useMutationOptionsType, useMutationResultType, voidStatement, } from './hooks-ast';
12
- import { getCreateMutationFileName, getCreateMutationHookName, getCreateMutationName, getDeleteMutationFileName, getDeleteMutationHookName, getDeleteMutationName, getPrimaryKeyInfo, getTableNames, getUpdateMutationFileName, getUpdateMutationHookName, getUpdateMutationName, hasValidPrimaryKey, lcFirst, } from './utils';
12
+ import { getBulkCreateMutationFileName, getBulkCreateMutationHookName, getBulkDeleteMutationFileName, getBulkDeleteMutationHookName, getBulkUpdateMutationFileName, getBulkUpdateMutationHookName, getBulkUpsertMutationFileName, getBulkUpsertMutationHookName, getCreateMutationFileName, getCreateMutationHookName, getCreateMutationName, getDeleteMutationFileName, getDeleteMutationHookName, getDeleteMutationName, getPrimaryKeyInfo, getTableNames, getUpdateMutationFileName, getUpdateMutationHookName, getUpdateMutationName, hasValidPrimaryKey, lcFirst, } from './utils';
13
13
  function buildMutationResultType(mutationName, singularName, relationTypeName, selectType) {
14
14
  return typeLiteralWithProps([
15
15
  {
@@ -377,6 +377,185 @@ export function generateDeleteMutationHook(table, options = {}) {
377
377
  content: generateHookFileCode(table.description || `Delete mutation hook for ${typeName}`, statements),
378
378
  };
379
379
  }
380
+ function generateBulkMutationHook(table, op, options = {}) {
381
+ const { reactQueryEnabled = true, useCentralizedKeys = true } = options;
382
+ if (!reactQueryEnabled)
383
+ return null;
384
+ const mutationFieldName = (() => {
385
+ switch (op) {
386
+ case 'bulkCreate': return table.query?.bulkInsert;
387
+ case 'bulkUpsert': return table.query?.bulkUpsert;
388
+ case 'bulkUpdate': return table.query?.bulkUpdate;
389
+ case 'bulkDelete': return table.query?.bulkDelete;
390
+ }
391
+ })();
392
+ if (!mutationFieldName)
393
+ return null;
394
+ const { typeName, singularName } = getTableNames(table);
395
+ const hookName = (() => {
396
+ switch (op) {
397
+ case 'bulkCreate': return getBulkCreateMutationHookName(table);
398
+ case 'bulkUpsert': return getBulkUpsertMutationHookName(table);
399
+ case 'bulkUpdate': return getBulkUpdateMutationHookName(table);
400
+ case 'bulkDelete': return getBulkDeleteMutationHookName(table);
401
+ }
402
+ })();
403
+ const fileName = (() => {
404
+ switch (op) {
405
+ case 'bulkCreate': return getBulkCreateMutationFileName(table);
406
+ case 'bulkUpsert': return getBulkUpsertMutationFileName(table);
407
+ case 'bulkUpdate': return getBulkUpdateMutationFileName(table);
408
+ case 'bulkDelete': return getBulkDeleteMutationFileName(table);
409
+ }
410
+ })();
411
+ const keysName = `${lcFirst(typeName)}Keys`;
412
+ const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
413
+ const selectTypeName = `${typeName}Select`;
414
+ const relationTypeName = `${typeName}WithRelations`;
415
+ const createInputTypeName = `Create${typeName}Input`;
416
+ const patchTypeName = `${typeName}Patch`;
417
+ const filterTypeName = `${typeName}Filter`;
418
+ const statements = [];
419
+ // Imports
420
+ statements.push(createImportDeclaration('@tanstack/react-query', [
421
+ 'useMutation',
422
+ 'useQueryClient',
423
+ ]));
424
+ statements.push(createImportDeclaration('@tanstack/react-query', ['UseMutationOptions', 'UseMutationResult'], true));
425
+ statements.push(createImportDeclaration('../client', ['getClient']));
426
+ statements.push(createImportDeclaration('../selection', ['buildSelectionArgs']));
427
+ statements.push(createImportDeclaration('../selection', ['SelectionConfig'], true));
428
+ if (useCentralizedKeys) {
429
+ statements.push(createImportDeclaration('../query-keys', [keysName]));
430
+ statements.push(createImportDeclaration('../mutation-keys', [mutationKeysName]));
431
+ }
432
+ // Determine which types to import
433
+ const typeImports = [selectTypeName, relationTypeName];
434
+ if (op === 'bulkCreate' || op === 'bulkUpsert') {
435
+ typeImports.push(createInputTypeName);
436
+ }
437
+ if (op === 'bulkUpdate') {
438
+ typeImports.push(patchTypeName);
439
+ typeImports.push(filterTypeName);
440
+ }
441
+ if (op === 'bulkDelete') {
442
+ typeImports.push(filterTypeName);
443
+ }
444
+ statements.push(createImportDeclaration('../../orm/input-types', typeImports, true));
445
+ statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'BulkMutationResult', 'HookStrictSelect'], true));
446
+ // Re-exports
447
+ statements.push(createTypeReExport(typeImports, '../../orm/input-types'));
448
+ // Build the variable type for the mutationFn parameter
449
+ const varType = (() => {
450
+ switch (op) {
451
+ case 'bulkCreate':
452
+ return t.tsTypeLiteral([
453
+ t.tsPropertySignature(t.identifier('data'), t.tsTypeAnnotation(t.tsArrayType(t.tsIndexedAccessType(typeRef(createInputTypeName), t.tsLiteralType(t.stringLiteral(singularName)))))),
454
+ (() => {
455
+ const p = t.tsPropertySignature(t.identifier('onConflict'), t.tsTypeAnnotation(t.tsUnknownKeyword()));
456
+ p.optional = true;
457
+ return p;
458
+ })(),
459
+ ]);
460
+ case 'bulkUpsert':
461
+ return t.tsTypeLiteral([
462
+ t.tsPropertySignature(t.identifier('data'), t.tsTypeAnnotation(t.tsArrayType(t.tsIndexedAccessType(typeRef(createInputTypeName), t.tsLiteralType(t.stringLiteral(singularName)))))),
463
+ t.tsPropertySignature(t.identifier('onConflict'), t.tsTypeAnnotation(t.tsUnknownKeyword())),
464
+ ]);
465
+ case 'bulkUpdate':
466
+ return t.tsTypeLiteral([
467
+ t.tsPropertySignature(t.identifier('where'), t.tsTypeAnnotation(typeRef(filterTypeName))),
468
+ t.tsPropertySignature(t.identifier('data'), t.tsTypeAnnotation(typeRef(patchTypeName))),
469
+ ]);
470
+ case 'bulkDelete':
471
+ return t.tsTypeLiteral([
472
+ t.tsPropertySignature(t.identifier('where'), t.tsTypeAnnotation(typeRef(filterTypeName))),
473
+ ]);
474
+ }
475
+ })();
476
+ // Result type: BulkMutationResult<InferSelectResult<Relation, S>>
477
+ const bulkResultType = (sel) => typeRef('BulkMutationResult', [inferSelectResultType(relationTypeName, sel)]);
478
+ // Overload with fields
479
+ const o1ParamType = t.tsIntersectionType([
480
+ t.tsTypeLiteral([
481
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(buildFieldsSelectionType(sRef(), selectTypeName))),
482
+ ]),
483
+ useMutationOptionsType(bulkResultType(sRef()), varType),
484
+ ]);
485
+ const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(bulkResultType(sRef()), varType));
486
+ addJSDocComment(o1, [
487
+ table.description || `Bulk ${op.replace('bulk', '').toLowerCase()} mutation hook for ${typeName}`,
488
+ ]);
489
+ statements.push(o1);
490
+ // Implementation
491
+ const implSelProp = t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName))));
492
+ const implParamType = t.tsIntersectionType([
493
+ t.tsTypeLiteral([implSelProp]),
494
+ omitType(typeRef('UseMutationOptions', [
495
+ t.tsAnyKeyword(),
496
+ typeRef('Error'),
497
+ varType,
498
+ ]), ['mutationFn']),
499
+ ]);
500
+ const body = [];
501
+ body.push(buildSelectionArgsCall(selectTypeName));
502
+ body.push(destructureParamsWithSelection('mutationOptions'));
503
+ body.push(voidStatement('_selection'));
504
+ body.push(constDecl('queryClient', callExpr('useQueryClient', [])));
505
+ const mutationKeyExpr = useCentralizedKeys
506
+ ? callExpr(t.memberExpression(t.identifier(mutationKeysName), t.identifier(op)), [])
507
+ : undefined;
508
+ // Build the ORM method call depending on the operation
509
+ const ormMethodName = op;
510
+ const mutationFnArgs = (() => {
511
+ switch (op) {
512
+ case 'bulkCreate':
513
+ return t.objectExpression([
514
+ shorthandProp('data'),
515
+ objectProp('onConflict', t.memberExpression(t.identifier('vars'), t.identifier('onConflict'))),
516
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
517
+ ]);
518
+ case 'bulkUpsert':
519
+ return t.objectExpression([
520
+ shorthandProp('data'),
521
+ objectProp('onConflict', t.memberExpression(t.identifier('vars'), t.identifier('onConflict'))),
522
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
523
+ ]);
524
+ case 'bulkUpdate':
525
+ return t.objectExpression([
526
+ objectProp('where', t.memberExpression(t.identifier('vars'), t.identifier('where'))),
527
+ shorthandProp('data'),
528
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
529
+ ]);
530
+ case 'bulkDelete':
531
+ return t.objectExpression([
532
+ objectProp('where', t.memberExpression(t.identifier('vars'), t.identifier('where'))),
533
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
534
+ ]);
535
+ }
536
+ })();
537
+ const varsParam = createFunctionParam('vars', varType);
538
+ const mutationFnExpr = t.arrowFunctionExpression([varsParam], getClientCallUnwrap(singularName, ormMethodName, mutationFnArgs));
539
+ // onSuccess: invalidate lists
540
+ const listKeyExpr = useCentralizedKeys
541
+ ? callExpr(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
542
+ : t.arrayExpression([
543
+ t.stringLiteral(typeName.toLowerCase()),
544
+ t.stringLiteral('list'),
545
+ ]);
546
+ const onSuccessFn = t.arrowFunctionExpression([], t.blockStatement([
547
+ t.expressionStatement(callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([objectProp('queryKey', listKeyExpr)])])),
548
+ ]));
549
+ body.push(returnUseMutation(mutationFnExpr, [
550
+ objectProp('onSuccess', onSuccessFn),
551
+ spreadObj(t.identifier('mutationOptions')),
552
+ ], mutationKeyExpr));
553
+ statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
554
+ return {
555
+ fileName,
556
+ content: generateHookFileCode(table.description || `Bulk ${op.replace('bulk', '').toLowerCase()} mutation hook for ${typeName}`, statements),
557
+ };
558
+ }
380
559
  export function generateAllMutationHooks(tables, options = {}) {
381
560
  const files = [];
382
561
  for (const table of tables) {
@@ -392,6 +571,14 @@ export function generateAllMutationHooks(tables, options = {}) {
392
571
  if (deleteHook) {
393
572
  files.push(deleteHook);
394
573
  }
574
+ // Bulk mutation hooks
575
+ const bulkOps = ['bulkCreate', 'bulkUpsert', 'bulkUpdate', 'bulkDelete'];
576
+ for (const op of bulkOps) {
577
+ const hook = generateBulkMutationHook(table, op, options);
578
+ if (hook) {
579
+ files.push(hook);
580
+ }
581
+ }
395
582
  }
396
583
  return files;
397
584
  }
@@ -31,6 +31,4 @@ export declare function generateSelectTypesFile(): GeneratedClientFile;
31
31
  /**
32
32
  * Generate the main index.ts with createClient factory
33
33
  */
34
- export declare function generateCreateClientFile(tables: Table[], hasCustomQueries: boolean, hasCustomMutations: boolean, options?: {
35
- nodeHttpAdapter?: boolean;
36
- }): GeneratedClientFile;
34
+ export declare function generateCreateClientFile(tables: Table[], hasCustomQueries: boolean, hasCustomMutations: boolean): GeneratedClientFile;
@@ -86,7 +86,7 @@ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false
86
86
  /**
87
87
  * Generate the main index.ts with createClient factory
88
88
  */
89
- export function generateCreateClientFile(tables, hasCustomQueries, hasCustomMutations, options) {
89
+ export function generateCreateClientFile(tables, hasCustomQueries, hasCustomMutations) {
90
90
  const statements = [];
91
91
  // Add imports
92
92
  // Import OrmClient (value) and OrmClientConfig (type) separately
@@ -128,12 +128,6 @@ export function generateCreateClientFile(tables, hasCustomQueries, hasCustomMuta
128
128
  statements.push(t.exportAllDeclaration(t.stringLiteral('./select-types')));
129
129
  // Re-export all models
130
130
  statements.push(t.exportAllDeclaration(t.stringLiteral('./models')));
131
- // Re-export NodeHttpAdapter when enabled (for use in any Node.js application)
132
- if (options?.nodeHttpAdapter) {
133
- statements.push(t.exportNamedDeclaration(null, [
134
- t.exportSpecifier(t.identifier('NodeHttpAdapter'), t.identifier('NodeHttpAdapter')),
135
- ], t.stringLiteral('./node-fetch')));
136
- }
137
131
  // Re-export custom operations
138
132
  if (hasCustomQueries) {
139
133
  statements.push(t.exportNamedDeclaration(null, [
@@ -90,7 +90,7 @@ export function generateOrm(options) {
90
90
  const typesBarrel = generateTypesBarrel(useSharedTypes);
91
91
  files.push({ path: typesBarrel.fileName, content: typesBarrel.content });
92
92
  // 7. Generate main index.ts with createClient
93
- const indexFile = generateCreateClientFile(tables, hasCustomQueries, hasCustomMutations, { nodeHttpAdapter: !!options.config.nodeHttpAdapter });
93
+ const indexFile = generateCreateClientFile(tables, hasCustomQueries, hasCustomMutations);
94
94
  files.push({ path: indexFile.fileName, content: indexFile.content });
95
95
  return {
96
96
  files,
@@ -7,7 +7,7 @@
7
7
  import * as t from '@babel/types';
8
8
  import { singularize } from 'inflekt';
9
9
  import { generateCode } from '../babel-ast';
10
- import { getCreateInputTypeName, getCreateMutationName, getDeleteInputTypeName, getDeleteMutationName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getSingleRowQueryName, getTableNames, hasValidPrimaryKey, lcFirst, ucFirst, } from '../utils';
10
+ import { getCreateInputTypeName, getCreateMutationName, getDeleteInputTypeName, getDeleteMutationName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, hasValidPrimaryKey, lcFirst, ucFirst, } from '../utils';
11
11
  function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false) {
12
12
  const specifiers = namedImports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
13
13
  const decl = t.importDeclaration(specifiers, t.stringLiteral(moduleSpecifier));
@@ -83,7 +83,10 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
83
83
  const pkField = pkFields[0];
84
84
  const pluralQueryName = table.query?.all ?? pluralName;
85
85
  const singleQueryName = table.query?.one;
86
- const singleResultFieldName = getSingleRowQueryName(table);
86
+ // The unwrapped result key for findFirst/findOne — must be the friendly
87
+ // singular noun (e.g. "animal"), NOT the GraphQL by-id query name (e.g.
88
+ // "animalById"), so the surface aligns with the rest of the SDK.
89
+ const singleResultFieldName = singularName;
87
90
  const createMutationName = table.query?.create ?? `create${typeName}`;
88
91
  const updateMutationName = table.query?.update;
89
92
  const deleteMutationName = table.query?.delete;
@@ -96,6 +99,12 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
96
99
  const jt = allTables?.find((tb) => tb.name === r.junctionTable);
97
100
  return jt?.query?.delete != null;
98
101
  });
102
+ // Detect which bulk mutations are available for this table
103
+ const bulkInsertMutationName = table.query?.bulkInsert ?? null;
104
+ const bulkUpsertMutationName = table.query?.bulkUpsert ?? null;
105
+ const bulkUpdateMutationName = table.query?.bulkUpdate ?? null;
106
+ const bulkDeleteMutationName = table.query?.bulkDelete ?? null;
107
+ const hasBulk = !!(bulkInsertMutationName || bulkUpsertMutationName || bulkUpdateMutationName || bulkDeleteMutationName);
99
108
  const queryBuilderImports = [
100
109
  'QueryBuilder',
101
110
  'buildFindManyDocument',
@@ -105,6 +114,10 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
105
114
  'buildUpdateByPkDocument',
106
115
  'buildDeleteByPkDocument',
107
116
  ...(needsJunctionRemove ? ['buildJunctionRemoveDocument'] : []),
117
+ ...(bulkInsertMutationName ? ['buildBulkInsertDocument'] : []),
118
+ ...(bulkUpsertMutationName ? ['buildBulkUpsertDocument'] : []),
119
+ ...(bulkUpdateMutationName ? ['buildBulkUpdateDocument'] : []),
120
+ ...(bulkDeleteMutationName ? ['buildBulkDeleteDocument'] : []),
108
121
  ];
109
122
  statements.push(createImportDeclaration('../query-builder', queryBuilderImports));
110
123
  statements.push(createImportDeclaration('../select-types', [
@@ -114,6 +127,7 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
114
127
  'CreateArgs',
115
128
  'UpdateArgs',
116
129
  'DeleteArgs',
130
+ ...(hasBulk ? ['BulkInsertArgs', 'BulkUpsertArgs', 'BulkUpdateArgs', 'BulkDeleteArgs', 'BulkMutationResult'] : []),
117
131
  'InferSelectResult',
118
132
  'StrictSelect',
119
133
  ], true));
@@ -192,15 +206,17 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
192
206
  const findFirstTypeArgs = [
193
207
  (sel) => sel,
194
208
  () => t.tsTypeReference(t.identifier(whereTypeName)),
209
+ () => t.tsTypeReference(t.identifier(orderByTypeName)),
195
210
  ];
196
211
  const argsType = (sel) => t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation(findFirstTypeArgs.map(fn => fn(sel))));
197
212
  const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
198
213
  t.tsTypeLiteral([
199
- t.tsPropertySignature(t.identifier(pluralQueryName), t.tsTypeAnnotation(t.tsTypeLiteral([
200
- t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
214
+ t.tsPropertySignature(t.identifier(singleResultFieldName), t.tsTypeAnnotation(t.tsUnionType([
215
+ t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
201
216
  t.tsTypeReference(t.identifier(relationTypeName)),
202
217
  sel,
203
- ]))))),
218
+ ])),
219
+ t.tsNullKeyword(),
204
220
  ]))),
205
221
  ]),
206
222
  ])));
@@ -213,6 +229,10 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
213
229
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
214
230
  const findFirstObjProps = [
215
231
  t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
232
+ t.objectProperty(t.identifier('orderBy'), t.tsAsExpression(t.optionalMemberExpression(t.identifier('args'), t.identifier('orderBy'), false, true), t.tsUnionType([
233
+ t.tsArrayType(t.tsStringKeyword()),
234
+ t.tsUndefinedKeyword(),
235
+ ]))),
216
236
  ];
217
237
  const bodyArgs = [
218
238
  t.stringLiteral(typeName),
@@ -220,9 +240,23 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
220
240
  selectExpr,
221
241
  t.objectExpression(findFirstObjProps),
222
242
  t.stringLiteral(whereTypeName),
243
+ t.stringLiteral(orderByTypeName),
223
244
  t.identifier('connectionFieldsMap'),
224
245
  ];
225
- classBody.push(createClassMethod('findFirst', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, pluralQueryName)));
246
+ const transformDataParam = t.identifier('data');
247
+ const transformedNodesProp = t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
248
+ t.tsTypeReference(t.identifier(relationTypeName)),
249
+ sRef(),
250
+ ])))));
251
+ transformedNodesProp.optional = true;
252
+ const transformedCollectionProp = t.tsPropertySignature(t.identifier(pluralQueryName), t.tsTypeAnnotation(t.tsTypeLiteral([transformedNodesProp])));
253
+ transformedCollectionProp.optional = true;
254
+ transformDataParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeLiteral([transformedCollectionProp]));
255
+ const firstNodeExpr = t.optionalMemberExpression(t.optionalMemberExpression(t.memberExpression(t.identifier('data'), t.identifier(pluralQueryName)), t.identifier('nodes'), false, true), t.numericLiteral(0), true, true);
256
+ const transformFn = t.arrowFunctionExpression([transformDataParam], t.objectExpression([
257
+ t.objectProperty(t.stringLiteral(singleResultFieldName), t.logicalExpression('??', firstNodeExpr, t.nullLiteral())),
258
+ ]));
259
+ classBody.push(createClassMethod('findFirst', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, singleResultFieldName, [t.objectProperty(t.identifier('transform'), transformFn)])));
226
260
  }
227
261
  // ── findOne ────────────────────────────────────────────────────────────
228
262
  if (hasValidPrimaryKey(table)) {
@@ -413,6 +447,134 @@ export function generateModelFile(table, _useSharedTypes, options, allTables) {
413
447
  ];
414
448
  classBody.push(createClassMethod('delete', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildDeleteByPkDocument', bodyArgs, 'mutation', typeName, deleteMutationName)));
415
449
  }
450
+ // ── bulkCreate ──────────────────────────────────────────────────────────
451
+ if (bulkInsertMutationName) {
452
+ const bulkInsertInputTypeName = `BulkCreate${typeName}Input`;
453
+ const dataType = () => t.tsIndexedAccessType(t.tsTypeReference(t.identifier(createInputTypeName)), t.tsLiteralType(t.stringLiteral(singularName)));
454
+ const argsType = (sel) => t.tsTypeReference(t.identifier('BulkInsertArgs'), t.tsTypeParameterInstantiation([sel, dataType()]));
455
+ const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
456
+ t.tsTypeReference(t.identifier('BulkMutationResult'), t.tsTypeParameterInstantiation([
457
+ t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
458
+ t.tsTypeReference(t.identifier(relationTypeName)),
459
+ sel,
460
+ ])),
461
+ ])),
462
+ ])));
463
+ const implParam = t.identifier('args');
464
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
465
+ argsType(sRef()),
466
+ t.tsTypeLiteral([requiredSelectProp()]),
467
+ strictSelectGuard(selectTypeName),
468
+ ]));
469
+ const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
470
+ const bodyArgs = [
471
+ t.stringLiteral(typeName),
472
+ t.stringLiteral(bulkInsertMutationName),
473
+ selectExpr,
474
+ t.memberExpression(t.identifier('args'), t.identifier('data')),
475
+ t.stringLiteral(bulkInsertInputTypeName),
476
+ t.memberExpression(t.identifier('args'), t.identifier('onConflict')),
477
+ t.identifier('connectionFieldsMap'),
478
+ ];
479
+ classBody.push(createClassMethod('bulkCreate', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildBulkInsertDocument', bodyArgs, 'mutation', typeName, bulkInsertMutationName)));
480
+ }
481
+ // ── bulkUpsert ─────────────────────────────────────────────────────────
482
+ if (bulkUpsertMutationName) {
483
+ const bulkUpsertInputTypeName = `BulkUpsert${typeName}Input`;
484
+ const dataType = () => t.tsIndexedAccessType(t.tsTypeReference(t.identifier(createInputTypeName)), t.tsLiteralType(t.stringLiteral(singularName)));
485
+ const argsType = (sel) => t.tsTypeReference(t.identifier('BulkUpsertArgs'), t.tsTypeParameterInstantiation([sel, dataType()]));
486
+ const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
487
+ t.tsTypeReference(t.identifier('BulkMutationResult'), t.tsTypeParameterInstantiation([
488
+ t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
489
+ t.tsTypeReference(t.identifier(relationTypeName)),
490
+ sel,
491
+ ])),
492
+ ])),
493
+ ])));
494
+ const implParam = t.identifier('args');
495
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
496
+ argsType(sRef()),
497
+ t.tsTypeLiteral([requiredSelectProp()]),
498
+ strictSelectGuard(selectTypeName),
499
+ ]));
500
+ const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
501
+ const bodyArgs = [
502
+ t.stringLiteral(typeName),
503
+ t.stringLiteral(bulkUpsertMutationName),
504
+ selectExpr,
505
+ t.memberExpression(t.identifier('args'), t.identifier('data')),
506
+ t.stringLiteral(bulkUpsertInputTypeName),
507
+ t.memberExpression(t.identifier('args'), t.identifier('onConflict')),
508
+ t.identifier('connectionFieldsMap'),
509
+ ];
510
+ classBody.push(createClassMethod('bulkUpsert', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildBulkUpsertDocument', bodyArgs, 'mutation', typeName, bulkUpsertMutationName)));
511
+ }
512
+ // ── bulkUpdate ─────────────────────────────────────────────────────────
513
+ if (bulkUpdateMutationName) {
514
+ const bulkUpdateInputTypeName = `BulkUpdate${typeName}Input`;
515
+ const argsType = (sel) => t.tsTypeReference(t.identifier('BulkUpdateArgs'), t.tsTypeParameterInstantiation([
516
+ sel,
517
+ t.tsTypeReference(t.identifier(whereTypeName)),
518
+ t.tsTypeReference(t.identifier(patchTypeName)),
519
+ ]));
520
+ const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
521
+ t.tsTypeReference(t.identifier('BulkMutationResult'), t.tsTypeParameterInstantiation([
522
+ t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
523
+ t.tsTypeReference(t.identifier(relationTypeName)),
524
+ sel,
525
+ ])),
526
+ ])),
527
+ ])));
528
+ const implParam = t.identifier('args');
529
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
530
+ argsType(sRef()),
531
+ t.tsTypeLiteral([requiredSelectProp()]),
532
+ strictSelectGuard(selectTypeName),
533
+ ]));
534
+ const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
535
+ const bodyArgs = [
536
+ t.stringLiteral(typeName),
537
+ t.stringLiteral(bulkUpdateMutationName),
538
+ selectExpr,
539
+ t.memberExpression(t.identifier('args'), t.identifier('where')),
540
+ t.memberExpression(t.identifier('args'), t.identifier('data')),
541
+ t.stringLiteral(bulkUpdateInputTypeName),
542
+ t.identifier('connectionFieldsMap'),
543
+ ];
544
+ classBody.push(createClassMethod('bulkUpdate', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildBulkUpdateDocument', bodyArgs, 'mutation', typeName, bulkUpdateMutationName)));
545
+ }
546
+ // ── bulkDelete ─────────────────────────────────────────────────────────
547
+ if (bulkDeleteMutationName) {
548
+ const bulkDeleteInputTypeName = `BulkDelete${typeName}Input`;
549
+ const argsType = (sel) => t.tsTypeReference(t.identifier('BulkDeleteArgs'), t.tsTypeParameterInstantiation([
550
+ sel,
551
+ t.tsTypeReference(t.identifier(whereTypeName)),
552
+ ]));
553
+ const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
554
+ t.tsTypeReference(t.identifier('BulkMutationResult'), t.tsTypeParameterInstantiation([
555
+ t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
556
+ t.tsTypeReference(t.identifier(relationTypeName)),
557
+ sel,
558
+ ])),
559
+ ])),
560
+ ])));
561
+ const implParam = t.identifier('args');
562
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
563
+ argsType(sRef()),
564
+ t.tsTypeLiteral([requiredSelectProp()]),
565
+ strictSelectGuard(selectTypeName),
566
+ ]));
567
+ const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
568
+ const bodyArgs = [
569
+ t.stringLiteral(typeName),
570
+ t.stringLiteral(bulkDeleteMutationName),
571
+ selectExpr,
572
+ t.memberExpression(t.identifier('args'), t.identifier('where')),
573
+ t.stringLiteral(bulkDeleteInputTypeName),
574
+ t.identifier('connectionFieldsMap'),
575
+ ];
576
+ classBody.push(createClassMethod('bulkDelete', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildBulkDeleteDocument', bodyArgs, 'mutation', typeName, bulkDeleteMutationName)));
577
+ }
416
578
  // ── M:N add/remove methods ────────────────────────────────────────────
417
579
  for (const rel of m2nRels) {
418
580
  if (!rel.fieldName)
@@ -167,9 +167,10 @@ export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
167
167
  /**
168
168
  * Arguments for findFirst/findUnique operations
169
169
  */
170
- export interface FindFirstArgs<TSelect, TWhere> {
170
+ export interface FindFirstArgs<TSelect, TWhere, TOrderBy> {
171
171
  select?: TSelect;
172
172
  where?: TWhere;
173
+ orderBy?: TOrderBy[];
173
174
  }
174
175
  /**
175
176
  * Arguments for create operations
@@ -433,7 +433,7 @@ export function generateSingleQueryHook(table, options = {}) {
433
433
  t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
434
434
  t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName)))),
435
435
  ];
436
- statements.push(exportAsyncFunction(fetchFnName, null, [createFunctionParam('params', t.tsTypeLiteral(fImplProps))], fBody));
436
+ statements.push(exportAsyncFunction(fetchFnName, null, [createFunctionParam('params', t.tsTypeLiteral(fImplProps))], fBody, typeRef('Promise', [t.tsAnyKeyword()])));
437
437
  }
438
438
  // Prefetch function
439
439
  if (reactQueryEnabled) {
@@ -101,6 +101,14 @@ export declare function getUpdateMutationName(table: Table): string;
101
101
  * Get the GraphQL mutation name for deleting
102
102
  */
103
103
  export declare function getDeleteMutationName(table: Table): string;
104
+ export declare function getBulkCreateMutationHookName(table: Table): string;
105
+ export declare function getBulkUpsertMutationHookName(table: Table): string;
106
+ export declare function getBulkUpdateMutationHookName(table: Table): string;
107
+ export declare function getBulkDeleteMutationHookName(table: Table): string;
108
+ export declare function getBulkCreateMutationFileName(table: Table): string;
109
+ export declare function getBulkUpsertMutationFileName(table: Table): string;
110
+ export declare function getBulkUpdateMutationFileName(table: Table): string;
111
+ export declare function getBulkDeleteMutationFileName(table: Table): string;
104
112
  /**
105
113
  * Get PostGraphile filter type name
106
114
  * e.g., "CarFilter"
@@ -154,6 +154,37 @@ export function getDeleteMutationName(table) {
154
154
  return table.query?.delete || `delete${table.name}`;
155
155
  }
156
156
  // ============================================================================
157
+ // Bulk mutation naming helpers
158
+ // ============================================================================
159
+ export function getBulkCreateMutationHookName(table) {
160
+ const { typeName } = getTableNames(table);
161
+ return `useBulkCreate${typeName}Mutation`;
162
+ }
163
+ export function getBulkUpsertMutationHookName(table) {
164
+ const { typeName } = getTableNames(table);
165
+ return `useBulkUpsert${typeName}Mutation`;
166
+ }
167
+ export function getBulkUpdateMutationHookName(table) {
168
+ const { typeName } = getTableNames(table);
169
+ return `useBulkUpdate${typeName}Mutation`;
170
+ }
171
+ export function getBulkDeleteMutationHookName(table) {
172
+ const { typeName } = getTableNames(table);
173
+ return `useBulkDelete${typeName}Mutation`;
174
+ }
175
+ export function getBulkCreateMutationFileName(table) {
176
+ return `${getBulkCreateMutationHookName(table)}.ts`;
177
+ }
178
+ export function getBulkUpsertMutationFileName(table) {
179
+ return `${getBulkUpsertMutationHookName(table)}.ts`;
180
+ }
181
+ export function getBulkUpdateMutationFileName(table) {
182
+ return `${getBulkUpdateMutationHookName(table)}.ts`;
183
+ }
184
+ export function getBulkDeleteMutationFileName(table) {
185
+ return `${getBulkDeleteMutationHookName(table)}.ts`;
186
+ }
187
+ // ============================================================================
157
188
  // Type names
158
189
  // ============================================================================
159
190
  /**
@@ -47,9 +47,6 @@ export async function generate(options = {}, internalOptions) {
47
47
  const runReactQuery = config.reactQuery ?? false;
48
48
  const runCli = internalOptions?.skipCli ? false : !!config.cli;
49
49
  const runOrm = runReactQuery || !!config.cli || (options.orm !== undefined ? !!options.orm : false);
50
- // Auto-enable nodeHttpAdapter when CLI is enabled, unless explicitly set to false
51
- const useNodeHttpAdapter = options.nodeHttpAdapter === true ||
52
- (runCli && options.nodeHttpAdapter !== false);
53
50
  const schemaEnabled = !!options.schema?.enabled;
54
51
  if (!schemaEnabled && !runReactQuery && !runOrm && !runCli) {
55
52
  return {
@@ -184,22 +181,13 @@ export async function generate(options = {}, internalOptions) {
184
181
  mutations: customOperations.mutations,
185
182
  typeRegistry: customOperations.typeRegistry,
186
183
  },
187
- config: { ...config, nodeHttpAdapter: useNodeHttpAdapter },
184
+ config,
188
185
  sharedTypesPath: bothEnabled ? '..' : undefined,
189
186
  });
190
187
  filesToWrite.push(...files.map((file) => ({
191
188
  ...file,
192
189
  path: path.posix.join('orm', file.path),
193
190
  })));
194
- // Generate NodeHttpAdapter in ORM output when enabled
195
- if (useNodeHttpAdapter) {
196
- const { generateNodeFetchFile } = await import('./codegen/cli/utils-generator');
197
- const nodeFetchFile = generateNodeFetchFile();
198
- filesToWrite.push({
199
- path: path.posix.join('orm', nodeFetchFile.fileName),
200
- content: nodeFetchFile.content,
201
- });
202
- }
203
191
  }
204
192
  // Generate CLI commands
205
193
  if (runCli) {
@@ -210,7 +198,7 @@ export async function generate(options = {}, internalOptions) {
210
198
  queries: customOperations.queries,
211
199
  mutations: customOperations.mutations,
212
200
  },
213
- config: { ...config, nodeHttpAdapter: useNodeHttpAdapter },
201
+ config,
214
202
  typeRegistry: customOperations.typeRegistry,
215
203
  });
216
204
  filesToWrite.push(...files.map((file) => ({
@@ -540,16 +528,11 @@ export async function generateMulti(options) {
540
528
  if (useUnifiedCli && cliTargets.length > 0 && !dryRun) {
541
529
  const cliConfig = typeof unifiedCli === 'object' ? unifiedCli : {};
542
530
  const toolName = cliConfig.toolName ?? 'app';
543
- // Auto-enable nodeHttpAdapter for unified CLI unless explicitly disabled
544
- // Check first target config for explicit nodeHttpAdapter setting
545
531
  const firstTargetConfig = configs[names[0]];
546
- const multiNodeHttpAdapter = firstTargetConfig?.nodeHttpAdapter === true ||
547
- (firstTargetConfig?.nodeHttpAdapter !== false);
548
532
  const { files } = generateMultiTargetCli({
549
533
  toolName,
550
534
  builtinNames: cliConfig.builtinNames,
551
535
  targets: cliTargets,
552
- nodeHttpAdapter: multiNodeHttpAdapter,
553
536
  entryPoint: cliConfig.entryPoint,
554
537
  });
555
538
  const cliFilesToWrite = files.map((file) => ({