@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.
- package/core/codegen/cli/arg-mapper.d.ts +1 -1
- package/core/codegen/cli/arg-mapper.js +15 -11
- package/core/codegen/cli/command-map-generator.d.ts +1 -0
- package/core/codegen/cli/command-map-generator.js +4 -0
- package/core/codegen/cli/config-command-generator.d.ts +11 -0
- package/core/codegen/cli/config-command-generator.js +458 -0
- package/core/codegen/cli/custom-command-generator.js +4 -3
- package/core/codegen/cli/docs-generator.d.ts +6 -5
- package/core/codegen/cli/docs-generator.js +167 -64
- package/core/codegen/cli/helpers-generator.d.ts +15 -0
- package/core/codegen/cli/helpers-generator.js +119 -0
- package/core/codegen/cli/index.d.ts +4 -0
- package/core/codegen/cli/index.js +21 -3
- package/core/codegen/cli/table-command-generator.d.ts +15 -0
- package/core/codegen/cli/table-command-generator.js +20 -1
- package/core/codegen/docs-utils.d.ts +26 -1
- package/core/codegen/docs-utils.js +105 -0
- package/core/codegen/orm/index.js +3 -2
- package/core/codegen/orm/input-types-generator.d.ts +3 -1
- package/core/codegen/orm/input-types-generator.js +123 -17
- package/core/codegen/orm/model-generator.d.ts +6 -2
- package/core/codegen/orm/model-generator.js +59 -29
- package/core/codegen/orm/select-types.d.ts +4 -2
- package/core/codegen/scalars.js +8 -0
- package/core/codegen/templates/cli-entry.ts +2 -2
- package/core/codegen/templates/cli-utils.ts +28 -0
- package/core/codegen/templates/query-builder.ts +28 -5
- package/core/codegen/templates/select-types.ts +4 -2
- package/core/generate.js +14 -4
- package/esm/core/codegen/cli/arg-mapper.d.ts +1 -1
- package/esm/core/codegen/cli/arg-mapper.js +15 -11
- package/esm/core/codegen/cli/command-map-generator.d.ts +1 -0
- package/esm/core/codegen/cli/command-map-generator.js +4 -0
- package/esm/core/codegen/cli/config-command-generator.d.ts +11 -0
- package/esm/core/codegen/cli/config-command-generator.js +422 -0
- package/esm/core/codegen/cli/custom-command-generator.js +4 -3
- package/esm/core/codegen/cli/docs-generator.d.ts +6 -5
- package/esm/core/codegen/cli/docs-generator.js +168 -65
- package/esm/core/codegen/cli/helpers-generator.d.ts +15 -0
- package/esm/core/codegen/cli/helpers-generator.js +83 -0
- package/esm/core/codegen/cli/index.d.ts +4 -0
- package/esm/core/codegen/cli/index.js +18 -2
- package/esm/core/codegen/cli/table-command-generator.d.ts +15 -0
- package/esm/core/codegen/cli/table-command-generator.js +20 -3
- package/esm/core/codegen/docs-utils.d.ts +26 -1
- package/esm/core/codegen/docs-utils.js +102 -0
- package/esm/core/codegen/orm/index.js +3 -2
- package/esm/core/codegen/orm/input-types-generator.d.ts +3 -1
- package/esm/core/codegen/orm/input-types-generator.js +123 -17
- package/esm/core/codegen/orm/model-generator.d.ts +6 -2
- package/esm/core/codegen/orm/model-generator.js +59 -29
- package/esm/core/codegen/orm/select-types.d.ts +4 -2
- package/esm/core/codegen/scalars.js +8 -0
- package/esm/core/generate.js +14 -4
- package/esm/types/config.d.ts +9 -0
- package/esm/types/config.js +1 -0
- package/package.json +11 -11
- package/types/config.d.ts +9 -0
- 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
|
-
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|