@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
@@ -7,9 +7,5 @@ export interface MultiTargetExecutorInput {
7
7
  endpoint: string;
8
8
  ormImportPath: string;
9
9
  }
10
- export interface ExecutorOptions {
11
- /** Enable NodeHttpAdapter for *.localhost subdomain routing */
12
- nodeHttpAdapter?: boolean;
13
- }
14
- export declare function generateExecutorFile(toolName: string, options?: ExecutorOptions): GeneratedFile;
15
- export declare function generateMultiTargetExecutorFile(toolName: string, targets: MultiTargetExecutorInput[], options?: ExecutorOptions): GeneratedFile;
10
+ export declare function generateExecutorFile(toolName: string): GeneratedFile;
11
+ export declare function generateMultiTargetExecutorFile(toolName: string, targets: MultiTargetExecutorInput[]): GeneratedFile;
@@ -44,12 +44,8 @@ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false
44
44
  decl.importKind = typeOnly ? 'type' : 'value';
45
45
  return decl;
46
46
  }
47
- function generateExecutorFile(toolName, options) {
47
+ function generateExecutorFile(toolName) {
48
48
  const statements = [];
49
- // Import NodeHttpAdapter for *.localhost subdomain routing
50
- if (options?.nodeHttpAdapter) {
51
- statements.push(createImportDeclaration('./node-fetch', ['NodeHttpAdapter']));
52
- }
53
49
  statements.push(createImportDeclaration('appstash', ['createConfigStore']));
54
50
  statements.push(createImportDeclaration('../orm', ['createClient']));
55
51
  statements.push(t.variableDeclaration('const', [
@@ -109,26 +105,12 @@ function generateExecutorFile(toolName, options) {
109
105
  ]))),
110
106
  ])),
111
107
  ])),
112
- // Build createClient config — use NodeHttpAdapter for *.localhost endpoints
113
- ...(options?.nodeHttpAdapter
114
- ? [
115
- t.returnStatement(t.callExpression(t.identifier('createClient'), [
116
- t.objectExpression([
117
- t.objectProperty(t.identifier('adapter'), t.newExpression(t.identifier('NodeHttpAdapter'), [
118
- t.memberExpression(t.identifier('ctx'), t.identifier('endpoint')),
119
- t.identifier('headers'),
120
- ])),
121
- ]),
122
- ])),
123
- ]
124
- : [
125
- t.returnStatement(t.callExpression(t.identifier('createClient'), [
126
- t.objectExpression([
127
- t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('ctx'), t.identifier('endpoint'))),
128
- t.objectProperty(t.identifier('headers'), t.identifier('headers')),
129
- ]),
130
- ])),
108
+ t.returnStatement(t.callExpression(t.identifier('createClient'), [
109
+ t.objectExpression([
110
+ t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('ctx'), t.identifier('endpoint'))),
111
+ t.objectProperty(t.identifier('headers'), t.identifier('headers')),
131
112
  ]),
113
+ ])),
132
114
  ]);
133
115
  const getClientFunc = t.functionDeclaration(t.identifier('getClient'), [contextNameParam], getClientBody);
134
116
  statements.push(t.exportNamedDeclaration(getClientFunc));
@@ -139,12 +121,8 @@ function generateExecutorFile(toolName, options) {
139
121
  content: header + '\n' + code,
140
122
  };
141
123
  }
142
- function generateMultiTargetExecutorFile(toolName, targets, options) {
124
+ function generateMultiTargetExecutorFile(toolName, targets) {
143
125
  const statements = [];
144
- // Import NodeHttpAdapter for *.localhost subdomain routing
145
- if (options?.nodeHttpAdapter) {
146
- statements.push(createImportDeclaration('./node-fetch', ['NodeHttpAdapter']));
147
- }
148
126
  statements.push(createImportDeclaration('appstash', ['createConfigStore']));
149
127
  for (const target of targets) {
150
128
  const aliasName = `create${target.name[0].toUpperCase()}${target.name.slice(1)}Client`;
@@ -235,26 +213,12 @@ function generateMultiTargetExecutorFile(toolName, targets, options) {
235
213
  ]), t.blockStatement([
236
214
  t.expressionStatement(t.assignmentExpression('=', t.identifier('endpoint'), t.logicalExpression('||', t.memberExpression(t.identifier('defaultEndpoints'), t.identifier('targetName'), true), t.stringLiteral('')))),
237
215
  ])),
238
- // Build createClient config — use NodeHttpAdapter for *.localhost endpoints
239
- ...(options?.nodeHttpAdapter
240
- ? [
241
- t.returnStatement(t.callExpression(t.identifier('createFn'), [
242
- t.objectExpression([
243
- t.objectProperty(t.identifier('adapter'), t.newExpression(t.identifier('NodeHttpAdapter'), [
244
- t.identifier('endpoint'),
245
- t.identifier('headers'),
246
- ])),
247
- ]),
248
- ])),
249
- ]
250
- : [
251
- t.returnStatement(t.callExpression(t.identifier('createFn'), [
252
- t.objectExpression([
253
- t.objectProperty(t.identifier('endpoint'), t.identifier('endpoint')),
254
- t.objectProperty(t.identifier('headers'), t.identifier('headers')),
255
- ]),
256
- ])),
216
+ t.returnStatement(t.callExpression(t.identifier('createFn'), [
217
+ t.objectExpression([
218
+ t.objectProperty(t.identifier('endpoint'), t.identifier('endpoint')),
219
+ t.objectProperty(t.identifier('headers'), t.identifier('headers')),
257
220
  ]),
221
+ ])),
258
222
  ]);
259
223
  const getClientFunc = t.functionDeclaration(t.identifier('getClient'), [targetNameParam, contextNameParam], getClientBody);
260
224
  statements.push(t.exportNamedDeclaration(getClientFunc));
@@ -39,8 +39,6 @@ export interface GenerateMultiTargetCliOptions {
39
39
  toolName: string;
40
40
  builtinNames?: BuiltinNames;
41
41
  targets: MultiTargetCliTarget[];
42
- /** Enable NodeHttpAdapter for *.localhost subdomain routing */
43
- nodeHttpAdapter?: boolean;
44
42
  /** Generate a runnable index.ts entry point */
45
43
  entryPoint?: boolean;
46
44
  }
@@ -19,11 +19,7 @@ function generateCli(options) {
19
19
  const toolName = typeof cliConfig === 'object' && cliConfig.toolName
20
20
  ? cliConfig.toolName
21
21
  : 'app';
22
- // Use top-level nodeHttpAdapter from config (auto-enabled for CLI by generate.ts)
23
- const useNodeHttpAdapter = !!config.nodeHttpAdapter;
24
- const executorFile = (0, executor_generator_1.generateExecutorFile)(toolName, {
25
- nodeHttpAdapter: useNodeHttpAdapter,
26
- });
22
+ const executorFile = (0, executor_generator_1.generateExecutorFile)(toolName);
27
23
  files.push(executorFile);
28
24
  const utilsFile = (0, utils_generator_1.generateUtilsFile)();
29
25
  files.push(utilsFile);
@@ -32,10 +28,6 @@ function generateCli(options) {
32
28
  if (hasAnyEmbeddings) {
33
29
  files.push((0, utils_generator_1.generateEmbedderFile)());
34
30
  }
35
- // Generate node HTTP adapter if configured (for *.localhost subdomain routing)
36
- if (useNodeHttpAdapter) {
37
- files.push((0, utils_generator_1.generateNodeFetchFile)());
38
- }
39
31
  const contextFile = (0, infra_generator_1.generateContextCommand)(toolName);
40
32
  files.push(contextFile);
41
33
  const authFile = (0, infra_generator_1.generateAuthCommand)(toolName);
@@ -97,9 +89,7 @@ function generateMultiTargetCli(options) {
97
89
  endpoint: t.endpoint,
98
90
  ormImportPath: t.ormImportPath,
99
91
  }));
100
- const executorFile = (0, executor_generator_1.generateMultiTargetExecutorFile)(toolName, executorInputs, {
101
- nodeHttpAdapter: !!options.nodeHttpAdapter,
102
- });
92
+ const executorFile = (0, executor_generator_1.generateMultiTargetExecutorFile)(toolName, executorInputs);
103
93
  files.push(executorFile);
104
94
  const utilsFile = (0, utils_generator_1.generateUtilsFile)();
105
95
  files.push(utilsFile);
@@ -108,10 +98,6 @@ function generateMultiTargetCli(options) {
108
98
  if (hasAnyMtEmbeddings) {
109
99
  files.push((0, utils_generator_1.generateEmbedderFile)());
110
100
  }
111
- // Generate node HTTP adapter if configured (for *.localhost subdomain routing)
112
- if (options.nodeHttpAdapter) {
113
- files.push((0, utils_generator_1.generateNodeFetchFile)());
114
- }
115
101
  const contextFile = (0, infra_generator_1.generateMultiTargetContextCommand)(toolName, builtinNames.context, targets.map((t) => ({ name: t.name, endpoint: t.endpoint })));
116
102
  files.push(contextFile);
117
103
  const authFile = (0, infra_generator_1.generateAuthCommandWithName)(toolName, builtinNames.auth);
@@ -312,7 +312,7 @@ function buildFindManyArgsType(table) {
312
312
  }
313
313
  /**
314
314
  * Build the FindFirstArgs type instantiation for a table:
315
- * FindFirstArgs<SelectType, FilterType> & { select: SelectType }
315
+ * FindFirstArgs<SelectType, FilterType, OrderByType> & { select: SelectType }
316
316
  *
317
317
  * The intersection with { select: SelectType } makes select required,
318
318
  * matching what the ORM's findFirst method expects.
@@ -321,9 +321,11 @@ function buildFindFirstArgsType(table) {
321
321
  const { typeName } = (0, utils_1.getTableNames)(table);
322
322
  const selectTypeName = `${typeName}Select`;
323
323
  const whereTypeName = (0, utils_1.getFilterTypeName)(table);
324
+ const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
324
325
  const findFirstType = t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
325
326
  t.tsTypeReference(t.identifier(selectTypeName)),
326
327
  t.tsTypeReference(t.identifier(whereTypeName)),
328
+ t.tsTypeReference(t.identifier(orderByTypeName)),
327
329
  ]));
328
330
  // Intersect with { select: SelectType } to make select required
329
331
  return t.tsIntersectionType([
@@ -716,6 +718,114 @@ function buildMutationHandler(table, operation, vectorFieldNames, targetName, ty
716
718
  t.tryStatement(t.blockStatement(tryBody), buildErrorCatch(`Failed to ${operation} record.`)),
717
719
  ]), false, true);
718
720
  }
721
+ function buildBulkMutationHandler(table, operation, targetName) {
722
+ const { singularName } = (0, utils_1.getTableNames)(table);
723
+ const selectObj = buildSelectObject(table);
724
+ // Map CLI op name to ORM method name
725
+ const ormMethod = (() => {
726
+ switch (operation) {
727
+ case 'bulk-create': return 'bulkCreate';
728
+ case 'bulk-upsert': return 'bulkUpsert';
729
+ case 'bulk-update': return 'bulkUpdate';
730
+ case 'bulk-delete': return 'bulkDelete';
731
+ }
732
+ })();
733
+ const tryBody = [];
734
+ if (operation === 'bulk-create' || operation === 'bulk-upsert') {
735
+ // Parse --data (JSON array) from argv
736
+ tryBody.push(t.variableDeclaration('const', [
737
+ t.variableDeclarator(t.identifier('dataRaw'), t.memberExpression(t.identifier('argv'), t.identifier('data'))),
738
+ ]));
739
+ tryBody.push(t.ifStatement(t.unaryExpression('!', t.identifier('dataRaw')), t.blockStatement([
740
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [t.stringLiteral(`--data is required for ${operation}. Provide a JSON array.`)])),
741
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
742
+ ])));
743
+ tryBody.push(t.variableDeclaration('const', [
744
+ t.variableDeclarator(t.identifier('data'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('dataRaw'), t.tsStringKeyword())])),
745
+ ]));
746
+ let ormArgs;
747
+ if (operation === 'bulk-upsert') {
748
+ // Also parse --on-conflict
749
+ tryBody.push(t.variableDeclaration('const', [
750
+ t.variableDeclarator(t.identifier('onConflictRaw'), t.memberExpression(t.identifier('argv'), t.identifier('on-conflict'))),
751
+ ]));
752
+ tryBody.push(t.variableDeclaration('const', [
753
+ t.variableDeclarator(t.identifier('onConflict'), t.conditionalExpression(t.identifier('onConflictRaw'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('onConflictRaw'), t.tsStringKeyword())]), t.objectExpression([]))),
754
+ ]));
755
+ ormArgs = t.objectExpression([
756
+ t.objectProperty(t.identifier('data'), t.identifier('data')),
757
+ t.objectProperty(t.identifier('onConflict'), t.identifier('onConflict')),
758
+ t.objectProperty(t.identifier('select'), selectObj),
759
+ ]);
760
+ }
761
+ else {
762
+ ormArgs = t.objectExpression([
763
+ t.objectProperty(t.identifier('data'), t.identifier('data')),
764
+ t.objectProperty(t.identifier('select'), selectObj),
765
+ ]);
766
+ }
767
+ tryBody.push(buildGetClientStatement(targetName));
768
+ tryBody.push(t.variableDeclaration('const', [
769
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, ormMethod, ormArgs))),
770
+ ]));
771
+ }
772
+ else if (operation === 'bulk-update') {
773
+ // Parse --where (JSON) and --data (JSON)
774
+ tryBody.push(t.variableDeclaration('const', [
775
+ t.variableDeclarator(t.identifier('whereRaw'), t.memberExpression(t.identifier('argv'), t.identifier('where'))),
776
+ ]));
777
+ tryBody.push(t.variableDeclaration('const', [
778
+ t.variableDeclarator(t.identifier('dataRaw'), t.memberExpression(t.identifier('argv'), t.identifier('data'))),
779
+ ]));
780
+ tryBody.push(t.ifStatement(t.logicalExpression('||', t.unaryExpression('!', t.identifier('whereRaw')), t.unaryExpression('!', t.identifier('dataRaw'))), t.blockStatement([
781
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [t.stringLiteral('--where and --data are required for bulk-update. Provide JSON objects.')])),
782
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
783
+ ])));
784
+ tryBody.push(t.variableDeclaration('const', [
785
+ t.variableDeclarator(t.identifier('where'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('whereRaw'), t.tsStringKeyword())])),
786
+ ]));
787
+ tryBody.push(t.variableDeclaration('const', [
788
+ t.variableDeclarator(t.identifier('data'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('dataRaw'), t.tsStringKeyword())])),
789
+ ]));
790
+ tryBody.push(buildGetClientStatement(targetName));
791
+ tryBody.push(t.variableDeclaration('const', [
792
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, ormMethod, t.objectExpression([
793
+ t.objectProperty(t.identifier('where'), t.identifier('where')),
794
+ t.objectProperty(t.identifier('data'), t.identifier('data')),
795
+ t.objectProperty(t.identifier('select'), selectObj),
796
+ ])))),
797
+ ]));
798
+ }
799
+ else {
800
+ // bulk-delete: parse --where (JSON)
801
+ tryBody.push(t.variableDeclaration('const', [
802
+ t.variableDeclarator(t.identifier('whereRaw'), t.memberExpression(t.identifier('argv'), t.identifier('where'))),
803
+ ]));
804
+ tryBody.push(t.ifStatement(t.unaryExpression('!', t.identifier('whereRaw')), t.blockStatement([
805
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [t.stringLiteral('--where is required for bulk-delete. Provide a JSON object.')])),
806
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
807
+ ])));
808
+ tryBody.push(t.variableDeclaration('const', [
809
+ t.variableDeclarator(t.identifier('where'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('whereRaw'), t.tsStringKeyword())])),
810
+ ]));
811
+ tryBody.push(buildGetClientStatement(targetName));
812
+ tryBody.push(t.variableDeclaration('const', [
813
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, ormMethod, t.objectExpression([
814
+ t.objectProperty(t.identifier('where'), t.identifier('where')),
815
+ t.objectProperty(t.identifier('select'), selectObj),
816
+ ])))),
817
+ ]));
818
+ }
819
+ tryBody.push(buildJsonLog(t.identifier('result')));
820
+ const argvParam = t.identifier('argv');
821
+ argvParam.typeAnnotation = buildArgvType();
822
+ const prompterParam = t.identifier('prompter');
823
+ prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
824
+ const handlerName = `handle${(0, utils_1.toPascalCase)(operation)}`;
825
+ return t.functionDeclaration(t.identifier(handlerName), [argvParam, prompterParam], t.blockStatement([
826
+ t.tryStatement(t.blockStatement(tryBody), buildErrorCatch(`Failed to ${operation}.`)),
827
+ ]), false, true);
828
+ }
719
829
  function generateTableCommand(table, options) {
720
830
  const { singularName, typeName } = (0, utils_1.getTableNames)(table);
721
831
  const commandName = (0, inflekt_1.toKebabCase)(singularName);
@@ -793,6 +903,11 @@ function generateTableCommand(table, options) {
793
903
  const embedderPath = options?.targetName ? '../../embedder' : '../embedder';
794
904
  statements.push(createImportDeclaration(embedderPath, ['resolveEmbedder', 'autoEmbedWhere', 'autoEmbedInput']));
795
905
  }
906
+ // Detect bulk mutations
907
+ const hasBulkCreate = !!table.query?.bulkInsert;
908
+ const hasBulkUpsert = !!table.query?.bulkUpsert;
909
+ const hasBulkUpdate = !!table.query?.bulkUpdate;
910
+ const hasBulkDelete = !!table.query?.bulkDelete;
796
911
  const subcommands = ['list', 'find-first'];
797
912
  if (hasSearchFields)
798
913
  subcommands.push('search');
@@ -803,6 +918,14 @@ function generateTableCommand(table, options) {
803
918
  subcommands.push('update');
804
919
  if (hasDelete)
805
920
  subcommands.push('delete');
921
+ if (hasBulkCreate)
922
+ subcommands.push('bulk-create');
923
+ if (hasBulkUpsert)
924
+ subcommands.push('bulk-upsert');
925
+ if (hasBulkUpdate)
926
+ subcommands.push('bulk-update');
927
+ if (hasBulkDelete)
928
+ subcommands.push('bulk-delete');
806
929
  const usageLines = [
807
930
  '',
808
931
  `${commandName} <command>`,
@@ -823,7 +946,15 @@ function generateTableCommand(table, options) {
823
946
  }
824
947
  if (hasDelete)
825
948
  usageLines.push(` delete Delete a ${singularName}`);
826
- usageLines.push('', 'List Options:', ' --limit <n> Max number of records to return (forward pagination)', ' --last <n> Number of records from the end (backward pagination)', ' --after <cursor> Cursor for forward pagination', ' --before <cursor> Cursor for backward pagination', ' --offset <n> Number of records to skip', ' --select <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.name.equalTo foo)', ' --condition.<f>.<op> Condition filter (dot-notation)', ' --orderBy <values> Comma-separated ordering values (e.g. NAME_ASC,CREATED_AT_DESC)', '', 'Find-First Options:', ' --select <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.status.equalTo active)', ' --condition.<f>.<op> Condition filter (dot-notation)', '');
949
+ if (hasBulkCreate)
950
+ usageLines.push(` bulk-create Bulk create ${singularName} records`);
951
+ if (hasBulkUpsert)
952
+ usageLines.push(` bulk-upsert Bulk upsert ${singularName} records`);
953
+ if (hasBulkUpdate)
954
+ usageLines.push(` bulk-update Bulk update ${singularName} records`);
955
+ if (hasBulkDelete)
956
+ usageLines.push(` bulk-delete Bulk delete ${singularName} records`);
957
+ usageLines.push('', 'List Options:', ' --limit <n> Max number of records to return (forward pagination)', ' --last <n> Number of records from the end (backward pagination)', ' --after <cursor> Cursor for forward pagination', ' --before <cursor> Cursor for backward pagination', ' --offset <n> Number of records to skip', ' --select <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.name.equalTo foo)', ' --condition.<f>.<op> Condition filter (dot-notation)', ' --orderBy <values> Comma-separated ordering values (e.g. NAME_ASC,CREATED_AT_DESC)', '', 'Find-First Options:', ' --select <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.status.equalTo active)', ' --condition.<f>.<op> Condition filter (dot-notation)', ' --orderBy <values> Comma-separated ordering values (e.g. NAME_ASC,CREATED_AT_DESC)', '');
827
958
  if (hasSearchFields) {
828
959
  usageLines.push('Search Options:', ' <query> Search query string (required)', ' --limit <n> Max number of records to return', ' --offset <n> Number of records to skip', ' --select <fields> Comma-separated list of fields to return', ' --orderBy <values> Comma-separated list of ordering values');
829
960
  if (hasEmbeddings) {
@@ -906,6 +1037,14 @@ function generateTableCommand(table, options) {
906
1037
  statements.push(buildMutationHandler(table, 'update', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
907
1038
  if (hasDelete)
908
1039
  statements.push(buildMutationHandler(table, 'delete', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
1040
+ if (hasBulkCreate)
1041
+ statements.push(buildBulkMutationHandler(table, 'bulk-create', tn));
1042
+ if (hasBulkUpsert)
1043
+ statements.push(buildBulkMutationHandler(table, 'bulk-upsert', tn));
1044
+ if (hasBulkUpdate)
1045
+ statements.push(buildBulkMutationHandler(table, 'bulk-update', tn));
1046
+ if (hasBulkDelete)
1047
+ statements.push(buildBulkMutationHandler(table, 'bulk-delete', tn));
909
1048
  const header = (0, utils_1.getGeneratedFileHeader)(`CLI commands for ${table.name}`);
910
1049
  const code = (0, babel_ast_1.generateCode)(statements);
911
1050
  return {
@@ -8,14 +8,6 @@ import type { GeneratedFile } from './executor-generator';
8
8
  * and mutation input parsing.
9
9
  */
10
10
  export declare function generateUtilsFile(): GeneratedFile;
11
- /**
12
- * Generate a node-fetch.ts file with NodeHttpAdapter for CLI.
13
- *
14
- * Provides a GraphQLAdapter implementation using node:http/node:https
15
- * instead of the Fetch API. This cleanly handles *.localhost subdomain
16
- * routing (DNS resolution + Host header) without any global patching.
17
- */
18
- export declare function generateNodeFetchFile(): GeneratedFile;
19
11
  /**
20
12
  * Generate an index.ts entry point file for the CLI.
21
13
  *
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.generateUtilsFile = generateUtilsFile;
37
- exports.generateNodeFetchFile = generateNodeFetchFile;
38
37
  exports.generateEntryPointFile = generateEntryPointFile;
39
38
  exports.generateEmbedderFile = generateEmbedderFile;
40
39
  const fs = __importStar(require("fs"));
@@ -79,19 +78,6 @@ function generateUtilsFile() {
79
78
  content: readTemplateFile('cli-utils.ts', 'CLI utility functions for type coercion and input handling'),
80
79
  };
81
80
  }
82
- /**
83
- * Generate a node-fetch.ts file with NodeHttpAdapter for CLI.
84
- *
85
- * Provides a GraphQLAdapter implementation using node:http/node:https
86
- * instead of the Fetch API. This cleanly handles *.localhost subdomain
87
- * routing (DNS resolution + Host header) without any global patching.
88
- */
89
- function generateNodeFetchFile() {
90
- return {
91
- fileName: 'node-fetch.ts',
92
- content: readTemplateFile('node-fetch.ts', 'Node HTTP adapter for localhost subdomain routing'),
93
- };
94
- }
95
81
  /**
96
82
  * Generate an index.ts entry point file for the CLI.
97
83
  *
@@ -113,6 +113,24 @@ function generateEntityMutationKeysDeclaration(table, relationships) {
113
113
  const deleteProp = t.objectProperty(t.identifier('delete'), deleteArrowFn);
114
114
  (0, babel_ast_1.addJSDocComment)(deleteProp, [`Delete ${singularName} mutation key`]);
115
115
  properties.push(deleteProp);
116
+ // Bulk mutation keys (only if table has bulk operations)
117
+ const bulkOps = [
118
+ { key: 'bulkCreate', queryField: table.query?.bulkInsert },
119
+ { key: 'bulkUpsert', queryField: table.query?.bulkUpsert },
120
+ { key: 'bulkUpdate', queryField: table.query?.bulkUpdate },
121
+ { key: 'bulkDelete', queryField: table.query?.bulkDelete },
122
+ ];
123
+ for (const { key, queryField } of bulkOps) {
124
+ if (!queryField)
125
+ continue;
126
+ const arrowFn = t.arrowFunctionExpression([], (0, babel_ast_1.constArray)([
127
+ t.stringLiteral('mutation'),
128
+ t.stringLiteral(entityKey),
129
+ t.stringLiteral(key),
130
+ ]));
131
+ const prop = t.objectProperty(t.identifier(key), arrowFn);
132
+ properties.push(prop);
133
+ }
116
134
  return t.exportNamedDeclaration(t.variableDeclaration('const', [
117
135
  t.variableDeclarator(t.identifier(keysName), (0, babel_ast_1.asConst)(t.objectExpression(properties))),
118
136
  ]));
@@ -416,6 +416,185 @@ function generateDeleteMutationHook(table, options = {}) {
416
416
  content: (0, hooks_ast_1.generateHookFileCode)(table.description || `Delete mutation hook for ${typeName}`, statements),
417
417
  };
418
418
  }
419
+ function generateBulkMutationHook(table, op, options = {}) {
420
+ const { reactQueryEnabled = true, useCentralizedKeys = true } = options;
421
+ if (!reactQueryEnabled)
422
+ return null;
423
+ const mutationFieldName = (() => {
424
+ switch (op) {
425
+ case 'bulkCreate': return table.query?.bulkInsert;
426
+ case 'bulkUpsert': return table.query?.bulkUpsert;
427
+ case 'bulkUpdate': return table.query?.bulkUpdate;
428
+ case 'bulkDelete': return table.query?.bulkDelete;
429
+ }
430
+ })();
431
+ if (!mutationFieldName)
432
+ return null;
433
+ const { typeName, singularName } = (0, utils_1.getTableNames)(table);
434
+ const hookName = (() => {
435
+ switch (op) {
436
+ case 'bulkCreate': return (0, utils_1.getBulkCreateMutationHookName)(table);
437
+ case 'bulkUpsert': return (0, utils_1.getBulkUpsertMutationHookName)(table);
438
+ case 'bulkUpdate': return (0, utils_1.getBulkUpdateMutationHookName)(table);
439
+ case 'bulkDelete': return (0, utils_1.getBulkDeleteMutationHookName)(table);
440
+ }
441
+ })();
442
+ const fileName = (() => {
443
+ switch (op) {
444
+ case 'bulkCreate': return (0, utils_1.getBulkCreateMutationFileName)(table);
445
+ case 'bulkUpsert': return (0, utils_1.getBulkUpsertMutationFileName)(table);
446
+ case 'bulkUpdate': return (0, utils_1.getBulkUpdateMutationFileName)(table);
447
+ case 'bulkDelete': return (0, utils_1.getBulkDeleteMutationFileName)(table);
448
+ }
449
+ })();
450
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
451
+ const mutationKeysName = `${(0, utils_1.lcFirst)(typeName)}MutationKeys`;
452
+ const selectTypeName = `${typeName}Select`;
453
+ const relationTypeName = `${typeName}WithRelations`;
454
+ const createInputTypeName = `Create${typeName}Input`;
455
+ const patchTypeName = `${typeName}Patch`;
456
+ const filterTypeName = `${typeName}Filter`;
457
+ const statements = [];
458
+ // Imports
459
+ statements.push((0, hooks_ast_1.createImportDeclaration)('@tanstack/react-query', [
460
+ 'useMutation',
461
+ 'useQueryClient',
462
+ ]));
463
+ statements.push((0, hooks_ast_1.createImportDeclaration)('@tanstack/react-query', ['UseMutationOptions', 'UseMutationResult'], true));
464
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../client', ['getClient']));
465
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../selection', ['buildSelectionArgs']));
466
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../selection', ['SelectionConfig'], true));
467
+ if (useCentralizedKeys) {
468
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../query-keys', [keysName]));
469
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../mutation-keys', [mutationKeysName]));
470
+ }
471
+ // Determine which types to import
472
+ const typeImports = [selectTypeName, relationTypeName];
473
+ if (op === 'bulkCreate' || op === 'bulkUpsert') {
474
+ typeImports.push(createInputTypeName);
475
+ }
476
+ if (op === 'bulkUpdate') {
477
+ typeImports.push(patchTypeName);
478
+ typeImports.push(filterTypeName);
479
+ }
480
+ if (op === 'bulkDelete') {
481
+ typeImports.push(filterTypeName);
482
+ }
483
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../../orm/input-types', typeImports, true));
484
+ statements.push((0, hooks_ast_1.createImportDeclaration)('../../orm/select-types', ['InferSelectResult', 'BulkMutationResult', 'HookStrictSelect'], true));
485
+ // Re-exports
486
+ statements.push((0, hooks_ast_1.createTypeReExport)(typeImports, '../../orm/input-types'));
487
+ // Build the variable type for the mutationFn parameter
488
+ const varType = (() => {
489
+ switch (op) {
490
+ case 'bulkCreate':
491
+ return t.tsTypeLiteral([
492
+ t.tsPropertySignature(t.identifier('data'), t.tsTypeAnnotation(t.tsArrayType(t.tsIndexedAccessType((0, hooks_ast_1.typeRef)(createInputTypeName), t.tsLiteralType(t.stringLiteral(singularName)))))),
493
+ (() => {
494
+ const p = t.tsPropertySignature(t.identifier('onConflict'), t.tsTypeAnnotation(t.tsUnknownKeyword()));
495
+ p.optional = true;
496
+ return p;
497
+ })(),
498
+ ]);
499
+ case 'bulkUpsert':
500
+ return t.tsTypeLiteral([
501
+ t.tsPropertySignature(t.identifier('data'), t.tsTypeAnnotation(t.tsArrayType(t.tsIndexedAccessType((0, hooks_ast_1.typeRef)(createInputTypeName), t.tsLiteralType(t.stringLiteral(singularName)))))),
502
+ t.tsPropertySignature(t.identifier('onConflict'), t.tsTypeAnnotation(t.tsUnknownKeyword())),
503
+ ]);
504
+ case 'bulkUpdate':
505
+ return t.tsTypeLiteral([
506
+ t.tsPropertySignature(t.identifier('where'), t.tsTypeAnnotation((0, hooks_ast_1.typeRef)(filterTypeName))),
507
+ t.tsPropertySignature(t.identifier('data'), t.tsTypeAnnotation((0, hooks_ast_1.typeRef)(patchTypeName))),
508
+ ]);
509
+ case 'bulkDelete':
510
+ return t.tsTypeLiteral([
511
+ t.tsPropertySignature(t.identifier('where'), t.tsTypeAnnotation((0, hooks_ast_1.typeRef)(filterTypeName))),
512
+ ]);
513
+ }
514
+ })();
515
+ // Result type: BulkMutationResult<InferSelectResult<Relation, S>>
516
+ const bulkResultType = (sel) => (0, hooks_ast_1.typeRef)('BulkMutationResult', [(0, hooks_ast_1.inferSelectResultType)(relationTypeName, sel)]);
517
+ // Overload with fields
518
+ const o1ParamType = t.tsIntersectionType([
519
+ t.tsTypeLiteral([
520
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(buildFieldsSelectionType((0, hooks_ast_1.sRef)(), selectTypeName))),
521
+ ]),
522
+ (0, hooks_ast_1.useMutationOptionsType)(bulkResultType((0, hooks_ast_1.sRef)()), varType),
523
+ ]);
524
+ const o1 = (0, hooks_ast_1.exportDeclareFunction)(hookName, (0, hooks_ast_1.createSTypeParam)(selectTypeName), [(0, hooks_ast_1.createFunctionParam)('params', o1ParamType)], (0, hooks_ast_1.useMutationResultType)(bulkResultType((0, hooks_ast_1.sRef)()), varType));
525
+ (0, hooks_ast_1.addJSDocComment)(o1, [
526
+ table.description || `Bulk ${op.replace('bulk', '').toLowerCase()} mutation hook for ${typeName}`,
527
+ ]);
528
+ statements.push(o1);
529
+ // Implementation
530
+ const implSelProp = t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation((0, hooks_ast_1.selectionConfigType)((0, hooks_ast_1.typeRef)(selectTypeName))));
531
+ const implParamType = t.tsIntersectionType([
532
+ t.tsTypeLiteral([implSelProp]),
533
+ (0, hooks_ast_1.omitType)((0, hooks_ast_1.typeRef)('UseMutationOptions', [
534
+ t.tsAnyKeyword(),
535
+ (0, hooks_ast_1.typeRef)('Error'),
536
+ varType,
537
+ ]), ['mutationFn']),
538
+ ]);
539
+ const body = [];
540
+ body.push((0, hooks_ast_1.buildSelectionArgsCall)(selectTypeName));
541
+ body.push((0, hooks_ast_1.destructureParamsWithSelection)('mutationOptions'));
542
+ body.push((0, hooks_ast_1.voidStatement)('_selection'));
543
+ body.push((0, hooks_ast_1.constDecl)('queryClient', (0, hooks_ast_1.callExpr)('useQueryClient', [])));
544
+ const mutationKeyExpr = useCentralizedKeys
545
+ ? (0, hooks_ast_1.callExpr)(t.memberExpression(t.identifier(mutationKeysName), t.identifier(op)), [])
546
+ : undefined;
547
+ // Build the ORM method call depending on the operation
548
+ const ormMethodName = op;
549
+ const mutationFnArgs = (() => {
550
+ switch (op) {
551
+ case 'bulkCreate':
552
+ return t.objectExpression([
553
+ (0, hooks_ast_1.shorthandProp)('data'),
554
+ (0, hooks_ast_1.objectProp)('onConflict', t.memberExpression(t.identifier('vars'), t.identifier('onConflict'))),
555
+ (0, hooks_ast_1.objectProp)('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
556
+ ]);
557
+ case 'bulkUpsert':
558
+ return t.objectExpression([
559
+ (0, hooks_ast_1.shorthandProp)('data'),
560
+ (0, hooks_ast_1.objectProp)('onConflict', t.memberExpression(t.identifier('vars'), t.identifier('onConflict'))),
561
+ (0, hooks_ast_1.objectProp)('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
562
+ ]);
563
+ case 'bulkUpdate':
564
+ return t.objectExpression([
565
+ (0, hooks_ast_1.objectProp)('where', t.memberExpression(t.identifier('vars'), t.identifier('where'))),
566
+ (0, hooks_ast_1.shorthandProp)('data'),
567
+ (0, hooks_ast_1.objectProp)('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
568
+ ]);
569
+ case 'bulkDelete':
570
+ return t.objectExpression([
571
+ (0, hooks_ast_1.objectProp)('where', t.memberExpression(t.identifier('vars'), t.identifier('where'))),
572
+ (0, hooks_ast_1.objectProp)('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
573
+ ]);
574
+ }
575
+ })();
576
+ const varsParam = (0, hooks_ast_1.createFunctionParam)('vars', varType);
577
+ const mutationFnExpr = t.arrowFunctionExpression([varsParam], (0, hooks_ast_1.getClientCallUnwrap)(singularName, ormMethodName, mutationFnArgs));
578
+ // onSuccess: invalidate lists
579
+ const listKeyExpr = useCentralizedKeys
580
+ ? (0, hooks_ast_1.callExpr)(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
581
+ : t.arrayExpression([
582
+ t.stringLiteral(typeName.toLowerCase()),
583
+ t.stringLiteral('list'),
584
+ ]);
585
+ const onSuccessFn = t.arrowFunctionExpression([], t.blockStatement([
586
+ t.expressionStatement((0, hooks_ast_1.callExpr)(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([(0, hooks_ast_1.objectProp)('queryKey', listKeyExpr)])])),
587
+ ]));
588
+ body.push((0, hooks_ast_1.returnUseMutation)(mutationFnExpr, [
589
+ (0, hooks_ast_1.objectProp)('onSuccess', onSuccessFn),
590
+ (0, hooks_ast_1.spreadObj)(t.identifier('mutationOptions')),
591
+ ], mutationKeyExpr));
592
+ statements.push((0, hooks_ast_1.exportFunction)(hookName, null, [(0, hooks_ast_1.createFunctionParam)('params', implParamType)], body));
593
+ return {
594
+ fileName,
595
+ content: (0, hooks_ast_1.generateHookFileCode)(table.description || `Bulk ${op.replace('bulk', '').toLowerCase()} mutation hook for ${typeName}`, statements),
596
+ };
597
+ }
419
598
  function generateAllMutationHooks(tables, options = {}) {
420
599
  const files = [];
421
600
  for (const table of tables) {
@@ -431,6 +610,14 @@ function generateAllMutationHooks(tables, options = {}) {
431
610
  if (deleteHook) {
432
611
  files.push(deleteHook);
433
612
  }
613
+ // Bulk mutation hooks
614
+ const bulkOps = ['bulkCreate', 'bulkUpsert', 'bulkUpdate', 'bulkDelete'];
615
+ for (const op of bulkOps) {
616
+ const hook = generateBulkMutationHook(table, op, options);
617
+ if (hook) {
618
+ files.push(hook);
619
+ }
620
+ }
434
621
  }
435
622
  return files;
436
623
  }
@@ -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;