@saptools/service-flow 0.1.18 → 0.1.20

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/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  containsSupportedOutboundCall,
4
4
  discoverRepositories,
5
5
  linkWorkspace,
6
+ normalizeODataOperationInvocationPath,
6
7
  normalizePath,
7
8
  parseCdsFile,
8
9
  parseDecorators,
@@ -11,7 +12,7 @@ import {
11
12
  parsePackageJson,
12
13
  parseServiceBindings,
13
14
  trace
14
- } from "./chunk-DUGM7FRA.js";
15
+ } from "./chunk-EKLZJJW7.js";
15
16
 
16
17
  // src/cli.ts
17
18
  import { Command } from "commander";
@@ -187,7 +188,7 @@ function migrate(db) {
187
188
  // package.json
188
189
  var package_default = {
189
190
  name: "@saptools/service-flow",
190
- version: "0.1.18",
191
+ version: "0.1.20",
191
192
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
192
193
  type: "module",
193
194
  publishConfig: {
@@ -1176,7 +1177,13 @@ function parserQualityDiagnostics(db, strict) {
1176
1177
  const outboundTotal = Number(outbound.total ?? 0);
1177
1178
  const outboundWithoutOwnership = Number(outbound.withoutOwnership ?? 0);
1178
1179
  const outboundWithoutOwnershipRatio = outboundTotal === 0 ? 0 : Number((outboundWithoutOwnership / outboundTotal).toFixed(4));
1180
+ const remoteQuery = remoteQueryTargetQuality(db);
1181
+ const invocation = odataInvocationResolutionQuality(db);
1182
+ const remoteAction = remoteActionTargetQuality(db);
1179
1183
  return [
1184
+ remoteQuery,
1185
+ invocation,
1186
+ remoteAction,
1180
1187
  { severity: Number(evidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_symbol_call_evidence_quality", message: "Symbol-call evidence JSON object aggregate", total: Number(evidence.total ?? 0), nonObject: Number(evidence.nonObject ?? 0) },
1181
1188
  { severity: Number(outboundEvidence.missing ?? 0) + Number(outboundEvidence.invalid ?? 0) + Number(outboundEvidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_outbound_evidence_quality", message: "Outbound parser evidence JSON object aggregate", total: Number(outboundEvidence.total ?? 0), missing: Number(outboundEvidence.missing ?? 0), invalid: Number(outboundEvidence.invalid ?? 0), nonObject: Number(outboundEvidence.nonObject ?? 0), examples: outboundEvidenceExamples },
1182
1189
  { severity: Number(graphEvidence.nonObject ?? 0) > 0 || Number(graphEvidence.withOutboundEvidence ?? 0) < Number(graphEvidence.total ?? 0) ? "warning" : "info", code: "strict_graph_evidence_quality", message: "Call-derived graph evidence and parser-evidence propagation aggregate", total: Number(graphEvidence.total ?? 0), nonObject: Number(graphEvidence.nonObject ?? 0), withOutboundEvidence: Number(graphEvidence.withOutboundEvidence ?? 0), examples: graphEvidenceExamples },
@@ -1187,6 +1194,60 @@ function parserQualityDiagnostics(db, strict) {
1187
1194
  { severity: outboundWithoutOwnershipRatio > outboundUnownedThreshold ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total: outboundTotal, withoutOwnership: outboundWithoutOwnership, withoutOwnershipRatio: outboundWithoutOwnershipRatio, withoutOwnershipRatioThreshold: outboundUnownedThreshold, ownerlessByType, ownerlessByCategory, ownerlessExamples }
1188
1195
  ];
1189
1196
  }
1197
+ function remoteQueryTargetQuality(db) {
1198
+ const aggregate = db.prepare(`SELECT COUNT(*) total,
1199
+ SUM(CASE WHEN e.edge_type='HANDLER_RUNS_REMOTE_QUERY' AND e.status='terminal' THEN 1 ELSE 0 END) terminal,
1200
+ SUM(CASE WHEN e.edge_type='HANDLER_RUNS_REMOTE_QUERY' AND e.to_id GLOB '[0-9]*' THEN 1 ELSE 0 END) numericTargets,
1201
+ SUM(CASE WHEN e.edge_type='UNRESOLVED_EDGE' OR e.status='unresolved' THEN 1 ELSE 0 END) unresolved
1202
+ FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT) WHERE c.call_type='remote_query'`).get();
1203
+ const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,c.query_entity queryEntity,e.edge_type edgeType,e.status status,e.to_id target
1204
+ FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1205
+ WHERE c.call_type='remote_query' AND (e.id IS NULL OR e.edge_type<>'HANDLER_RUNS_REMOTE_QUERY' OR e.status<>'terminal' OR e.to_id GLOB '[0-9]*')
1206
+ ORDER BY c.source_file,c.source_line LIMIT 5`).all();
1207
+ const numericTargets = Number(aggregate.numericTargets ?? 0);
1208
+ const unresolved = Number(aggregate.unresolved ?? 0);
1209
+ return { severity: numericTargets + unresolved > 0 ? "warning" : "info", code: "strict_remote_query_target_quality", message: "Remote query terminal target quality aggregate", totalRemoteQueryCalls: Number(aggregate.total ?? 0), terminalRemoteQueryEdges: Number(aggregate.terminal ?? 0), numericTargetCount: numericTargets, unresolvedRemoteQueryCount: unresolved, examples };
1210
+ }
1211
+ function remoteActionTargetQuality(db) {
1212
+ const aggregate = db.prepare(`SELECT COUNT(*) total,
1213
+ SUM(CASE WHEN e.status='unresolved' THEN 1 ELSE 0 END) unresolved,
1214
+ SUM(CASE WHEN e.status='unresolved' AND e.to_id GLOB '[0-9]*' THEN 1 ELSE 0 END) numericTargets,
1215
+ SUM(CASE WHEN e.status='unresolved' AND (e.to_id='Remote action: unknown path' OR e.to_id='Remote action: dynamic path') THEN 1 ELSE 0 END) semanticTargets
1216
+ FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT) WHERE c.call_type='remote_action'`).get();
1217
+ const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,c.operation_path_expr operationPath,e.status status,e.to_id target
1218
+ FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1219
+ WHERE c.call_type='remote_action' AND e.status='unresolved' AND e.to_id GLOB '[0-9]*' ORDER BY c.source_file,c.source_line LIMIT 5`).all();
1220
+ const numericTargets = Number(aggregate.numericTargets ?? 0);
1221
+ return { severity: numericTargets > 0 ? "warning" : "info", code: "strict_remote_action_target_quality", message: "Remote action unresolved target quality aggregate", totalRemoteActionCalls: Number(aggregate.total ?? 0), unresolvedRemoteActionCalls: Number(aggregate.unresolved ?? 0), numericUnresolvedTargetCount: numericTargets, semanticUnknownOrDynamicTargetCount: Number(aggregate.semanticTargets ?? 0), examples };
1222
+ }
1223
+ function odataInvocationResolutionQuality(db) {
1224
+ const aggregate = db.prepare(`SELECT COUNT(*) total,
1225
+ SUM(CASE WHEN e.status='resolved' THEN 1 ELSE 0 END) resolved,
1226
+ SUM(CASE WHEN e.status='dynamic' THEN 1 ELSE 0 END) dynamic,
1227
+ SUM(CASE WHEN e.status='ambiguous' THEN 1 ELSE 0 END) ambiguous,
1228
+ SUM(CASE WHEN e.status='unresolved' THEN 1 ELSE 0 END) unresolved
1229
+ FROM outbound_calls c JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1230
+ WHERE c.call_type='remote_action' AND c.operation_path_expr LIKE '%(%'`).get();
1231
+ const rows = db.prepare(`SELECT c.id id,c.operation_path_expr operationPathExpr,c.source_file sourceFile,c.source_line sourceLine,e.status status,e.unresolved_reason unresolvedReason
1232
+ FROM outbound_calls c JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1233
+ WHERE c.call_type='remote_action' AND e.status IN ('unresolved','ambiguous') AND c.operation_path_expr LIKE '%(%'
1234
+ ORDER BY c.source_file,c.source_line LIMIT 100`).all();
1235
+ const examples = [];
1236
+ let unresolvedMatchingIndexedOperation = 0;
1237
+ let ambiguousNormalizedCalls = 0;
1238
+ for (const row of rows) {
1239
+ const normalized = normalizeODataOperationInvocationPath(row.operationPathExpr);
1240
+ if (!normalized?.wasInvocation) continue;
1241
+ const normalizedName = normalized.normalizedOperationPath.replace(/^\//, "");
1242
+ const simpleName = normalizedName.split(".").at(-1) ?? normalizedName;
1243
+ const candidates = db.prepare("SELECT s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.operation_path IN (?,?) OR o.operation_name IN (?,?) ORDER BY s.service_path,o.operation_name LIMIT 5").all(normalized.normalizedOperationPath, `/${simpleName}`, normalizedName, simpleName);
1244
+ if (candidates.length === 0) continue;
1245
+ if (row.status === "ambiguous") ambiguousNormalizedCalls += 1;
1246
+ if (row.status === "unresolved") unresolvedMatchingIndexedOperation += 1;
1247
+ if (examples.length < 5) examples.push({ sourceFile: row.sourceFile, sourceLine: row.sourceLine, rawOperationPath: row.operationPathExpr, normalizedOperationPath: normalized.normalizedOperationPath, candidateCount: candidates.length, candidates });
1248
+ }
1249
+ return { severity: unresolvedMatchingIndexedOperation + ambiguousNormalizedCalls > 0 ? "warning" : "info", code: "strict_odata_invocation_resolution_quality", message: "OData invocation-path resolution quality aggregate", totalInvocationRemoteActions: Number(aggregate.total ?? 0), resolvedInvocationCalls: Number(aggregate.resolved ?? 0), dynamicInvocationCalls: Number(aggregate.dynamic ?? 0), ambiguousInvocationCalls: Number(aggregate.ambiguous ?? 0), unresolvedInvocationCalls: Number(aggregate.unresolved ?? 0), ambiguousNormalizedCalls, unresolvedNormalizedCallsWithIndexedCandidates: unresolvedMatchingIndexedOperation, examples };
1250
+ }
1190
1251
  function createProgram() {
1191
1252
  const program = new Command();
1192
1253
  program.name("service-flow").description(