@memberjunction/query-gen 0.0.1 → 2.126.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.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +34 -0
- package/COORDINATOR.md +768 -0
- package/IMPLEMENTATION_PLAN.md +1753 -0
- package/LLM_ENTITY_GROUPING_PLAN.md +977 -0
- package/README.md +675 -29
- package/dist/cli/commands/export.d.ts +15 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +178 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +19 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +282 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +17 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +193 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/config.d.ts +51 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +142 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +57 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/EntityGrouper.d.ts +74 -0
- package/dist/core/EntityGrouper.d.ts.map +1 -0
- package/dist/core/EntityGrouper.js +246 -0
- package/dist/core/EntityGrouper.js.map +1 -0
- package/dist/core/MetadataExporter.d.ts +59 -0
- package/dist/core/MetadataExporter.d.ts.map +1 -0
- package/dist/core/MetadataExporter.js +151 -0
- package/dist/core/MetadataExporter.js.map +1 -0
- package/dist/core/QueryDatabaseWriter.d.ts +50 -0
- package/dist/core/QueryDatabaseWriter.d.ts.map +1 -0
- package/dist/core/QueryDatabaseWriter.js +152 -0
- package/dist/core/QueryDatabaseWriter.js.map +1 -0
- package/dist/core/QueryFixer.d.ts +48 -0
- package/dist/core/QueryFixer.d.ts.map +1 -0
- package/dist/core/QueryFixer.js +115 -0
- package/dist/core/QueryFixer.js.map +1 -0
- package/dist/core/QueryRefiner.d.ts +94 -0
- package/dist/core/QueryRefiner.d.ts.map +1 -0
- package/dist/core/QueryRefiner.js +267 -0
- package/dist/core/QueryRefiner.js.map +1 -0
- package/dist/core/QueryTester.d.ts +70 -0
- package/dist/core/QueryTester.d.ts.map +1 -0
- package/dist/core/QueryTester.js +243 -0
- package/dist/core/QueryTester.js.map +1 -0
- package/dist/core/QueryWriter.d.ts +57 -0
- package/dist/core/QueryWriter.d.ts.map +1 -0
- package/dist/core/QueryWriter.js +184 -0
- package/dist/core/QueryWriter.js.map +1 -0
- package/dist/core/QuestionGenerator.d.ts +58 -0
- package/dist/core/QuestionGenerator.d.ts.map +1 -0
- package/dist/core/QuestionGenerator.js +145 -0
- package/dist/core/QuestionGenerator.js.map +1 -0
- package/dist/data/schema.d.ts +230 -0
- package/dist/data/schema.d.ts.map +1 -0
- package/dist/data/schema.js +6 -0
- package/dist/data/schema.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/PromptNames.d.ts +32 -0
- package/dist/prompts/PromptNames.d.ts.map +1 -0
- package/dist/prompts/PromptNames.js +35 -0
- package/dist/prompts/PromptNames.js.map +1 -0
- package/dist/utils/category-builder.d.ts +28 -0
- package/dist/utils/category-builder.d.ts.map +1 -0
- package/dist/utils/category-builder.js +90 -0
- package/dist/utils/category-builder.js.map +1 -0
- package/dist/utils/entity-helpers.d.ts +49 -0
- package/dist/utils/entity-helpers.d.ts.map +1 -0
- package/dist/utils/entity-helpers.js +189 -0
- package/dist/utils/entity-helpers.js.map +1 -0
- package/dist/utils/error-handlers.d.ts +19 -0
- package/dist/utils/error-handlers.d.ts.map +1 -0
- package/dist/utils/error-handlers.js +41 -0
- package/dist/utils/error-handlers.js.map +1 -0
- package/dist/utils/graph-helpers.d.ts +51 -0
- package/dist/utils/graph-helpers.d.ts.map +1 -0
- package/dist/utils/graph-helpers.js +82 -0
- package/dist/utils/graph-helpers.js.map +1 -0
- package/dist/utils/prompt-helpers.d.ts +25 -0
- package/dist/utils/prompt-helpers.d.ts.map +1 -0
- package/dist/utils/prompt-helpers.js +66 -0
- package/dist/utils/prompt-helpers.js.map +1 -0
- package/dist/utils/query-helpers.d.ts +23 -0
- package/dist/utils/query-helpers.d.ts.map +1 -0
- package/dist/utils/query-helpers.js +34 -0
- package/dist/utils/query-helpers.js.map +1 -0
- package/dist/utils/user-helpers.d.ts +15 -0
- package/dist/utils/user-helpers.d.ts.map +1 -0
- package/dist/utils/user-helpers.js +32 -0
- package/dist/utils/user-helpers.js.map +1 -0
- package/dist/vectors/EmbeddingService.d.ts +58 -0
- package/dist/vectors/EmbeddingService.d.ts.map +1 -0
- package/dist/vectors/EmbeddingService.js +90 -0
- package/dist/vectors/EmbeddingService.js.map +1 -0
- package/dist/vectors/SimilaritySearch.d.ts +51 -0
- package/dist/vectors/SimilaritySearch.d.ts.map +1 -0
- package/dist/vectors/SimilaritySearch.js +85 -0
- package/dist/vectors/SimilaritySearch.js.map +1 -0
- package/docs/API.md +1040 -0
- package/docs/ARCHITECTURE.md +1120 -0
- package/examples/advanced-usage.ts +401 -0
- package/examples/basic-usage.ts +285 -0
- package/package.json +48 -6
- package/src/cli/commands/export.ts +173 -0
- package/src/cli/commands/generate.ts +330 -0
- package/src/cli/commands/validate.ts +185 -0
- package/src/cli/config.ts +203 -0
- package/src/cli/index.ts +63 -0
- package/src/core/EntityGrouper.ts +318 -0
- package/src/core/MetadataExporter.ts +148 -0
- package/src/core/QueryDatabaseWriter.ts +187 -0
- package/src/core/QueryFixer.ts +153 -0
- package/src/core/QueryRefiner.ts +382 -0
- package/src/core/QueryTester.ts +264 -0
- package/src/core/QueryWriter.ts +239 -0
- package/src/core/QuestionGenerator.ts +199 -0
- package/src/data/golden-queries.json +1371 -0
- package/src/data/schema.ts +252 -0
- package/src/index.ts +49 -0
- package/src/prompts/PromptNames.ts +36 -0
- package/src/utils/category-builder.ts +97 -0
- package/src/utils/entity-helpers.ts +203 -0
- package/src/utils/error-handlers.ts +41 -0
- package/src/utils/graph-helpers.ts +99 -0
- package/src/utils/prompt-helpers.ts +79 -0
- package/src/utils/query-helpers.ts +32 -0
- package/src/utils/user-helpers.ts +39 -0
- package/src/vectors/EmbeddingService.ts +109 -0
- package/src/vectors/SimilaritySearch.ts +108 -0
- package/tsconfig.json +39 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for QueryGen package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { EntityInfo, EntityRelationshipInfo } from '@memberjunction/core';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a group of related entities for query generation
|
|
9
|
+
*/
|
|
10
|
+
export interface EntityGroup {
|
|
11
|
+
entities: EntityInfo[];
|
|
12
|
+
relationships: EntityRelationshipInfo[];
|
|
13
|
+
primaryEntity: EntityInfo;
|
|
14
|
+
relationshipType: 'single' | 'parent-child' | 'many-to-many';
|
|
15
|
+
|
|
16
|
+
// LLM-generated semantic metadata
|
|
17
|
+
businessDomain: string; // e.g., "Sales Pipeline", "Inventory Management"
|
|
18
|
+
businessRationale: string; // Why this grouping makes business sense
|
|
19
|
+
expectedQuestionTypes: string[]; // e.g., ["trend_analysis", "aggregation", "comparison"]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Entity metadata formatted for AI prompts
|
|
24
|
+
*/
|
|
25
|
+
export interface EntityMetadataForPrompt {
|
|
26
|
+
entityName: string;
|
|
27
|
+
description: string;
|
|
28
|
+
schemaName: string;
|
|
29
|
+
baseTable: string;
|
|
30
|
+
baseView: string;
|
|
31
|
+
fields: EntityFieldMetadata[];
|
|
32
|
+
relationships: EntityRelationshipMetadata[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Entity field metadata for prompts
|
|
37
|
+
*/
|
|
38
|
+
export interface EntityFieldMetadata {
|
|
39
|
+
name: string;
|
|
40
|
+
displayName: string;
|
|
41
|
+
type: string;
|
|
42
|
+
sqlFullType: string;
|
|
43
|
+
description: string;
|
|
44
|
+
isPrimaryKey: boolean;
|
|
45
|
+
isForeignKey: boolean;
|
|
46
|
+
isVirtual: boolean;
|
|
47
|
+
allowsNull: boolean;
|
|
48
|
+
relatedEntity?: string;
|
|
49
|
+
isRequired: boolean;
|
|
50
|
+
defaultValue?: string;
|
|
51
|
+
possibleValues?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Entity relationship metadata for prompts
|
|
56
|
+
*/
|
|
57
|
+
export interface EntityRelationshipMetadata {
|
|
58
|
+
type: 'one-to-many' | 'many-to-one' | 'many-to-many';
|
|
59
|
+
relatedEntity: string;
|
|
60
|
+
relatedEntityView: string;
|
|
61
|
+
relatedEntitySchema: string;
|
|
62
|
+
foreignKeyField: string;
|
|
63
|
+
description: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Business question generated by AI
|
|
68
|
+
*/
|
|
69
|
+
export interface BusinessQuestion {
|
|
70
|
+
userQuestion: string;
|
|
71
|
+
description: string;
|
|
72
|
+
technicalDescription: string;
|
|
73
|
+
complexity: 'simple' | 'medium' | 'complex';
|
|
74
|
+
requiresAggregation: boolean;
|
|
75
|
+
requiresJoins: boolean;
|
|
76
|
+
entities: string[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* SQL query generated by AI
|
|
81
|
+
* Note: selectClause removed - QueryEntity automatically creates QueryFieldEntity records from SQL
|
|
82
|
+
*/
|
|
83
|
+
export interface GeneratedQuery {
|
|
84
|
+
sql: string;
|
|
85
|
+
parameters: QueryParameter[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Query output field definition
|
|
90
|
+
*/
|
|
91
|
+
export interface QueryOutputField {
|
|
92
|
+
name: string;
|
|
93
|
+
description: string;
|
|
94
|
+
type: string;
|
|
95
|
+
optional: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Query parameter definition
|
|
100
|
+
*/
|
|
101
|
+
export interface QueryParameter {
|
|
102
|
+
name: string;
|
|
103
|
+
type: string;
|
|
104
|
+
isRequired: boolean;
|
|
105
|
+
description: string;
|
|
106
|
+
usage: string[];
|
|
107
|
+
defaultValue: string | null;
|
|
108
|
+
sampleValue: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Golden query for few-shot learning
|
|
113
|
+
*/
|
|
114
|
+
export interface GoldenQuery {
|
|
115
|
+
name: string;
|
|
116
|
+
userQuestion: string;
|
|
117
|
+
description: string;
|
|
118
|
+
technicalDescription: string;
|
|
119
|
+
sql: string;
|
|
120
|
+
parameters: QueryParameter[];
|
|
121
|
+
selectClause: QueryOutputField[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Similarity search result
|
|
126
|
+
* Note: nameSim excluded since queries don't have names until after generation
|
|
127
|
+
*/
|
|
128
|
+
export interface SimilarQuery {
|
|
129
|
+
query: GoldenQuery;
|
|
130
|
+
similarity: number;
|
|
131
|
+
fieldScores: {
|
|
132
|
+
userQuestionSim: number;
|
|
133
|
+
descSim: number;
|
|
134
|
+
techDescSim: number;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Query test result
|
|
140
|
+
*/
|
|
141
|
+
export interface QueryTestResult {
|
|
142
|
+
success: boolean;
|
|
143
|
+
renderedSQL?: string;
|
|
144
|
+
rowCount?: number;
|
|
145
|
+
sampleRows?: unknown[];
|
|
146
|
+
attempts?: number;
|
|
147
|
+
error?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Query evaluation result
|
|
152
|
+
*/
|
|
153
|
+
export interface QueryEvaluation {
|
|
154
|
+
answersQuestion: boolean;
|
|
155
|
+
confidence: number;
|
|
156
|
+
reasoning: string;
|
|
157
|
+
suggestions: string[];
|
|
158
|
+
needsRefinement: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Refined query result
|
|
163
|
+
*/
|
|
164
|
+
export interface RefinedQuery {
|
|
165
|
+
query: GeneratedQuery;
|
|
166
|
+
testResult: QueryTestResult;
|
|
167
|
+
evaluation: QueryEvaluation;
|
|
168
|
+
refinementCount: number;
|
|
169
|
+
reachedMaxRefinements?: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Query category information for metadata export and database write
|
|
174
|
+
*/
|
|
175
|
+
export interface QueryCategoryInfo {
|
|
176
|
+
/** Name of the category */
|
|
177
|
+
name: string;
|
|
178
|
+
/** Parent category name (null for root categories) */
|
|
179
|
+
parentName: string | null;
|
|
180
|
+
/** Category description */
|
|
181
|
+
description: string;
|
|
182
|
+
/** Full category path (e.g., "Golden-Queries/Members") */
|
|
183
|
+
path: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validated query ready for export
|
|
188
|
+
*/
|
|
189
|
+
export interface ValidatedQuery {
|
|
190
|
+
businessQuestion: BusinessQuestion;
|
|
191
|
+
query: GeneratedQuery;
|
|
192
|
+
testResult: QueryTestResult;
|
|
193
|
+
evaluation: QueryEvaluation;
|
|
194
|
+
entityGroup: EntityGroup;
|
|
195
|
+
category: QueryCategoryInfo;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Metadata export result
|
|
200
|
+
*/
|
|
201
|
+
export interface ExportResult {
|
|
202
|
+
success: boolean;
|
|
203
|
+
outputPath: string;
|
|
204
|
+
queryCount: number;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Database write result
|
|
209
|
+
*/
|
|
210
|
+
export interface WriteResult {
|
|
211
|
+
success: boolean;
|
|
212
|
+
results: string[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Query metadata record for MJ metadata format
|
|
217
|
+
*
|
|
218
|
+
* Note: QueryFields and QueryParameters are not included here.
|
|
219
|
+
* They are automatically extracted by QueryEntity.server.ts using
|
|
220
|
+
* AI analysis of the SQL template during the Save() operation.
|
|
221
|
+
*/
|
|
222
|
+
export interface QueryMetadataRecord {
|
|
223
|
+
fields: {
|
|
224
|
+
Name: string;
|
|
225
|
+
CategoryID: string;
|
|
226
|
+
UserQuestion: string;
|
|
227
|
+
Description: string;
|
|
228
|
+
TechnicalDescription: string;
|
|
229
|
+
SQL: string;
|
|
230
|
+
OriginalSQL: string;
|
|
231
|
+
UsesTemplate: boolean;
|
|
232
|
+
Status: string;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Embeddings for a query or golden query
|
|
238
|
+
* Note: name is excluded since queries don't have names until after generation
|
|
239
|
+
*/
|
|
240
|
+
export interface QueryEmbeddings {
|
|
241
|
+
userQuestion: number[];
|
|
242
|
+
description: number[];
|
|
243
|
+
technicalDescription: number[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Golden query with embeddings
|
|
248
|
+
*/
|
|
249
|
+
export interface EmbeddedGoldenQuery {
|
|
250
|
+
query: GoldenQuery;
|
|
251
|
+
embeddings: QueryEmbeddings;
|
|
252
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryGen package main entry point
|
|
3
|
+
*
|
|
4
|
+
* @memberjunction/query-gen
|
|
5
|
+
*
|
|
6
|
+
* AI-powered generation of domain-specific SQL query templates with
|
|
7
|
+
* automatic testing, refinement, and metadata export.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Export core classes
|
|
11
|
+
export { EntityGrouper } from './core/EntityGrouper';
|
|
12
|
+
export { QuestionGenerator } from './core/QuestionGenerator';
|
|
13
|
+
export { QueryWriter } from './core/QueryWriter';
|
|
14
|
+
export { QueryTester } from './core/QueryTester';
|
|
15
|
+
export { QueryFixer } from './core/QueryFixer';
|
|
16
|
+
export { QueryRefiner } from './core/QueryRefiner';
|
|
17
|
+
export { MetadataExporter } from './core/MetadataExporter';
|
|
18
|
+
export { QueryDatabaseWriter } from './core/QueryDatabaseWriter';
|
|
19
|
+
|
|
20
|
+
// Export utility classes
|
|
21
|
+
export { SimilaritySearch } from './vectors/SimilaritySearch';
|
|
22
|
+
export { EmbeddingService } from './vectors/EmbeddingService';
|
|
23
|
+
|
|
24
|
+
// Export types
|
|
25
|
+
export * from './data/schema';
|
|
26
|
+
|
|
27
|
+
// Export prompt names
|
|
28
|
+
export * from './prompts/PromptNames';
|
|
29
|
+
|
|
30
|
+
// Export configuration
|
|
31
|
+
export { QueryGenConfig, loadConfig } from './cli/config';
|
|
32
|
+
|
|
33
|
+
// Export CLI commands
|
|
34
|
+
export { generateCommand } from './cli/commands/generate';
|
|
35
|
+
export { validateCommand } from './cli/commands/validate';
|
|
36
|
+
export { exportCommand } from './cli/commands/export';
|
|
37
|
+
|
|
38
|
+
// Export utilities
|
|
39
|
+
export { extractErrorMessage, requireValue, getPropertyOrDefault } from './utils/error-handlers';
|
|
40
|
+
export {
|
|
41
|
+
formatEntityMetadataForPrompt,
|
|
42
|
+
formatEntityGroupForPrompt,
|
|
43
|
+
findEntityById,
|
|
44
|
+
getPrimaryKeyFields,
|
|
45
|
+
getForeignKeyFields,
|
|
46
|
+
hasRelationships,
|
|
47
|
+
getRelationshipCount
|
|
48
|
+
} from './utils/entity-helpers';
|
|
49
|
+
export { generateQueryName } from './utils/query-helpers';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static prompt name constants for QueryGen
|
|
3
|
+
*
|
|
4
|
+
* All AI prompts used by QueryGen system with their exact names
|
|
5
|
+
* as defined in the AI Prompt metadata.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Business Question Generator prompt
|
|
10
|
+
* Generates 1-2 business questions per entity group
|
|
11
|
+
*/
|
|
12
|
+
export const PROMPT_BUSINESS_QUESTION_GENERATOR = 'Business Question Generator';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* SQL Query Writer prompt
|
|
16
|
+
* Generates Nunjucks SQL templates from business questions
|
|
17
|
+
*/
|
|
18
|
+
export const PROMPT_SQL_QUERY_WRITER = 'SQL Query Writer';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* SQL Query Fixer prompt
|
|
22
|
+
* Fixes SQL syntax and logic errors in generated queries
|
|
23
|
+
*/
|
|
24
|
+
export const PROMPT_SQL_QUERY_FIXER = 'SQL Query Fixer';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Query Result Evaluator prompt
|
|
28
|
+
* Evaluates if a query correctly answers the business question
|
|
29
|
+
*/
|
|
30
|
+
export const PROMPT_QUERY_EVALUATOR = 'Query Result Evaluator';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Query Refiner prompt
|
|
34
|
+
* Refines queries based on evaluation feedback
|
|
35
|
+
*/
|
|
36
|
+
export const PROMPT_QUERY_REFINER = 'Query Refiner';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CategoryBuilder - Builds QueryCategoryInfo from configuration and entity groups
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the logic for determining what categories should be created
|
|
5
|
+
* for queries based on the QueryGen configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { QueryCategoryInfo, EntityGroup } from '../data/schema';
|
|
9
|
+
import { QueryGenConfig } from '../cli/config';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build category information for a query based on configuration
|
|
13
|
+
*
|
|
14
|
+
* @param config - QueryGen configuration
|
|
15
|
+
* @param entityGroup - Entity group for the query
|
|
16
|
+
* @returns QueryCategoryInfo with full category details
|
|
17
|
+
*/
|
|
18
|
+
export function buildQueryCategory(
|
|
19
|
+
config: QueryGenConfig,
|
|
20
|
+
entityGroup: EntityGroup
|
|
21
|
+
): QueryCategoryInfo {
|
|
22
|
+
const rootCategoryName = config.rootQueryCategory || 'Auto-Generated';
|
|
23
|
+
|
|
24
|
+
if (config.autoCreateEntityQueryCategories) {
|
|
25
|
+
// Create entity-specific category under root
|
|
26
|
+
const entityName = entityGroup?.primaryEntity?.Name;
|
|
27
|
+
|
|
28
|
+
if (!entityName) {
|
|
29
|
+
console.warn('buildQueryCategory: primaryEntity.Name is undefined, falling back to root category');
|
|
30
|
+
return {
|
|
31
|
+
name: rootCategoryName,
|
|
32
|
+
parentName: null,
|
|
33
|
+
description: 'Automatically generated queries from query-gen tool',
|
|
34
|
+
path: rootCategoryName
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
name: entityName,
|
|
40
|
+
parentName: rootCategoryName,
|
|
41
|
+
description: `Queries for the ${entityName} entity`,
|
|
42
|
+
path: `${rootCategoryName}/${entityName}`
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
// Use root category only
|
|
46
|
+
return {
|
|
47
|
+
name: rootCategoryName,
|
|
48
|
+
parentName: null,
|
|
49
|
+
description: 'Automatically generated queries from query-gen tool',
|
|
50
|
+
path: rootCategoryName
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extract all unique categories from validated queries
|
|
57
|
+
* Returns categories in hierarchical order (root first, then children)
|
|
58
|
+
*
|
|
59
|
+
* IMPORTANT: Also creates parent categories for any child categories
|
|
60
|
+
* (e.g., if "Golden-Queries/Members" is provided, also creates "Golden-Queries")
|
|
61
|
+
*
|
|
62
|
+
* @param categories - Array of QueryCategoryInfo from validated queries
|
|
63
|
+
* @returns Unique categories sorted hierarchically (includes auto-generated parents)
|
|
64
|
+
*/
|
|
65
|
+
export function extractUniqueCategories(categories: QueryCategoryInfo[]): QueryCategoryInfo[] {
|
|
66
|
+
const uniqueMap = new Map<string, QueryCategoryInfo>();
|
|
67
|
+
|
|
68
|
+
// Collect all unique categories (by path)
|
|
69
|
+
for (const cat of categories) {
|
|
70
|
+
if (!uniqueMap.has(cat.path)) {
|
|
71
|
+
uniqueMap.set(cat.path, cat);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If this is a child category, ensure parent category exists
|
|
75
|
+
if (cat.parentName && !uniqueMap.has(cat.parentName)) {
|
|
76
|
+
uniqueMap.set(cat.parentName, {
|
|
77
|
+
name: cat.parentName,
|
|
78
|
+
parentName: null,
|
|
79
|
+
description: 'Automatically generated queries from query-gen tool',
|
|
80
|
+
path: cat.parentName
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Convert to array and sort: root categories first, then children
|
|
86
|
+
const result = Array.from(uniqueMap.values());
|
|
87
|
+
result.sort((a, b) => {
|
|
88
|
+
// Root categories (no parent) come first
|
|
89
|
+
if (a.parentName === null && b.parentName !== null) return -1;
|
|
90
|
+
if (a.parentName !== null && b.parentName === null) return 1;
|
|
91
|
+
|
|
92
|
+
// Then sort alphabetically by path
|
|
93
|
+
return a.path.localeCompare(b.path);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity helper utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for working with entity metadata and formatting for AI prompts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EntityInfo, EntityFieldInfo } from '@memberjunction/core';
|
|
8
|
+
import {
|
|
9
|
+
EntityMetadataForPrompt,
|
|
10
|
+
EntityFieldMetadata,
|
|
11
|
+
EntityRelationshipMetadata,
|
|
12
|
+
EntityGroup
|
|
13
|
+
} from '../data/schema';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format entity metadata for AI prompt consumption
|
|
17
|
+
* Includes schema name, base view, fields, and relationships
|
|
18
|
+
*
|
|
19
|
+
* CRITICAL: Must include schemaName and baseView for functional SQL generation
|
|
20
|
+
*
|
|
21
|
+
* @param entity - Entity to format
|
|
22
|
+
* @param allEntities - All available entities for relationship lookups
|
|
23
|
+
* @returns Formatted entity metadata ready for AI prompts
|
|
24
|
+
*/
|
|
25
|
+
export function formatEntityMetadataForPrompt(entity: EntityInfo, allEntities: EntityInfo[]): EntityMetadataForPrompt {
|
|
26
|
+
return {
|
|
27
|
+
entityName: entity.Name,
|
|
28
|
+
description: entity.Description || '',
|
|
29
|
+
schemaName: entity.SchemaName || 'dbo',
|
|
30
|
+
baseTable: entity.BaseTable || entity.Name,
|
|
31
|
+
baseView: entity.BaseView || `vw${entity.Name}`,
|
|
32
|
+
fields: formatEntityFields(entity),
|
|
33
|
+
relationships: formatEntityRelationships(entity, allEntities),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format entity fields for AI prompt
|
|
39
|
+
* Includes field metadata with types, descriptions, and relationship info
|
|
40
|
+
* Labels internal (__mj_*) and virtual fields appropriately
|
|
41
|
+
*/
|
|
42
|
+
function formatEntityFields(entity: EntityInfo): EntityFieldMetadata[] {
|
|
43
|
+
return entity.Fields.map((field) => {
|
|
44
|
+
const isInternalField = field.Name.startsWith('__mj_');
|
|
45
|
+
const isVirtualField = field.IsVirtual || false;
|
|
46
|
+
|
|
47
|
+
let description = field.Description || '';
|
|
48
|
+
|
|
49
|
+
// Add labels for special fields
|
|
50
|
+
if (isInternalField) {
|
|
51
|
+
description = `[INTERNAL] ${description}`;
|
|
52
|
+
} else if (isVirtualField) {
|
|
53
|
+
description = `[VIRTUAL] ${description}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract possible values from EntityFieldValues if available
|
|
57
|
+
const possibleValues = field.EntityFieldValues && field.EntityFieldValues.length > 0
|
|
58
|
+
? field.EntityFieldValues.map(efv => efv.Value)
|
|
59
|
+
: undefined;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
name: field.Name,
|
|
63
|
+
displayName: field.DisplayName || field.Name,
|
|
64
|
+
type: field.Type,
|
|
65
|
+
sqlFullType: field.SQLFullType,
|
|
66
|
+
description,
|
|
67
|
+
isPrimaryKey: field.IsPrimaryKey || false,
|
|
68
|
+
isForeignKey: field.RelatedEntityID != null && field.RelatedEntityID.trim().length > 0,
|
|
69
|
+
isVirtual: isVirtualField,
|
|
70
|
+
allowsNull: field.AllowsNull,
|
|
71
|
+
relatedEntity: field.RelatedEntity || undefined,
|
|
72
|
+
isRequired: !field.AllowsNull,
|
|
73
|
+
defaultValue: field.DefaultValue || undefined,
|
|
74
|
+
possibleValues,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Format entity relationships for AI prompt
|
|
81
|
+
* Includes schema and view names for proper JOIN generation
|
|
82
|
+
* Only includes relationships to entities in the current entity group
|
|
83
|
+
* Uses EntityInfo.RelatedEntities getter for pre-computed relationships
|
|
84
|
+
*/
|
|
85
|
+
function formatEntityRelationships(entity: EntityInfo, allEntities: EntityInfo[]): EntityRelationshipMetadata[] {
|
|
86
|
+
// Create set of entity names in the group for filtering
|
|
87
|
+
const entityNamesInGroup = new Set(allEntities.map(e => e.Name));
|
|
88
|
+
|
|
89
|
+
// Use entity.RelatedEntities getter which returns EntityRelationshipInfo[]
|
|
90
|
+
return entity.RelatedEntities
|
|
91
|
+
.filter(rel => entityNamesInGroup.has(rel.RelatedEntity)) // Only include relationships within the group
|
|
92
|
+
.map(rel => {
|
|
93
|
+
const relatedEntity = findEntityById(rel.RelatedEntityID, allEntities);
|
|
94
|
+
|
|
95
|
+
// Determine the foreign key field based on relationship type and available fields
|
|
96
|
+
let foreignKeyField: string;
|
|
97
|
+
let joinDescription: string;
|
|
98
|
+
|
|
99
|
+
const currentSchema = entity.SchemaName || 'dbo';
|
|
100
|
+
const currentView = entity.BaseView || `vw${entity.Name}`;
|
|
101
|
+
const relatedSchema = relatedEntity?.SchemaName || 'dbo';
|
|
102
|
+
const relatedView = relatedEntity?.BaseView || `vw${rel.RelatedEntity}`;
|
|
103
|
+
|
|
104
|
+
if (rel.EntityKeyField && rel.EntityKeyField.trim() !== '') {
|
|
105
|
+
// Current entity has the foreign key
|
|
106
|
+
foreignKeyField = rel.EntityKeyField;
|
|
107
|
+
const relatedJoinField = rel.RelatedEntityJoinField || 'ID';
|
|
108
|
+
joinDescription = `${currentSchema}.${currentView}.${foreignKeyField} = ${relatedSchema}.${relatedView}.${relatedJoinField}`;
|
|
109
|
+
} else if (rel.RelatedEntityJoinField && rel.RelatedEntityJoinField.trim() !== '') {
|
|
110
|
+
// Related entity has the foreign key pointing back to this entity
|
|
111
|
+
foreignKeyField = rel.RelatedEntityJoinField;
|
|
112
|
+
joinDescription = `${relatedSchema}.${relatedView}.${foreignKeyField} = ${currentSchema}.${currentView}.ID`;
|
|
113
|
+
} else {
|
|
114
|
+
// No foreign key field specified (possibly many-to-many through join table)
|
|
115
|
+
foreignKeyField = '';
|
|
116
|
+
joinDescription = `Related via ${rel.JoinView || 'join table'}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
type: mapRelationshipType(rel.Type),
|
|
121
|
+
relatedEntity: rel.RelatedEntity,
|
|
122
|
+
relatedEntityView: relatedEntity?.BaseView || `vw${rel.RelatedEntity}`,
|
|
123
|
+
relatedEntitySchema: relatedEntity?.SchemaName || 'dbo',
|
|
124
|
+
foreignKeyField,
|
|
125
|
+
description: joinDescription,
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Map MJ relationship types to QueryGen types
|
|
132
|
+
*/
|
|
133
|
+
function mapRelationshipType(mjType: string): 'one-to-many' | 'many-to-one' | 'many-to-many' {
|
|
134
|
+
const normalized = mjType.toLowerCase().replace(/\s+/g, '-');
|
|
135
|
+
if (normalized === 'many-to-one') return 'many-to-one';
|
|
136
|
+
if (normalized === 'one-to-many') return 'one-to-many';
|
|
137
|
+
if (normalized === 'many-to-many') return 'many-to-many';
|
|
138
|
+
return 'many-to-one'; // Default fallback
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if a field is a foreign key field
|
|
143
|
+
*/
|
|
144
|
+
function isForeignKeyField(field: EntityFieldInfo): boolean {
|
|
145
|
+
return field.RelatedEntityID != null && field.RelatedEntityID.trim().length > 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Find entity by name in array
|
|
150
|
+
*/
|
|
151
|
+
function findEntityByName(name: string, entities: EntityInfo[]): EntityInfo | undefined {
|
|
152
|
+
return entities.find((e) => e.Name === name);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Find entity by ID in array
|
|
157
|
+
*/
|
|
158
|
+
export function findEntityById(id: string, entities: EntityInfo[]): EntityInfo | undefined {
|
|
159
|
+
return entities.find((e) => e.ID === id);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get primary key field(s) for an entity
|
|
164
|
+
*/
|
|
165
|
+
export function getPrimaryKeyFields(entity: EntityInfo): EntityFieldInfo[] {
|
|
166
|
+
return entity.Fields.filter((f) => f.IsPrimaryKey);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get foreign key fields for an entity
|
|
171
|
+
*/
|
|
172
|
+
export function getForeignKeyFields(entity: EntityInfo): EntityFieldInfo[] {
|
|
173
|
+
return entity.Fields.filter((f) => isForeignKeyField(f));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if an entity has any relationships
|
|
178
|
+
* Uses EntityInfo.RelatedEntities getter
|
|
179
|
+
*/
|
|
180
|
+
export function hasRelationships(entity: EntityInfo, allEntities: EntityInfo[]): boolean {
|
|
181
|
+
return entity.RelatedEntities.length > 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get count of relationships for an entity
|
|
186
|
+
* Uses EntityInfo.RelatedEntities getter
|
|
187
|
+
*/
|
|
188
|
+
export function getRelationshipCount(entity: EntityInfo, allEntities: EntityInfo[]): number {
|
|
189
|
+
return entity.RelatedEntities.length;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Format an entire entity group for AI prompt consumption
|
|
194
|
+
* Converts all entities in the group to structured metadata
|
|
195
|
+
*
|
|
196
|
+
* @param entityGroup - Entity group to format
|
|
197
|
+
* @returns Array of formatted entity metadata for Nunjucks template
|
|
198
|
+
*/
|
|
199
|
+
export function formatEntityGroupForPrompt(entityGroup: EntityGroup): EntityMetadataForPrompt[] {
|
|
200
|
+
return entityGroup.entities.map((entity) =>
|
|
201
|
+
formatEntityMetadataForPrompt(entity, entityGroup.entities)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities
|
|
3
|
+
*
|
|
4
|
+
* Standardized error handling functions following MJ patterns
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract error message from unknown error type
|
|
9
|
+
* Uses MJ's extractErrorMessage pattern
|
|
10
|
+
*/
|
|
11
|
+
export function extractErrorMessage(error: unknown, context: string): string {
|
|
12
|
+
if (error instanceof Error) {
|
|
13
|
+
return `${context}: ${error.message}`;
|
|
14
|
+
}
|
|
15
|
+
if (typeof error === 'string') {
|
|
16
|
+
return `${context}: ${error}`;
|
|
17
|
+
}
|
|
18
|
+
return `${context}: Unknown error occurred`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate required value is not null/undefined
|
|
23
|
+
*/
|
|
24
|
+
export function requireValue<T>(value: T | null | undefined, fieldName: string): T {
|
|
25
|
+
if (value === null || value === undefined) {
|
|
26
|
+
throw new Error(`Required value '${fieldName}' is missing`);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get property with fallback value
|
|
33
|
+
*/
|
|
34
|
+
export function getPropertyOrDefault<T>(
|
|
35
|
+
obj: Record<string, unknown>,
|
|
36
|
+
key: string,
|
|
37
|
+
defaultValue: T
|
|
38
|
+
): T {
|
|
39
|
+
const value = obj[key];
|
|
40
|
+
return value !== undefined ? (value as T) : defaultValue;
|
|
41
|
+
}
|