@constructive-io/graphql-codegen 4.9.0 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/core/codegen/cli/arg-mapper.d.ts +1 -1
  2. package/core/codegen/cli/arg-mapper.js +15 -11
  3. package/core/codegen/cli/command-map-generator.d.ts +1 -0
  4. package/core/codegen/cli/command-map-generator.js +4 -0
  5. package/core/codegen/cli/config-command-generator.d.ts +11 -0
  6. package/core/codegen/cli/config-command-generator.js +458 -0
  7. package/core/codegen/cli/custom-command-generator.js +4 -3
  8. package/core/codegen/cli/docs-generator.d.ts +6 -5
  9. package/core/codegen/cli/docs-generator.js +167 -64
  10. package/core/codegen/cli/helpers-generator.d.ts +15 -0
  11. package/core/codegen/cli/helpers-generator.js +119 -0
  12. package/core/codegen/cli/index.d.ts +4 -0
  13. package/core/codegen/cli/index.js +21 -3
  14. package/core/codegen/cli/table-command-generator.d.ts +15 -0
  15. package/core/codegen/cli/table-command-generator.js +20 -1
  16. package/core/codegen/docs-utils.d.ts +26 -1
  17. package/core/codegen/docs-utils.js +105 -0
  18. package/core/codegen/orm/index.js +3 -2
  19. package/core/codegen/orm/input-types-generator.d.ts +3 -1
  20. package/core/codegen/orm/input-types-generator.js +123 -17
  21. package/core/codegen/orm/model-generator.d.ts +6 -2
  22. package/core/codegen/orm/model-generator.js +59 -29
  23. package/core/codegen/orm/select-types.d.ts +4 -2
  24. package/core/codegen/scalars.js +8 -0
  25. package/core/codegen/templates/cli-entry.ts +2 -2
  26. package/core/codegen/templates/cli-utils.ts +28 -0
  27. package/core/codegen/templates/query-builder.ts +28 -5
  28. package/core/codegen/templates/select-types.ts +4 -2
  29. package/core/generate.js +14 -4
  30. package/esm/core/codegen/cli/arg-mapper.d.ts +1 -1
  31. package/esm/core/codegen/cli/arg-mapper.js +15 -11
  32. package/esm/core/codegen/cli/command-map-generator.d.ts +1 -0
  33. package/esm/core/codegen/cli/command-map-generator.js +4 -0
  34. package/esm/core/codegen/cli/config-command-generator.d.ts +11 -0
  35. package/esm/core/codegen/cli/config-command-generator.js +422 -0
  36. package/esm/core/codegen/cli/custom-command-generator.js +4 -3
  37. package/esm/core/codegen/cli/docs-generator.d.ts +6 -5
  38. package/esm/core/codegen/cli/docs-generator.js +168 -65
  39. package/esm/core/codegen/cli/helpers-generator.d.ts +15 -0
  40. package/esm/core/codegen/cli/helpers-generator.js +83 -0
  41. package/esm/core/codegen/cli/index.d.ts +4 -0
  42. package/esm/core/codegen/cli/index.js +18 -2
  43. package/esm/core/codegen/cli/table-command-generator.d.ts +15 -0
  44. package/esm/core/codegen/cli/table-command-generator.js +20 -3
  45. package/esm/core/codegen/docs-utils.d.ts +26 -1
  46. package/esm/core/codegen/docs-utils.js +102 -0
  47. package/esm/core/codegen/orm/index.js +3 -2
  48. package/esm/core/codegen/orm/input-types-generator.d.ts +3 -1
  49. package/esm/core/codegen/orm/input-types-generator.js +123 -17
  50. package/esm/core/codegen/orm/model-generator.d.ts +6 -2
  51. package/esm/core/codegen/orm/model-generator.js +59 -29
  52. package/esm/core/codegen/orm/select-types.d.ts +4 -2
  53. package/esm/core/codegen/scalars.js +8 -0
  54. package/esm/core/generate.js +14 -4
  55. package/esm/types/config.d.ts +9 -0
  56. package/esm/types/config.js +1 -0
  57. package/package.json +11 -11
  58. package/types/config.d.ts +9 -0
  59. package/types/config.js +1 -0
@@ -0,0 +1,83 @@
1
+ import * as t from '@babel/types';
2
+ import { generateCode } from '../babel-ast';
3
+ import { getGeneratedFileHeader } from '../utils';
4
+ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false) {
5
+ const specifiers = namedImports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
6
+ const decl = t.importDeclaration(specifiers, t.stringLiteral(moduleSpecifier));
7
+ decl.importKind = typeOnly ? 'type' : 'value';
8
+ return decl;
9
+ }
10
+ /**
11
+ * Generate helpers.ts with typed per-target client factories.
12
+ *
13
+ * Each target gets a `createXxxClient(contextName?)` function that uses
14
+ * `store.getClientConfig(targetName, contextName)` under the hood with
15
+ * 3-tier resolution: appstash store -> env vars -> throw.
16
+ *
17
+ * Also re-exports the store for direct config/var access.
18
+ */
19
+ export function generateHelpersFile(toolName, targets) {
20
+ const statements = [];
21
+ // import { createConfigStore } from 'appstash';
22
+ statements.push(createImportDeclaration('appstash', ['createConfigStore']));
23
+ // import type { ClientConfig } from 'appstash';
24
+ statements.push(createImportDeclaration('appstash', ['ClientConfig'], true));
25
+ // Import createClient from each target's ORM
26
+ for (const target of targets) {
27
+ const aliasName = `create${target.name[0].toUpperCase()}${target.name.slice(1)}OrmClient`;
28
+ const specifier = t.importSpecifier(t.identifier(aliasName), t.identifier('createClient'));
29
+ statements.push(t.importDeclaration([specifier], t.stringLiteral(target.ormImportPath)));
30
+ }
31
+ // const store = createConfigStore('toolName');
32
+ statements.push(t.variableDeclaration('const', [
33
+ t.variableDeclarator(t.identifier('store'), t.callExpression(t.identifier('createConfigStore'), [
34
+ t.stringLiteral(toolName),
35
+ ])),
36
+ ]));
37
+ // export const getStore = () => store;
38
+ const getStoreExport = t.variableDeclaration('const', [
39
+ t.variableDeclarator(t.identifier('getStore'), t.arrowFunctionExpression([], t.identifier('store'))),
40
+ ]);
41
+ statements.push(t.exportNamedDeclaration(getStoreExport));
42
+ // export function getClientConfig(targetName: string, contextName?: string): ClientConfig
43
+ const targetNameParam = t.identifier('targetName');
44
+ targetNameParam.typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword());
45
+ const contextNameParam = t.identifier('contextName');
46
+ contextNameParam.optional = true;
47
+ contextNameParam.typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword());
48
+ const getClientConfigBody = t.blockStatement([
49
+ t.returnStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('getClientConfig')), [t.identifier('targetName'), t.identifier('contextName')])),
50
+ ]);
51
+ const getClientConfigFunc = t.functionDeclaration(t.identifier('getClientConfig'), [targetNameParam, contextNameParam], getClientConfigBody);
52
+ // Add return type annotation
53
+ const returnTypeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('ClientConfig')));
54
+ getClientConfigFunc.returnType = returnTypeAnnotation;
55
+ statements.push(t.exportNamedDeclaration(getClientConfigFunc));
56
+ // Generate typed factory for each target
57
+ for (const target of targets) {
58
+ const factoryName = `create${target.name[0].toUpperCase()}${target.name.slice(1)}Client`;
59
+ const ormAliasName = `create${target.name[0].toUpperCase()}${target.name.slice(1)}OrmClient`;
60
+ const ctxParam = t.identifier('contextName');
61
+ ctxParam.optional = true;
62
+ ctxParam.typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword());
63
+ const factoryBody = t.blockStatement([
64
+ t.variableDeclaration('const', [
65
+ t.variableDeclarator(t.identifier('config'), t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('getClientConfig')), [t.stringLiteral(target.name), t.identifier('contextName')])),
66
+ ]),
67
+ t.returnStatement(t.callExpression(t.identifier(ormAliasName), [
68
+ t.objectExpression([
69
+ t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('config'), t.identifier('endpoint'))),
70
+ t.objectProperty(t.identifier('headers'), t.memberExpression(t.identifier('config'), t.identifier('headers'))),
71
+ ]),
72
+ ])),
73
+ ]);
74
+ const factoryFunc = t.functionDeclaration(t.identifier(factoryName), [ctxParam], factoryBody);
75
+ statements.push(t.exportNamedDeclaration(factoryFunc));
76
+ }
77
+ const header = getGeneratedFileHeader('SDK helpers — typed per-target client factories with 3-tier credential resolution');
78
+ const code = generateCode(statements);
79
+ return {
80
+ fileName: 'helpers.ts',
81
+ content: header + '\n' + code,
82
+ };
83
+ }
@@ -47,12 +47,16 @@ export interface GenerateMultiTargetCliOptions {
47
47
  export declare function resolveBuiltinNames(targetNames: string[], userOverrides?: BuiltinNames): {
48
48
  auth: string;
49
49
  context: string;
50
+ config: string;
50
51
  };
51
52
  export declare function generateMultiTargetCli(options: GenerateMultiTargetCliOptions): GenerateCliResult;
52
53
  export { generateExecutorFile, generateMultiTargetExecutorFile } from './executor-generator';
53
54
  export { generateTableCommand } from './table-command-generator';
54
55
  export { generateCustomCommand } from './custom-command-generator';
55
56
  export { generateCommandMap, generateMultiTargetCommandMap } from './command-map-generator';
57
+ export { generateConfigCommand } from './config-command-generator';
58
+ export { generateHelpersFile } from './helpers-generator';
59
+ export type { HelpersGeneratorInput } from './helpers-generator';
56
60
  export { generateContextCommand, generateAuthCommand, generateMultiTargetContextCommand, generateAuthCommandWithName, } from './infra-generator';
57
61
  export { generateReadme, generateAgentsDocs, getCliMcpTools, generateSkills, generateMultiTargetReadme, generateMultiTargetAgentsDocs, getMultiTargetCliMcpTools, generateMultiTargetSkills, } from './docs-generator';
58
62
  export type { MultiTargetDocsInput } from './docs-generator';
@@ -1,6 +1,8 @@
1
1
  import { generateCommandMap, generateMultiTargetCommandMap } from './command-map-generator';
2
+ import { generateConfigCommand } from './config-command-generator';
2
3
  import { generateCustomCommand } from './custom-command-generator';
3
4
  import { generateExecutorFile, generateMultiTargetExecutorFile } from './executor-generator';
5
+ import { generateHelpersFile } from './helpers-generator';
4
6
  import { generateAuthCommand, generateAuthCommandWithName, generateContextCommand, generateMultiTargetContextCommand, } from './infra-generator';
5
7
  import { generateTableCommand } from './table-command-generator';
6
8
  import { generateUtilsFile, generateNodeFetchFile, generateEntryPointFile } from './utils-generator';
@@ -62,13 +64,17 @@ export function generateCli(options) {
62
64
  export function resolveBuiltinNames(targetNames, userOverrides) {
63
65
  let authName = userOverrides?.auth ?? 'auth';
64
66
  let contextName = userOverrides?.context ?? 'context';
67
+ let configName = userOverrides?.config ?? 'config';
65
68
  if (targetNames.includes(authName) && !userOverrides?.auth) {
66
69
  authName = 'credentials';
67
70
  }
68
71
  if (targetNames.includes(contextName) && !userOverrides?.context) {
69
72
  contextName = 'env';
70
73
  }
71
- return { auth: authName, context: contextName };
74
+ if (targetNames.includes(configName) && !userOverrides?.config) {
75
+ configName = 'vars';
76
+ }
77
+ return { auth: authName, context: contextName, config: configName };
72
78
  }
73
79
  export function generateMultiTargetCli(options) {
74
80
  const { toolName, targets } = options;
@@ -94,6 +100,14 @@ export function generateMultiTargetCli(options) {
94
100
  files.push(contextFile);
95
101
  const authFile = generateAuthCommandWithName(toolName, builtinNames.auth);
96
102
  files.push(authFile);
103
+ const configFile = generateConfigCommand(toolName, builtinNames.config);
104
+ files.push(configFile);
105
+ const helpersInputs = targets.map((t) => ({
106
+ name: t.name,
107
+ ormImportPath: t.ormImportPath,
108
+ }));
109
+ const helpersFile = generateHelpersFile(toolName, helpersInputs);
110
+ files.push(helpersFile);
97
111
  let totalTables = 0;
98
112
  let totalQueries = 0;
99
113
  let totalMutations = 0;
@@ -145,7 +159,7 @@ export function generateMultiTargetCli(options) {
145
159
  tables: totalTables,
146
160
  customQueries: totalQueries,
147
161
  customMutations: totalMutations,
148
- infraFiles: 4,
162
+ infraFiles: 6,
149
163
  totalFiles: files.length,
150
164
  },
151
165
  };
@@ -154,6 +168,8 @@ export { generateExecutorFile, generateMultiTargetExecutorFile } from './executo
154
168
  export { generateTableCommand } from './table-command-generator';
155
169
  export { generateCustomCommand } from './custom-command-generator';
156
170
  export { generateCommandMap, generateMultiTargetCommandMap } from './command-map-generator';
171
+ export { generateConfigCommand } from './config-command-generator';
172
+ export { generateHelpersFile } from './helpers-generator';
157
173
  export { generateContextCommand, generateAuthCommand, generateMultiTargetContextCommand, generateAuthCommandWithName, } from './infra-generator';
158
174
  export { generateReadme, generateAgentsDocs, getCliMcpTools, generateSkills, generateMultiTargetReadme, generateMultiTargetAgentsDocs, getMultiTargetCliMcpTools, generateMultiTargetSkills, } from './docs-generator';
159
175
  export { resolveDocsConfig } from '../docs-utils';
@@ -1,5 +1,20 @@
1
1
  import type { CleanTable, TypeRegistry } from '../../../types/schema';
2
2
  import type { GeneratedFile } from './executor-generator';
3
+ /**
4
+ * Get the set of field names that have defaults in the create input type.
5
+ * Looks up the CreateXInput -> inner input type (e.g. DatabaseInput) in the
6
+ * TypeRegistry and checks each field's defaultValue from introspection.
7
+ */
8
+ /**
9
+ * Resolve the inner input type from a CreateXInput or UpdateXInput type.
10
+ * The CreateXInput has an inner field (e.g. "database" of type DatabaseInput)
11
+ * that contains the actual field definitions.
12
+ */
13
+ export declare function resolveInnerInputType(inputTypeName: string, typeRegistry: TypeRegistry): {
14
+ name: string;
15
+ fields: Set<string>;
16
+ } | null;
17
+ export declare function getFieldsWithDefaults(table: CleanTable, typeRegistry?: TypeRegistry): Set<string>;
3
18
  export interface TableCommandOptions {
4
19
  targetName?: string;
5
20
  executorImportPath?: string;
@@ -67,6 +67,22 @@ function getTsTypeForField(field) {
67
67
  return t.tsStringKeyword();
68
68
  }
69
69
  }
70
+ /**
71
+ * Maps a GraphQL scalar type to the appropriate inquirerer question type.
72
+ * Used by table CRUD commands to generate semantic prompts.
73
+ */
74
+ function getQuestionTypeForField(field) {
75
+ const gqlType = field.type.gqlType.replace(/!/g, '');
76
+ switch (gqlType) {
77
+ case 'Boolean':
78
+ return 'boolean';
79
+ case 'JSON':
80
+ case 'GeoJSON':
81
+ return 'json';
82
+ default:
83
+ return 'text';
84
+ }
85
+ }
70
86
  function buildFieldSchemaObject(table) {
71
87
  const fields = getScalarFields(table);
72
88
  return t.objectExpression(fields.map((f) => {
@@ -214,7 +230,7 @@ function buildGetHandler(table, targetName) {
214
230
  * The CreateXInput has an inner field (e.g. "database" of type DatabaseInput)
215
231
  * that contains the actual field definitions.
216
232
  */
217
- function resolveInnerInputType(inputTypeName, typeRegistry) {
233
+ export function resolveInnerInputType(inputTypeName, typeRegistry) {
218
234
  const inputType = typeRegistry.get(inputTypeName);
219
235
  if (!inputType?.inputFields)
220
236
  return null;
@@ -232,7 +248,7 @@ function resolveInnerInputType(inputTypeName, typeRegistry) {
232
248
  }
233
249
  return null;
234
250
  }
235
- function getFieldsWithDefaults(table, typeRegistry) {
251
+ export function getFieldsWithDefaults(table, typeRegistry) {
236
252
  const fieldsWithDefaults = new Set();
237
253
  if (!typeRegistry)
238
254
  return fieldsWithDefaults;
@@ -306,8 +322,9 @@ function buildMutationHandler(table, operation, targetName, typeRegistry, ormTyp
306
322
  // For update: all fields are optional (user only updates what they want)
307
323
  const isRequired = operation === 'create' && !fieldsWithDefaults.has(field.name);
308
324
  const hasDefault = fieldsWithDefaults.has(field.name);
325
+ const questionType = getQuestionTypeForField(field);
309
326
  const questionProps = [
310
- t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
327
+ t.objectProperty(t.identifier('type'), t.stringLiteral(questionType)),
311
328
  t.objectProperty(t.identifier('name'), t.stringLiteral(field.name)),
312
329
  t.objectProperty(t.identifier('message'), t.stringLiteral(field.name)),
313
330
  t.objectProperty(t.identifier('required'), t.booleanLiteral(isRequired)),
@@ -1,5 +1,5 @@
1
1
  import type { DocsConfig } from '../../types/config';
2
- import type { CleanField, CleanOperation, CleanTable } from '../../types/schema';
2
+ import type { CleanArgument, CleanField, CleanOperation, CleanTable, TypeRegistry } from '../../types/schema';
3
3
  export interface GeneratedDocFile {
4
4
  fileName: string;
5
5
  content: string;
@@ -36,6 +36,31 @@ export declare function resolveDocsConfig(docs: DocsConfig | boolean | undefined
36
36
  export declare function formatArgType(arg: CleanOperation['args'][number]): string;
37
37
  export declare function formatTypeRef(t: CleanOperation['args'][number]['type']): string;
38
38
  export declare function getEditableFields(table: CleanTable): CleanField[];
39
+ /**
40
+ * Represents a flattened argument for docs/skills generation.
41
+ * INPUT_OBJECT args are expanded to dot-notation fields.
42
+ */
43
+ export interface FlattenedArg {
44
+ /** Flag name for CLI usage, e.g. 'input.email' or 'clientMutationId' */
45
+ flag: string;
46
+ /** Human-readable type string */
47
+ type: string;
48
+ /** Whether the argument is required */
49
+ required: boolean;
50
+ /** Description from schema */
51
+ description?: string;
52
+ }
53
+ /**
54
+ * Strip internal type prefixes for cleaner docs display.
55
+ * e.g. 'ConstructiveInternalTypeEmail' -> 'Email'
56
+ */
57
+ export declare function cleanTypeName(name: string): string;
58
+ export declare function flattenArgs(args: CleanArgument[], registry?: TypeRegistry): FlattenedArg[];
59
+ /**
60
+ * Build CLI flags string from flattened args.
61
+ * e.g. '--input.email <value> --input.password <value>'
62
+ */
63
+ export declare function flattenedArgsToFlags(flatArgs: FlattenedArg[]): string;
39
64
  export declare function gqlTypeToJsonSchemaType(gqlType: string): string;
40
65
  export declare function buildSkillFile(skill: SkillDefinition, referenceNames?: string[]): string;
41
66
  export declare function buildSkillReference(ref: SkillReferenceDefinition): string;
@@ -67,6 +67,108 @@ export function getEditableFields(table) {
67
67
  f.name !== 'createdAt' &&
68
68
  f.name !== 'updatedAt');
69
69
  }
70
+ function unwrapNonNull(typeRef) {
71
+ if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
72
+ return { inner: typeRef.ofType, required: true };
73
+ }
74
+ return { inner: typeRef, required: false };
75
+ }
76
+ function resolveBaseType(typeRef) {
77
+ if ((typeRef.kind === 'NON_NULL' || typeRef.kind === 'LIST') && typeRef.ofType) {
78
+ return resolveBaseType(typeRef.ofType);
79
+ }
80
+ return typeRef;
81
+ }
82
+ /**
83
+ * Strip internal type prefixes for cleaner docs display.
84
+ * e.g. 'ConstructiveInternalTypeEmail' -> 'Email'
85
+ */
86
+ export function cleanTypeName(name) {
87
+ if (name.startsWith('ConstructiveInternalType')) {
88
+ return name.slice('ConstructiveInternalType'.length);
89
+ }
90
+ return name;
91
+ }
92
+ function getScalarTypeName(typeRef) {
93
+ const base = resolveBaseType(typeRef);
94
+ return cleanTypeName(base.name ?? 'String');
95
+ }
96
+ /**
97
+ * Flatten operation args for docs/skills, expanding INPUT_OBJECT types
98
+ * to dot-notation fields (e.g. input.email, input.password).
99
+ * Mirrors the logic in arg-mapper.ts buildQuestionsArray.
100
+ */
101
+ /**
102
+ * Resolve inputFields for an INPUT_OBJECT type.
103
+ * Checks the CleanTypeRef first, then falls back to the TypeRegistry.
104
+ */
105
+ function resolveInputFields(typeRef, registry) {
106
+ if (typeRef.inputFields && typeRef.inputFields.length > 0) {
107
+ return typeRef.inputFields;
108
+ }
109
+ if (registry && typeRef.name) {
110
+ const resolved = registry.get(typeRef.name);
111
+ if (resolved?.kind === 'INPUT_OBJECT' && resolved.inputFields && resolved.inputFields.length > 0) {
112
+ return resolved.inputFields;
113
+ }
114
+ }
115
+ return undefined;
116
+ }
117
+ export function flattenArgs(args, registry) {
118
+ const result = [];
119
+ for (const arg of args) {
120
+ const { inner, required } = unwrapNonNull(arg.type);
121
+ const base = resolveBaseType(arg.type);
122
+ // Try to resolve inputFields from the inner type first (unwrapped NON_NULL)
123
+ const innerFields = inner.kind === 'INPUT_OBJECT'
124
+ ? resolveInputFields(inner, registry)
125
+ : undefined;
126
+ if (innerFields) {
127
+ for (const field of innerFields) {
128
+ const { required: fieldRequired } = unwrapNonNull(field.type);
129
+ result.push({
130
+ flag: `${arg.name}.${field.name}`,
131
+ type: getScalarTypeName(field.type),
132
+ required: required && fieldRequired,
133
+ description: field.description,
134
+ });
135
+ }
136
+ }
137
+ else {
138
+ // Try the base type (unwrapped LIST+NON_NULL)
139
+ const baseFields = base.kind === 'INPUT_OBJECT'
140
+ ? resolveInputFields(base, registry)
141
+ : undefined;
142
+ if (baseFields) {
143
+ for (const field of baseFields) {
144
+ const { required: fieldRequired } = unwrapNonNull(field.type);
145
+ result.push({
146
+ flag: `${arg.name}.${field.name}`,
147
+ type: getScalarTypeName(field.type),
148
+ required: required && fieldRequired,
149
+ description: field.description,
150
+ });
151
+ }
152
+ }
153
+ else {
154
+ result.push({
155
+ flag: arg.name,
156
+ type: getScalarTypeName(arg.type),
157
+ required,
158
+ description: arg.description,
159
+ });
160
+ }
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+ /**
166
+ * Build CLI flags string from flattened args.
167
+ * e.g. '--input.email <value> --input.password <value>'
168
+ */
169
+ export function flattenedArgsToFlags(flatArgs) {
170
+ return flatArgs.map((a) => `--${a.flag} <value>`).join(' ');
171
+ }
70
172
  export function gqlTypeToJsonSchemaType(gqlType) {
71
173
  switch (gqlType) {
72
174
  case 'Int':
@@ -9,6 +9,7 @@ import { generateAllModelFiles } from './model-generator';
9
9
  export function generateOrm(options) {
10
10
  const { tables, customOperations, sharedTypesPath } = options;
11
11
  const commentsEnabled = options.config.codegen?.comments !== false;
12
+ const conditionEnabled = options.config.codegen?.condition !== false;
12
13
  const files = [];
13
14
  // Use shared types when a sharedTypesPath is provided (unified output mode)
14
15
  const useSharedTypes = !!sharedTypesPath;
@@ -29,7 +30,7 @@ export function generateOrm(options) {
29
30
  content: selectTypesFile.content,
30
31
  });
31
32
  // 2. Generate model files
32
- const modelFiles = generateAllModelFiles(tables, useSharedTypes);
33
+ const modelFiles = generateAllModelFiles(tables, useSharedTypes, { condition: conditionEnabled });
33
34
  for (const modelFile of modelFiles) {
34
35
  files.push({
35
36
  path: `models/${modelFile.fileName}`,
@@ -66,7 +67,7 @@ export function generateOrm(options) {
66
67
  }
67
68
  }
68
69
  }
69
- const inputTypesFile = generateInputTypesFile(typeRegistry ?? new Map(), usedInputTypes, tables, usedPayloadTypes, commentsEnabled);
70
+ const inputTypesFile = generateInputTypesFile(typeRegistry ?? new Map(), usedInputTypes, tables, usedPayloadTypes, commentsEnabled, { condition: conditionEnabled });
70
71
  files.push({
71
72
  path: inputTypesFile.fileName,
72
73
  content: inputTypesFile.content,
@@ -18,4 +18,6 @@ export declare function collectPayloadTypeNames(operations: Array<{
18
18
  /**
19
19
  * Generate comprehensive input-types.ts file using Babel AST
20
20
  */
21
- export declare function generateInputTypesFile(typeRegistry: TypeRegistry, usedInputTypes: Set<string>, tables?: CleanTable[], usedPayloadTypes?: Set<string>, comments?: boolean): GeneratedInputTypesFile;
21
+ export declare function generateInputTypesFile(typeRegistry: TypeRegistry, usedInputTypes: Set<string>, tables?: CleanTable[], usedPayloadTypes?: Set<string>, comments?: boolean, options?: {
22
+ condition?: boolean;
23
+ }): GeneratedInputTypesFile;
@@ -214,6 +214,11 @@ const SCALAR_FILTER_CONFIGS = [
214
214
  operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
215
215
  },
216
216
  { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
217
+ // VectorFilter: equality/distinct operators for vector columns on Filter types.
218
+ // Similarity search uses condition types (embeddingNearby), not filters, but
219
+ // connection-filter may still generate a filter for vector columns. This ensures
220
+ // the generated type uses number[] rather than being silently omitted.
221
+ { name: 'VectorFilter', tsType: 'number[]', operators: ['equality', 'distinct'] },
217
222
  // List filters (for array fields like string[], int[], uuid[])
218
223
  {
219
224
  name: 'StringListFilter',
@@ -710,10 +715,15 @@ function generateTableFilterTypes(tables) {
710
715
  // ============================================================================
711
716
  /**
712
717
  * Build properties for a table condition interface
713
- * Condition types are simpler than Filter types - they use direct value equality
718
+ * Condition types are simpler than Filter types - they use direct value equality.
719
+ *
720
+ * Also merges any extra fields from the GraphQL schema's condition type
721
+ * (e.g., plugin-injected fields like vectorEmbedding from VectorSearchPlugin)
722
+ * that are not derived from the table's own columns.
714
723
  */
715
- function buildTableConditionProperties(table) {
724
+ function buildTableConditionProperties(table, typeRegistry) {
716
725
  const properties = [];
726
+ const generatedFieldNames = new Set();
717
727
  for (const field of table.fields) {
718
728
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
719
729
  if (isRelationField(field.name, table))
@@ -725,17 +735,38 @@ function buildTableConditionProperties(table) {
725
735
  type: `${tsType} | null`,
726
736
  optional: true,
727
737
  });
738
+ generatedFieldNames.add(field.name);
739
+ }
740
+ // Merge any additional fields from the schema's condition type
741
+ // (e.g., plugin-added fields like vectorEmbedding from VectorSearchPlugin)
742
+ if (typeRegistry) {
743
+ const conditionTypeName = getConditionTypeName(table);
744
+ const conditionType = typeRegistry.get(conditionTypeName);
745
+ if (conditionType?.kind === 'INPUT_OBJECT' &&
746
+ conditionType.inputFields) {
747
+ for (const field of conditionType.inputFields) {
748
+ if (generatedFieldNames.has(field.name))
749
+ continue;
750
+ const tsType = typeRefToTs(field.type);
751
+ properties.push({
752
+ name: field.name,
753
+ type: tsType,
754
+ optional: true,
755
+ description: stripSmartComments(field.description, true),
756
+ });
757
+ }
758
+ }
728
759
  }
729
760
  return properties;
730
761
  }
731
762
  /**
732
763
  * Generate table condition type statements
733
764
  */
734
- function generateTableConditionTypes(tables) {
765
+ function generateTableConditionTypes(tables, typeRegistry) {
735
766
  const statements = [];
736
767
  for (const table of tables) {
737
768
  const conditionName = getConditionTypeName(table);
738
- statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table)));
769
+ statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table, typeRegistry)));
739
770
  }
740
771
  if (statements.length > 0) {
741
772
  addSectionComment(statements, 'Table Condition Types');
@@ -746,9 +777,13 @@ function generateTableConditionTypes(tables) {
746
777
  // OrderBy Types Generator (AST-based)
747
778
  // ============================================================================
748
779
  /**
749
- * Build OrderBy union type values
780
+ * Build OrderBy union type values.
781
+ *
782
+ * Also merges any extra values from the GraphQL schema's orderBy enum
783
+ * (e.g., plugin-injected values like EMBEDDING_DISTANCE_ASC/DESC
784
+ * from VectorSearchPlugin).
750
785
  */
751
- function buildOrderByValues(table) {
786
+ function buildOrderByValues(table, typeRegistry) {
752
787
  const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
753
788
  for (const field of table.fields) {
754
789
  if (isRelationField(field.name, table))
@@ -757,16 +792,30 @@ function buildOrderByValues(table) {
757
792
  values.push(`${upperSnake}_ASC`);
758
793
  values.push(`${upperSnake}_DESC`);
759
794
  }
795
+ // Merge any additional values from the schema's orderBy enum type
796
+ // (e.g., plugin-added values like EMBEDDING_DISTANCE_ASC/DESC)
797
+ if (typeRegistry) {
798
+ const orderByTypeName = getOrderByTypeName(table);
799
+ const orderByType = typeRegistry.get(orderByTypeName);
800
+ if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
801
+ const existingValues = new Set(values);
802
+ for (const value of orderByType.enumValues) {
803
+ if (!existingValues.has(value)) {
804
+ values.push(value);
805
+ }
806
+ }
807
+ }
808
+ }
760
809
  return values;
761
810
  }
762
811
  /**
763
812
  * Generate OrderBy type statements
764
813
  */
765
- function generateOrderByTypes(tables) {
814
+ function generateOrderByTypes(tables, typeRegistry) {
766
815
  const statements = [];
767
816
  for (const table of tables) {
768
817
  const enumName = getOrderByTypeName(table);
769
- const values = buildOrderByValues(table);
818
+ const values = buildOrderByValues(table, typeRegistry);
770
819
  const unionType = createStringLiteralUnion(values);
771
820
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(enumName), null, unionType);
772
821
  statements.push(t.exportNamedDeclaration(typeAlias));
@@ -975,9 +1024,9 @@ function buildTableCrudTypeNames(tables) {
975
1024
  /**
976
1025
  * Generate custom input type statements from TypeRegistry
977
1026
  */
978
- function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments = true) {
1027
+ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments = true, alreadyGeneratedTypes) {
979
1028
  const statements = [];
980
- const generatedTypes = new Set();
1029
+ const generatedTypes = new Set(alreadyGeneratedTypes ?? []);
981
1030
  const typesToGenerate = new Set(Array.from(usedInputTypes));
982
1031
  // Filter out types we've already generated (exact matches for table CRUD types only)
983
1032
  if (tableCrudTypes) {
@@ -1017,11 +1066,12 @@ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes,
1017
1066
  optional,
1018
1067
  description: stripSmartComments(field.description, comments),
1019
1068
  });
1020
- // Follow nested Input types
1069
+ // Follow nested types (Input objects, Enums, etc.) that exist in the registry
1021
1070
  const baseType = getTypeBaseName(field.type);
1022
1071
  if (baseType &&
1023
- baseType.endsWith('Input') &&
1024
- !generatedTypes.has(baseType)) {
1072
+ !SCALAR_NAMES.has(baseType) &&
1073
+ !generatedTypes.has(baseType) &&
1074
+ typeRegistry.has(baseType)) {
1025
1075
  typesToGenerate.add(baseType);
1026
1076
  }
1027
1077
  }
@@ -1205,12 +1255,49 @@ function generateConnectionFieldsMap(tables, tableByName) {
1205
1255
  return statements;
1206
1256
  }
1207
1257
  // ============================================================================
1258
+ // Plugin-Injected Type Collector
1259
+ // ============================================================================
1260
+ /**
1261
+ * Collect extra input type names referenced by plugin-injected condition fields.
1262
+ *
1263
+ * When plugins (like VectorSearchPlugin) inject fields into condition types,
1264
+ * they reference types (like VectorNearbyInput, VectorMetric) that also need
1265
+ * to be generated. This function discovers those types by comparing the
1266
+ * schema's condition type fields against the table's own columns.
1267
+ */
1268
+ function collectConditionExtraInputTypes(tables, typeRegistry) {
1269
+ const extraTypes = new Set();
1270
+ for (const table of tables) {
1271
+ const conditionTypeName = getConditionTypeName(table);
1272
+ const conditionType = typeRegistry.get(conditionTypeName);
1273
+ if (!conditionType ||
1274
+ conditionType.kind !== 'INPUT_OBJECT' ||
1275
+ !conditionType.inputFields) {
1276
+ continue;
1277
+ }
1278
+ const tableFieldNames = new Set(table.fields
1279
+ .filter((f) => !isRelationField(f.name, table))
1280
+ .map((f) => f.name));
1281
+ for (const field of conditionType.inputFields) {
1282
+ if (tableFieldNames.has(field.name))
1283
+ continue;
1284
+ // Collect the base type name of this extra field
1285
+ const baseName = getTypeBaseName(field.type);
1286
+ if (baseName && !SCALAR_NAMES.has(baseName)) {
1287
+ extraTypes.add(baseName);
1288
+ }
1289
+ }
1290
+ }
1291
+ return extraTypes;
1292
+ }
1293
+ // ============================================================================
1208
1294
  // Main Generator (AST-based)
1209
1295
  // ============================================================================
1210
1296
  /**
1211
1297
  * Generate comprehensive input-types.ts file using Babel AST
1212
1298
  */
1213
- export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes, comments = true) {
1299
+ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes, comments = true, options) {
1300
+ const conditionEnabled = options?.condition !== false;
1214
1301
  const statements = [];
1215
1302
  const tablesList = tables ?? [];
1216
1303
  const hasTables = tablesList.length > 0;
@@ -1235,9 +1322,15 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1235
1322
  // 4. Table filter types
1236
1323
  statements.push(...generateTableFilterTypes(tablesList));
1237
1324
  // 4b. Table condition types (simple equality filter)
1238
- statements.push(...generateTableConditionTypes(tablesList));
1325
+ // Pass typeRegistry to merge plugin-injected condition fields
1326
+ // (e.g., vectorEmbedding from VectorSearchPlugin)
1327
+ if (conditionEnabled) {
1328
+ statements.push(...generateTableConditionTypes(tablesList, typeRegistry));
1329
+ }
1239
1330
  // 5. OrderBy types
1240
- statements.push(...generateOrderByTypes(tablesList));
1331
+ // Pass typeRegistry to merge plugin-injected orderBy values
1332
+ // (e.g., EMBEDDING_DISTANCE_ASC/DESC from VectorSearchPlugin)
1333
+ statements.push(...generateOrderByTypes(tablesList, typeRegistry));
1241
1334
  // 6. CRUD input types
1242
1335
  statements.push(...generateAllCrudInputTypes(tablesList, typeRegistry));
1243
1336
  }
@@ -1245,8 +1338,21 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1245
1338
  // Always emit this export so generated model/custom-op imports stay valid.
1246
1339
  statements.push(...generateConnectionFieldsMap(tablesList, tableByName));
1247
1340
  // 7. Custom input types from TypeRegistry
1341
+ // Also include any extra types referenced by plugin-injected condition fields
1342
+ const mergedUsedInputTypes = new Set(usedInputTypes);
1343
+ if (hasTables && conditionEnabled) {
1344
+ const conditionExtraTypes = collectConditionExtraInputTypes(tablesList, typeRegistry);
1345
+ for (const typeName of conditionExtraTypes) {
1346
+ mergedUsedInputTypes.add(typeName);
1347
+ }
1348
+ }
1248
1349
  const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
1249
- statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments));
1350
+ // Pass customScalarTypes + enumTypes as already-generated to avoid duplicate declarations
1351
+ const alreadyGenerated = new Set([
1352
+ ...customScalarTypes,
1353
+ ...enumTypes,
1354
+ ]);
1355
+ statements.push(...generateCustomInputTypes(typeRegistry, mergedUsedInputTypes, tableCrudTypes, comments, alreadyGenerated));
1250
1356
  // 8. Payload/return types for custom operations
1251
1357
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
1252
1358
  const alreadyGeneratedTypes = new Set();