@saptools/service-flow 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +3 -3
- package/dist/{chunk-NBAO2R2H.js → chunk-LQT67AU5.js} +223 -21
- package/dist/chunk-LQT67AU5.js.map +1 -0
- package/dist/cli.js +93 -80
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-NBAO2R2H.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
- Persist unresolved `OPERATION_IMPLEMENTED_BY_HANDLER` audit edges when implementation candidates exist but all are rejected, including ranked candidate evidence and rejected reasons.
|
|
6
|
+
- Added a conservative helper-owned implementation path for unique registered helper handlers that implement model-oriented CDS operations without direct package dependency evidence, while keeping multiple helper matches ambiguous and local service-path contradictions rejected.
|
|
7
|
+
- Trace output now includes operation-to-handler implementation hops and terminal handler nodes in JSON/table/Mermaid-compatible edge data, including runtime-resolved operation targets.
|
|
8
|
+
- Doctor now reports rejected implementation candidates and strict remote-target implementation coverage gaps without making entity-only services noisy by default.
|
|
9
|
+
- Updated Node compatibility errors, package metadata, and README wording so current behavior is not described with stale release-specific version strings.
|
|
10
|
+
|
|
11
|
+
## 0.1.9
|
|
12
|
+
|
|
13
|
+
- Fixed implementation dependency matching by binding graph repository ids as text when comparing against `graph_edges.from_id` and `graph_edges.to_id`, restoring cross-package application-to-model and application-to-handler evidence under `node:sqlite`.
|
|
14
|
+
- Improved implementation candidate ranking with model/application/handler package ownership, exact local service-path evidence, and cross-package dependency/import signals so duplicate operation names in different services resolve to the correct registered handler without false ambiguity.
|
|
15
|
+
- Expanded `OPERATION_IMPLEMENTED_BY_HANDLER` evidence with candidate ranks, scores, accepted/rejected reasons, package identities, service/operation paths, and dependency/ownership signals for auditability.
|
|
16
|
+
- Added neutral regression coverage for cross-package app/model/handler registration, duplicate same-name service operations, and graph-id string comparison behavior.
|
|
17
|
+
|
|
3
18
|
## 0.1.8
|
|
4
19
|
|
|
5
20
|
- Replaced regex-only handler registration extraction with TypeScript AST evidence for direct handler arrays, identifier arrays, spreads, imported arrays, default exports, aliases, and safe relative re-exports.
|
package/README.md
CHANGED
|
@@ -47,12 +47,12 @@ npm install @saptools/service-flow
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
> [!NOTE]
|
|
50
|
-
> Requires **Node.js ≥ 24.0.0** for the bundled `node:sqlite` runtime.
|
|
50
|
+
> Requires **Node.js ≥ 24.0.0** for the bundled `node:sqlite` runtime. The CLI uses a persistent SQLite driver (`node:sqlite` in supported Node builds) for bound parameters, transactions, WAL, busy timeouts, and read-only query commands. The analyzer is static: it reads files and package metadata, but it does not start CAP services, connect to SAP BTP, or execute application code.
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
### Correctness notes
|
|
55
|
+
### Correctness notes
|
|
56
56
|
|
|
57
57
|
- Runtime `--var` values are considered only for dynamic, ambiguous, or unresolved **remote** graph edges whose alias, destination, service path, or operation path expressions contain supplied placeholders. Local database, external HTTP, event, and already resolved static edges keep their persisted status, target, reason, and confidence. Partial substitutions remain dynamic and report the missing placeholder names.
|
|
58
58
|
- `trace` and `graph` both accept repeatable `--var key=value` options. Effective substitutions are rendered in trace evidence without mutating the persisted graph. Confidence values are bounded to `[0, 1]`.
|
|
@@ -60,7 +60,7 @@ npm install @saptools/service-flow
|
|
|
60
60
|
- Helper-package dependency edges prefer exact indexed package names. Duplicate package-name candidates are persisted as ambiguous evidence rather than silently selecting one repository.
|
|
61
61
|
- Handler registration parsing is AST-based for common `createCombinedHandler({ handler: ... })` forms: direct arrays, arrays assembled with spreads, non-`handlers` array names, aliased class imports, default-imported arrays, named exported arrays, and safe relative re-exports. Class-level rows keep registration file/line and import evidence.
|
|
62
62
|
- Implementation edges require both operation compatibility and registration evidence. Same-repository registrations do not need a self-dependency edge; cross-package matches use registration or handler-package dependencies on the model package. Duplicate strong candidates are stored as ambiguous implementation edges.
|
|
63
|
-
- Traces
|
|
63
|
+
- Traces render persisted `OPERATION_IMPLEMENTED_BY_HANDLER` hops after static or runtime remote operation resolution, including terminal handler nodes and ambiguous or unresolved implementation evidence when traversal cannot continue.
|
|
64
64
|
- Repository fingerprints include source content, package name/version, dependencies and devDependencies, scripts, normalized `cds.requires` (including nested credentials), package file content, and the analyzer version. Metadata-only changes therefore trigger reindexing.
|
|
65
65
|
- Index publication is designed around the last-good snapshot: failed parse or persistence attempts are recorded as diagnostics and must not be mixed with older graph facts. After indexing changes, relink before relying on graph/trace output; doctor reports stale or inconsistent stores where detectable.
|
|
66
66
|
- Source discovery, file reads, hashing, parsing, and publication are all inside the repository-level protected indexing flow. A failed read keeps the previous fingerprint and facts, marks the repository failed, and records a `source_read_failed` diagnostic; a later successful index clears superseded read-failure diagnostics during fact publication.
|
|
@@ -1177,7 +1177,7 @@ function linkWorkspace(db, workspaceId, vars = {}) {
|
|
|
1177
1177
|
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
1178
1178
|
const impl = linkImplementations(db, workspaceId, generation);
|
|
1179
1179
|
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
1180
|
-
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount };
|
|
1180
|
+
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount };
|
|
1181
1181
|
});
|
|
1182
1182
|
}
|
|
1183
1183
|
function nextGraphGeneration(db, workspaceId) {
|
|
@@ -1225,23 +1225,45 @@ function callEvidence(call, resolution, servicePath, op, destination) {
|
|
|
1225
1225
|
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, targetRepo: resolution.target?.repoName, targetOperation: resolution.target?.operationName, helperChain: call.helperChainJson ? JSON.parse(String(call.helperChainJson)) : void 0, candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
1226
1226
|
}
|
|
1227
1227
|
function linkImplementations(db, workspaceId, generation) {
|
|
1228
|
-
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.package_name modelPackage FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
1228
|
+
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
1229
1229
|
let edgeCount = 0;
|
|
1230
1230
|
let resolvedCount = 0;
|
|
1231
1231
|
let ambiguousCount = 0;
|
|
1232
|
+
let unresolvedCount = 0;
|
|
1232
1233
|
for (const operation of operations) {
|
|
1233
|
-
const
|
|
1234
|
-
if (
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1234
|
+
const candidates = rankedImplementationCandidates(db, workspaceId, operation);
|
|
1235
|
+
if (candidates.length === 0) continue;
|
|
1236
|
+
const accepted = candidates.filter((candidate) => candidate.accepted);
|
|
1237
|
+
const topScore = accepted[0]?.score ?? 0;
|
|
1238
|
+
const winners = accepted.filter((candidate) => candidate.score === topScore);
|
|
1239
|
+
const unique = winners.length === 1 ? winners[0] : void 0;
|
|
1240
|
+
const evidence = {
|
|
1241
|
+
servicePath: operation.servicePath,
|
|
1242
|
+
operationPath: operation.operationPath,
|
|
1243
|
+
operationName: operation.operationName,
|
|
1244
|
+
modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
|
|
1245
|
+
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
1246
|
+
};
|
|
1247
|
+
if (accepted.length === 0) {
|
|
1248
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidence), 0, "No implementation candidate passed policy", generation);
|
|
1249
|
+
edgeCount += 1;
|
|
1250
|
+
unresolvedCount += 1;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : winners.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
|
|
1237
1254
|
edgeCount += 1;
|
|
1238
1255
|
if (unique) resolvedCount += 1;
|
|
1239
1256
|
else ambiguousCount += 1;
|
|
1240
1257
|
}
|
|
1241
|
-
return { edgeCount, resolvedCount, ambiguousCount };
|
|
1258
|
+
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
1259
|
+
}
|
|
1260
|
+
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
1261
|
+
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
1262
|
+
return rows2.map((row) => scoreImplementationCandidate(row, operation)).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
1242
1263
|
}
|
|
1243
1264
|
function implementationCandidates(db, workspaceId, operation) {
|
|
1244
|
-
const
|
|
1265
|
+
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
1266
|
+
return db.prepare(`SELECT DISTINCT
|
|
1245
1267
|
hm.id methodId,
|
|
1246
1268
|
hc.id classId,
|
|
1247
1269
|
hc.class_name className,
|
|
@@ -1250,21 +1272,153 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1250
1272
|
hr.repo_id applicationRepoId,
|
|
1251
1273
|
hr.registration_file registrationFile,
|
|
1252
1274
|
hr.registration_line registrationLine,
|
|
1275
|
+
hr.registration_kind registrationKind,
|
|
1253
1276
|
hr.import_source importSource,
|
|
1254
1277
|
handlerRepo.id handlerRepoId,
|
|
1255
1278
|
handlerRepo.name handlerRepo,
|
|
1279
|
+
handlerRepo.package_name handlerPackage,
|
|
1256
1280
|
appRepo.name applicationRepo,
|
|
1281
|
+
appRepo.package_name applicationPackage,
|
|
1282
|
+
? modelRepoId,
|
|
1283
|
+
? modelRepo,
|
|
1284
|
+
? modelPackage,
|
|
1285
|
+
? modelKind,
|
|
1286
|
+
? servicePath,
|
|
1287
|
+
? operationPath,
|
|
1288
|
+
? operationName,
|
|
1289
|
+
CASE WHEN appRepo.id=? THEN 1 ELSE 0 END modelIsApplicationRepo,
|
|
1290
|
+
CASE WHEN handlerRepo.id=? THEN 1 ELSE 0 END modelIsHandlerRepo,
|
|
1257
1291
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
1258
|
-
CASE WHEN EXISTS (SELECT 1 FROM
|
|
1259
|
-
CASE WHEN EXISTS (SELECT 1 FROM
|
|
1292
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
|
|
1293
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
1294
|
+
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON (localClass.id=localReg.handler_class_id OR localClass.class_name=localReg.class_name) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
1295
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
1296
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
1297
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
1260
1298
|
FROM handler_methods hm
|
|
1261
1299
|
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
1262
1300
|
JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
|
|
1263
1301
|
JOIN handler_registrations hr ON (hr.handler_class_id=hc.id OR (hr.class_name=hc.class_name AND (hr.repo_id=hc.repo_id OR hr.import_source IS NOT NULL)))
|
|
1264
1302
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
1265
1303
|
WHERE appRepo.workspace_id=?
|
|
1266
|
-
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
|
|
1267
|
-
|
|
1304
|
+
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
|
|
1305
|
+
operation.modelRepoId,
|
|
1306
|
+
operation.modelRepo,
|
|
1307
|
+
operation.modelPackage,
|
|
1308
|
+
operation.modelKind,
|
|
1309
|
+
operation.servicePath,
|
|
1310
|
+
operation.operationPath,
|
|
1311
|
+
operation.operationName,
|
|
1312
|
+
operation.modelRepoId,
|
|
1313
|
+
operation.modelRepoId,
|
|
1314
|
+
operation.servicePath,
|
|
1315
|
+
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1316
|
+
operation.operationName,
|
|
1317
|
+
operation.operationName,
|
|
1318
|
+
modelRepoGraphId,
|
|
1319
|
+
modelRepoGraphId,
|
|
1320
|
+
workspaceId,
|
|
1321
|
+
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1322
|
+
operation.operationName,
|
|
1323
|
+
operation.operationName
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
function scoreImplementationCandidate(row, operation) {
|
|
1327
|
+
const acceptedReasons = [];
|
|
1328
|
+
const rejectedReasons = [];
|
|
1329
|
+
let score = 0;
|
|
1330
|
+
const modelIsApplicationRepo = flag(row.modelIsApplicationRepo);
|
|
1331
|
+
const modelIsHandlerRepo = flag(row.modelIsHandlerRepo);
|
|
1332
|
+
const localServicePathMatch = flag(row.localServicePathMatch);
|
|
1333
|
+
const applicationHasLocalServices = flag(row.applicationHasLocalServices);
|
|
1334
|
+
const appDependsOnModel = flag(row.appDependsOnModel);
|
|
1335
|
+
const applicationHasLocalRegistrationForOperation = flag(row.applicationHasLocalRegistrationForOperation);
|
|
1336
|
+
const appDependsOnHandler = flag(row.appDependsOnHandler);
|
|
1337
|
+
const handlerDependsOnModel = flag(row.handlerDependsOnModel);
|
|
1338
|
+
const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
|
|
1339
|
+
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
1340
|
+
const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
|
|
1341
|
+
const methodMatches = true;
|
|
1342
|
+
const registeredAndLinked = sameRepoRegistration && importSource;
|
|
1343
|
+
const helperOwned = modelOriented && methodMatches && registeredAndLinked && sameRepoRegistration && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
1344
|
+
if (modelIsApplicationRepo) {
|
|
1345
|
+
score += 100;
|
|
1346
|
+
acceptedReasons.push("model package equals registration package");
|
|
1347
|
+
}
|
|
1348
|
+
if (modelIsHandlerRepo) {
|
|
1349
|
+
score += 100;
|
|
1350
|
+
acceptedReasons.push("model package equals handler package");
|
|
1351
|
+
}
|
|
1352
|
+
if (localServicePathMatch) {
|
|
1353
|
+
score += 80;
|
|
1354
|
+
acceptedReasons.push("registration package contains exact local service path");
|
|
1355
|
+
} else if (applicationHasLocalServices && !appDependsOnModel && !modelIsApplicationRepo) {
|
|
1356
|
+
rejectedReasons.push(`registration package has local services but none match ${String(operation.servicePath ?? "")}`);
|
|
1357
|
+
}
|
|
1358
|
+
if (appDependsOnModel) {
|
|
1359
|
+
score += 70;
|
|
1360
|
+
acceptedReasons.push("registration package depends on model package");
|
|
1361
|
+
}
|
|
1362
|
+
if (appDependsOnHandler) {
|
|
1363
|
+
score += 30;
|
|
1364
|
+
acceptedReasons.push("registration package depends on handler package");
|
|
1365
|
+
}
|
|
1366
|
+
if (handlerDependsOnModel) {
|
|
1367
|
+
score += 20;
|
|
1368
|
+
acceptedReasons.push("handler package depends on model package");
|
|
1369
|
+
}
|
|
1370
|
+
if (helperOwned) {
|
|
1371
|
+
score += 60;
|
|
1372
|
+
acceptedReasons.push("unique registered helper implementation for model-only operation");
|
|
1373
|
+
}
|
|
1374
|
+
if (importSource) {
|
|
1375
|
+
score += 10;
|
|
1376
|
+
acceptedReasons.push("registration imports handler class");
|
|
1377
|
+
}
|
|
1378
|
+
const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
|
|
1379
|
+
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
1380
|
+
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
1381
|
+
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
1382
|
+
const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
|
|
1383
|
+
if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
1384
|
+
return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
|
|
1385
|
+
}
|
|
1386
|
+
function candidateEvidence(candidate, rank) {
|
|
1387
|
+
return {
|
|
1388
|
+
rank,
|
|
1389
|
+
score: candidate.score,
|
|
1390
|
+
accepted: candidate.accepted,
|
|
1391
|
+
acceptedReasons: candidate.acceptedReasons,
|
|
1392
|
+
rejectedReasons: candidate.rejectedReasons,
|
|
1393
|
+
methodId: candidate.methodId,
|
|
1394
|
+
classId: candidate.classId,
|
|
1395
|
+
className: candidate.className,
|
|
1396
|
+
sourceFile: candidate.sourceFile,
|
|
1397
|
+
sourceLine: candidate.sourceLine,
|
|
1398
|
+
registration: { file: candidate.registrationFile, line: candidate.registrationLine, kind: candidate.registrationKind, importSource: candidate.importSource },
|
|
1399
|
+
applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
|
|
1400
|
+
handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
|
|
1401
|
+
modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
|
|
1402
|
+
servicePath: candidate.servicePath,
|
|
1403
|
+
operationPath: candidate.operationPath,
|
|
1404
|
+
operationName: candidate.operationName,
|
|
1405
|
+
signals: {
|
|
1406
|
+
directOwnership: { modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo), modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo) },
|
|
1407
|
+
localServicePathMatch: flag(candidate.localServicePathMatch),
|
|
1408
|
+
applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
|
|
1409
|
+
applicationHasLocalRegistrationForOperation: flag(candidate.applicationHasLocalRegistrationForOperation),
|
|
1410
|
+
appDependsOnModel: flag(candidate.appDependsOnModel),
|
|
1411
|
+
appDependsOnHandler: flag(candidate.appDependsOnHandler),
|
|
1412
|
+
handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
|
|
1413
|
+
sameRepoRegistration: flag(candidate.sameRepoRegistration)
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function flag(value) {
|
|
1418
|
+
return Boolean(Number(value ?? 0));
|
|
1419
|
+
}
|
|
1420
|
+
function graphId(value) {
|
|
1421
|
+
return String(value);
|
|
1268
1422
|
}
|
|
1269
1423
|
function normalizedOperation(value) {
|
|
1270
1424
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
@@ -1305,9 +1459,17 @@ function sourceFilesForStart(db, repoId, start) {
|
|
|
1305
1459
|
operation,
|
|
1306
1460
|
operation
|
|
1307
1461
|
);
|
|
1308
|
-
if (
|
|
1309
|
-
if (
|
|
1310
|
-
|
|
1462
|
+
if (rows2.length > 0) return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
1463
|
+
if (start.servicePath && operation) {
|
|
1464
|
+
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile
|
|
1465
|
+
FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
|
|
1466
|
+
JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved' AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
|
|
1467
|
+
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
1468
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
1469
|
+
WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation);
|
|
1470
|
+
if (implRows.length > 0) return new Set(implRows.map((row) => row.sourceFile).filter(Boolean));
|
|
1471
|
+
}
|
|
1472
|
+
return void 0;
|
|
1311
1473
|
}
|
|
1312
1474
|
function startScope(db, start) {
|
|
1313
1475
|
const repo = start.repo ? db.prepare(
|
|
@@ -1340,11 +1502,19 @@ function handlerFilesForOperation(db, operationId) {
|
|
|
1340
1502
|
).all(op.repoId, operation, operation, op.operationName);
|
|
1341
1503
|
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
1342
1504
|
}
|
|
1505
|
+
function implementationEdge(db, operationId) {
|
|
1506
|
+
return db.prepare("SELECT * FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND from_kind='operation' AND from_id=? ORDER BY CASE status WHEN 'resolved' THEN 0 WHEN 'ambiguous' THEN 1 ELSE 2 END,id LIMIT 1").get(operationId);
|
|
1507
|
+
}
|
|
1508
|
+
function handlerMethodNode(db, methodId) {
|
|
1509
|
+
const row = db.prepare(`SELECT hm.id methodId,hm.method_name methodName,hm.decorator_value decoratorValue,hm.source_line sourceLine,hc.class_name className,hc.source_file sourceFile,r.name repoName,r.id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(methodId);
|
|
1510
|
+
if (!row) return void 0;
|
|
1511
|
+
return { id: `handler_method:${methodId}`, kind: "handler_method", label: `${String(row.repoName)}:${String(row.className)}.${String(row.methodName)}`, ...row };
|
|
1512
|
+
}
|
|
1343
1513
|
function implementationScope(db, operationId) {
|
|
1344
|
-
const edge = db
|
|
1345
|
-
if (!edge) return { files: /* @__PURE__ */ new Set() };
|
|
1514
|
+
const edge = implementationEdge(db, operationId);
|
|
1515
|
+
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
1346
1516
|
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id WHERE hm.id=?").get(edge.to_id);
|
|
1347
|
-
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []) };
|
|
1517
|
+
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), edge };
|
|
1348
1518
|
}
|
|
1349
1519
|
function includeCall(type, options) {
|
|
1350
1520
|
if (!options.includeDb && type === "local_db_query") return false;
|
|
@@ -1443,6 +1613,22 @@ function trace(db, start, options) {
|
|
|
1443
1613
|
const edges = [];
|
|
1444
1614
|
const nodes = /* @__PURE__ */ new Map();
|
|
1445
1615
|
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, depth: 1 }] : [];
|
|
1616
|
+
if (start.servicePath && (start.operation ?? start.operationPath)) {
|
|
1617
|
+
const startOperation = normalizeOperation(start.operation ?? start.operationPath);
|
|
1618
|
+
const row = db.prepare(`SELECT o.id operationId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE s.service_path=? AND (o.operation_path=? OR o.operation_name=?) ORDER BY o.id LIMIT 1`).get(start.servicePath, startOperation, startOperation);
|
|
1619
|
+
if (row?.operationId !== void 0) {
|
|
1620
|
+
const opId = String(row.operationId);
|
|
1621
|
+
const op = operationNode(db, opId);
|
|
1622
|
+
const impl = implementationScope(db, opId);
|
|
1623
|
+
if (op) nodes.set(String(op.id), op);
|
|
1624
|
+
if (impl.edge) {
|
|
1625
|
+
const implEvidence = JSON.parse(String(impl.edge.evidence_json || "{}"));
|
|
1626
|
+
const handlerNode = impl.edge.status === "resolved" ? handlerMethodNode(db, impl.edge.to_id) : void 0;
|
|
1627
|
+
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
1628
|
+
edges.push({ step: 1, type: "operation_implemented_by_handler", from: op?.label ? String(op.label) : `${start.servicePath}/${startOperation ?? ""}`, to: handlerNode?.label ? String(handlerNode.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: implEvidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: impl.edge.status === "resolved" ? void 0 : String(impl.edge.unresolved_reason ?? impl.edge.status) });
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1446
1632
|
const seenScopes = /* @__PURE__ */ new Set();
|
|
1447
1633
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
1448
1634
|
while (queue.length > 0) {
|
|
@@ -1498,10 +1684,26 @@ function trace(db, start, options) {
|
|
|
1498
1684
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
1499
1685
|
unresolvedReason: effective.unresolvedReason
|
|
1500
1686
|
});
|
|
1501
|
-
if (effectiveRow.to_kind === "operation"
|
|
1687
|
+
if (effectiveRow.to_kind === "operation") {
|
|
1502
1688
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
1689
|
+
if (implementation.edge) {
|
|
1690
|
+
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
1691
|
+
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : void 0;
|
|
1692
|
+
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
1693
|
+
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
1694
|
+
edges.push({
|
|
1695
|
+
step: current.depth + 1,
|
|
1696
|
+
type: "operation_implemented_by_handler",
|
|
1697
|
+
from: to,
|
|
1698
|
+
to: implTo,
|
|
1699
|
+
evidence: implEvidence,
|
|
1700
|
+
confidence: Number(implementation.edge.confidence ?? 0),
|
|
1701
|
+
unresolvedReason: implementation.edge.status === "resolved" ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
if (current.depth >= maxDepth) continue;
|
|
1503
1705
|
const files = implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id);
|
|
1504
|
-
if (files.size > 0) {
|
|
1706
|
+
if (implementation.edge?.status === "resolved" && files.size > 0) {
|
|
1505
1707
|
const targetRepoId = implementation.repoId ?? db.prepare(
|
|
1506
1708
|
"SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?"
|
|
1507
1709
|
).get(effectiveRow.to_id)?.repoId;
|
|
@@ -1548,4 +1750,4 @@ export {
|
|
|
1548
1750
|
linkWorkspace,
|
|
1549
1751
|
trace
|
|
1550
1752
|
};
|
|
1551
|
-
//# sourceMappingURL=chunk-
|
|
1753
|
+
//# sourceMappingURL=chunk-LQT67AU5.js.map
|