/** * Smart GraphQL Tool - 83% Token Reduction * * GraphQL query optimizer with intelligent features: * - Query complexity analysis (depth, breadth, field count) * - Optimization suggestions (fragment extraction, field reduction) * - Response caching with query fingerprinting * - Schema introspection caching * - Batched query detection * - N+1 query problem detection * - Token-optimized output */ import { CacheEngine } from '../../core/cache-engine.js'; import { TokenCounter } from '../../core/token-counter.js'; import { MetricsCollector } from '../../core/metrics.js'; interface SmartGraphQLOptions { /** * GraphQL query to analyze */ query: string; /** * Query variables (optional) */ variables?: Record; /** * Operation name (optional) */ operationName?: string; /** * GraphQL endpoint for schema introspection (optional) */ endpoint?: string; /** * Enable complexity analysis (default: true) */ analyzeComplexity?: boolean; /** * Detect N+1 query problems (default: true) */ detectN1?: boolean; /** * Suggest query optimizations (default: true) */ suggestOptimizations?: boolean; /** * Force fresh analysis (bypass cache) */ force?: boolean; /** * Cache TTL in seconds (default: 300 = 5 minutes) */ ttl?: number; } interface ComplexityMetrics { depth: number; breadth: number; fieldCount: number; score: number; } interface FragmentSuggestion { name: string; fields: string[]; /** How many places select this exact field set. */ usage: number; } interface FieldReduction { field: string; reason: string; impact: 'high' | 'medium' | 'low'; } interface BatchOpportunity { queries: string[]; reason: string; estimatedSavings: string; } interface N1Problem { field: string; location: string; severity: 'high' | 'medium' | 'low'; suggestion: string; } interface QueryAnalysis { operation: 'query' | 'mutation' | 'subscription'; name?: string; fields: string[]; complexity: ComplexityMetrics; } interface Optimizations { fragmentSuggestions: FragmentSuggestion[]; fieldReductions: FieldReduction[]; batchOpportunities: BatchOpportunity[]; n1Problems: N1Problem[]; } interface SchemaInfo { types: number; queries: number; mutations: number; subscriptions: number; } interface SmartGraphQLResult { query: QueryAnalysis; optimizations?: Optimizations; schema?: SchemaInfo; cached: boolean; metrics: { originalTokens: number; compactedTokens: number; reductionPercentage: number; }; } export declare class SmartGraphQL { private cache; private tokenCounter; private metrics; constructor(cache: CacheEngine, tokenCounter: TokenCounter, metrics: MetricsCollector); run(options: SmartGraphQLOptions): Promise; private analyzeQuery; private parseQuery; private extractFragments; /** * Parses a selection set into a real tree. * * THE OLD PARSER HAD NO TREE, AND EVERY ANALYSIS DEPENDED ON ONE. * * It ran one regex across the WHOLE query, deduplicated field names globally * with a `seenFields` set, and returned every identifier it found as a * top-level selection. So nesting was invented rather than observed: for * * user { posts(first: 20) { comments(first: 10) { author { name } } } * settings { theme locale notifications { email push sms } } } * * it reported `settings` as a top-level list with an N+1 problem, and never * saw the real one -- 20 posts each fetching 10 comments. It also collapsed * every repeat of a field, which is exactly the repetition the fragment * suggestions exist to find. * * GraphQL selection syntax is small enough to parse properly: alias, name, * arguments, directives, and an optional nested set. Doing so fixes all four * consumers -- complexity, field extraction, fragments and N+1 -- at once. * * @param body the INSIDE of a selection set, without its braces */ private parseSelections; /** * The selection set of the operation itself, without its header. */ private parseOperationBody; private calculateComplexity; private extractFields; /** * Repeated field groups worth extracting into a fragment. * * THREE THINGS WERE WRONG, AND ALL THREE WERE VISIBLE IN ONE RESPONSE. * * The key was `${parentName}:${fields}`, so the SAME field set reached by two * differently-named parents produced two suggestions -- a real query returned * `idFragment [avatarUrl, id, name] usage 23` and `bodyFragment [avatarUrl, * id, name] usage 23`, which are one finding printed twice. The name came * from whichever parent was seen first, so `bodyFragment` described a group * containing no `body`. And `reason: "Field group repeated 23 times"` * restated `usage: 23` in prose, costing tokens to say nothing. * * Keying on the field SET fixes the duplication, naming from the content * fixes the label, and dropping `reason` removes the restatement. */ private detectFragmentOpportunities; private detectFieldReductions; private getSelectionDepth; private detectBatchOpportunities; /** * Arguments that only ever appear on a field returning a LIST. * * Without a schema this is the strongest evidence available, and it is * evidence rather than a guess: `posts(first: 20)` is a list because it is * being paginated. */ private static readonly PAGINATION_ARGS; /** Selection-set names that are list containers by convention. */ private static readonly LIST_CONTAINERS; /** * Whether a field returns a list. * * `name.endsWith('s')` was the whole test. It calls `settings`, `status`, * `address` and `analysis` lists, and that false positive was reported to * users as a high-severity N+1 problem on a plain object. */ private isListField; /** * Finds the N+1 shape: a list whose members each pull another list. * * This used to flag ANY field ending in 's' that had nested objects, which * reported `settings -> theme, locale, notifications` as high severity while * missing `posts(first: 20) { comments(first: 10) }` -- the actual N+1, and * the one the query was written to demonstrate. * * Reporting the multiplication is what makes it actionable: 20 posts each * fetching 10 comments is 200 round trips, and that number is the argument * for a DataLoader. */ private detectN1Problems; private introspectSchema; private transformOutput; private generateCacheKey; private getCachedResult; private cacheResult; } export declare function getSmartGraphQL(cache: CacheEngine, tokenCounter: TokenCounter, metrics: MetricsCollector): SmartGraphQL; export declare function runSmartGraphQL(options: SmartGraphQLOptions): Promise; export declare const SMART_GRAPHQL_TOOL_DEFINITION: { name: string; description: string; inputSchema: { type: "object"; properties: { query: { type: "string"; description: string; }; variables: { type: "object"; description: string; }; operationName: { type: "string"; description: string; }; endpoint: { type: "string"; description: string; }; analyzeComplexity: { type: "boolean"; description: string; }; detectN1: { type: "boolean"; description: string; }; suggestOptimizations: { type: "boolean"; description: string; }; force: { type: "boolean"; description: string; }; ttl: { type: "number"; description: string; }; }; required: string[]; }; }; export type { SmartGraphQLOptions, SmartGraphQLResult, ComplexityMetrics, FragmentSuggestion, FieldReduction, BatchOpportunity, N1Problem, QueryAnalysis, Optimizations, SchemaInfo, }; //# sourceMappingURL=smart-graphql.d.ts.map