@saptools/service-flow 0.1.2 → 0.1.3

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/discovery/discover-repositories.ts","../src/utils/path-utils.ts","../src/parsers/package-json-parser.ts","../src/parsers/cds-parser.ts","../src/parsers/decorator-parser.ts","../src/parsers/ts-project.ts","../src/parsers/handler-registration-parser.ts","../src/utils/redaction.ts","../src/parsers/outbound-call-parser.ts","../src/parsers/service-binding-parser.ts","../src/linker/dynamic-edge-resolver.ts","../src/linker/service-resolver.ts","../src/linker/helper-package-linker.ts","../src/linker/cross-repo-linker.ts","../src/trace/trace-engine.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { DiscoveredRepository } from '../types.js';\nimport { relativePath } from '../utils/path-utils.js';\nexport async function discoverRepositories(\n rootPath: string,\n ignore: readonly string[]\n): Promise<DiscoveredRepository[]> {\n const root = path.resolve(rootPath);\n const ignored = new Set(ignore);\n const found: DiscoveredRepository[] = [];\n async function walk(dir: string): Promise<void> {\n const rel = relativePath(root, dir);\n if (rel !== '.' && rel.split('/').some((part) => ignored.has(part))) return;\n let entries: Array<{ name: string; isDirectory: () => boolean }>;\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n if (entries.some((e) => e.name === '.git' || e.name === '.git-fixture')) {\n found.push({\n name: path.basename(dir),\n absolutePath: dir,\n relativePath: relativePath(root, dir),\n isGitRepo: true\n });\n return;\n }\n for (const entry of entries)\n if (entry.isDirectory() && !ignore.includes(entry.name))\n await walk(path.join(dir, entry.name));\n }\n await walk(root);\n return found.sort((a, b) => a.relativePath.localeCompare(b.relativePath));\n}\n","import path from 'node:path';\nexport function normalizePath(value: string): string {\n return value.split(path.sep).join('/');\n}\nexport function relativePath(root: string, value: string): string {\n return normalizePath(path.relative(root, value) || '.');\n}\nexport function ensureLeadingSlash(value: string): string {\n return value.startsWith('/') ? value : `/${value}`;\n}\nexport function stripQuotes(value: string): string {\n return value.replace(/^['\"`]|['\"`]$/g, '');\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CdsRequire, PackageFacts } from '../types.js';\nfunction recordOfString(value: unknown): Record<string, string> {\n if (!value || typeof value !== 'object') return {};\n return Object.fromEntries(\n Object.entries(value).filter(([, v]) => typeof v === 'string')\n ) as Record<string, string>;\n}\nfunction readRequires(cds: unknown): CdsRequire[] {\n const requires =\n cds && typeof cds === 'object' && 'requires' in cds\n ? (cds as { requires?: unknown }).requires\n : undefined;\n if (!requires || typeof requires !== 'object') return [];\n return Object.entries(requires).flatMap(([alias, raw]) => {\n if (!raw || typeof raw !== 'object') return [];\n const obj = raw as Record<string, unknown>;\n const credentials =\n obj.credentials && typeof obj.credentials === 'object'\n ? (obj.credentials as Record<string, unknown>)\n : {};\n return [\n {\n alias,\n kind: typeof obj.kind === 'string' ? obj.kind : undefined,\n model: typeof obj.model === 'string' ? obj.model : undefined,\n destination:\n typeof credentials.destination === 'string'\n ? credentials.destination\n : undefined,\n servicePath:\n typeof credentials.path === 'string' ? credentials.path : undefined,\n requestTimeout:\n typeof credentials.requestTimeout === 'number'\n ? credentials.requestTimeout\n : undefined,\n rawJson: JSON.stringify(raw)\n }\n ];\n });\n}\nexport async function parsePackageJson(\n repoPath: string\n): Promise<PackageFacts> {\n try {\n const raw = await fs.readFile(path.join(repoPath, 'package.json'), 'utf8');\n const json = JSON.parse(raw) as Record<string, unknown>;\n return {\n packageName: typeof json.name === 'string' ? json.name : undefined,\n packageVersion:\n typeof json.version === 'string' ? json.version : undefined,\n dependencies: {\n ...recordOfString(json.dependencies),\n ...recordOfString(json.devDependencies)\n },\n cdsRequires: readRequires(json.cds),\n scripts: recordOfString(json.scripts)\n };\n } catch {\n return { dependencies: {}, cdsRequires: [], scripts: {} };\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CdsOperationFact, CdsServiceFact } from '../types.js';\nimport { ensureLeadingSlash, normalizePath } from '../utils/path-utils.js';\n\nfunction lineOf(text: string, index: number): number {\n return text.slice(0, index).split('\\n').length;\n}\n\nfunction maskCommentsAndStrings(text: string): string {\n let out = '';\n let mode: 'code' | 'line' | 'block' | 'single' | 'double' | 'template' =\n 'code';\n for (let i = 0; i < text.length; i += 1) {\n const c = text[i] ?? '';\n const n = text[i + 1] ?? '';\n if (mode === 'code' && c === '/' && n === '/') {\n mode = 'line';\n out += ' ';\n i += 1;\n continue;\n }\n if (mode === 'code' && c === '/' && n === '*') {\n mode = 'block';\n out += ' ';\n i += 1;\n continue;\n }\n if (mode === 'line' && c === '\\n') mode = 'code';\n if (mode === 'block' && c === '*' && n === '/') {\n mode = 'code';\n out += ' ';\n i += 1;\n continue;\n }\n if (mode === 'code' && (c === \"'\" || c === '\"' || c === '`')) {\n mode = c === \"'\" ? 'single' : c === '\"' ? 'double' : 'template';\n out += ' ';\n continue;\n }\n if ((mode === 'single' && c === \"'\") || (mode === 'double' && c === '\"') || (mode === 'template' && c === '`'))\n mode = 'code';\n out += mode === 'code' || c === '\\n' ? c : ' ';\n }\n return out;\n}\n\nfunction readAnnotation(text: string, index: number): { end: number; raw: string } | undefined {\n if (text[index] !== '@') return undefined;\n let i = index + 1;\n while (/\\s/.test(text[i] ?? '')) i += 1;\n if (text[i] !== '(') return undefined;\n let depth = 0;\n for (; i < text.length; i += 1) {\n if (text[i] === '(') depth += 1;\n if (text[i] === ')') depth -= 1;\n if (depth === 0) return { end: i + 1, raw: text.slice(index, i + 1) };\n }\n return undefined;\n}\n\nfunction collectAnnotations(text: string, index: number): { end: number; raw: string } {\n let i = index;\n let raw = '';\n while (i < text.length) {\n while (/\\s/.test(text[i] ?? '')) i += 1;\n const annotation = readAnnotation(text, i);\n if (!annotation) break;\n raw += annotation.raw;\n i = annotation.end;\n }\n return { end: i, raw };\n}\n\nfunction pathAnnotation(raw: string): string | undefined {\n return /path\\s*:\\s*['\"]([^'\"]+)['\"]/s.exec(raw)?.[1];\n}\n\nfunction matchingBrace(text: string, open: number): number {\n let depth = 0;\n for (let i = open; i < text.length; i += 1) {\n if (text[i] === '{') depth += 1;\n if (text[i] === '}') depth -= 1;\n if (depth === 0) return i;\n }\n return text.length - 1;\n}\n\nfunction operationsFromBody(text: string, maskedBody: string, bodyOffset: number, filePath: string): CdsOperationFact[] {\n return [...maskedBody.matchAll(/\\b(action|function|event)\\s+(\\w+)\\s*(?:\\(([^)]*)\\))?\\s*(?:returns\\s+([^;{]+))?/g)].map((m) => ({\n operationType: (m[1] as 'action' | 'function' | 'event') ?? 'action',\n operationName: m[2] ?? 'unknown',\n operationPath: ensureLeadingSlash(m[2] ?? 'unknown'),\n paramsJson: JSON.stringify((m[3] ?? '').split(',').map((part) => part.trim()).filter(Boolean)),\n returnType: m[4]?.trim(),\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, bodyOffset + (m.index ?? 0))\n }));\n}\n\nexport async function parseCdsFile(repoPath: string, filePath: string): Promise<CdsServiceFact[]> {\n const absolute = path.join(repoPath, filePath);\n const text = await fs.readFile(absolute, 'utf8');\n const masked = maskCommentsAndStrings(text);\n const namespace = /namespace\\s+([\\w.]+)\\s*;/.exec(masked)?.[1];\n const services: CdsServiceFact[] = [];\n const pendingAnnotations: Array<{ end: number; raw: string }> = [];\n for (const a of masked.matchAll(/@\\s*\\(/g)) {\n const annotation = collectAnnotations(masked, a.index ?? 0);\n pendingAnnotations.push(annotation);\n }\n const serviceRegex = /\\b(extend\\s+)?service\\s+([\\w.]+)\\b/g;\n let match: RegExpExecArray | null;\n while ((match = serviceRegex.exec(masked))) {\n const afterName = collectAnnotations(masked, serviceRegex.lastIndex);\n const open = masked.indexOf('{', afterName.end);\n if (open === -1) continue;\n const matchIndex = match.index;\n const prefix = pendingAnnotations\n .filter((a) => a.end <= matchIndex && matchIndex - a.end < 8)\n .map((a) => a.raw)\n .join('');\n const annotations = `${prefix}${afterName.raw}`;\n const end = matchingBrace(masked, open);\n const body = masked.slice(open + 1, end);\n const name = match[2] ?? 'UnknownService';\n const serviceName = name.split('.').pop() ?? name;\n const servicePath = ensureLeadingSlash(pathAnnotation(annotations) ?? serviceName);\n services.push({\n namespace,\n serviceName,\n qualifiedName: name.includes('.') ? name : namespace ? `${namespace}.${name}` : name,\n servicePath,\n isExtend: Boolean(match[1]),\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, match.index),\n operations: operationsFromBody(text, body, open + 1, filePath)\n });\n serviceRegex.lastIndex = end + 1;\n }\n const baseOps = new Map(services.filter((s) => !s.isExtend).map((s) => [s.qualifiedName, s.operations]));\n for (const service of services.filter((s) => s.isExtend && s.operations.length === 0)) {\n const inherited = baseOps.get(service.qualifiedName) ?? baseOps.get(service.serviceName);\n if (inherited) service.operations = inherited.map((op) => ({ ...op, sourceFile: service.sourceFile, sourceLine: service.sourceLine }));\n }\n return services;\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport ts from 'typescript';\nimport type { HandlerClassFact } from '../types.js';\nimport { createSourceFile } from './ts-project.js';\nimport { normalizePath, stripQuotes } from '../utils/path-utils.js';\nfunction line(sf: ts.SourceFile, pos: number): number {\n return sf.getLineAndCharacterOfPosition(pos).line + 1;\n}\nfunction decs(node: ts.Node): ts.Decorator[] {\n return ts.canHaveDecorators(node) ? [...(ts.getDecorators(node) ?? [])] : [];\n}\nfunction callName(d: ts.Decorator): string {\n const e = d.expression;\n return ts.isCallExpression(e) ? e.expression.getText() : e.getText();\n}\nfunction firstArg(d: ts.Decorator): string {\n const e = d.expression;\n return ts.isCallExpression(e) && e.arguments[0]\n ? e.arguments[0].getText()\n : '';\n}\nexport async function parseDecorators(\n repoPath: string,\n filePath: string\n): Promise<HandlerClassFact[]> {\n const text = await fs.readFile(path.join(repoPath, filePath), 'utf8');\n const sf = createSourceFile(filePath, text);\n const constants = new Map<string, string>();\n const handlers: HandlerClassFact[] = [];\n function visit(node: ts.Node): void {\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.initializer &&\n ts.isStringLiteralLike(node.initializer)\n )\n constants.set(node.name.text, node.initializer.text);\n if (ts.isClassDeclaration(node)) {\n const className = node.name?.text ?? 'AnonymousHandler';\n const hasHandler = decs(node).some((d) => callName(d) === 'Handler');\n const methods = node.members.filter(ts.isMethodDeclaration).flatMap((m) =>\n decs(m)\n .filter((d) =>\n ['Func', 'Action', 'On', 'Event'].includes(callName(d))\n )\n .map((d) => {\n const raw = firstArg(d);\n const value =\n raw.startsWith('\"') || raw.startsWith(\"'\") || raw.startsWith('`')\n ? stripQuotes(raw)\n : (constants.get(raw) ??\n (raw.endsWith('.name') ? raw.split('.').at(-2) : undefined));\n return {\n methodName: m.name.getText(),\n decoratorKind: callName(d),\n decoratorValue: value,\n decoratorRawExpression: raw,\n sourceFile: normalizePath(filePath),\n sourceLine: line(sf, m.getStart())\n };\n })\n );\n if (hasHandler || methods.length > 0)\n handlers.push({\n className,\n sourceFile: normalizePath(filePath),\n sourceLine: line(sf, node.getStart()),\n methods\n });\n }\n ts.forEachChild(node, visit);\n }\n visit(sf);\n return handlers;\n}\n","import ts from 'typescript';\nexport function createSourceFile(\n filePath: string,\n text: string\n): ts.SourceFile {\n return ts.createSourceFile(\n filePath,\n text,\n ts.ScriptTarget.Latest,\n true,\n filePath.endsWith('.js') ? ts.ScriptKind.JS : ts.ScriptKind.TS\n );\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { HandlerRegistrationFact } from '../types.js';\nimport { normalizePath } from '../utils/path-utils.js';\nfunction lineOf(text: string, idx: number): number {\n return text.slice(0, idx).split('\\n').length;\n}\nexport async function parseHandlerRegistrations(\n repoPath: string,\n filePath: string\n): Promise<HandlerRegistrationFact[]> {\n const text = await fs.readFile(path.join(repoPath, filePath), 'utf8');\n const out: HandlerRegistrationFact[] = [];\n for (const m of text.matchAll(\n /createCombinedHandler\\s*\\(|srv\\.prepend\\s*\\(|cds\\.serve\\s*\\(/g\n ))\n out.push({\n registrationFile: normalizePath(filePath),\n registrationLine: lineOf(text, m.index ?? 0),\n registrationKind: m[0].startsWith('cds')\n ? 'cds.serve'\n : 'combined-handler',\n confidence: 0.8\n });\n for (const m of text.matchAll(\n /(?:const|export\\s+const)\\s+handlers\\s*=\\s*\\[([\\s\\S]*?)\\]/g\n ))\n for (const c of (m[1] ?? '').matchAll(/\\b(\\w+Handler)\\b/g))\n out.push({\n className: c[1],\n registrationFile: normalizePath(filePath),\n registrationLine: lineOf(text, (m.index ?? 0) + (c.index ?? 0)),\n registrationKind: 'handler-array',\n confidence: 0.9\n });\n return out;\n}\n","const SENSITIVE = /authorization|cookie|token|secret|password|key|credential/i;\nexport function redactText(text: string): string {\n return text.replace(\n /(authorization|cookie|token|secret|password|key|credential)\\s*[:=]\\s*(['\"`]?)[^,'\"`}\\s]+\\2/gi,\n '$1: [REDACTED]'\n );\n}\nexport function redactValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactValue);\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value))\n out[k] = SENSITIVE.test(k) ? '[REDACTED]' : redactValue(v);\n return out;\n }\n return typeof value === 'string' ? redactText(value) : value;\n}\nexport function summarizeExpression(text: string): string {\n return redactText(text).slice(0, 240);\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { OutboundCallFact } from '../types.js';\nimport { normalizePath, stripQuotes } from '../utils/path-utils.js';\nimport { summarizeExpression } from '../utils/redaction.js';\nfunction lineOf(text: string, idx: number): number {\n return text.slice(0, idx).split('\\n').length;\n}\nfunction firstArg(body: string, key: string): string | undefined {\n return new RegExp(`${key}\\\\s*:\\\\s*([^,}\\\\n]+)`).exec(body)?.[1]?.trim();\n}\nfunction matchingParen(text: string, open: number): number {\n let depth = 0;\n let quote: string | undefined;\n let escaped = false;\n for (let i = open; i < text.length; i += 1) {\n const ch = text[i] ?? '';\n if (quote) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === quote) quote = undefined;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n quote = ch;\n continue;\n }\n if (ch === '(') depth += 1;\n if (ch === ')') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\nfunction argumentForCall(expr: string, marker: string): string | undefined {\n const idx = expr.indexOf(marker);\n if (idx < 0) return undefined;\n const open = expr.indexOf('(', idx + marker.length);\n if (open < 0) return undefined;\n const close = matchingParen(expr, open);\n return close > open ? expr.slice(open + 1, close).trim() : undefined;\n}\nfunction entityFromArg(arg: string | undefined): string | undefined {\n if (!arg) return undefined;\n const first = arg.split(',')[0]?.trim();\n if (!first) return undefined;\n return stripQuotes(first).replace(/^this\\./, '');\n}\nfunction extractQueryEntity(expr: string): string | undefined {\n return (\n entityFromArg(argumentForCall(expr, 'SELECT.one.from')) ??\n entityFromArg(argumentForCall(expr, 'SELECT.from')) ??\n entityFromArg(argumentForCall(expr, 'INSERT.into')) ??\n entityFromArg(argumentForCall(expr, 'UPDATE')) ??\n entityFromArg(argumentForCall(expr, 'DELETE.from'))\n );\n}\nexport async function parseOutboundCalls(\n repoPath: string,\n filePath: string,\n): Promise<OutboundCallFact[]> {\n const text = await fs.readFile(path.join(repoPath, filePath), 'utf8');\n const out: OutboundCallFact[] = [];\n for (const m of text.matchAll(/(\\w+)\\.send\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)/g)) {\n const body = m[2] ?? '';\n const query = firstArg(body, 'query');\n const op = firstArg(body, 'path') ?? firstArg(body, 'event');\n out.push({\n callType: query ? 'remote_query' : 'remote_action',\n serviceVariableName: m[1],\n method: stripQuotes(firstArg(body, 'method') ?? 'POST'),\n operationPathExpr: op\n ? `/${stripQuotes(op).replace(/^\\//, '')}`\n : undefined,\n queryEntity: extractQueryEntity(query ?? ''),\n payloadSummary: summarizeExpression(body),\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, m.index ?? 0),\n confidence: op || query ? 0.8 : 0.4,\n });\n }\n for (const m of text.matchAll(/cds\\.run\\s*\\(/g)) {\n const open = (m.index ?? 0) + m[0].lastIndexOf('(');\n const close = matchingParen(text, open);\n const expr = close > open ? text.slice(open + 1, close) : '';\n const entity = extractQueryEntity(expr);\n out.push({\n callType: 'local_db_query',\n queryEntity: entity,\n payloadSummary: summarizeExpression(expr),\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, m.index ?? 0),\n confidence: entity ? 0.9 : 0.55,\n unresolvedReason: entity\n ? undefined\n : 'Could not resolve CAP query target entity from nested expression',\n });\n }\n for (const m of text.matchAll(\n /(\\w+)\\.(emit|publish|on)\\s*\\(\\s*(['\"`])([^'\"`]+)\\3/g,\n ))\n out.push({\n callType: m[2] === 'on' ? 'async_subscribe' : 'async_emit',\n serviceVariableName: m[1],\n eventNameExpr: m[4],\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, m.index ?? 0),\n confidence: 0.8,\n });\n for (const m of text.matchAll(\n /(?:axios\\s*\\(|executeHttpRequest\\s*\\(|useOrFetchDestination\\s*\\()([\\s\\S]*?)\\)/g,\n ))\n out.push({\n callType: 'external_http',\n payloadSummary: summarizeExpression(m[1] ?? ''),\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, m.index ?? 0),\n confidence: 0.7,\n unresolvedReason:\n 'External HTTP destination is outside indexed CAP services',\n });\n for (const m of text.matchAll(\n /cds\\.services(?:\\[['\"]([^'\"]+)['\"]\\]|\\.(\\w+))\\.(\\w+)\\s*\\(/g,\n ))\n out.push({\n callType: 'local_service_call',\n operationPathExpr: `/${m[3] ?? ''}`,\n payloadSummary: m[1] ?? m[2],\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(text, m.index ?? 0),\n confidence: 0.75,\n });\n return out;\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport ts from 'typescript';\nimport type { ServiceBindingFact } from '../types.js';\nimport { normalizePath } from '../utils/path-utils.js';\n\ninterface HelperBinding {\n exportedName: string;\n alias?: string;\n destinationExpr?: string;\n servicePathExpr?: string;\n isDynamic: boolean;\n placeholders: string[];\n sourceFile: string;\n sourceLine: number;\n}\ninterface ImportBinding {\n localName: string;\n exportedName: string;\n sourceFile?: string;\n}\n\nfunction lineOf(sf: ts.SourceFile, node: ts.Node): number {\n return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;\n}\nfunction stringValue(node: ts.Expression | undefined): string | undefined {\n if (!node) return undefined;\n if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node))\n return node.text;\n if (ts.isTemplateExpression(node))\n return node.getText().replace(/^`|`$/g, '');\n return node.getText();\n}\nfunction placeholders(value?: string): string[] {\n return [...(value ?? '').matchAll(/\\$\\{\\s*(\\w+)\\s*\\}/g)]\n .map((m) => m[1] ?? '')\n .filter(Boolean);\n}\nfunction connectFactFromCall(\n call: ts.CallExpression,\n):\n | Omit<HelperBinding, 'exportedName' | 'sourceFile' | 'sourceLine'>\n | undefined {\n const expr = call.expression;\n if (!ts.isPropertyAccessExpression(expr) || expr.name.text !== 'to')\n return undefined;\n const inner = expr.expression;\n if (\n !ts.isPropertyAccessExpression(inner) ||\n inner.name.text !== 'connect' ||\n inner.expression.getText() !== 'cds'\n )\n return undefined;\n const first = call.arguments[0];\n if (!first) return undefined;\n if (\n ts.isStringLiteralLike(first) ||\n ts.isNoSubstitutionTemplateLiteral(first)\n )\n return { alias: first.text, isDynamic: false, placeholders: [] };\n if (!ts.isObjectLiteralExpression(first))\n return {\n servicePathExpr: first.getText(),\n isDynamic: true,\n placeholders: [],\n };\n let destinationExpr: string | undefined;\n let servicePathExpr: string | undefined;\n function visitObject(obj: ts.ObjectLiteralExpression): void {\n for (const prop of obj.properties) {\n if (!ts.isPropertyAssignment(prop)) continue;\n const name =\n ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name)\n ? prop.name.text\n : undefined;\n if (name === 'destination')\n destinationExpr = stringValue(prop.initializer);\n if (name === 'path' || name === 'servicePath')\n servicePathExpr = stringValue(prop.initializer);\n if (ts.isObjectLiteralExpression(prop.initializer))\n visitObject(prop.initializer);\n }\n }\n visitObject(first);\n const ph = [\n ...placeholders(destinationExpr),\n ...placeholders(servicePathExpr),\n ];\n return {\n destinationExpr,\n servicePathExpr,\n isDynamic: ph.length > 0 || (!destinationExpr && !servicePathExpr),\n placeholders: ph,\n };\n}\nfunction unwrapCall(expr: ts.Expression): ts.CallExpression | undefined {\n if (ts.isAwaitExpression(expr)) return unwrapCall(expr.expression);\n if (ts.isCallExpression(expr)) return expr;\n return undefined;\n}\nfunction findConnectInExpression(\n expr: ts.Expression,\n):\n | Omit<HelperBinding, 'exportedName' | 'sourceFile' | 'sourceLine'>\n | undefined {\n const direct = unwrapCall(expr);\n if (direct) {\n const fact = connectFactFromCall(direct);\n if (fact) return fact;\n }\n let found:\n | Omit<HelperBinding, 'exportedName' | 'sourceFile' | 'sourceLine'>\n | undefined;\n function visit(node: ts.Node): void {\n if (found) return;\n if (ts.isCallExpression(node)) found = connectFactFromCall(node);\n if (!found) ts.forEachChild(node, visit);\n }\n visit(expr);\n return found;\n}\nasync function readSource(abs: string): Promise<ts.SourceFile | undefined> {\n try {\n const text = await fs.readFile(abs, 'utf8');\n return ts.createSourceFile(\n abs,\n text,\n ts.ScriptTarget.Latest,\n true,\n ts.ScriptKind.TS,\n );\n } catch {\n return undefined;\n }\n}\nasync function resolveImport(\n repoPath: string,\n fromFile: string,\n spec: string,\n): Promise<string | undefined> {\n if (!spec.startsWith('.')) return undefined;\n const base = path.resolve(repoPath, path.dirname(fromFile), spec);\n for (const candidate of [\n base,\n `${base}.ts`,\n `${base}.js`,\n path.join(base, 'index.ts'),\n path.join(base, 'index.js'),\n ]) {\n try {\n const st = await fs.stat(candidate);\n if (st.isFile()) return normalizePath(path.relative(repoPath, candidate));\n } catch {\n /* continue */\n }\n }\n return undefined;\n}\nasync function importsFor(\n repoPath: string,\n filePath: string,\n sf: ts.SourceFile,\n): Promise<ImportBinding[]> {\n const imports: ImportBinding[] = [];\n for (const stmt of sf.statements) {\n if (\n !ts.isImportDeclaration(stmt) ||\n !ts.isStringLiteralLike(stmt.moduleSpecifier)\n )\n continue;\n const sourceFile = await resolveImport(\n repoPath,\n filePath,\n stmt.moduleSpecifier.text,\n );\n const clause = stmt.importClause;\n if (!clause) continue;\n if (clause.name)\n imports.push({\n localName: clause.name.text,\n exportedName: 'default',\n sourceFile,\n });\n const bindings = clause.namedBindings;\n if (bindings && ts.isNamedImports(bindings))\n for (const el of bindings.elements)\n imports.push({\n localName: el.name.text,\n exportedName: el.propertyName?.text ?? el.name.text,\n sourceFile,\n });\n }\n return imports;\n}\nasync function helperBindings(\n repoPath: string,\n filePath: string,\n): Promise<HelperBinding[]> {\n const sf = await readSource(path.join(repoPath, filePath));\n if (!sf) return [];\n const sourceFileAst = sf;\n const out: HelperBinding[] = [];\n for (const stmt of sf.statements) {\n const exported = ts.canHaveModifiers(stmt)\n ? (ts\n .getModifiers(stmt)\n ?.some(\n (m: ts.ModifierLike) => m.kind === ts.SyntaxKind.ExportKeyword,\n ) ?? false)\n : false;\n if (ts.isFunctionDeclaration(stmt) && stmt.name) {\n let fact:\n | Omit<HelperBinding, 'exportedName' | 'sourceFile' | 'sourceLine'>\n | undefined;\n stmt.forEachChild(function visit(node): void {\n if (!fact && ts.isReturnStatement(node) && node.expression)\n fact = findConnectInExpression(node.expression);\n if (!fact) ts.forEachChild(node, visit);\n });\n if (fact && exported)\n out.push({\n ...fact,\n exportedName: stmt.name.text,\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(sf, stmt),\n });\n }\n if (ts.isVariableStatement(stmt))\n for (const decl of stmt.declarationList.declarations) {\n if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;\n const fact = findConnectInExpression(decl.initializer);\n if (fact && exported)\n out.push({\n ...fact,\n exportedName: decl.name.text,\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(sourceFileAst, decl),\n });\n }\n }\n return out;\n}\n\nexport async function parseServiceBindings(\n repoPath: string,\n filePath: string,\n): Promise<ServiceBindingFact[]> {\n const sf = await readSource(path.join(repoPath, filePath));\n if (!sf) return [];\n const sourceFileAst = sf;\n const out: ServiceBindingFact[] = [];\n const imports = await importsFor(repoPath, filePath, sf);\n const helperCache = new Map<string, HelperBinding[]>();\n async function importedHelper(\n localName: string,\n ): Promise<{ imp: ImportBinding; helper: HelperBinding } | undefined> {\n const imp = imports.find((i) => i.localName === localName && i.sourceFile);\n if (!imp?.sourceFile) return undefined;\n if (!helperCache.has(imp.sourceFile))\n helperCache.set(\n imp.sourceFile,\n await helperBindings(repoPath, imp.sourceFile),\n );\n const helper = helperCache\n .get(imp.sourceFile)\n ?.find((h) => h.exportedName === imp.exportedName);\n return helper ? { imp, helper } : undefined;\n }\n async function recordVariable(decl: ts.VariableDeclaration): Promise<void> {\n if (!ts.isIdentifier(decl.name) || !decl.initializer) return;\n const call = unwrapCall(decl.initializer);\n if (!call) return;\n const direct = connectFactFromCall(call);\n if (direct)\n out.push({\n variableName: decl.name.text,\n ...direct,\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(sourceFileAst, decl),\n });\n else if (ts.isIdentifier(call.expression)) {\n const resolved = await importedHelper(call.expression.text);\n if (resolved)\n out.push({\n variableName: decl.name.text,\n alias: resolved.helper.alias,\n destinationExpr: resolved.helper.destinationExpr,\n servicePathExpr: resolved.helper.servicePathExpr,\n isDynamic: resolved.helper.isDynamic,\n placeholders: resolved.helper.placeholders,\n sourceFile: normalizePath(filePath),\n sourceLine: lineOf(sourceFileAst, decl),\n helperChain: [\n {\n callerVariable: decl.name.text,\n importedHelper: call.expression.text,\n importSource: resolved.imp.sourceFile,\n exportedSymbol: resolved.imp.exportedName,\n helperSourceFile: resolved.helper.sourceFile,\n helperSourceLine: resolved.helper.sourceLine,\n },\n ],\n });\n }\n }\n const declarations: ts.VariableDeclaration[] = [];\n function collectDeclarations(node: ts.Node): void {\n if (ts.isVariableDeclaration(node)) declarations.push(node);\n ts.forEachChild(node, collectDeclarations);\n }\n collectDeclarations(sourceFileAst);\n for (const decl of declarations) await recordVariable(decl);\n return out;\n}\n","export function applyVariables(\n template: string | undefined,\n vars: Record<string, string>\n): string | undefined {\n if (!template) return undefined;\n return template.replace(\n /\\$\\{\\s*(\\w+)\\s*\\}/g,\n (_m, key: string) => vars[key] ?? `\\${${key}}`\n );\n}\nexport function extractPlaceholders(template: string | undefined): string[] {\n return [...(template ?? '').matchAll(/\\$\\{\\s*(\\w+)\\s*\\}/g)]\n .map((m) => m[1] ?? '')\n .filter(Boolean);\n}\n","import type { Db } from '../db/connection.js';\nexport interface OperationTarget {\n operationId: number;\n repoName: string;\n servicePath: string;\n operationPath: string;\n operationName: string;\n sourceFile: string;\n sourceLine: number;\n score: number;\n reasons: string[];\n}\nexport interface OperationResolution {\n status: 'resolved' | 'ambiguous' | 'unresolved' | 'dynamic';\n target?: OperationTarget;\n candidates: OperationTarget[];\n reasons: string[];\n}\nfunction rows(\n db: Db,\n operationPath: string,\n workspaceId?: number,\n): OperationTarget[] {\n return db\n .prepare(\n `SELECT o.id operationId,r.name repoName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score,'' reasons\n FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id\n WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path=? OR o.operation_name=?) ORDER BY r.name,s.service_path,o.operation_name`,\n )\n .all(\n workspaceId,\n workspaceId,\n operationPath,\n operationPath.replace(/^\\//, ''),\n ) as unknown as OperationTarget[];\n}\nexport function resolveOperation(\n db: Db,\n signals: {\n servicePath?: string;\n alias?: string;\n destination?: string;\n operationPath?: string;\n hasExplicitOverride?: boolean;\n isDynamic?: boolean;\n },\n workspaceId?: number,\n): OperationResolution {\n if (!signals.operationPath)\n return {\n status: 'unresolved',\n candidates: [],\n reasons: ['missing_operation_path'],\n };\n const candidates = rows(db, signals.operationPath, workspaceId).map((c) => ({\n ...c,\n score: 0.2,\n reasons: ['operation_path_match'],\n }));\n if (candidates.length === 0)\n return {\n status: 'unresolved',\n candidates: [],\n reasons: ['no_operation_candidates'],\n };\n const hasStrongSignal = Boolean(\n signals.servicePath ||\n signals.alias ||\n signals.destination ||\n signals.hasExplicitOverride,\n );\n for (const c of candidates) {\n if (signals.servicePath && c.servicePath === signals.servicePath) {\n c.score += 0.75;\n c.reasons.push('exact_service_path');\n }\n if (signals.servicePath && c.servicePath !== signals.servicePath) {\n c.score -= 0.1;\n c.reasons.push('service_path_mismatch');\n }\n if (signals.hasExplicitOverride) {\n c.score += 0.2;\n c.reasons.push('explicit_dynamic_override');\n }\n }\n candidates.sort(\n (a, b) => b.score - a.score || a.repoName.localeCompare(b.repoName),\n );\n const best = candidates[0];\n const second = candidates[1];\n if (signals.isDynamic && !signals.hasExplicitOverride && !signals.servicePath)\n return {\n status: 'dynamic',\n candidates,\n reasons: ['dynamic_target_without_override'],\n };\n if (!hasStrongSignal)\n return {\n status: candidates.length > 1 ? 'ambiguous' : 'unresolved',\n candidates,\n reasons: ['operation_path_only_has_no_strong_target_signal'],\n };\n if (\n best &&\n best.score >= 0.9 &&\n (!second || best.score - second.score >= 0.25)\n )\n return {\n status: 'resolved',\n target: best,\n candidates,\n reasons: best.reasons,\n };\n return {\n status: candidates.length > 1 ? 'ambiguous' : 'unresolved',\n candidates,\n reasons: ['candidate_score_below_resolution_threshold'],\n };\n}\nexport function findOperation(\n db: Db,\n servicePath: string | undefined,\n operationPath: string | undefined,\n workspaceId?: number,\n): OperationTarget | undefined {\n return resolveOperation(db, { servicePath, operationPath }, workspaceId)\n .target;\n}\n","import type { Db } from '../db/connection.js';\nexport function linkHelperPackages(db: Db, workspaceId: number): number {\n const repos = db\n .prepare(\n 'SELECT id,name,dependencies_json FROM repositories WHERE workspace_id=?'\n )\n .all(workspaceId) as Array<{\n id: number;\n name: string;\n dependencies_json: string;\n }>;\n let count = 0;\n for (const repo of repos) {\n const deps = JSON.parse(repo.dependencies_json) as Record<string, string>;\n for (const dep of Object.keys(deps)) {\n const helper = repos.find((r) => r.name === dep || dep.endsWith(r.name));\n if (helper) {\n db.prepare(\n 'INSERT INTO graph_edges(workspace_id,edge_type,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic) VALUES(?,?,?,?,?,?,?,?,?)'\n ).run(\n workspaceId,\n 'REPO_IMPORTS_HELPER_PACKAGE',\n 'repo',\n String(repo.id),\n 'repo',\n String(helper.id),\n 0.9,\n JSON.stringify({ dependency: dep }),\n 0\n );\n count += 1;\n }\n }\n }\n return count;\n}\n","import type { Db } from '../db/connection.js';\nimport { applyVariables } from './dynamic-edge-resolver.js';\nimport { resolveOperation } from './service-resolver.js';\nimport { linkHelperPackages } from './helper-package-linker.js';\nexport function linkWorkspace(\n db: Db,\n workspaceId: number,\n vars: Record<string, string> = {},\n): { edgeCount: number; unresolvedCount: number } {\n db.prepare('DELETE FROM graph_edges WHERE workspace_id=?').run(workspaceId);\n let edges = linkHelperPackages(db, workspaceId);\n let unresolved = 0;\n const calls = db\n .prepare(\n `SELECT c.*,r.name repoName,b.alias,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.is_dynamic isDynamic,b.placeholders_json placeholdersJson,b.helper_chain_json helperChainJson,req.service_path requireServicePath,req.destination requireDestination FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id LEFT JOIN service_bindings b ON b.id=c.service_binding_id LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias WHERE r.workspace_id=?`,\n )\n .all(workspaceId) as Array<Record<string, unknown>>;\n for (const call of calls) {\n const callType = String(call.call_type);\n const op = applyVariables(String(call.operation_path_expr ?? ''), vars);\n const servicePath = applyVariables(\n (call.servicePathExpr as string | undefined) ??\n (call.requireServicePath as string | undefined),\n vars,\n );\n const destination =\n (call.destinationExpr as string | undefined) ??\n (call.requireDestination as string | undefined);\n const isDynamic = Boolean(Number(call.isDynamic ?? 0));\n const resolution = callType.startsWith('remote')\n ? resolveOperation(\n db,\n {\n servicePath,\n operationPath: op,\n alias: call.alias as string | undefined,\n destination,\n isDynamic,\n hasExplicitOverride: Object.keys(vars).length > 0,\n },\n workspaceId,\n )\n : { status: 'unresolved' as const, candidates: [], reasons: [] };\n const target = resolution.target;\n const evidence = {\n sourceFile: call.source_file,\n sourceLine: call.source_line,\n file: call.source_file,\n line: call.source_line,\n repo: call.repoName,\n serviceAlias: call.alias,\n destination,\n servicePath,\n operationPath: op,\n targetRepo: target?.repoName,\n targetOperation: target?.operationName,\n helperChain: call.helperChainJson\n ? (JSON.parse(String(call.helperChainJson)) as unknown)\n : undefined,\n candidates: resolution.candidates,\n candidateCount: resolution.candidates.length,\n resolutionStatus: resolution.status,\n resolutionReasons: resolution.reasons,\n };\n if (target) {\n db.prepare(\n 'INSERT INTO graph_edges(workspace_id,edge_type,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic) VALUES(?,?,?,?,?,?,?,?,?)',\n ).run(\n workspaceId,\n 'REMOTE_CALL_RESOLVES_TO_OPERATION',\n 'call',\n String(call.id),\n 'operation',\n String(target.operationId),\n target.score,\n JSON.stringify(evidence),\n isDynamic ? 1 : 0,\n );\n edges += 1;\n } else {\n const edgeType =\n callType === 'local_db_query'\n ? 'HANDLER_RUNS_DB_QUERY'\n : callType === 'external_http'\n ? 'HANDLER_CALLS_EXTERNAL_HTTP'\n : callType === 'async_emit'\n ? 'HANDLER_EMITS_EVENT'\n : callType === 'async_subscribe'\n ? 'EVENT_CONSUMED_BY_HANDLER'\n : resolution.status === 'dynamic'\n ? 'DYNAMIC_EDGE_CANDIDATE'\n : 'UNRESOLVED_EDGE';\n db.prepare(\n 'INSERT INTO graph_edges(workspace_id,edge_type,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?)',\n ).run(\n workspaceId,\n edgeType,\n 'call',\n String(call.id),\n callType.startsWith('async_') ? 'event' : 'external',\n String(call.event_name_expr ?? call.query_entity ?? op ?? call.id),\n Number(call.confidence ?? 0.2),\n JSON.stringify(evidence),\n isDynamic || resolution.status === 'dynamic' ? 1 : 0,\n String(\n call.unresolved_reason ??\n (resolution.status === 'ambiguous'\n ? 'Ambiguous operation candidates require a strong service signal'\n : resolution.status === 'dynamic'\n ? 'Dynamic target requires runtime variable overrides'\n : 'No indexed target operation matched'),\n ),\n );\n edges += 1;\n unresolved += edgeType === 'UNRESOLVED_EDGE' ? 1 : 0;\n }\n }\n return { edgeCount: edges, unresolvedCount: unresolved };\n}\n","import type { Db } from '../db/connection.js';\nimport { applyVariables } from '../linker/dynamic-edge-resolver.js';\nimport type { TraceEdge, TraceResult, TraceStart } from '../types.js';\n\ninterface RepoRef {\n id: number;\n name: string;\n}\ninterface StartScope {\n repo?: RepoRef;\n sourceFiles?: Set<string>;\n selectorMatched: boolean;\n}\ninterface CallRow extends Record<string, unknown> {\n id: number;\n repo_id: number;\n repoName: string;\n source_file: string;\n source_line: number;\n call_type: string;\n confidence: number;\n}\ninterface GraphRow extends Record<string, unknown> {\n id: number;\n edge_type: string;\n from_id: string;\n to_kind: string;\n to_id: string;\n confidence: number;\n evidence_json: string;\n unresolved_reason?: string;\n}\ninterface Candidate {\n servicePath?: string;\n operationPath?: string;\n repoName?: string;\n operationName?: string;\n score?: number;\n}\n\nfunction normalizeOperation(value: string | undefined): string | undefined {\n if (!value) return undefined;\n return value.startsWith('/') ? value.slice(1) : value;\n}\nfunction positiveDepth(value: number): number {\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;\n}\nfunction sourceFilesForStart(\n db: Db,\n repoId: number | undefined,\n start: TraceStart,\n): Set<string> | undefined {\n const handler = start.handler;\n const operation = normalizeOperation(start.operation ?? start.operationPath);\n if (!handler && !operation) return undefined;\n const rows = db\n .prepare(\n `SELECT DISTINCT hc.source_file sourceFile\n FROM handler_classes hc LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id\n WHERE (? IS NULL OR hc.repo_id=?) AND (? IS NULL OR hc.class_name=? OR hm.method_name=?)\n AND (? IS NULL OR hm.decorator_value=? OR hm.method_name=?)`,\n )\n .all(\n repoId,\n repoId,\n handler,\n handler,\n handler,\n operation,\n operation,\n operation,\n ) as Array<{ sourceFile?: string }>;\n if (rows.length === 0) return undefined;\n return new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]);\n}\nfunction startScope(db: Db, start: TraceStart): StartScope {\n const repo = start.repo\n ? (db\n .prepare(\n 'SELECT id,name FROM repositories WHERE name=? OR package_name=?',\n )\n .get(start.repo, start.repo) as RepoRef | undefined)\n : undefined;\n if (start.repo && !repo) return { repo, selectorMatched: false };\n const sourceFiles = sourceFilesForStart(db, repo?.id, start);\n const hasSelector = Boolean(\n start.handler ?? start.operation ?? start.operationPath,\n );\n return {\n repo,\n sourceFiles,\n selectorMatched: !hasSelector || sourceFiles !== undefined,\n };\n}\nfunction handlerFilesForOperation(db: Db, operationId: string): Set<string> {\n const op = db\n .prepare(\n `SELECT o.operation_name operationName,o.operation_path operationPath,s.repo_id repoId\n FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`,\n )\n .get(operationId) as\n | { operationName?: string; operationPath?: string; repoId?: number }\n | undefined;\n if (!op) return new Set();\n const operation = normalizeOperation(op.operationPath ?? op.operationName);\n const rows = db\n .prepare(\n `SELECT DISTINCT hc.source_file sourceFile FROM handler_classes hc\n JOIN handler_methods hm ON hm.handler_class_id=hc.id\n WHERE hc.repo_id=? AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`,\n )\n .all(op.repoId, operation, operation, op.operationName) as Array<{\n sourceFile?: string;\n }>;\n return new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]);\n}\nfunction includeCall(\n type: string,\n options: {\n includeExternal?: boolean;\n includeDb?: boolean;\n includeAsync?: boolean;\n },\n): boolean {\n if (!options.includeDb && type === 'local_db_query') return false;\n if (!options.includeExternal && type === 'external_http') return false;\n if (!options.includeAsync && type.startsWith('async_')) return false;\n return true;\n}\nfunction graphForCalls(db: Db, callIds: number[]): Map<number, GraphRow[]> {\n const map = new Map<number, GraphRow[]>();\n if (callIds.length === 0) return map;\n const rows = db\n .prepare(\n `SELECT * FROM graph_edges WHERE from_kind='call' AND from_id IN (${callIds.map(() => '?').join(',')}) ORDER BY id`,\n )\n .all(...callIds) as GraphRow[];\n for (const row of rows) {\n const id = Number(row.from_id);\n map.set(id, [...(map.get(id) ?? []), row]);\n }\n return map;\n}\nfunction candidatesFromEvidence(\n evidence: Record<string, unknown>,\n): Candidate[] {\n return Array.isArray(evidence.candidates)\n ? evidence.candidates.filter(\n (candidate): candidate is Candidate =>\n typeof candidate === 'object' && candidate !== null,\n )\n : [];\n}\nfunction evidenceWithRuntimeVariables(\n evidence: Record<string, unknown>,\n vars: Record<string, string> | undefined,\n): Record<string, unknown> {\n if (!vars || Object.keys(vars).length === 0) return evidence;\n const servicePath =\n typeof evidence.servicePath === 'string'\n ? applyVariables(evidence.servicePath, vars)\n : undefined;\n const operationPath =\n typeof evidence.operationPath === 'string'\n ? applyVariables(evidence.operationPath, vars)\n : undefined;\n const candidates = candidatesFromEvidence(evidence);\n const matched = servicePath\n ? candidates.find((candidate) => candidate.servicePath === servicePath)\n : undefined;\n return {\n ...evidence,\n servicePath: servicePath ?? evidence.servicePath,\n operationPath: operationPath ?? evidence.operationPath,\n runtimeVariablesApplied: true,\n runtimeResolvedCandidate: matched\n ? {\n repoName: matched.repoName,\n servicePath: matched.servicePath,\n operationPath: matched.operationPath,\n operationName: matched.operationName,\n score: matched.score,\n }\n : undefined,\n };\n}\nfunction edgeTarget(row: GraphRow, evidence: Record<string, unknown>): string {\n const runtimeCandidate = evidence.runtimeResolvedCandidate as\n | Candidate\n | undefined;\n if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath)\n return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;\n const servicePath =\n typeof evidence.servicePath === 'string' ? evidence.servicePath : undefined;\n const operationPath =\n typeof evidence.operationPath === 'string'\n ? evidence.operationPath\n : undefined;\n const targetOperation =\n typeof evidence.targetOperation === 'string'\n ? evidence.targetOperation\n : undefined;\n const targetRepo =\n typeof evidence.targetRepo === 'string' ? evidence.targetRepo : '';\n return servicePath && operationPath\n ? `${servicePath}${operationPath}`\n : targetOperation\n ? `${targetRepo}:${targetOperation}`\n : row.to_id;\n}\nexport function trace(\n db: Db,\n start: TraceStart,\n options: {\n depth: number;\n vars?: Record<string, string>;\n includeExternal?: boolean;\n includeDb?: boolean;\n includeAsync?: boolean;\n },\n): TraceResult {\n const scope = startScope(db, start);\n const diagnostics = db\n .prepare(\n 'SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)',\n )\n .all(scope.repo?.id, scope.repo?.id) as Array<Record<string, unknown>>;\n if (!scope.selectorMatched)\n diagnostics.unshift({\n severity: 'warning',\n code: 'trace_start_not_found',\n message: 'No handler source matched the requested trace start selector',\n });\n const maxDepth = positiveDepth(options.depth);\n const edges: TraceEdge[] = [];\n const nodes = new Map<string, Record<string, unknown>>();\n const queue: Array<{ repoId?: number; files?: Set<string>; depth: number }> =\n scope.selectorMatched\n ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, depth: 1 }]\n : [];\n const seenScopes = new Set<string>();\n const seenEdges = new Set<number>();\n while (queue.length > 0) {\n const current = queue.shift();\n if (!current || current.depth > maxDepth) continue;\n const key = `${current.repoId ?? '*'}:${[...(current.files ?? new Set(['*']))].sort().join(',')}`;\n if (seenScopes.has(key)) continue;\n seenScopes.add(key);\n const calls = db\n .prepare(\n `SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) ORDER BY c.source_file,c.source_line`,\n )\n .all(current.repoId, current.repoId) as CallRow[];\n const filtered = calls.filter(\n (c) =>\n (!current.files || current.files.has(String(c.source_file))) &&\n includeCall(String(c.call_type), options),\n );\n const graph = graphForCalls(\n db,\n filtered.map((c) => Number(c.id)),\n );\n for (const call of filtered) {\n const callNode = `call:${call.id}`;\n nodes.set(callNode, {\n id: callNode,\n kind: 'outbound_call',\n repo: call.repoName,\n file: call.source_file,\n line: call.source_line,\n callType: call.call_type,\n });\n const graphRows = graph.get(Number(call.id)) ?? [];\n for (const row of graphRows) {\n if (seenEdges.has(Number(row.id))) continue;\n seenEdges.add(Number(row.id));\n const rawEvidence = JSON.parse(\n String(row.evidence_json || '{}'),\n ) as Record<string, unknown>;\n const evidence = evidenceWithRuntimeVariables(\n rawEvidence,\n options.vars,\n );\n const targetNode = `${row.to_kind}:${row.to_id}`;\n nodes.set(targetNode, {\n id: targetNode,\n kind: row.to_kind,\n label: row.to_id,\n ...evidence,\n });\n const to = edgeTarget(row, evidence);\n edges.push({\n step: current.depth,\n type: String(call.call_type),\n from: `${call.repoName}:${call.source_file}`,\n to,\n evidence,\n confidence: Number(row.confidence ?? call.confidence),\n unresolvedReason: row.unresolved_reason,\n });\n if (row.to_kind === 'operation' && current.depth < maxDepth) {\n const files = handlerFilesForOperation(db, row.to_id);\n if (files.size > 0) {\n const targetRepoId = db\n .prepare(\n 'SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?',\n )\n .get(row.to_id)?.repoId as number | undefined;\n const nextKey = `${targetRepoId ?? '*'}:${[...files].sort().join(',')}`;\n if (seenScopes.has(nextKey))\n edges.push({\n step: current.depth + 1,\n type: 'cycle',\n from: to,\n to: nextKey,\n evidence: { ...evidence, cycle: true },\n confidence: 1,\n unresolvedReason:\n 'Cycle detected; downstream scope already visited',\n });\n else\n queue.push({\n repoId: targetRepoId,\n files,\n depth: current.depth + 1,\n });\n }\n }\n }\n }\n }\n return { start, nodes: [...nodes.values()], edges, diagnostics };\n}\n"],"mappings":"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","names":["path","path","fs","path","fs","path","path","fs","fs","path","ts","ts","fs","path","fs","path","lineOf","fs","path","fs","path","lineOf","firstArg","fs","path","fs","path","ts","lineOf","ts","fs","path","rows"]}
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  parsePackageJson,
11
11
  parseServiceBindings,
12
12
  trace
13
- } from "./chunk-6U4QKQEL.js";
13
+ } from "./chunk-SIKIGT42.js";
14
14
 
15
15
  // src/cli.ts
16
16
  import { Command } from "commander";
@@ -60,6 +60,11 @@ async function saveWorkspaceConfig(config) {
60
60
  `${JSON.stringify(config, null, 2)}
61
61
  `
62
62
  );
63
+ if (path.dirname(config.dbPath) === path.dirname(configPath(config.rootPath)))
64
+ await fs.writeFile(
65
+ path.join(path.dirname(config.dbPath), ".service-flow-state"),
66
+ "service-flow\n"
67
+ );
63
68
  }
64
69
  async function loadWorkspaceConfig(workspace) {
65
70
  const root = path.resolve(workspace ?? process.cwd());
@@ -95,7 +100,7 @@ CREATE TABLE IF NOT EXISTS symbols (id INTEGER PRIMARY KEY, repo_id INTEGER NOT
95
100
  CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, class_name TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL);
96
101
  CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL);
97
102
  CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL);
98
- CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL);
103
+ CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, helper_chain_json TEXT);
99
104
  CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, confidence REAL NOT NULL, unresolved_reason TEXT);
100
105
  CREATE TABLE IF NOT EXISTS graph_edges (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, edge_type TEXT NOT NULL, from_kind TEXT NOT NULL, from_id TEXT NOT NULL, to_kind TEXT NOT NULL, to_id TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, is_dynamic INTEGER NOT NULL, unresolved_reason TEXT);
101
106
  CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, status TEXT NOT NULL, repo_count INTEGER NOT NULL, file_count INTEGER NOT NULL, diagnostic_count INTEGER NOT NULL);
@@ -110,6 +115,11 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, rep
110
115
  // src/db/migrations.ts
111
116
  function migrate(db) {
112
117
  db.exec(schemaSql);
118
+ const columns = db.prepare("PRAGMA table_info(service_bindings)").all();
119
+ if (!columns.some((column) => column.name === "helper_chain_json"))
120
+ db.prepare(
121
+ "ALTER TABLE service_bindings ADD COLUMN helper_chain_json TEXT"
122
+ ).run();
113
123
  }
114
124
 
115
125
  // src/db/connection.ts
@@ -273,12 +283,9 @@ function insertService(db, repoId, s) {
273
283
  const stmt = db.prepare(
274
284
  "INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
275
285
  );
276
- db.prepare("INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)").run(
277
- "service",
278
- s.qualifiedName,
279
- s.servicePath,
280
- String(repoId)
281
- );
286
+ db.prepare(
287
+ "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
288
+ ).run("service", s.qualifiedName, s.servicePath, String(repoId));
282
289
  for (const o of s.operations)
283
290
  stmt.run(
284
291
  id,
@@ -347,7 +354,7 @@ function insertRegistrations(db, repoId, rows) {
347
354
  }
348
355
  function insertBindings(db, repoId, rows) {
349
356
  const stmt = db.prepare(
350
- "INSERT INTO service_bindings(repo_id,variable_name,alias,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?,?)"
357
+ "INSERT INTO service_bindings(repo_id,variable_name,alias,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,helper_chain_json) VALUES(?,?,?,?,?,?,?,?,?,?)"
351
358
  );
352
359
  for (const r of rows)
353
360
  stmt.run(
@@ -359,7 +366,8 @@ function insertBindings(db, repoId, rows) {
359
366
  r.isDynamic ? 1 : 0,
360
367
  JSON.stringify(r.placeholders),
361
368
  r.sourceFile,
362
- r.sourceLine
369
+ r.sourceLine,
370
+ r.helperChain ? JSON.stringify(r.helperChain) : null
363
371
  );
364
372
  }
365
373
  function insertCalls(db, repoId, rows) {
@@ -670,7 +678,7 @@ function createProgram() {
670
678
  const program = new Command();
671
679
  program.name("service-flow").description(
672
680
  "Trace SAP CAP service-to-service flows across multi-repository workspaces"
673
- ).version("0.1.2");
681
+ ).version("0.1.3");
674
682
  program.command("init").argument("<workspace>").option("--db <path>").option("--ignore <pattern...>").action(
675
683
  (workspace, opts) => void init(workspace, opts).catch(fail)
676
684
  );
@@ -757,7 +765,14 @@ function createProgram() {
757
765
  const repo = opts.repo ? repoByName(db, opts.repo) : void 0;
758
766
  const rows = db.prepare(
759
767
  "SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)"
760
- ).all(repo?.id, repo?.id, opts.operation, opts.operation, opts.operation ? `/${opts.operation}` : void 0, opts.operation ? `%${opts.operation}%` : void 0);
768
+ ).all(
769
+ repo?.id,
770
+ repo?.id,
771
+ opts.operation,
772
+ opts.operation,
773
+ opts.operation ? `/${opts.operation}` : void 0,
774
+ opts.operation ? `%${opts.operation}%` : void 0
775
+ );
761
776
  process.stdout.write(renderJson(rows));
762
777
  }).catch(fail)
763
778
  );
@@ -800,14 +815,17 @@ function createProgram() {
800
815
  process.stdout.write(renderJson(rows));
801
816
  }).catch(fail)
802
817
  );
803
- program.command("doctor").option("--workspace <path>").action(
818
+ program.command("doctor").option("--workspace <path>").option("--strict").action(
804
819
  (opts) => void withWorkspace(opts.workspace, (db) => {
805
820
  const diagnostics = db.prepare(
806
821
  "SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id"
807
- ).all();
822
+ ).all(Boolean(opts.strict));
808
823
  const health = db.prepare(
809
- `SELECT 'warning' severity,'service_without_operations' code,'CDS service has no indexed operations' message,s.source_file sourceFile,s.source_line sourceLine
810
- FROM cds_services s LEFT JOIN cds_operations o ON o.service_id=s.id WHERE o.id IS NULL
824
+ `SELECT 'info' severity,'entity_only_service' code,'CDS service has no action/function/event operations; this can be valid for entity-only services' message,s.source_file sourceFile,s.source_line sourceLine
825
+ FROM cds_services s LEFT JOIN cds_operations o ON o.service_id=s.id WHERE o.id IS NULL AND ?
826
+ UNION ALL
827
+ SELECT 'warning','extend_service_unresolved_base','Extend service has no indexed local operations; verify base service resolution',s.source_file,s.source_line
828
+ FROM cds_services s LEFT JOIN cds_operations o ON o.service_id=s.id WHERE o.id IS NULL AND s.is_extend=1
811
829
  UNION ALL
812
830
  SELECT 'warning','handler_without_service','Repository has handlers but no CDS services',hc.source_file,hc.source_line
813
831
  FROM handler_classes hc JOIN repositories r ON r.id=hc.repo_id
@@ -815,7 +833,7 @@ function createProgram() {
815
833
  UNION ALL
816
834
  SELECT 'warning','search_index_empty','Search index is empty after indexing',NULL,NULL
817
835
  WHERE NOT EXISTS (SELECT 1 FROM search_index)`
818
- ).all();
836
+ ).all(Boolean(opts.strict));
819
837
  const allDiagnostics = [...diagnostics, ...health];
820
838
  process.stdout.write(
821
839
  allDiagnostics.length ? renderJson(allDiagnostics) : `${pc.green("No diagnostics recorded")}
@@ -826,12 +844,29 @@ function createProgram() {
826
844
  program.command("clean").option("--workspace <path>").option("--db-only").action(
827
845
  (opts) => void (async () => {
828
846
  const config = await loadWorkspaceConfig(opts.workspace);
847
+ const dbDir = path6.resolve(path6.dirname(config.dbPath));
848
+ const workspaceRoot = path6.resolve(config.rootPath);
829
849
  await fs6.rm(config.dbPath, { force: true });
830
- if (!opts.dbOnly)
831
- await fs6.rm(path6.dirname(config.dbPath), {
832
- recursive: true,
833
- force: true
834
- });
850
+ if (!opts.dbOnly) {
851
+ const marker = path6.join(dbDir, ".service-flow-state");
852
+ const dangerous = /* @__PURE__ */ new Set([
853
+ path6.parse(dbDir).root,
854
+ "/tmp",
855
+ process.env.HOME ? path6.resolve(process.env.HOME) : "",
856
+ workspaceRoot
857
+ ]);
858
+ let ownsState;
859
+ try {
860
+ ownsState = (await fs6.stat(marker)).isFile();
861
+ } catch {
862
+ ownsState = false;
863
+ }
864
+ if (!ownsState || dangerous.has(dbDir))
865
+ throw new Error(
866
+ `Refusing to recursively delete unowned or dangerous state directory: ${dbDir}. Use --db-only to remove only the database file.`
867
+ );
868
+ await fs6.rm(dbDir, { recursive: true, force: true });
869
+ }
835
870
  process.stdout.write("Cleaned service-flow state\n");
836
871
  })().catch(fail)
837
872
  );