graphql-watchdog 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/detector/instrumenter.ts","../src/detector/analyzer.ts","../src/cost/analyzer.ts","../src/cost/rules.ts","../src/cost/suggestions.ts","../src/cost/dynamic.ts","../src/cache/store.ts","../src/cache/normalizer.ts","../src/cache/invalidator.ts","../src/cache/backend.ts","../src/cache/redis.ts","../src/cache/cloudflare-kv.ts","../src/plugins/yoga.ts","../src/plugins/apollo.ts","../src/reporter/terminal.ts","../src/reporter/json.ts","../src/reporter/dashboard.ts","../src/reporter/index.ts"],"sourcesContent":["// graphql-watchdog - GraphQL performance toolkit\n// N+1 detection, normalized caching, cost analysis, and CI regression testing\n\nexport { ResolverInstrumenter } from './detector/index.js';\nexport { analyzeForN1 } from './detector/analyzer.js';\nexport { analyzeCost, costLimitRule } from './cost/index.js';\nexport type { CostBreakdown } from './cost/index.js';\nexport { suggestOptimizations } from './cost/suggestions.js';\nexport type { OptimizationSuggestion } from './cost/suggestions.js';\nexport { DynamicCostTracker } from './cost/dynamic.js';\nexport type { ResolverTimingData, ResolverTimingEntry } from './cost/dynamic.js';\nexport { ResponseCache } from './cache/index.js';\nexport { normalizeResponse, generateCacheKey } from './cache/normalizer.js';\nexport type { NormalizedEntity } from './cache/normalizer.js';\nexport { getMutationTypes } from './cache/invalidator.js';\nexport { MemoryCacheBackend } from './cache/backend.js';\nexport type { CacheBackend } from './cache/backend.js';\nexport { RedisCacheBackend } from './cache/redis.js';\nexport type { RedisBackendConfig } from './cache/redis.js';\nexport { CloudflareKVBackend } from './cache/cloudflare-kv.js';\nexport type { CloudflareKVConfig, KVNamespace } from './cache/cloudflare-kv.js';\nexport { useWatchdog } from './plugins/yoga.js';\nexport { watchdogApolloPlugin } from './plugins/apollo.js';\nexport { generateReport } from './reporter/index.js';\nexport { generateDashboard, calculatePerformanceScore } from './reporter/dashboard.js';\nexport type { ReportFormat } from './reporter/index.js';\nexport type {\n WatchdogConfig,\n CostConfig,\n CacheConfig,\n N1Detection,\n PerformanceReport,\n CacheStats,\n ResolverCall,\n OperationReport,\n} from './types/index.js';\n","import type { ResolverCall } from '../types/index.js';\nimport type { DynamicCostTracker } from '../cost/dynamic.js';\n\nexport class ResolverInstrumenter {\n private calls: ResolverCall[] = [];\n private costTracker: DynamicCostTracker | null = null;\n\n constructor(options?: { costTracker?: DynamicCostTracker }) {\n this.costTracker = options?.costTracker ?? null;\n }\n\n instrumentResolvers(resolvers: Record<string, Record<string, unknown>>): Record<string, Record<string, unknown>> {\n const instrumented: Record<string, Record<string, unknown>> = {};\n\n for (const typeName of Object.keys(resolvers)) {\n instrumented[typeName] = {};\n const typeResolvers = resolvers[typeName];\n\n for (const fieldName of Object.keys(typeResolvers)) {\n const originalResolver = typeResolvers[fieldName];\n\n if (typeof originalResolver === 'function') {\n instrumented[typeName][fieldName] = async (\n parent: Record<string, unknown> | null | undefined,\n args: unknown,\n context: unknown,\n info: unknown,\n ) => {\n const parentId = parent ? String(parent.id ?? parent._id ?? null) : null;\n const batchKey = `${typeName}.${fieldName}`;\n const timestamp = Date.now();\n const startTime = performance.now();\n\n try {\n const result = await (originalResolver as Function)(parent, args, context, info);\n const duration = performance.now() - startTime;\n\n this.calls.push({\n fieldName,\n typeName,\n parentId,\n timestamp,\n duration,\n batchKey,\n });\n\n // Feed timing data to dynamic cost tracker\n if (this.costTracker) {\n this.costTracker.recordTiming(typeName, fieldName, duration);\n }\n\n return result;\n } catch (error) {\n const duration = performance.now() - startTime;\n\n this.calls.push({\n fieldName,\n typeName,\n parentId,\n timestamp,\n duration,\n batchKey,\n });\n\n // Feed timing data even on errors\n if (this.costTracker) {\n this.costTracker.recordTiming(typeName, fieldName, duration);\n }\n\n throw error;\n }\n };\n } else {\n instrumented[typeName][fieldName] = originalResolver;\n }\n }\n }\n\n return instrumented;\n }\n\n getCalls(): ResolverCall[] {\n return [...this.calls];\n }\n\n reset(): void {\n this.calls = [];\n }\n}\n","import type { ResolverCall, N1Detection } from '../types/index.js';\n\nexport function analyzeForN1(calls: ResolverCall[], threshold = 3): N1Detection[] {\n // Group calls by batchKey\n const groups = new Map<string, ResolverCall[]>();\n\n for (const call of calls) {\n const existing = groups.get(call.batchKey) ?? [];\n existing.push(call);\n groups.set(call.batchKey, existing);\n }\n\n const detections: N1Detection[] = [];\n\n for (const [batchKey, groupCalls] of groups) {\n if (groupCalls.length >= threshold) {\n // Find the likely parent field by looking at other calls that happened before\n // and had only 1 call (the \"1\" in N+1)\n const parentField = findParentField(batchKey, calls, groups);\n const [typeName, fieldName] = batchKey.split('.');\n\n const severity: 'critical' | 'warning' = groupCalls.length >= 10 ? 'critical' : 'warning';\n\n const suggestion = `const ${fieldName}Loader = new DataLoader(async (ids) => { /* batch load ${typeName} by ids */ });`;\n\n detections.push({\n field: batchKey,\n parentField,\n callCount: groupCalls.length,\n suggestion,\n severity,\n });\n }\n }\n\n // Sort by callCount descending\n detections.sort((a, b) => b.callCount - a.callCount);\n\n return detections;\n}\n\nfunction findParentField(\n batchKey: string,\n allCalls: ResolverCall[],\n groups: Map<string, ResolverCall[]>,\n): string {\n const targetCalls = groups.get(batchKey) ?? [];\n if (targetCalls.length === 0) return 'unknown';\n\n const firstTargetTimestamp = Math.min(...targetCalls.map((c) => c.timestamp));\n\n // Find calls that happened before the N+1 calls and are likely the parent query\n // The parent is typically a list-returning field that triggered the repeated calls\n let bestCandidate = 'unknown';\n let bestTimestamp = -Infinity;\n\n for (const [key, keyCalls] of groups) {\n if (key === batchKey) continue;\n // The parent field should have fewer calls and happened before\n if (keyCalls.length < targetCalls.length) {\n const maxTimestamp = Math.max(...keyCalls.map((c) => c.timestamp));\n if (maxTimestamp <= firstTargetTimestamp && maxTimestamp > bestTimestamp) {\n bestTimestamp = maxTimestamp;\n bestCandidate = key;\n }\n }\n }\n\n return bestCandidate;\n}\n","import {\n type DocumentNode,\n type GraphQLSchema,\n type GraphQLOutputType,\n visit,\n Kind,\n TypeInfo,\n visitWithTypeInfo,\n isListType,\n isNonNullType,\n} from 'graphql';\nimport type { CostConfig } from '../types/index.js';\n\nexport interface CostBreakdown {\n totalCost: number;\n fieldCosts: { path: string; cost: number }[];\n exceeds: boolean;\n limit: number;\n}\n\nfunction isListLikeType(type: GraphQLOutputType): boolean {\n if (isListType(type)) return true;\n if (isNonNullType(type)) return isListLikeType(type.ofType);\n return false;\n}\n\nexport function analyzeCost(\n document: DocumentNode,\n schema: GraphQLSchema,\n config: CostConfig = {},\n variables?: Record<string, unknown>,\n): CostBreakdown {\n const defaultFieldCost = config.defaultFieldCost ?? 1;\n const defaultListMultiplier = config.defaultListMultiplier ?? 10;\n const maxCost = config.maxCost ?? Infinity;\n const costMap = config.costMap ?? {};\n\n const fieldCosts: { path: string; cost: number }[] = [];\n const typeInfo = new TypeInfo(schema);\n\n // Stack to track multipliers at each nesting level\n const multiplierStack: number[] = [1];\n const pathStack: string[] = [];\n\n visit(\n document,\n visitWithTypeInfo(typeInfo, {\n Field: {\n enter(node) {\n const fieldName = node.name.value;\n const parentType = typeInfo.getParentType();\n const fieldDef = typeInfo.getFieldDef();\n const typeName = parentType?.name ?? '';\n\n pathStack.push(fieldName);\n const path = pathStack.join('.');\n\n // Determine field cost from costMap or default\n const costKey = `${typeName}.${fieldName}`;\n const baseCost = costMap[costKey] ?? defaultFieldCost;\n\n // Get current multiplier from parent\n const currentMultiplier = multiplierStack[multiplierStack.length - 1];\n const fieldCost = baseCost * currentMultiplier;\n\n fieldCosts.push({ path, cost: fieldCost });\n\n // Determine multiplier for children\n let childMultiplier = currentMultiplier;\n if (fieldDef) {\n const returnType = fieldDef.type;\n if (isListLikeType(returnType)) {\n // Check for first/limit/last arguments in variables or literal args\n let listSize = defaultListMultiplier;\n if (node.arguments) {\n for (const arg of node.arguments) {\n if (['first', 'limit', 'last'].includes(arg.name.value)) {\n if (arg.value.kind === Kind.INT) {\n listSize = parseInt(arg.value.value, 10);\n } else if (arg.value.kind === Kind.VARIABLE && variables) {\n const varName = arg.value.name.value;\n if (typeof variables[varName] === 'number') {\n listSize = variables[varName] as number;\n }\n }\n }\n }\n }\n childMultiplier = currentMultiplier * listSize;\n }\n }\n\n multiplierStack.push(childMultiplier);\n },\n leave() {\n pathStack.pop();\n multiplierStack.pop();\n },\n },\n }),\n );\n\n const totalCost = fieldCosts.reduce((sum, f) => sum + f.cost, 0);\n\n return {\n totalCost,\n fieldCosts,\n exceeds: totalCost > maxCost,\n limit: maxCost,\n };\n}\n","import { GraphQLError, type GraphQLSchema, type ASTVisitor } from 'graphql';\nimport type { CostConfig } from '../types/index.js';\nimport { analyzeCost } from './analyzer.js';\n\nexport function costLimitRule(\n schema: GraphQLSchema,\n config: CostConfig,\n): (context: { getDocument: () => import('graphql').DocumentNode }) => ASTVisitor {\n return function costLimitValidationRule(context) {\n return {\n Document: {\n leave(node) {\n const breakdown = analyzeCost(node, schema, config);\n if (breakdown.exceeds) {\n (context as unknown as { reportError: (e: GraphQLError) => void }).reportError(\n new GraphQLError(\n `Query cost ${breakdown.totalCost} exceeds maximum allowed cost of ${config.maxCost}`,\n ),\n );\n }\n },\n },\n };\n };\n}\n","import {\n type DocumentNode,\n type GraphQLSchema,\n type GraphQLOutputType,\n visit,\n Kind,\n TypeInfo,\n visitWithTypeInfo,\n isListType,\n isNonNullType,\n isObjectType,\n} from 'graphql';\nimport type { CostBreakdown } from './analyzer.js';\nimport type { CostConfig } from '../types/index.js';\n\nexport interface OptimizationSuggestion {\n type: 'pagination' | 'field-pruning' | 'fragment' | 'dataloader' | 'depth-reduction';\n severity: 'high' | 'medium' | 'low';\n field: string;\n message: string;\n estimatedSaving: number;\n}\n\nfunction isListLikeType(type: GraphQLOutputType): boolean {\n if (isListType(type)) return true;\n if (isNonNullType(type)) return isListLikeType(type.ofType);\n return false;\n}\n\nfunction unwrapType(type: GraphQLOutputType): GraphQLOutputType {\n if (isNonNullType(type) || isListType(type)) {\n return unwrapType(type.ofType);\n }\n return type;\n}\n\nfunction getMaxDepth(node: DocumentNode): number {\n let maxDepth = 0;\n let currentDepth = 0;\n\n visit(node, {\n Field: {\n enter() {\n currentDepth++;\n if (currentDepth > maxDepth) maxDepth = currentDepth;\n },\n leave() {\n currentDepth--;\n },\n },\n });\n\n return maxDepth;\n}\n\ninterface SelectionFingerprint {\n fieldNames: string;\n path: string;\n}\n\nfunction getSelectionFingerprints(document: DocumentNode): SelectionFingerprint[] {\n const fingerprints: SelectionFingerprint[] = [];\n const pathStack: string[] = [];\n\n visit(document, {\n Field: {\n enter(node) {\n pathStack.push(node.name.value);\n\n if (node.selectionSet && node.selectionSet.selections.length > 0) {\n const fieldNames = node.selectionSet.selections\n .filter((s) => s.kind === Kind.FIELD)\n .map((s) => (s as { name: { value: string } }).name.value)\n .sort()\n .join(',');\n\n if (fieldNames) {\n fingerprints.push({\n fieldNames,\n path: pathStack.join('.'),\n });\n }\n }\n },\n leave() {\n pathStack.pop();\n },\n },\n });\n\n return fingerprints;\n}\n\nexport function suggestOptimizations(\n breakdown: CostBreakdown,\n document: DocumentNode,\n schema: GraphQLSchema,\n config?: CostConfig,\n): OptimizationSuggestion[] {\n const suggestions: OptimizationSuggestion[] = [];\n const defaultListMultiplier = config?.defaultListMultiplier ?? 10;\n\n // 1. PAGINATION: If a list field has no first/limit arg and high multiplier\n const typeInfo = new TypeInfo(schema);\n visit(\n document,\n visitWithTypeInfo(typeInfo, {\n Field(node) {\n const fieldDef = typeInfo.getFieldDef();\n if (!fieldDef) return;\n\n if (isListLikeType(fieldDef.type)) {\n const hasPaginationArg = node.arguments?.some((arg) =>\n ['first', 'limit', 'last', 'take'].includes(arg.name.value),\n );\n\n if (!hasPaginationArg) {\n const parentType = typeInfo.getParentType();\n const fieldPath = parentType\n ? `${parentType.name}.${node.name.value}`\n : node.name.value;\n\n // Estimate saving: defaultListMultiplier contributes to unbounded results\n const fieldCostEntry = breakdown.fieldCosts.find((fc) =>\n fc.path.endsWith(node.name.value),\n );\n const estimatedSaving = fieldCostEntry\n ? fieldCostEntry.cost * 0.5\n : defaultListMultiplier;\n\n suggestions.push({\n type: 'pagination',\n severity: 'high',\n field: fieldPath,\n message: `Add \\`first: N\\` argument to ${fieldPath} to limit results and reduce cost`,\n estimatedSaving,\n });\n }\n }\n },\n }),\n );\n\n // 2. FIELD PRUNING: If a field contributes >30% of total cost\n if (breakdown.totalCost > 0) {\n for (const fc of breakdown.fieldCosts) {\n const contribution = fc.cost / breakdown.totalCost;\n if (contribution > 0.3 && fc.path.split('.').length >= 3) {\n suggestions.push({\n type: 'field-pruning',\n severity: 'medium',\n field: fc.path,\n message: `Field ${fc.path} contributes ${(contribution * 100).toFixed(0)}% of query cost (${fc.cost}/${breakdown.totalCost}). Consider removing or simplifying`,\n estimatedSaving: fc.cost * 0.3,\n });\n }\n }\n }\n\n // 3. DEPTH REDUCTION: If query depth exceeds 5 levels\n const depth = getMaxDepth(document);\n if (depth > 5) {\n suggestions.push({\n type: 'depth-reduction',\n severity: depth > 8 ? 'high' : 'medium',\n field: '<root>',\n message: `Query depth is ${depth} levels; consider splitting into separate queries or reducing nesting`,\n estimatedSaving: breakdown.totalCost * 0.2,\n });\n }\n\n // 4. FRAGMENT: If the same selection set appears multiple times\n const fingerprints = getSelectionFingerprints(document);\n const seen = new Map<string, string[]>();\n for (const fp of fingerprints) {\n const existing = seen.get(fp.fieldNames) ?? [];\n existing.push(fp.path);\n seen.set(fp.fieldNames, existing);\n }\n for (const [fieldNames, paths] of seen) {\n if (paths.length >= 2) {\n const fields = fieldNames.split(',').join(', ');\n suggestions.push({\n type: 'fragment',\n severity: 'low',\n field: paths[0],\n message: `Selection set (${fields}) appears ${paths.length} times at ${paths.join(', ')}; use a fragment to reduce duplication`,\n estimatedSaving: breakdown.totalCost * 0.05,\n });\n }\n }\n\n // 5. DATALOADER: Look for fields that appear under list parents (potential N+1)\n const typeInfo2 = new TypeInfo(schema);\n const listParentStack: boolean[] = [false];\n\n visit(\n document,\n visitWithTypeInfo(typeInfo2, {\n Field: {\n enter(node) {\n const fieldDef = typeInfo2.getFieldDef();\n const isUnderList = listParentStack[listParentStack.length - 1];\n\n if (fieldDef && isUnderList && isObjectType(unwrapType(fieldDef.type))) {\n const parentType = typeInfo2.getParentType();\n const fieldPath = parentType\n ? `${parentType.name}.${node.name.value}`\n : node.name.value;\n\n suggestions.push({\n type: 'dataloader',\n severity: 'high',\n field: fieldPath,\n message: `${fieldPath} resolves an object under a list parent, likely causing N+1 queries. Use DataLoader for batching`,\n estimatedSaving: breakdown.totalCost * 0.3,\n });\n }\n\n const isList = fieldDef ? isListLikeType(fieldDef.type) : false;\n listParentStack.push(isList || isUnderList);\n },\n leave() {\n listParentStack.pop();\n },\n },\n }),\n );\n\n // Sort by estimated saving descending\n suggestions.sort((a, b) => b.estimatedSaving - a.estimatedSaving);\n\n return suggestions;\n}\n","import type { CostConfig } from '../types/index.js';\n\nexport interface ResolverTimingEntry {\n avgDuration: number; // ms\n p95Duration: number; // ms\n callCount: number;\n lastUpdated: number; // timestamp\n}\n\nexport interface ResolverTimingData {\n [fieldPath: string]: ResolverTimingEntry;\n}\n\nexport class DynamicCostTracker {\n private timingData: ResolverTimingData = {};\n // Track sorted durations for p95 calculation (bounded)\n private durationSamples: Map<string, number[]> = new Map();\n private maxSamples = 1000;\n\n /**\n * Record resolver timing from instrumentation.\n * Uses a running average to avoid unbounded memory growth.\n */\n recordTiming(typeName: string, fieldName: string, durationMs: number): void {\n const key = `${typeName}.${fieldName}`;\n const existing = this.timingData[key];\n\n if (existing) {\n // Update running average\n const newCount = existing.callCount + 1;\n const newAvg = existing.avgDuration + (durationMs - existing.avgDuration) / newCount;\n\n // Update p95 samples (bounded ring buffer approach)\n const samples = this.durationSamples.get(key) ?? [];\n if (samples.length >= this.maxSamples) {\n // Remove oldest sample\n samples.shift();\n }\n samples.push(durationMs);\n this.durationSamples.set(key, samples);\n\n const sorted = [...samples].sort((a, b) => a - b);\n const p95Index = Math.ceil(sorted.length * 0.95) - 1;\n\n this.timingData[key] = {\n avgDuration: newAvg,\n p95Duration: sorted[Math.max(0, p95Index)],\n callCount: newCount,\n lastUpdated: Date.now(),\n };\n } else {\n this.timingData[key] = {\n avgDuration: durationMs,\n p95Duration: durationMs,\n callCount: 1,\n lastUpdated: Date.now(),\n };\n this.durationSamples.set(key, [durationMs]);\n }\n }\n\n /**\n * Get dynamic cost config based on recorded data.\n * Maps actual resolver performance to cost weights.\n * Fields taking baselineDuration ms = cost 1, linear scaling.\n */\n toCostConfig(options?: {\n baselineDuration?: number; // ms -- cost=1 equivalent (default 10ms)\n roundTo?: number; // round costs to nearest N (default 1)\n }): CostConfig {\n const baseline = options?.baselineDuration ?? 10;\n const roundTo = options?.roundTo ?? 1;\n\n const costMap: Record<string, number> = {};\n\n for (const [field, timing] of Object.entries(this.timingData)) {\n let cost = timing.avgDuration / baseline;\n if (roundTo > 0) {\n cost = Math.round(cost / roundTo) * roundTo;\n }\n // Ensure minimum cost of 1\n costMap[field] = Math.max(1, cost);\n }\n\n return { costMap };\n }\n\n /** Export timing data for persistence */\n export(): ResolverTimingData {\n return JSON.parse(JSON.stringify(this.timingData));\n }\n\n /** Import previously saved timing data */\n import(data: ResolverTimingData): void {\n for (const [key, entry] of Object.entries(data)) {\n this.timingData[key] = { ...entry };\n // Reconstruct approximate samples from the existing data\n // We cannot recover the full sample set, so we use the avg as a single representative\n if (!this.durationSamples.has(key)) {\n this.durationSamples.set(key, [entry.avgDuration]);\n }\n }\n }\n\n /** Get stats summary */\n getStats(): {\n trackedFields: number;\n totalCalls: number;\n slowestFields: Array<{ field: string; avgDuration: number }>;\n } {\n const entries = Object.entries(this.timingData);\n const totalCalls = entries.reduce((sum, [, e]) => sum + e.callCount, 0);\n const slowestFields = entries\n .sort(([, a], [, b]) => b.avgDuration - a.avgDuration)\n .slice(0, 10)\n .map(([field, entry]) => ({ field, avgDuration: entry.avgDuration }));\n\n return {\n trackedFields: entries.length,\n totalCalls,\n slowestFields,\n };\n }\n}\n","import type { CacheConfig, CacheStats } from '../types/index.js';\nimport type { NormalizedEntity } from './normalizer.js';\nimport type { CacheBackend } from './backend.js';\n\ninterface CacheEntry {\n data: unknown;\n entities: NormalizedEntity[];\n expiry: number;\n lastAccess: number;\n}\n\nexport class ResponseCache {\n private cache: Map<string, CacheEntry> = new Map();\n private typeIndex: Map<string, Set<string>> = new Map(); // typename -> Set<cacheKey>\n private maxSize: number;\n private ttl: number;\n private stats: CacheStats = { hits: 0, misses: 0, hitRate: 0, entries: 0 };\n private backend: CacheBackend | null;\n\n constructor(config?: CacheConfig) {\n this.maxSize = config?.maxSize ?? 1000;\n this.ttl = config?.ttl ?? 60000;\n this.backend = config?.backend ?? null;\n }\n\n set(cacheKey: string, data: unknown, entities: NormalizedEntity[]): void {\n if (this.backend) {\n // Use backend asynchronously, fire-and-forget for sync interface compat\n const entry: CacheEntry = {\n data,\n entities,\n expiry: Date.now() + this.ttl,\n lastAccess: Date.now(),\n };\n this.backend.set(cacheKey, JSON.stringify(entry), this.ttl).catch(() => {});\n // Still maintain type index in memory for invalidation\n this.updateTypeIndex(cacheKey, entities);\n this.updateEntryCount();\n return;\n }\n\n // Evict LRU if over maxSize\n if (this.cache.size >= this.maxSize && !this.cache.has(cacheKey)) {\n this.evictLRU();\n }\n\n const entry: CacheEntry = {\n data,\n entities,\n expiry: Date.now() + this.ttl,\n lastAccess: Date.now(),\n };\n\n this.cache.set(cacheKey, entry);\n this.updateTypeIndex(cacheKey, entities);\n this.updateEntryCount();\n }\n\n get(cacheKey: string): unknown | null {\n if (this.backend) {\n // For sync compatibility, backend-based get is async-only\n // Use getAsync for backend-based retrieval\n // Fallback: return null for sync calls with a backend\n this.stats.misses++;\n this.updateHitRate();\n return null;\n }\n\n const entry = this.cache.get(cacheKey);\n\n if (!entry) {\n this.stats.misses++;\n this.updateHitRate();\n return null;\n }\n\n // Check expiry\n if (Date.now() > entry.expiry) {\n this.deleteEntry(cacheKey);\n this.stats.misses++;\n this.updateHitRate();\n return null;\n }\n\n entry.lastAccess = Date.now();\n this.stats.hits++;\n this.updateHitRate();\n return entry.data;\n }\n\n async getAsync(cacheKey: string): Promise<unknown | null> {\n if (this.backend) {\n const raw = await this.backend.get(cacheKey);\n if (!raw) {\n this.stats.misses++;\n this.updateHitRate();\n return null;\n }\n\n try {\n const entry: CacheEntry = JSON.parse(raw);\n if (Date.now() > entry.expiry) {\n await this.backend.del(cacheKey);\n this.stats.misses++;\n this.updateHitRate();\n return null;\n }\n\n entry.lastAccess = Date.now();\n // Update in backend\n await this.backend.set(cacheKey, JSON.stringify(entry), this.ttl);\n this.stats.hits++;\n this.updateHitRate();\n return entry.data;\n } catch {\n this.stats.misses++;\n this.updateHitRate();\n return null;\n }\n }\n\n // Delegate to sync get for in-memory\n return this.get(cacheKey);\n }\n\n invalidateByType(typename: string): number {\n const keys = this.typeIndex.get(typename);\n if (!keys) return 0;\n\n const count = keys.size;\n const keysToDelete = [...keys];\n\n if (this.backend) {\n // Async deletion, fire-and-forget\n this.backend.delMany(keysToDelete).catch(() => {});\n }\n\n for (const key of keysToDelete) {\n this.deleteEntry(key);\n }\n\n return count;\n }\n\n invalidateByEntity(typename: string, id: string): number {\n const keys = this.typeIndex.get(typename);\n if (!keys) return 0;\n\n let count = 0;\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n const entry = this.cache.get(key);\n if (entry) {\n const hasEntity = entry.entities.some(\n (e) => e.__typename === typename && e.id === String(id),\n );\n if (hasEntity) {\n keysToDelete.push(key);\n count++;\n }\n }\n }\n\n if (this.backend && keysToDelete.length > 0) {\n this.backend.delMany(keysToDelete).catch(() => {});\n }\n\n for (const key of keysToDelete) {\n this.deleteEntry(key);\n }\n\n return count;\n }\n\n getStats(): CacheStats {\n return { ...this.stats };\n }\n\n clear(): void {\n if (this.backend) {\n this.backend.clear().catch(() => {});\n }\n this.cache.clear();\n this.typeIndex.clear();\n this.stats = { hits: 0, misses: 0, hitRate: 0, entries: 0 };\n }\n\n private updateTypeIndex(cacheKey: string, entities: NormalizedEntity[]): void {\n for (const entity of entities) {\n let typeSet = this.typeIndex.get(entity.__typename);\n if (!typeSet) {\n typeSet = new Set();\n this.typeIndex.set(entity.__typename, typeSet);\n }\n typeSet.add(cacheKey);\n }\n }\n\n private deleteEntry(cacheKey: string): void {\n const entry = this.cache.get(cacheKey);\n if (!entry) return;\n\n // Remove from type index\n for (const entity of entry.entities) {\n const typeSet = this.typeIndex.get(entity.__typename);\n if (typeSet) {\n typeSet.delete(cacheKey);\n if (typeSet.size === 0) {\n this.typeIndex.delete(entity.__typename);\n }\n }\n }\n\n this.cache.delete(cacheKey);\n this.updateEntryCount();\n }\n\n private evictLRU(): void {\n let oldestKey: string | null = null;\n let oldestAccess = Infinity;\n\n for (const [key, entry] of this.cache) {\n if (entry.lastAccess < oldestAccess) {\n oldestAccess = entry.lastAccess;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n this.deleteEntry(oldestKey);\n }\n }\n\n private updateHitRate(): void {\n const total = this.stats.hits + this.stats.misses;\n this.stats.hitRate = total > 0 ? this.stats.hits / total : 0;\n }\n\n private updateEntryCount(): void {\n this.stats.entries = this.cache.size;\n }\n}\n","import { createHash } from 'node:crypto';\n\nexport interface NormalizedEntity {\n __typename: string;\n id: string;\n data: Record<string, unknown>;\n}\n\nexport function normalizeResponse(\n data: Record<string, unknown>,\n operationName: string | null,\n variables?: Record<string, unknown>,\n): { entities: NormalizedEntity[]; cacheKey: string } {\n const entities: NormalizedEntity[] = [];\n\n function extractEntities(obj: unknown): unknown {\n if (obj === null || obj === undefined) return obj;\n\n if (Array.isArray(obj)) {\n return obj.map((item) => extractEntities(item));\n }\n\n if (typeof obj === 'object') {\n const record = obj as Record<string, unknown>;\n const typename = record.__typename as string | undefined;\n const id = record.id ?? record._id;\n\n if (typename && id !== undefined) {\n const data: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n if (key === '__typename' || key === 'id' || key === '_id') continue;\n data[key] = extractEntities(value);\n }\n\n entities.push({\n __typename: typename,\n id: String(id),\n data,\n });\n\n return { __ref: `${typename}:${id}` };\n }\n\n // Non-entity object, recurse into values\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n result[key] = extractEntities(value);\n }\n return result;\n }\n\n return obj;\n }\n\n extractEntities(data);\n\n const cacheKey = generateCacheKey(operationName, variables);\n\n return { entities, cacheKey };\n}\n\nexport function generateCacheKey(\n operationName: string | null,\n variables?: Record<string, unknown>,\n): string {\n const raw = JSON.stringify({ op: operationName, vars: variables ?? {} });\n return createHash('sha256').update(raw).digest('hex').slice(0, 16);\n}\n","import {\n type DocumentNode,\n type GraphQLSchema,\n visit,\n TypeInfo,\n visitWithTypeInfo,\n isObjectType,\n isNonNullType,\n isListType,\n type GraphQLOutputType,\n} from 'graphql';\n\nfunction unwrapType(type: GraphQLOutputType): GraphQLOutputType {\n if (isNonNullType(type) || isListType(type)) {\n return unwrapType(type.ofType);\n }\n return type;\n}\n\nexport function getMutationTypes(document: DocumentNode, schema: GraphQLSchema): string[] {\n const typeNames = new Set<string>();\n const typeInfo = new TypeInfo(schema);\n\n visit(\n document,\n visitWithTypeInfo(typeInfo, {\n OperationDefinition(node) {\n if (node.operation !== 'mutation') {\n return false; // Skip non-mutation operations\n }\n return undefined;\n },\n Field() {\n const fieldDef = typeInfo.getFieldDef();\n if (fieldDef) {\n const returnType = unwrapType(fieldDef.type);\n if (isObjectType(returnType)) {\n typeNames.add(returnType.name);\n }\n }\n },\n }),\n );\n\n return [...typeNames];\n}\n","export interface CacheBackend {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, ttlMs?: number): Promise<void>;\n del(key: string): Promise<void>;\n /** Get all keys matching a pattern */\n keys(pattern: string): Promise<string[]>;\n /** Delete multiple keys */\n delMany(keys: string[]): Promise<number>;\n clear(): Promise<void>;\n}\n\nexport class MemoryCacheBackend implements CacheBackend {\n private store = new Map<string, { value: string; expiry: number }>();\n\n async get(key: string): Promise<string | null> {\n const entry = this.store.get(key);\n if (!entry) return null;\n\n if (entry.expiry > 0 && Date.now() > entry.expiry) {\n this.store.delete(key);\n return null;\n }\n\n return entry.value;\n }\n\n async set(key: string, value: string, ttlMs?: number): Promise<void> {\n const expiry = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : 0;\n this.store.set(key, { value, expiry });\n }\n\n async del(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async keys(pattern: string): Promise<string[]> {\n const regex = this.patternToRegex(pattern);\n const result: string[] = [];\n for (const key of this.store.keys()) {\n if (regex.test(key)) {\n // Also check expiry\n const entry = this.store.get(key);\n if (entry && (entry.expiry === 0 || Date.now() <= entry.expiry)) {\n result.push(key);\n }\n }\n }\n return result;\n }\n\n async delMany(keys: string[]): Promise<number> {\n let count = 0;\n for (const key of keys) {\n if (this.store.delete(key)) {\n count++;\n }\n }\n return count;\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n private patternToRegex(pattern: string): RegExp {\n // Convert glob-like pattern to regex (supports * as wildcard)\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`);\n }\n}\n","import type { CacheBackend } from './backend.js';\n\nexport interface RedisBackendConfig {\n url?: string; // redis://localhost:6379\n host?: string; // default 'localhost'\n port?: number; // default 6379\n password?: string;\n db?: number; // default 0\n keyPrefix?: string; // default 'gql-watchdog:'\n}\n\nexport class RedisCacheBackend implements CacheBackend {\n private client: any = null;\n private config: Required<Pick<RedisBackendConfig, 'keyPrefix'>> & RedisBackendConfig;\n\n constructor(config?: RedisBackendConfig) {\n this.config = {\n keyPrefix: 'gql-watchdog:',\n ...config,\n };\n }\n\n async connect(): Promise<void> {\n if (this.client) return;\n\n let Redis: any;\n try {\n // Use variable to prevent TypeScript from resolving the module at build time\n const moduleName = 'ioredis';\n const ioredis = await (Function('m', 'return import(m)') as (m: string) => Promise<any>)(moduleName);\n Redis = ioredis.default ?? ioredis;\n } catch {\n throw new Error(\n 'ioredis is required for RedisCacheBackend. Install it with: npm install ioredis',\n );\n }\n\n if (this.config.url) {\n this.client = new Redis(this.config.url);\n } else {\n this.client = new Redis({\n host: this.config.host ?? 'localhost',\n port: this.config.port ?? 6379,\n password: this.config.password,\n db: this.config.db ?? 0,\n });\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n await this.client.quit();\n this.client = null;\n }\n }\n\n private prefixedKey(key: string): string {\n return `${this.config.keyPrefix}${key}`;\n }\n\n private ensureConnected(): void {\n if (!this.client) {\n throw new Error('RedisCacheBackend is not connected. Call connect() first.');\n }\n }\n\n async get(key: string): Promise<string | null> {\n this.ensureConnected();\n return this.client.get(this.prefixedKey(key));\n }\n\n async set(key: string, value: string, ttlMs?: number): Promise<void> {\n this.ensureConnected();\n const prefixed = this.prefixedKey(key);\n if (ttlMs && ttlMs > 0) {\n await this.client.set(prefixed, value, 'PX', ttlMs);\n } else {\n await this.client.set(prefixed, value);\n }\n }\n\n async del(key: string): Promise<void> {\n this.ensureConnected();\n await this.client.del(this.prefixedKey(key));\n }\n\n async keys(pattern: string): Promise<string[]> {\n this.ensureConnected();\n const prefixed = this.prefixedKey(pattern);\n const rawKeys: string[] = await this.client.keys(prefixed);\n const prefix = this.config.keyPrefix;\n return rawKeys.map((k: string) => k.startsWith(prefix) ? k.slice(prefix.length) : k);\n }\n\n async delMany(keys: string[]): Promise<number> {\n this.ensureConnected();\n if (keys.length === 0) return 0;\n const prefixedKeys = keys.map((k) => this.prefixedKey(k));\n return this.client.del(...prefixedKeys);\n }\n\n async clear(): Promise<void> {\n this.ensureConnected();\n const allKeys = await this.client.keys(`${this.config.keyPrefix}*`);\n if (allKeys.length > 0) {\n await this.client.del(...allKeys);\n }\n }\n}\n","import type { CacheBackend } from './backend.js';\n\n/** Type stub for Cloudflare Workers KV namespace binding (no dependency needed) */\nexport interface KVNamespace {\n get(key: string): Promise<string | null>;\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: { prefix?: string; cursor?: string }): Promise<{\n keys: { name: string }[];\n list_complete: boolean;\n cursor?: string;\n }>;\n}\n\nexport interface CloudflareKVConfig {\n namespace: KVNamespace;\n keyPrefix?: string; // default 'gql-watchdog:'\n}\n\nexport class CloudflareKVBackend implements CacheBackend {\n private ns: KVNamespace;\n private keyPrefix: string;\n\n constructor(config: CloudflareKVConfig) {\n this.ns = config.namespace;\n this.keyPrefix = config.keyPrefix ?? 'gql-watchdog:';\n }\n\n private prefixedKey(key: string): string {\n return `${this.keyPrefix}${key}`;\n }\n\n private unprefixKey(key: string): string {\n return key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key;\n }\n\n async get(key: string): Promise<string | null> {\n return this.ns.get(this.prefixedKey(key));\n }\n\n async set(key: string, value: string, ttlMs?: number): Promise<void> {\n const options: { expirationTtl?: number } = {};\n if (ttlMs && ttlMs > 0) {\n // KV expirationTtl is in seconds, minimum 60 seconds\n const ttlSec = Math.max(60, Math.ceil(ttlMs / 1000));\n options.expirationTtl = ttlSec;\n }\n await this.ns.put(this.prefixedKey(key), value, options);\n }\n\n async del(key: string): Promise<void> {\n await this.ns.delete(this.prefixedKey(key));\n }\n\n async keys(pattern: string): Promise<string[]> {\n // KV list only supports prefix-based listing, not glob patterns\n // Extract a prefix from the pattern (everything before the first wildcard)\n const prefixEnd = pattern.indexOf('*');\n const searchPrefix =\n prefixEnd >= 0\n ? this.prefixedKey(pattern.slice(0, prefixEnd))\n : this.prefixedKey(pattern);\n\n const result: string[] = [];\n let cursor: string | undefined;\n\n // Paginate through all matching keys\n do {\n const listResult = await this.ns.list({\n prefix: searchPrefix,\n cursor,\n });\n\n for (const key of listResult.keys) {\n const unprefixed = this.unprefixKey(key.name);\n // If pattern has a wildcard, do a simple match\n if (prefixEnd >= 0) {\n result.push(unprefixed);\n } else if (key.name === this.prefixedKey(pattern)) {\n result.push(unprefixed);\n }\n }\n\n cursor = listResult.list_complete ? undefined : listResult.cursor;\n } while (cursor);\n\n return result;\n }\n\n async delMany(keys: string[]): Promise<number> {\n let count = 0;\n for (const key of keys) {\n await this.ns.delete(this.prefixedKey(key));\n count++;\n }\n return count;\n }\n\n async clear(): Promise<void> {\n let cursor: string | undefined;\n\n do {\n const listResult = await this.ns.list({\n prefix: this.keyPrefix,\n cursor,\n });\n\n for (const key of listResult.keys) {\n await this.ns.delete(key.name);\n }\n\n cursor = listResult.list_complete ? undefined : listResult.cursor;\n } while (cursor);\n }\n}\n","import type { WatchdogConfig, N1Detection, ResolverCall } from '../types/index.js';\nimport { analyzeForN1 } from '../detector/analyzer.js';\nimport { ResponseCache } from '../cache/store.js';\nimport { normalizeResponse } from '../cache/normalizer.js';\nimport { getMutationTypes } from '../cache/invalidator.js';\nimport { isObjectType } from 'graphql';\nimport type { GraphQLSchema, DocumentNode } from 'graphql';\n\nexport interface WatchdogYogaPluginOptions extends WatchdogConfig {\n onDetection?: (detections: N1Detection[]) => void;\n}\n\nexport interface WatchdogPluginResult {\n n1Detections: N1Detection[];\n}\n\n// Track which schemas have already been instrumented to avoid double-wrapping\nconst instrumentedSchemas = new WeakSet<GraphQLSchema>();\n\n/**\n * Walk the schema's types and wrap every custom resolver so it records\n * calls to `context._watchdogCalls` (set per-request in onExecute).\n */\nfunction instrumentSchemaResolvers(schema: GraphQLSchema): void {\n if (instrumentedSchemas.has(schema)) return;\n instrumentedSchemas.add(schema);\n\n const typeMap = schema.getTypeMap();\n for (const typeName of Object.keys(typeMap)) {\n if (typeName.startsWith('__')) continue;\n const type = typeMap[typeName];\n if (!isObjectType(type)) continue;\n\n const fields = type.getFields();\n for (const fieldName of Object.keys(fields)) {\n const field = fields[fieldName];\n const originalResolve = field.resolve;\n if (!originalResolve) continue;\n\n field.resolve = (source, args, context, info) => {\n const calls: ResolverCall[] | undefined = context?._watchdogCalls;\n if (!calls) {\n return originalResolve(source, args, context, info);\n }\n\n const parentId = source\n ? String((source as Record<string, unknown>).id ?? (source as Record<string, unknown>)._id ?? null)\n : null;\n const batchKey = `${typeName}.${fieldName}`;\n const timestamp = Date.now();\n const startTime = performance.now();\n\n try {\n const result = originalResolve(source, args, context, info);\n\n // Handle async resolvers\n if (result && typeof (result as { then?: unknown }).then === 'function') {\n return (result as Promise<unknown>).then(\n (value) => {\n calls.push({ fieldName, typeName, parentId, timestamp, duration: performance.now() - startTime, batchKey });\n return value;\n },\n (error) => {\n calls.push({ fieldName, typeName, parentId, timestamp, duration: performance.now() - startTime, batchKey });\n throw error;\n },\n );\n }\n\n calls.push({ fieldName, typeName, parentId, timestamp, duration: performance.now() - startTime, batchKey });\n return result;\n } catch (error) {\n calls.push({ fieldName, typeName, parentId, timestamp, duration: performance.now() - startTime, batchKey });\n throw error;\n }\n };\n }\n }\n}\n\nexport function useWatchdog(config?: WatchdogYogaPluginOptions) {\n const cache = config?.enableCache ? new ResponseCache(config.cache) : null;\n\n return {\n onExecute({ args }: { args: { schema: GraphQLSchema; document: DocumentNode; contextValue?: Record<string, unknown>; operationName?: string | null; variableValues?: Record<string, unknown> | null } }) {\n // Instrument schema resolvers on first use\n if (config?.enableDetector !== false) {\n instrumentSchemaResolvers(args.schema);\n // Store per-request call tracking in context\n if (args.contextValue) {\n args.contextValue._watchdogCalls = [];\n }\n }\n\n const startTime = Date.now();\n\n return {\n onExecuteDone({ result }: { result: { data?: Record<string, unknown> | null; errors?: unknown[] } }) {\n const duration = Date.now() - startTime;\n const calls = ((args.contextValue?._watchdogCalls ?? []) as ResolverCall[]);\n\n // Analyze for N+1\n let n1Detections: N1Detection[] = [];\n if (config?.enableDetector !== false) {\n n1Detections = analyzeForN1(calls);\n\n if (n1Detections.length > 0) {\n if (config?.onDetection) {\n config.onDetection(n1Detections);\n } else {\n for (const detection of n1Detections) {\n console.warn(\n `[graphql-watchdog] N+1 detected: ${detection.field} (${detection.callCount} calls) — ${detection.suggestion}`,\n );\n }\n }\n }\n }\n\n // Handle caching\n if (cache && result.data && !result.errors?.length) {\n const operationName = args.operationName ?? null;\n const variables = args.variableValues ?? undefined;\n const { entities, cacheKey } = normalizeResponse(\n result.data,\n operationName,\n variables ?? undefined,\n );\n cache.set(cacheKey, result.data, entities);\n\n // Invalidate cache on mutations\n const mutationTypes = getMutationTypes(args.document, args.schema);\n for (const typeName of mutationTypes) {\n cache.invalidateByType(typeName);\n }\n }\n\n return { n1Detections, duration };\n },\n };\n },\n\n getCache(): ResponseCache | null {\n return cache;\n },\n };\n}\n","import type { WatchdogConfig, N1Detection } from '../types/index.js';\nimport { ResolverInstrumenter } from '../detector/instrumenter.js';\nimport { analyzeForN1 } from '../detector/analyzer.js';\nimport { ResponseCache } from '../cache/store.js';\nimport type { GraphQLSchema } from 'graphql';\n\nexport interface ApolloWatchdogContext {\n _watchdogInstrumenter?: ResolverInstrumenter;\n _watchdogStartTime?: number;\n}\n\nexport interface WatchdogApolloPluginOptions extends WatchdogConfig {\n onDetection?: (detections: N1Detection[]) => void;\n}\n\nexport function watchdogApolloPlugin(config?: WatchdogApolloPluginOptions) {\n const cache = config?.enableCache ? new ResponseCache(config.cache) : null;\n\n return {\n async requestDidStart({ schema: _schema }: { schema?: GraphQLSchema } = {}) {\n const instrumenter = new ResolverInstrumenter();\n\n return {\n async executionDidStart() {\n return {\n willResolveField({ info }: { info: { fieldName: string; parentType: { name: string }; path: { key: string | number } } }) {\n const fieldStartTime = performance.now();\n return (_error: unknown, _result: unknown) => {\n const duration = performance.now() - fieldStartTime;\n // Record the resolver call manually since we can't wrap resolvers in Apollo\n instrumenter['calls'].push({\n fieldName: info.fieldName,\n typeName: info.parentType.name,\n parentId: null,\n timestamp: Date.now(),\n duration,\n batchKey: `${info.parentType.name}.${info.fieldName}`,\n });\n };\n },\n };\n },\n\n async willSendResponse({ response: _response }: { response: { body?: { singleResult?: { data?: Record<string, unknown>; errors?: unknown[] } } } }) {\n const calls = instrumenter.getCalls();\n\n // Analyze for N+1\n if (config?.enableDetector !== false) {\n const n1Detections = analyzeForN1(calls);\n\n if (n1Detections.length > 0) {\n if (config?.onDetection) {\n config.onDetection(n1Detections);\n } else {\n for (const detection of n1Detections) {\n console.warn(\n `[graphql-watchdog] N+1 detected: ${detection.field} (${detection.callCount} calls)`,\n );\n }\n }\n }\n }\n },\n };\n },\n\n getCache(): ResponseCache | null {\n return cache;\n },\n };\n}\n","import type { PerformanceReport, N1Detection } from '../types/index.js';\nimport type { CostBreakdown } from '../cost/analyzer.js';\n\nconst RESET = '\\x1b[0m';\nconst BOLD = '\\x1b[1m';\nconst RED = '\\x1b[31m';\nconst YELLOW = '\\x1b[33m';\nconst GREEN = '\\x1b[32m';\nconst CYAN = '\\x1b[36m';\nconst DIM = '\\x1b[2m';\n\nexport function formatN1Detections(detections: N1Detection[]): string {\n if (detections.length === 0) {\n return `${GREEN}${BOLD}No N+1 queries detected${RESET}\\n`;\n }\n\n const lines: string[] = [\n `${RED}${BOLD}N+1 Query Detections (${detections.length})${RESET}`,\n '',\n ];\n\n for (const d of detections) {\n const severityColor = d.severity === 'critical' ? RED : YELLOW;\n const severityLabel = d.severity.toUpperCase();\n\n lines.push(\n ` ${severityColor}${BOLD}[${severityLabel}]${RESET} ${BOLD}${d.field}${RESET}`,\n );\n lines.push(` Parent: ${DIM}${d.parentField}${RESET}`);\n lines.push(` Calls: ${d.callCount}`);\n lines.push(` Fix: ${CYAN}${d.suggestion}${RESET}`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\nexport function formatCostBreakdown(breakdown: CostBreakdown): string {\n const statusColor = breakdown.exceeds ? RED : GREEN;\n const statusLabel = breakdown.exceeds ? 'EXCEEDS LIMIT' : 'WITHIN LIMIT';\n\n const lines: string[] = [\n `${BOLD}Query Cost Analysis${RESET}`,\n '',\n ` Total Cost: ${statusColor}${BOLD}${breakdown.totalCost}${RESET}${breakdown.limit < Infinity ? ` / ${breakdown.limit}` : ''}`,\n ` Status: ${statusColor}${statusLabel}${RESET}`,\n '',\n ` ${DIM}Field Costs:${RESET}`,\n ];\n\n for (const fc of breakdown.fieldCosts) {\n lines.push(` ${fc.path}: ${fc.cost}`);\n }\n\n lines.push('');\n return lines.join('\\n');\n}\n\nexport function formatPerformanceReport(report: PerformanceReport): string {\n const lines: string[] = [\n `${BOLD}${CYAN}graphql-watchdog Performance Report${RESET}`,\n `${DIM}${report.timestamp}${RESET}`,\n `${DIM}Total Duration: ${report.duration}ms${RESET}`,\n '',\n ];\n\n if (report.operations.length > 0) {\n lines.push(`${BOLD}Operations:${RESET}`);\n lines.push(\n ` ${'Operation'.padEnd(30)} ${'Duration'.padStart(10)} ${'Resolvers'.padStart(10)} ${'Cost'.padStart(8)}`,\n );\n lines.push(` ${'-'.repeat(30)} ${'-'.repeat(10)} ${'-'.repeat(10)} ${'-'.repeat(8)}`);\n\n for (const op of report.operations) {\n const name = op.operationName ?? '<anonymous>';\n lines.push(\n ` ${name.padEnd(30)} ${(op.duration + 'ms').padStart(10)} ${String(op.resolverCalls).padStart(10)} ${String(op.costEstimate).padStart(8)}`,\n );\n }\n lines.push('');\n }\n\n lines.push(formatN1Detections(report.n1Detections));\n\n if (report.cacheStats) {\n const { hits, misses, hitRate, entries } = report.cacheStats;\n lines.push(`${BOLD}Cache Stats:${RESET}`);\n lines.push(` Entries: ${entries}`);\n lines.push(` Hits: ${hits}`);\n lines.push(` Misses: ${misses}`);\n lines.push(` Hit Rate: ${(hitRate * 100).toFixed(1)}%`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n","import type { PerformanceReport } from '../types/index.js';\nimport type { CostBreakdown } from '../cost/analyzer.js';\n\nexport function formatReportAsJson(report: PerformanceReport): string {\n return JSON.stringify(report, null, 2);\n}\n\nexport function formatCostAsJson(breakdown: CostBreakdown): string {\n return JSON.stringify(breakdown, null, 2);\n}\n","import type { PerformanceReport, CacheStats } from '../types/index.js';\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\nexport function calculatePerformanceScore(report: PerformanceReport): number {\n let score = 100;\n\n // N+1 detections penalty\n for (const d of report.n1Detections) {\n if (d.severity === 'critical') {\n score -= 15;\n } else {\n score -= 8;\n }\n }\n\n // Cost penalty: high cost operations\n for (const op of report.operations) {\n if (op.costEstimate > 500) score -= 10;\n else if (op.costEstimate > 200) score -= 5;\n }\n\n // Cache hit rate bonus/penalty\n if (report.cacheStats) {\n if (report.cacheStats.hitRate < 0.3) score -= 10;\n else if (report.cacheStats.hitRate < 0.5) score -= 5;\n else if (report.cacheStats.hitRate > 0.8) score += 5;\n }\n\n // Slow operations penalty\n for (const op of report.operations) {\n if (op.duration > 1000) score -= 10;\n else if (op.duration > 500) score -= 5;\n }\n\n return Math.max(0, Math.min(100, score));\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return '#22c55e';\n if (score >= 60) return '#eab308';\n if (score >= 40) return '#f97316';\n return '#ef4444';\n}\n\nfunction severityColor(severity: string): string {\n if (severity === 'critical') return '#ef4444';\n return '#eab308';\n}\n\nfunction generateScoreGauge(score: number): string {\n const color = scoreColor(score);\n const angle = (score / 100) * 360;\n const radians = ((angle - 90) * Math.PI) / 180;\n const x = 50 + 40 * Math.cos(radians);\n const y = 50 + 40 * Math.sin(radians);\n const largeArc = angle > 180 ? 1 : 0;\n\n return `<svg viewBox=\"0 0 100 100\" width=\"120\" height=\"120\">\n <circle cx=\"50\" cy=\"50\" r=\"40\" fill=\"none\" stroke=\"#334155\" stroke-width=\"8\"/>\n ${\n score > 0\n ? `<path d=\"M 50 10 A 40 40 0 ${largeArc} 1 ${x.toFixed(1)} ${y.toFixed(1)}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"8\" stroke-linecap=\"round\"/>`\n : ''\n }\n <text x=\"50\" y=\"55\" text-anchor=\"middle\" fill=\"${color}\" font-size=\"20\" font-weight=\"bold\">${score}</text>\n </svg>`;\n}\n\nfunction generateCostBarChart(fieldCosts: { path: string; cost: number }[]): string {\n const top = fieldCosts\n .sort((a, b) => b.cost - a.cost)\n .slice(0, 10);\n\n if (top.length === 0) return '<p style=\"color:#94a3b8\">No cost data</p>';\n\n const maxCost = top[0].cost;\n const barHeight = 28;\n const chartHeight = top.length * barHeight + 20;\n\n const bars = top\n .map((fc, i) => {\n const barWidth = maxCost > 0 ? (fc.cost / maxCost) * 300 : 0;\n const y = i * barHeight + 10;\n const label = fc.path.length > 25 ? '...' + fc.path.slice(-22) : fc.path;\n return `\n <g>\n <rect x=\"120\" y=\"${y}\" width=\"${barWidth}\" height=\"20\" rx=\"3\" fill=\"#6366f1\" opacity=\"0.8\"/>\n <text x=\"115\" y=\"${y + 14}\" text-anchor=\"end\" fill=\"#94a3b8\" font-size=\"11\">${escapeHtml(label)}</text>\n <text x=\"${125 + barWidth}\" y=\"${y + 14}\" fill=\"#e2e8f0\" font-size=\"11\">${fc.cost}</text>\n </g>`;\n })\n .join('');\n\n return `<svg viewBox=\"0 0 500 ${chartHeight}\" width=\"100%\" height=\"${chartHeight}\">\n ${bars}\n </svg>`;\n}\n\nfunction generateCacheGauge(stats: CacheStats): string {\n const hitRate = stats.hitRate * 100;\n const color = hitRate >= 80 ? '#22c55e' : hitRate >= 50 ? '#eab308' : '#ef4444';\n\n return `<div style=\"text-align:center\">\n ${generateScoreGaugeGeneric(hitRate, color, '%')}\n <div style=\"margin-top:8px;color:#94a3b8;font-size:13px\">\n <span>Hits: ${stats.hits}</span> | <span>Misses: ${stats.misses}</span> | <span>Entries: ${stats.entries}</span>\n </div>\n </div>`;\n}\n\nfunction generateScoreGaugeGeneric(value: number, color: string, suffix: string): string {\n const angle = (Math.min(value, 100) / 100) * 360;\n const radians = ((angle - 90) * Math.PI) / 180;\n const x = 50 + 40 * Math.cos(radians);\n const y = 50 + 40 * Math.sin(radians);\n const largeArc = angle > 180 ? 1 : 0;\n\n return `<svg viewBox=\"0 0 100 100\" width=\"100\" height=\"100\">\n <circle cx=\"50\" cy=\"50\" r=\"40\" fill=\"none\" stroke=\"#334155\" stroke-width=\"8\"/>\n ${\n value > 0\n ? `<path d=\"M 50 10 A 40 40 0 ${largeArc} 1 ${x.toFixed(1)} ${y.toFixed(1)}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"8\" stroke-linecap=\"round\"/>`\n : ''\n }\n <text x=\"50\" y=\"50\" text-anchor=\"middle\" fill=\"${color}\" font-size=\"14\" font-weight=\"bold\">${value.toFixed(0)}${suffix}</text>\n </svg>`;\n}\n\nexport function generateDashboard(report: PerformanceReport): string {\n const score = calculatePerformanceScore(report);\n const scoreGauge = generateScoreGauge(score);\n\n // N+1 Hotspots\n const n1Rows = report.n1Detections\n .map(\n (d) => `<tr>\n <td style=\"color:${severityColor(d.severity)};font-weight:600\">${escapeHtml(d.field)}</td>\n <td>${d.callCount}</td>\n <td><span style=\"color:${severityColor(d.severity)};text-transform:uppercase;font-size:12px;font-weight:600\">${d.severity}</span></td>\n <td style=\"font-size:12px;color:#94a3b8\">${escapeHtml(d.suggestion)}</td>\n </tr>`,\n )\n .join('');\n\n // Cost breakdown\n const allFieldCosts = report.operations.length > 0\n ? report.operations.map((op) => ({\n path: op.operationName ?? '<anonymous>',\n cost: op.costEstimate,\n }))\n : [];\n\n // Slowest operations\n const sortedOps = [...report.operations].sort((a, b) => b.duration - a.duration);\n const slowOpsRows = sortedOps\n .map(\n (op) => `<tr>\n <td>${escapeHtml(op.operationName ?? '<anonymous>')}</td>\n <td>${op.duration}ms</td>\n <td>${op.resolverCalls}</td>\n <td>${op.costEstimate}</td>\n </tr>`,\n )\n .join('');\n\n // Cache stats section\n const cacheSection = report.cacheStats\n ? `<div class=\"card\">\n <h2>Cache Performance</h2>\n ${generateCacheGauge(report.cacheStats)}\n </div>`\n : '';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>graphql-watchdog Performance Dashboard</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;\n background: #0f172a;\n color: #e2e8f0;\n padding: 24px;\n line-height: 1.6;\n }\n h1 { color: #f1f5f9; font-size: 24px; margin-bottom: 4px; }\n h2 { color: #cbd5e1; font-size: 16px; margin-bottom: 12px; border-bottom: 1px solid #1e293b; padding-bottom: 8px; }\n .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }\n .header-meta { color: #64748b; font-size: 13px; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; margin-bottom: 16px; }\n .card {\n background: #1e293b;\n border-radius: 8px;\n padding: 20px;\n border: 1px solid #334155;\n }\n .score-card { display: flex; align-items: center; gap: 20px; }\n .score-label { font-size: 14px; color: #94a3b8; }\n .score-status { font-size: 18px; font-weight: 600; }\n table { width: 100%; border-collapse: collapse; font-size: 13px; }\n th { text-align: left; color: #64748b; font-weight: 600; padding: 8px 12px; border-bottom: 1px solid #334155; font-size: 12px; text-transform: uppercase; }\n td { padding: 8px 12px; border-bottom: 1px solid #1e293b; }\n tr:hover td { background: #334155; }\n .no-data { color: #64748b; font-style: italic; padding: 16px; }\n</style>\n</head>\n<body>\n\n<div class=\"header\">\n <div>\n <h1>graphql-watchdog Dashboard</h1>\n <div class=\"header-meta\">${escapeHtml(report.timestamp)} | Duration: ${report.duration}ms</div>\n </div>\n</div>\n\n<div class=\"grid\">\n <div class=\"card\">\n <h2>Performance Score</h2>\n <div class=\"score-card\">\n ${scoreGauge}\n <div>\n <div class=\"score-label\">Overall Health</div>\n <div class=\"score-status\" style=\"color:${scoreColor(score)}\">${score >= 80 ? 'Good' : score >= 60 ? 'Fair' : score >= 40 ? 'Needs Improvement' : 'Critical'}</div>\n <div class=\"score-label\" style=\"margin-top:4px\">\n ${report.n1Detections.length} N+1 issues |\n ${report.operations.length} operations\n ${report.cacheStats ? ` | ${(report.cacheStats.hitRate * 100).toFixed(0)}% cache hit rate` : ''}\n </div>\n </div>\n </div>\n </div>\n\n ${cacheSection}\n</div>\n\n<div class=\"grid\">\n <div class=\"card\">\n <h2>N+1 Hotspots</h2>\n ${\n report.n1Detections.length > 0\n ? `<table>\n <thead><tr><th>Field</th><th>Calls</th><th>Severity</th><th>Suggestion</th></tr></thead>\n <tbody>${n1Rows}</tbody>\n </table>`\n : '<div class=\"no-data\">No N+1 queries detected</div>'\n }\n </div>\n\n <div class=\"card\">\n <h2>Cost Breakdown</h2>\n ${generateCostBarChart(allFieldCosts)}\n </div>\n</div>\n\n<div class=\"card\" style=\"margin-top:16px\">\n <h2>Operations</h2>\n ${\n report.operations.length > 0\n ? `<table>\n <thead><tr><th>Operation</th><th>Duration</th><th>Resolver Calls</th><th>Cost</th></tr></thead>\n <tbody>${slowOpsRows}</tbody>\n </table>`\n : '<div class=\"no-data\">No operations recorded</div>'\n }\n</div>\n\n<script>\n(function() {\n try {\n var key = 'gql-watchdog-reports';\n var stored = JSON.parse(localStorage.getItem(key) || '[]');\n stored.push({\n timestamp: ${JSON.stringify(report.timestamp)},\n score: ${score},\n n1Count: ${report.n1Detections.length},\n opCount: ${report.operations.length},\n cacheHitRate: ${report.cacheStats ? report.cacheStats.hitRate : 0}\n });\n if (stored.length > 50) stored = stored.slice(-50);\n localStorage.setItem(key, JSON.stringify(stored));\n } catch(e) {}\n})();\n</script>\n\n</body>\n</html>`;\n}\n","import type { PerformanceReport } from '../types/index.js';\nimport { formatPerformanceReport } from './terminal.js';\nimport { formatReportAsJson } from './json.js';\nimport { generateDashboard } from './dashboard.js';\n\nexport type ReportFormat = 'terminal' | 'json' | 'dashboard';\n\nexport function generateReport(report: PerformanceReport, format: ReportFormat = 'terminal'): string {\n switch (format) {\n case 'json':\n return formatReportAsJson(report);\n case 'dashboard':\n return generateDashboard(report);\n case 'terminal':\n default:\n return formatPerformanceReport(report);\n }\n}\n\nexport { formatN1Detections, formatCostBreakdown, formatPerformanceReport } from './terminal.js';\nexport { formatReportAsJson, formatCostAsJson } from './json.js';\nexport { generateDashboard, calculatePerformanceScore } from './dashboard.js';\n"],"mappings":"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","names":["import_graphql","import_graphql","isListLikeType","entry","data","import_graphql","unwrapType","import_graphql","severityColor"]}
@@ -0,0 +1,321 @@
1
+ import * as graphql from 'graphql';
2
+ import { DocumentNode, GraphQLSchema, ASTVisitor } from 'graphql';
3
+
4
+ interface CacheBackend {
5
+ get(key: string): Promise<string | null>;
6
+ set(key: string, value: string, ttlMs?: number): Promise<void>;
7
+ del(key: string): Promise<void>;
8
+ /** Get all keys matching a pattern */
9
+ keys(pattern: string): Promise<string[]>;
10
+ /** Delete multiple keys */
11
+ delMany(keys: string[]): Promise<number>;
12
+ clear(): Promise<void>;
13
+ }
14
+ declare class MemoryCacheBackend implements CacheBackend {
15
+ private store;
16
+ get(key: string): Promise<string | null>;
17
+ set(key: string, value: string, ttlMs?: number): Promise<void>;
18
+ del(key: string): Promise<void>;
19
+ keys(pattern: string): Promise<string[]>;
20
+ delMany(keys: string[]): Promise<number>;
21
+ clear(): Promise<void>;
22
+ private patternToRegex;
23
+ }
24
+
25
+ interface ResolverCall {
26
+ fieldName: string;
27
+ typeName: string;
28
+ parentId: string | null;
29
+ timestamp: number;
30
+ duration: number;
31
+ batchKey: string;
32
+ }
33
+ interface N1Detection {
34
+ field: string;
35
+ parentField: string;
36
+ callCount: number;
37
+ suggestion: string;
38
+ severity: 'critical' | 'warning';
39
+ }
40
+ interface PerformanceReport {
41
+ timestamp: string;
42
+ duration: number;
43
+ operations: OperationReport[];
44
+ n1Detections: N1Detection[];
45
+ cacheStats?: CacheStats;
46
+ }
47
+ interface OperationReport {
48
+ operationName: string | null;
49
+ duration: number;
50
+ resolverCalls: number;
51
+ costEstimate: number;
52
+ }
53
+ interface CacheStats {
54
+ hits: number;
55
+ misses: number;
56
+ hitRate: number;
57
+ entries: number;
58
+ }
59
+ interface CostConfig {
60
+ defaultFieldCost?: number;
61
+ defaultListMultiplier?: number;
62
+ costMap?: Record<string, number>;
63
+ maxCost?: number;
64
+ }
65
+ interface WatchdogConfig {
66
+ enableDetector?: boolean;
67
+ enableCost?: boolean;
68
+ enableCache?: boolean;
69
+ cost?: CostConfig;
70
+ cache?: CacheConfig;
71
+ dynamicCost?: boolean;
72
+ dynamicCostBaseline?: number;
73
+ }
74
+ interface CacheConfig {
75
+ maxSize?: number;
76
+ ttl?: number;
77
+ invalidateOnMutation?: boolean;
78
+ backend?: CacheBackend;
79
+ }
80
+
81
+ interface ResolverTimingEntry {
82
+ avgDuration: number;
83
+ p95Duration: number;
84
+ callCount: number;
85
+ lastUpdated: number;
86
+ }
87
+ interface ResolverTimingData {
88
+ [fieldPath: string]: ResolverTimingEntry;
89
+ }
90
+ declare class DynamicCostTracker {
91
+ private timingData;
92
+ private durationSamples;
93
+ private maxSamples;
94
+ /**
95
+ * Record resolver timing from instrumentation.
96
+ * Uses a running average to avoid unbounded memory growth.
97
+ */
98
+ recordTiming(typeName: string, fieldName: string, durationMs: number): void;
99
+ /**
100
+ * Get dynamic cost config based on recorded data.
101
+ * Maps actual resolver performance to cost weights.
102
+ * Fields taking baselineDuration ms = cost 1, linear scaling.
103
+ */
104
+ toCostConfig(options?: {
105
+ baselineDuration?: number;
106
+ roundTo?: number;
107
+ }): CostConfig;
108
+ /** Export timing data for persistence */
109
+ export(): ResolverTimingData;
110
+ /** Import previously saved timing data */
111
+ import(data: ResolverTimingData): void;
112
+ /** Get stats summary */
113
+ getStats(): {
114
+ trackedFields: number;
115
+ totalCalls: number;
116
+ slowestFields: Array<{
117
+ field: string;
118
+ avgDuration: number;
119
+ }>;
120
+ };
121
+ }
122
+
123
+ declare class ResolverInstrumenter {
124
+ private calls;
125
+ private costTracker;
126
+ constructor(options?: {
127
+ costTracker?: DynamicCostTracker;
128
+ });
129
+ instrumentResolvers(resolvers: Record<string, Record<string, unknown>>): Record<string, Record<string, unknown>>;
130
+ getCalls(): ResolverCall[];
131
+ reset(): void;
132
+ }
133
+
134
+ declare function analyzeForN1(calls: ResolverCall[], threshold?: number): N1Detection[];
135
+
136
+ interface CostBreakdown {
137
+ totalCost: number;
138
+ fieldCosts: {
139
+ path: string;
140
+ cost: number;
141
+ }[];
142
+ exceeds: boolean;
143
+ limit: number;
144
+ }
145
+ declare function analyzeCost(document: DocumentNode, schema: GraphQLSchema, config?: CostConfig, variables?: Record<string, unknown>): CostBreakdown;
146
+
147
+ declare function costLimitRule(schema: GraphQLSchema, config: CostConfig): (context: {
148
+ getDocument: () => graphql.DocumentNode;
149
+ }) => ASTVisitor;
150
+
151
+ interface OptimizationSuggestion {
152
+ type: 'pagination' | 'field-pruning' | 'fragment' | 'dataloader' | 'depth-reduction';
153
+ severity: 'high' | 'medium' | 'low';
154
+ field: string;
155
+ message: string;
156
+ estimatedSaving: number;
157
+ }
158
+ declare function suggestOptimizations(breakdown: CostBreakdown, document: DocumentNode, schema: GraphQLSchema, config?: CostConfig): OptimizationSuggestion[];
159
+
160
+ interface NormalizedEntity {
161
+ __typename: string;
162
+ id: string;
163
+ data: Record<string, unknown>;
164
+ }
165
+ declare function normalizeResponse(data: Record<string, unknown>, operationName: string | null, variables?: Record<string, unknown>): {
166
+ entities: NormalizedEntity[];
167
+ cacheKey: string;
168
+ };
169
+ declare function generateCacheKey(operationName: string | null, variables?: Record<string, unknown>): string;
170
+
171
+ declare class ResponseCache {
172
+ private cache;
173
+ private typeIndex;
174
+ private maxSize;
175
+ private ttl;
176
+ private stats;
177
+ private backend;
178
+ constructor(config?: CacheConfig);
179
+ set(cacheKey: string, data: unknown, entities: NormalizedEntity[]): void;
180
+ get(cacheKey: string): unknown | null;
181
+ getAsync(cacheKey: string): Promise<unknown | null>;
182
+ invalidateByType(typename: string): number;
183
+ invalidateByEntity(typename: string, id: string): number;
184
+ getStats(): CacheStats;
185
+ clear(): void;
186
+ private updateTypeIndex;
187
+ private deleteEntry;
188
+ private evictLRU;
189
+ private updateHitRate;
190
+ private updateEntryCount;
191
+ }
192
+
193
+ declare function getMutationTypes(document: DocumentNode, schema: GraphQLSchema): string[];
194
+
195
+ interface RedisBackendConfig {
196
+ url?: string;
197
+ host?: string;
198
+ port?: number;
199
+ password?: string;
200
+ db?: number;
201
+ keyPrefix?: string;
202
+ }
203
+ declare class RedisCacheBackend implements CacheBackend {
204
+ private client;
205
+ private config;
206
+ constructor(config?: RedisBackendConfig);
207
+ connect(): Promise<void>;
208
+ disconnect(): Promise<void>;
209
+ private prefixedKey;
210
+ private ensureConnected;
211
+ get(key: string): Promise<string | null>;
212
+ set(key: string, value: string, ttlMs?: number): Promise<void>;
213
+ del(key: string): Promise<void>;
214
+ keys(pattern: string): Promise<string[]>;
215
+ delMany(keys: string[]): Promise<number>;
216
+ clear(): Promise<void>;
217
+ }
218
+
219
+ /** Type stub for Cloudflare Workers KV namespace binding (no dependency needed) */
220
+ interface KVNamespace {
221
+ get(key: string): Promise<string | null>;
222
+ put(key: string, value: string, options?: {
223
+ expirationTtl?: number;
224
+ }): Promise<void>;
225
+ delete(key: string): Promise<void>;
226
+ list(options?: {
227
+ prefix?: string;
228
+ cursor?: string;
229
+ }): Promise<{
230
+ keys: {
231
+ name: string;
232
+ }[];
233
+ list_complete: boolean;
234
+ cursor?: string;
235
+ }>;
236
+ }
237
+ interface CloudflareKVConfig {
238
+ namespace: KVNamespace;
239
+ keyPrefix?: string;
240
+ }
241
+ declare class CloudflareKVBackend implements CacheBackend {
242
+ private ns;
243
+ private keyPrefix;
244
+ constructor(config: CloudflareKVConfig);
245
+ private prefixedKey;
246
+ private unprefixKey;
247
+ get(key: string): Promise<string | null>;
248
+ set(key: string, value: string, ttlMs?: number): Promise<void>;
249
+ del(key: string): Promise<void>;
250
+ keys(pattern: string): Promise<string[]>;
251
+ delMany(keys: string[]): Promise<number>;
252
+ clear(): Promise<void>;
253
+ }
254
+
255
+ interface WatchdogYogaPluginOptions extends WatchdogConfig {
256
+ onDetection?: (detections: N1Detection[]) => void;
257
+ }
258
+ declare function useWatchdog(config?: WatchdogYogaPluginOptions): {
259
+ onExecute({ args }: {
260
+ args: {
261
+ schema: GraphQLSchema;
262
+ document: DocumentNode;
263
+ contextValue?: Record<string, unknown>;
264
+ operationName?: string | null;
265
+ variableValues?: Record<string, unknown> | null;
266
+ };
267
+ }): {
268
+ onExecuteDone({ result }: {
269
+ result: {
270
+ data?: Record<string, unknown> | null;
271
+ errors?: unknown[];
272
+ };
273
+ }): {
274
+ n1Detections: N1Detection[];
275
+ duration: number;
276
+ };
277
+ };
278
+ getCache(): ResponseCache | null;
279
+ };
280
+
281
+ interface WatchdogApolloPluginOptions extends WatchdogConfig {
282
+ onDetection?: (detections: N1Detection[]) => void;
283
+ }
284
+ declare function watchdogApolloPlugin(config?: WatchdogApolloPluginOptions): {
285
+ requestDidStart({ schema: _schema }?: {
286
+ schema?: GraphQLSchema;
287
+ }): Promise<{
288
+ executionDidStart(): Promise<{
289
+ willResolveField({ info }: {
290
+ info: {
291
+ fieldName: string;
292
+ parentType: {
293
+ name: string;
294
+ };
295
+ path: {
296
+ key: string | number;
297
+ };
298
+ };
299
+ }): (_error: unknown, _result: unknown) => void;
300
+ }>;
301
+ willSendResponse({ response: _response }: {
302
+ response: {
303
+ body?: {
304
+ singleResult?: {
305
+ data?: Record<string, unknown>;
306
+ errors?: unknown[];
307
+ };
308
+ };
309
+ };
310
+ }): Promise<void>;
311
+ }>;
312
+ getCache(): ResponseCache | null;
313
+ };
314
+
315
+ declare function calculatePerformanceScore(report: PerformanceReport): number;
316
+ declare function generateDashboard(report: PerformanceReport): string;
317
+
318
+ type ReportFormat = 'terminal' | 'json' | 'dashboard';
319
+ declare function generateReport(report: PerformanceReport, format?: ReportFormat): string;
320
+
321
+ export { type CacheBackend, type CacheConfig, type CacheStats, CloudflareKVBackend, type CloudflareKVConfig, type CostBreakdown, type CostConfig, DynamicCostTracker, type KVNamespace, MemoryCacheBackend, type N1Detection, type NormalizedEntity, type OperationReport, type OptimizationSuggestion, type PerformanceReport, type RedisBackendConfig, RedisCacheBackend, type ReportFormat, type ResolverCall, ResolverInstrumenter, type ResolverTimingData, type ResolverTimingEntry, ResponseCache, type WatchdogConfig, analyzeCost, analyzeForN1, calculatePerformanceScore, costLimitRule, generateCacheKey, generateDashboard, generateReport, getMutationTypes, normalizeResponse, suggestOptimizations, useWatchdog, watchdogApolloPlugin };