@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
package/core/generate.js CHANGED
@@ -89,9 +89,6 @@ async function generate(options = {}, internalOptions) {
89
89
  const runReactQuery = config.reactQuery ?? false;
90
90
  const runCli = internalOptions?.skipCli ? false : !!config.cli;
91
91
  const runOrm = runReactQuery || !!config.cli || (options.orm !== undefined ? !!options.orm : false);
92
- // Auto-enable nodeHttpAdapter when CLI is enabled, unless explicitly set to false
93
- const useNodeHttpAdapter = options.nodeHttpAdapter === true ||
94
- (runCli && options.nodeHttpAdapter !== false);
95
92
  const schemaEnabled = !!options.schema?.enabled;
96
93
  if (!schemaEnabled && !runReactQuery && !runOrm && !runCli) {
97
94
  return {
@@ -226,22 +223,13 @@ async function generate(options = {}, internalOptions) {
226
223
  mutations: customOperations.mutations,
227
224
  typeRegistry: customOperations.typeRegistry,
228
225
  },
229
- config: { ...config, nodeHttpAdapter: useNodeHttpAdapter },
226
+ config,
230
227
  sharedTypesPath: bothEnabled ? '..' : undefined,
231
228
  });
232
229
  filesToWrite.push(...files.map((file) => ({
233
230
  ...file,
234
231
  path: node_path_1.default.posix.join('orm', file.path),
235
232
  })));
236
- // Generate NodeHttpAdapter in ORM output when enabled
237
- if (useNodeHttpAdapter) {
238
- const { generateNodeFetchFile } = await Promise.resolve().then(() => __importStar(require('./codegen/cli/utils-generator')));
239
- const nodeFetchFile = generateNodeFetchFile();
240
- filesToWrite.push({
241
- path: node_path_1.default.posix.join('orm', nodeFetchFile.fileName),
242
- content: nodeFetchFile.content,
243
- });
244
- }
245
233
  }
246
234
  // Generate CLI commands
247
235
  if (runCli) {
@@ -252,7 +240,7 @@ async function generate(options = {}, internalOptions) {
252
240
  queries: customOperations.queries,
253
241
  mutations: customOperations.mutations,
254
242
  },
255
- config: { ...config, nodeHttpAdapter: useNodeHttpAdapter },
243
+ config,
256
244
  typeRegistry: customOperations.typeRegistry,
257
245
  });
258
246
  filesToWrite.push(...files.map((file) => ({
@@ -582,16 +570,11 @@ async function generateMulti(options) {
582
570
  if (useUnifiedCli && cliTargets.length > 0 && !dryRun) {
583
571
  const cliConfig = typeof unifiedCli === 'object' ? unifiedCli : {};
584
572
  const toolName = cliConfig.toolName ?? 'app';
585
- // Auto-enable nodeHttpAdapter for unified CLI unless explicitly disabled
586
- // Check first target config for explicit nodeHttpAdapter setting
587
573
  const firstTargetConfig = configs[names[0]];
588
- const multiNodeHttpAdapter = firstTargetConfig?.nodeHttpAdapter === true ||
589
- (firstTargetConfig?.nodeHttpAdapter !== false);
590
574
  const { files } = (0, cli_1.generateMultiTargetCli)({
591
575
  toolName,
592
576
  builtinNames: cliConfig.builtinNames,
593
577
  targets: cliTargets,
594
- nodeHttpAdapter: multiNodeHttpAdapter,
595
578
  entryPoint: cliConfig.entryPoint,
596
579
  });
597
580
  const cliFilesToWrite = files.map((file) => ({
@@ -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;
@@ -7,12 +7,8 @@ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false
7
7
  decl.importKind = typeOnly ? 'type' : 'value';
8
8
  return decl;
9
9
  }
10
- export function generateExecutorFile(toolName, options) {
10
+ export function generateExecutorFile(toolName) {
11
11
  const statements = [];
12
- // Import NodeHttpAdapter for *.localhost subdomain routing
13
- if (options?.nodeHttpAdapter) {
14
- statements.push(createImportDeclaration('./node-fetch', ['NodeHttpAdapter']));
15
- }
16
12
  statements.push(createImportDeclaration('appstash', ['createConfigStore']));
17
13
  statements.push(createImportDeclaration('../orm', ['createClient']));
18
14
  statements.push(t.variableDeclaration('const', [
@@ -72,26 +68,12 @@ export function generateExecutorFile(toolName, options) {
72
68
  ]))),
73
69
  ])),
74
70
  ])),
75
- // Build createClient config — use NodeHttpAdapter for *.localhost endpoints
76
- ...(options?.nodeHttpAdapter
77
- ? [
78
- t.returnStatement(t.callExpression(t.identifier('createClient'), [
79
- t.objectExpression([
80
- t.objectProperty(t.identifier('adapter'), t.newExpression(t.identifier('NodeHttpAdapter'), [
81
- t.memberExpression(t.identifier('ctx'), t.identifier('endpoint')),
82
- t.identifier('headers'),
83
- ])),
84
- ]),
85
- ])),
86
- ]
87
- : [
88
- t.returnStatement(t.callExpression(t.identifier('createClient'), [
89
- t.objectExpression([
90
- t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('ctx'), t.identifier('endpoint'))),
91
- t.objectProperty(t.identifier('headers'), t.identifier('headers')),
92
- ]),
93
- ])),
71
+ t.returnStatement(t.callExpression(t.identifier('createClient'), [
72
+ t.objectExpression([
73
+ t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('ctx'), t.identifier('endpoint'))),
74
+ t.objectProperty(t.identifier('headers'), t.identifier('headers')),
94
75
  ]),
76
+ ])),
95
77
  ]);
96
78
  const getClientFunc = t.functionDeclaration(t.identifier('getClient'), [contextNameParam], getClientBody);
97
79
  statements.push(t.exportNamedDeclaration(getClientFunc));
@@ -102,12 +84,8 @@ export function generateExecutorFile(toolName, options) {
102
84
  content: header + '\n' + code,
103
85
  };
104
86
  }
105
- export function generateMultiTargetExecutorFile(toolName, targets, options) {
87
+ export function generateMultiTargetExecutorFile(toolName, targets) {
106
88
  const statements = [];
107
- // Import NodeHttpAdapter for *.localhost subdomain routing
108
- if (options?.nodeHttpAdapter) {
109
- statements.push(createImportDeclaration('./node-fetch', ['NodeHttpAdapter']));
110
- }
111
89
  statements.push(createImportDeclaration('appstash', ['createConfigStore']));
112
90
  for (const target of targets) {
113
91
  const aliasName = `create${target.name[0].toUpperCase()}${target.name.slice(1)}Client`;
@@ -198,26 +176,12 @@ export function generateMultiTargetExecutorFile(toolName, targets, options) {
198
176
  ]), t.blockStatement([
199
177
  t.expressionStatement(t.assignmentExpression('=', t.identifier('endpoint'), t.logicalExpression('||', t.memberExpression(t.identifier('defaultEndpoints'), t.identifier('targetName'), true), t.stringLiteral('')))),
200
178
  ])),
201
- // Build createClient config — use NodeHttpAdapter for *.localhost endpoints
202
- ...(options?.nodeHttpAdapter
203
- ? [
204
- t.returnStatement(t.callExpression(t.identifier('createFn'), [
205
- t.objectExpression([
206
- t.objectProperty(t.identifier('adapter'), t.newExpression(t.identifier('NodeHttpAdapter'), [
207
- t.identifier('endpoint'),
208
- t.identifier('headers'),
209
- ])),
210
- ]),
211
- ])),
212
- ]
213
- : [
214
- t.returnStatement(t.callExpression(t.identifier('createFn'), [
215
- t.objectExpression([
216
- t.objectProperty(t.identifier('endpoint'), t.identifier('endpoint')),
217
- t.objectProperty(t.identifier('headers'), t.identifier('headers')),
218
- ]),
219
- ])),
179
+ t.returnStatement(t.callExpression(t.identifier('createFn'), [
180
+ t.objectExpression([
181
+ t.objectProperty(t.identifier('endpoint'), t.identifier('endpoint')),
182
+ t.objectProperty(t.identifier('headers'), t.identifier('headers')),
220
183
  ]),
184
+ ])),
221
185
  ]);
222
186
  const getClientFunc = t.functionDeclaration(t.identifier('getClient'), [targetNameParam, contextNameParam], getClientBody);
223
187
  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
  }
@@ -5,7 +5,7 @@ import { generateExecutorFile, generateMultiTargetExecutorFile } from './executo
5
5
  import { generateHelpersFile } from './helpers-generator';
6
6
  import { generateAuthCommand, generateAuthCommandWithName, generateContextCommand, generateMultiTargetContextCommand, } from './infra-generator';
7
7
  import { generateTableCommand } from './table-command-generator';
8
- import { generateUtilsFile, generateNodeFetchFile, generateEntryPointFile, generateEmbedderFile } from './utils-generator';
8
+ import { generateUtilsFile, generateEntryPointFile, generateEmbedderFile } from './utils-generator';
9
9
  export function generateCli(options) {
10
10
  const { tables, customOperations, config } = options;
11
11
  const files = [];
@@ -13,11 +13,7 @@ export function generateCli(options) {
13
13
  const toolName = typeof cliConfig === 'object' && cliConfig.toolName
14
14
  ? cliConfig.toolName
15
15
  : 'app';
16
- // Use top-level nodeHttpAdapter from config (auto-enabled for CLI by generate.ts)
17
- const useNodeHttpAdapter = !!config.nodeHttpAdapter;
18
- const executorFile = generateExecutorFile(toolName, {
19
- nodeHttpAdapter: useNodeHttpAdapter,
20
- });
16
+ const executorFile = generateExecutorFile(toolName);
21
17
  files.push(executorFile);
22
18
  const utilsFile = generateUtilsFile();
23
19
  files.push(utilsFile);
@@ -26,10 +22,6 @@ export function generateCli(options) {
26
22
  if (hasAnyEmbeddings) {
27
23
  files.push(generateEmbedderFile());
28
24
  }
29
- // Generate node HTTP adapter if configured (for *.localhost subdomain routing)
30
- if (useNodeHttpAdapter) {
31
- files.push(generateNodeFetchFile());
32
- }
33
25
  const contextFile = generateContextCommand(toolName);
34
26
  files.push(contextFile);
35
27
  const authFile = generateAuthCommand(toolName);
@@ -91,9 +83,7 @@ export function generateMultiTargetCli(options) {
91
83
  endpoint: t.endpoint,
92
84
  ormImportPath: t.ormImportPath,
93
85
  }));
94
- const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs, {
95
- nodeHttpAdapter: !!options.nodeHttpAdapter,
96
- });
86
+ const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs);
97
87
  files.push(executorFile);
98
88
  const utilsFile = generateUtilsFile();
99
89
  files.push(utilsFile);
@@ -102,10 +92,6 @@ export function generateMultiTargetCli(options) {
102
92
  if (hasAnyMtEmbeddings) {
103
93
  files.push(generateEmbedderFile());
104
94
  }
105
- // Generate node HTTP adapter if configured (for *.localhost subdomain routing)
106
- if (options.nodeHttpAdapter) {
107
- files.push(generateNodeFetchFile());
108
- }
109
95
  const contextFile = generateMultiTargetContextCommand(toolName, builtinNames.context, targets.map((t) => ({ name: t.name, endpoint: t.endpoint })));
110
96
  files.push(contextFile);
111
97
  const authFile = generateAuthCommandWithName(toolName, builtinNames.auth);
@@ -275,7 +275,7 @@ function buildFindManyArgsType(table) {
275
275
  }
276
276
  /**
277
277
  * Build the FindFirstArgs type instantiation for a table:
278
- * FindFirstArgs<SelectType, FilterType> & { select: SelectType }
278
+ * FindFirstArgs<SelectType, FilterType, OrderByType> & { select: SelectType }
279
279
  *
280
280
  * The intersection with { select: SelectType } makes select required,
281
281
  * matching what the ORM's findFirst method expects.
@@ -284,9 +284,11 @@ function buildFindFirstArgsType(table) {
284
284
  const { typeName } = getTableNames(table);
285
285
  const selectTypeName = `${typeName}Select`;
286
286
  const whereTypeName = getFilterTypeName(table);
287
+ const orderByTypeName = getOrderByTypeName(table);
287
288
  const findFirstType = t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
288
289
  t.tsTypeReference(t.identifier(selectTypeName)),
289
290
  t.tsTypeReference(t.identifier(whereTypeName)),
291
+ t.tsTypeReference(t.identifier(orderByTypeName)),
290
292
  ]));
291
293
  // Intersect with { select: SelectType } to make select required
292
294
  return t.tsIntersectionType([
@@ -679,6 +681,114 @@ function buildMutationHandler(table, operation, vectorFieldNames, targetName, ty
679
681
  t.tryStatement(t.blockStatement(tryBody), buildErrorCatch(`Failed to ${operation} record.`)),
680
682
  ]), false, true);
681
683
  }
684
+ function buildBulkMutationHandler(table, operation, targetName) {
685
+ const { singularName } = getTableNames(table);
686
+ const selectObj = buildSelectObject(table);
687
+ // Map CLI op name to ORM method name
688
+ const ormMethod = (() => {
689
+ switch (operation) {
690
+ case 'bulk-create': return 'bulkCreate';
691
+ case 'bulk-upsert': return 'bulkUpsert';
692
+ case 'bulk-update': return 'bulkUpdate';
693
+ case 'bulk-delete': return 'bulkDelete';
694
+ }
695
+ })();
696
+ const tryBody = [];
697
+ if (operation === 'bulk-create' || operation === 'bulk-upsert') {
698
+ // Parse --data (JSON array) from argv
699
+ tryBody.push(t.variableDeclaration('const', [
700
+ t.variableDeclarator(t.identifier('dataRaw'), t.memberExpression(t.identifier('argv'), t.identifier('data'))),
701
+ ]));
702
+ tryBody.push(t.ifStatement(t.unaryExpression('!', t.identifier('dataRaw')), t.blockStatement([
703
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [t.stringLiteral(`--data is required for ${operation}. Provide a JSON array.`)])),
704
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
705
+ ])));
706
+ tryBody.push(t.variableDeclaration('const', [
707
+ t.variableDeclarator(t.identifier('data'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('dataRaw'), t.tsStringKeyword())])),
708
+ ]));
709
+ let ormArgs;
710
+ if (operation === 'bulk-upsert') {
711
+ // Also parse --on-conflict
712
+ tryBody.push(t.variableDeclaration('const', [
713
+ t.variableDeclarator(t.identifier('onConflictRaw'), t.memberExpression(t.identifier('argv'), t.identifier('on-conflict'))),
714
+ ]));
715
+ tryBody.push(t.variableDeclaration('const', [
716
+ 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([]))),
717
+ ]));
718
+ ormArgs = t.objectExpression([
719
+ t.objectProperty(t.identifier('data'), t.identifier('data')),
720
+ t.objectProperty(t.identifier('onConflict'), t.identifier('onConflict')),
721
+ t.objectProperty(t.identifier('select'), selectObj),
722
+ ]);
723
+ }
724
+ else {
725
+ ormArgs = t.objectExpression([
726
+ t.objectProperty(t.identifier('data'), t.identifier('data')),
727
+ t.objectProperty(t.identifier('select'), selectObj),
728
+ ]);
729
+ }
730
+ tryBody.push(buildGetClientStatement(targetName));
731
+ tryBody.push(t.variableDeclaration('const', [
732
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, ormMethod, ormArgs))),
733
+ ]));
734
+ }
735
+ else if (operation === 'bulk-update') {
736
+ // Parse --where (JSON) and --data (JSON)
737
+ tryBody.push(t.variableDeclaration('const', [
738
+ t.variableDeclarator(t.identifier('whereRaw'), t.memberExpression(t.identifier('argv'), t.identifier('where'))),
739
+ ]));
740
+ tryBody.push(t.variableDeclaration('const', [
741
+ t.variableDeclarator(t.identifier('dataRaw'), t.memberExpression(t.identifier('argv'), t.identifier('data'))),
742
+ ]));
743
+ tryBody.push(t.ifStatement(t.logicalExpression('||', t.unaryExpression('!', t.identifier('whereRaw')), t.unaryExpression('!', t.identifier('dataRaw'))), t.blockStatement([
744
+ 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.')])),
745
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
746
+ ])));
747
+ tryBody.push(t.variableDeclaration('const', [
748
+ t.variableDeclarator(t.identifier('where'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('whereRaw'), t.tsStringKeyword())])),
749
+ ]));
750
+ tryBody.push(t.variableDeclaration('const', [
751
+ t.variableDeclarator(t.identifier('data'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('dataRaw'), t.tsStringKeyword())])),
752
+ ]));
753
+ tryBody.push(buildGetClientStatement(targetName));
754
+ tryBody.push(t.variableDeclaration('const', [
755
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, ormMethod, t.objectExpression([
756
+ t.objectProperty(t.identifier('where'), t.identifier('where')),
757
+ t.objectProperty(t.identifier('data'), t.identifier('data')),
758
+ t.objectProperty(t.identifier('select'), selectObj),
759
+ ])))),
760
+ ]));
761
+ }
762
+ else {
763
+ // bulk-delete: parse --where (JSON)
764
+ tryBody.push(t.variableDeclaration('const', [
765
+ t.variableDeclarator(t.identifier('whereRaw'), t.memberExpression(t.identifier('argv'), t.identifier('where'))),
766
+ ]));
767
+ tryBody.push(t.ifStatement(t.unaryExpression('!', t.identifier('whereRaw')), t.blockStatement([
768
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [t.stringLiteral('--where is required for bulk-delete. Provide a JSON object.')])),
769
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
770
+ ])));
771
+ tryBody.push(t.variableDeclaration('const', [
772
+ t.variableDeclarator(t.identifier('where'), t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.tsAsExpression(t.identifier('whereRaw'), t.tsStringKeyword())])),
773
+ ]));
774
+ tryBody.push(buildGetClientStatement(targetName));
775
+ tryBody.push(t.variableDeclaration('const', [
776
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, ormMethod, t.objectExpression([
777
+ t.objectProperty(t.identifier('where'), t.identifier('where')),
778
+ t.objectProperty(t.identifier('select'), selectObj),
779
+ ])))),
780
+ ]));
781
+ }
782
+ tryBody.push(buildJsonLog(t.identifier('result')));
783
+ const argvParam = t.identifier('argv');
784
+ argvParam.typeAnnotation = buildArgvType();
785
+ const prompterParam = t.identifier('prompter');
786
+ prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
787
+ const handlerName = `handle${toPascalCase(operation)}`;
788
+ return t.functionDeclaration(t.identifier(handlerName), [argvParam, prompterParam], t.blockStatement([
789
+ t.tryStatement(t.blockStatement(tryBody), buildErrorCatch(`Failed to ${operation}.`)),
790
+ ]), false, true);
791
+ }
682
792
  export function generateTableCommand(table, options) {
683
793
  const { singularName, typeName } = getTableNames(table);
684
794
  const commandName = toKebabCase(singularName);
@@ -756,6 +866,11 @@ export function generateTableCommand(table, options) {
756
866
  const embedderPath = options?.targetName ? '../../embedder' : '../embedder';
757
867
  statements.push(createImportDeclaration(embedderPath, ['resolveEmbedder', 'autoEmbedWhere', 'autoEmbedInput']));
758
868
  }
869
+ // Detect bulk mutations
870
+ const hasBulkCreate = !!table.query?.bulkInsert;
871
+ const hasBulkUpsert = !!table.query?.bulkUpsert;
872
+ const hasBulkUpdate = !!table.query?.bulkUpdate;
873
+ const hasBulkDelete = !!table.query?.bulkDelete;
759
874
  const subcommands = ['list', 'find-first'];
760
875
  if (hasSearchFields)
761
876
  subcommands.push('search');
@@ -766,6 +881,14 @@ export function generateTableCommand(table, options) {
766
881
  subcommands.push('update');
767
882
  if (hasDelete)
768
883
  subcommands.push('delete');
884
+ if (hasBulkCreate)
885
+ subcommands.push('bulk-create');
886
+ if (hasBulkUpsert)
887
+ subcommands.push('bulk-upsert');
888
+ if (hasBulkUpdate)
889
+ subcommands.push('bulk-update');
890
+ if (hasBulkDelete)
891
+ subcommands.push('bulk-delete');
769
892
  const usageLines = [
770
893
  '',
771
894
  `${commandName} <command>`,
@@ -786,7 +909,15 @@ export function generateTableCommand(table, options) {
786
909
  }
787
910
  if (hasDelete)
788
911
  usageLines.push(` delete Delete a ${singularName}`);
789
- 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)', '');
912
+ if (hasBulkCreate)
913
+ usageLines.push(` bulk-create Bulk create ${singularName} records`);
914
+ if (hasBulkUpsert)
915
+ usageLines.push(` bulk-upsert Bulk upsert ${singularName} records`);
916
+ if (hasBulkUpdate)
917
+ usageLines.push(` bulk-update Bulk update ${singularName} records`);
918
+ if (hasBulkDelete)
919
+ usageLines.push(` bulk-delete Bulk delete ${singularName} records`);
920
+ 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)', '');
790
921
  if (hasSearchFields) {
791
922
  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');
792
923
  if (hasEmbeddings) {
@@ -869,6 +1000,14 @@ export function generateTableCommand(table, options) {
869
1000
  statements.push(buildMutationHandler(table, 'update', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
870
1001
  if (hasDelete)
871
1002
  statements.push(buildMutationHandler(table, 'delete', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
1003
+ if (hasBulkCreate)
1004
+ statements.push(buildBulkMutationHandler(table, 'bulk-create', tn));
1005
+ if (hasBulkUpsert)
1006
+ statements.push(buildBulkMutationHandler(table, 'bulk-upsert', tn));
1007
+ if (hasBulkUpdate)
1008
+ statements.push(buildBulkMutationHandler(table, 'bulk-update', tn));
1009
+ if (hasBulkDelete)
1010
+ statements.push(buildBulkMutationHandler(table, 'bulk-delete', tn));
872
1011
  const header = getGeneratedFileHeader(`CLI commands for ${table.name}`);
873
1012
  const code = generateCode(statements);
874
1013
  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
  *
@@ -40,19 +40,6 @@ export function generateUtilsFile() {
40
40
  content: readTemplateFile('cli-utils.ts', 'CLI utility functions for type coercion and input handling'),
41
41
  };
42
42
  }
43
- /**
44
- * Generate a node-fetch.ts file with NodeHttpAdapter for CLI.
45
- *
46
- * Provides a GraphQLAdapter implementation using node:http/node:https
47
- * instead of the Fetch API. This cleanly handles *.localhost subdomain
48
- * routing (DNS resolution + Host header) without any global patching.
49
- */
50
- export function generateNodeFetchFile() {
51
- return {
52
- fileName: 'node-fetch.ts',
53
- content: readTemplateFile('node-fetch.ts', 'Node HTTP adapter for localhost subdomain routing'),
54
- };
55
- }
56
43
  /**
57
44
  * Generate an index.ts entry point file for the CLI.
58
45
  *
@@ -77,6 +77,24 @@ function generateEntityMutationKeysDeclaration(table, relationships) {
77
77
  const deleteProp = t.objectProperty(t.identifier('delete'), deleteArrowFn);
78
78
  addJSDocComment(deleteProp, [`Delete ${singularName} mutation key`]);
79
79
  properties.push(deleteProp);
80
+ // Bulk mutation keys (only if table has bulk operations)
81
+ const bulkOps = [
82
+ { key: 'bulkCreate', queryField: table.query?.bulkInsert },
83
+ { key: 'bulkUpsert', queryField: table.query?.bulkUpsert },
84
+ { key: 'bulkUpdate', queryField: table.query?.bulkUpdate },
85
+ { key: 'bulkDelete', queryField: table.query?.bulkDelete },
86
+ ];
87
+ for (const { key, queryField } of bulkOps) {
88
+ if (!queryField)
89
+ continue;
90
+ const arrowFn = t.arrowFunctionExpression([], constArray([
91
+ t.stringLiteral('mutation'),
92
+ t.stringLiteral(entityKey),
93
+ t.stringLiteral(key),
94
+ ]));
95
+ const prop = t.objectProperty(t.identifier(key), arrowFn);
96
+ properties.push(prop);
97
+ }
80
98
  return t.exportNamedDeclaration(t.variableDeclaration('const', [
81
99
  t.variableDeclarator(t.identifier(keysName), asConst(t.objectExpression(properties))),
82
100
  ]));