@saptools/service-flow 0.1.52 → 0.1.54

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +11 -12
  3. package/TECHNICAL-NOTE.md +18 -3
  4. package/dist/{chunk-PTLDSHRC.js → chunk-ERIZHM5C.js} +2646 -1067
  5. package/dist/chunk-ERIZHM5C.js.map +1 -0
  6. package/dist/cli.js +162 -13
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/package.json +1 -1
  10. package/src/cli/001-doctor-projection.ts +136 -0
  11. package/src/cli/doctor.ts +20 -3
  12. package/src/db/repositories.ts +10 -2
  13. package/src/linker/000-implementation-candidates.ts +674 -0
  14. package/src/linker/001-implementation-evidence-projection.ts +191 -0
  15. package/src/linker/002-call-evidence.ts +226 -0
  16. package/src/linker/cross-repo-linker.ts +27 -453
  17. package/src/linker/dynamic-edge-resolver.ts +35 -0
  18. package/src/linker/external-http-target.ts +24 -3
  19. package/src/linker/helper-package-linker.ts +18 -2
  20. package/src/linker/service-resolver.ts +45 -2
  21. package/src/output/doctor-output.ts +13 -4
  22. package/src/output/table-output.ts +15 -4
  23. package/src/trace/000-dynamic-target-types.ts +12 -0
  24. package/src/trace/001-dynamic-identity.ts +45 -17
  25. package/src/trace/003-dynamic-references.ts +236 -35
  26. package/src/trace/004-dynamic-candidate-sources.ts +155 -0
  27. package/src/trace/005-implementation-selection.ts +187 -0
  28. package/src/trace/006-contextual-projection.ts +30 -0
  29. package/src/trace/007-implementation-start-diagnostic.ts +61 -0
  30. package/src/trace/008-contextual-runtime-state.ts +294 -0
  31. package/src/trace/009-selected-handler-provenance.ts +186 -0
  32. package/src/trace/dynamic-targets.ts +205 -159
  33. package/src/trace/evidence.ts +132 -34
  34. package/src/trace/implementation-hints.ts +148 -8
  35. package/src/trace/selectors.ts +74 -9
  36. package/src/trace/trace-engine.ts +97 -125
  37. package/src/utils/000-bounded-projection.ts +166 -0
  38. package/dist/chunk-PTLDSHRC.js.map +0 -1
package/dist/cli.js CHANGED
@@ -29,12 +29,13 @@ import {
29
29
  parsePackageJson,
30
30
  parseServiceBindings,
31
31
  parseVars,
32
+ projectBoundedInOrder,
32
33
  reposByName,
33
34
  selectorRepoAmbiguousDiagnostic,
34
35
  trace,
35
36
  upsertRepository,
36
37
  upsertWorkspace
37
- } from "./chunk-PTLDSHRC.js";
38
+ } from "./chunk-ERIZHM5C.js";
38
39
 
39
40
  // src/cli.ts
40
41
  import { Command } from "commander";
@@ -236,7 +237,7 @@ function migrate(db) {
236
237
  // package.json
237
238
  var package_default = {
238
239
  name: "@saptools/service-flow",
239
- version: "0.1.52",
240
+ version: "0.1.54",
240
241
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
241
242
  type: "module",
242
243
  publishConfig: {
@@ -1130,13 +1131,128 @@ async function indexWorkspace(db, workspaceId, options) {
1130
1131
  }
1131
1132
  }
1132
1133
 
1134
+ // src/cli/001-doctor-projection.ts
1135
+ var boundedDoctorArrayKeys = /* @__PURE__ */ new Set([
1136
+ "candidates",
1137
+ "candidateScores",
1138
+ "candidateFamilies",
1139
+ "candidateEvidence",
1140
+ "candidatePaths",
1141
+ "candidateRawPaths",
1142
+ "candidateNormalizedOperationPaths",
1143
+ "normalizedCandidateOperations",
1144
+ "candidateLiterals",
1145
+ "bindingCandidates",
1146
+ "bindingAlternatives",
1147
+ "registrations",
1148
+ "implementationHintSuggestions",
1149
+ "selectableImplementationRepositories",
1150
+ "matchedHints",
1151
+ "candidateSuggestions",
1152
+ "dynamicTargetCandidates",
1153
+ "dynamicTargetCandidateSuggestions",
1154
+ "rejectedCandidates",
1155
+ "suggestedVarSets",
1156
+ "copyableExamples",
1157
+ "examples",
1158
+ "expandedExamples",
1159
+ "selectorSuggestions",
1160
+ "serviceSuggestions",
1161
+ "repositories"
1162
+ ]);
1163
+ function boundDoctorDiagnostics(diagnostics) {
1164
+ return diagnostics.map(boundDoctorDiagnostic);
1165
+ }
1166
+ function boundDoctorDiagnostic(diagnostic) {
1167
+ const bounded = boundDoctorValue(diagnostic);
1168
+ return isDiagnostic(bounded) ? bounded : {};
1169
+ }
1170
+ function boundDoctorValue(value) {
1171
+ if (Array.isArray(value)) return value.map(boundDoctorValue);
1172
+ if (!isDiagnostic(value)) return value;
1173
+ const input = value;
1174
+ const output = {};
1175
+ for (const [key, child] of Object.entries(input)) {
1176
+ if (!Array.isArray(child) || !boundedDoctorArrayKeys.has(key)) {
1177
+ output[key] = boundDoctorValue(child);
1178
+ continue;
1179
+ }
1180
+ const projection = projectBoundedInOrder(child.map(boundDoctorValue));
1181
+ output[key] = projection.items;
1182
+ addProjectionMetadata(output, input, key, projection);
1183
+ }
1184
+ return output;
1185
+ }
1186
+ function isDiagnostic(value) {
1187
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1188
+ }
1189
+ function addProjectionMetadata(output, input, key, projection) {
1190
+ const names = projectionNames(key);
1191
+ const total = Math.max(
1192
+ numericValue(input[names.total]),
1193
+ projection.totalCount,
1194
+ siblingCollectionCount(input, key)
1195
+ );
1196
+ output[names.total] = total;
1197
+ output[names.shown] = projection.shownCount;
1198
+ output[names.omitted] = Math.max(0, total - projection.shownCount);
1199
+ }
1200
+ function siblingCollectionCount(input, key) {
1201
+ if (key !== "examples" || !Array.isArray(input.expandedExamples)) return 0;
1202
+ return input.expandedExamples.length;
1203
+ }
1204
+ function projectionNames(key) {
1205
+ const stem = projectionStem(key);
1206
+ return {
1207
+ total: `${stem}Count`,
1208
+ shown: `shown${upperFirst(stem)}Count`,
1209
+ omitted: `omitted${upperFirst(stem)}Count`
1210
+ };
1211
+ }
1212
+ function projectionStem(key) {
1213
+ const stems = {
1214
+ candidates: "candidate",
1215
+ candidateScores: "candidateScore",
1216
+ candidateFamilies: "candidateFamily",
1217
+ candidateEvidence: "candidateEvidence",
1218
+ candidatePaths: "candidatePath",
1219
+ candidateRawPaths: "candidateRawPath",
1220
+ candidateNormalizedOperationPaths: "candidateNormalizedOperationPath",
1221
+ normalizedCandidateOperations: "normalizedCandidateOperation",
1222
+ candidateLiterals: "candidateLiteral",
1223
+ bindingCandidates: "bindingCandidate",
1224
+ bindingAlternatives: "bindingAlternative",
1225
+ registrations: "registration",
1226
+ implementationHintSuggestions: "implementationHintSuggestion",
1227
+ selectableImplementationRepositories: "selectableImplementationRepository",
1228
+ matchedHints: "matchedHint",
1229
+ candidateSuggestions: "candidateSuggestion",
1230
+ dynamicTargetCandidates: "dynamicTargetCandidate",
1231
+ dynamicTargetCandidateSuggestions: "dynamicTargetCandidateSuggestion",
1232
+ rejectedCandidates: "rejectedCandidate",
1233
+ suggestedVarSets: "suggestedVarSet",
1234
+ copyableExamples: "copyableExample",
1235
+ examples: "example",
1236
+ expandedExamples: "expandedExample",
1237
+ selectorSuggestions: "selectorSuggestion",
1238
+ serviceSuggestions: "serviceSuggestion"
1239
+ };
1240
+ return stems[key] ?? "repository";
1241
+ }
1242
+ function numericValue(value) {
1243
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1244
+ }
1245
+ function upperFirst(value) {
1246
+ return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
1247
+ }
1248
+
1133
1249
  // src/cli/doctor.ts
1134
1250
  function linkUpgradeWarnings(db) {
1135
1251
  return [...schemaDriftDiagnostics(db, true), ...analyzerVersionDiagnostics(db, true)].filter((item) => ["schema_legacy_columns_present", "external_target_columns_missing_data", "reindex_required_after_upgrade", "reindex_required_after_analyzer_upgrade"].includes(String(item.code)));
1136
1252
  }
1137
1253
  function doctorDiagnostics(db, strict, options = {}) {
1138
1254
  const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
1139
- return [
1255
+ return boundDoctorDiagnostics([
1140
1256
  ...diagnostics,
1141
1257
  ...healthDiagnostics(db, strict),
1142
1258
  ...remoteTargetWithoutImplementationDiagnostics(db, strict, Boolean(options.detail)),
@@ -1144,7 +1260,7 @@ function doctorDiagnostics(db, strict, options = {}) {
1144
1260
  ...schemaDriftDiagnostics(db, strict),
1145
1261
  ...analyzerVersionDiagnostics(db, strict),
1146
1262
  ...parserQualityDiagnostics(db, strict, options)
1147
- ];
1263
+ ]);
1148
1264
  }
1149
1265
  function healthDiagnostics(db, strict) {
1150
1266
  return db.prepare(
@@ -1356,6 +1472,10 @@ function addImplementationCategory(grouped, row) {
1356
1472
  const current = grouped.get(key) ?? { category, baseOperation, reason, candidateFamily: family, count: 0, servicePaths: [], examples: [] };
1357
1473
  const hintSuggestions = implementationSuggestions(evidence);
1358
1474
  const candidates = asRecords(evidence.candidates);
1475
+ const candidateCount2 = Math.max(
1476
+ numericValue2(evidence.candidateCount),
1477
+ candidates.length
1478
+ );
1359
1479
  current.count += 1;
1360
1480
  current.servicePaths.push(String(row.servicePath ?? evidence.servicePath ?? ""));
1361
1481
  current.examples.push({
@@ -1363,7 +1483,15 @@ function addImplementationCategory(grouped, row) {
1363
1483
  operation: row.operationName,
1364
1484
  status: row.status,
1365
1485
  reason: row.unresolvedReason,
1366
- candidateCount: candidates.length,
1486
+ candidateCount: candidateCount2,
1487
+ shownCandidateCount: Math.min(
1488
+ candidateCount2,
1489
+ numericValue2(evidence.shownCandidateCount) || candidates.length
1490
+ ),
1491
+ omittedCandidateCount: Math.max(
1492
+ 0,
1493
+ candidateCount2 - (numericValue2(evidence.shownCandidateCount) || candidates.length)
1494
+ ),
1367
1495
  candidateEvidence: candidates.slice(0, 3),
1368
1496
  implementationHintSuggestions: hintSuggestions
1369
1497
  });
@@ -1701,6 +1829,9 @@ function remoteActionNoBindingQuality(db) {
1701
1829
  function candidateCount(value) {
1702
1830
  return Number(parseObject(value).candidateCount ?? 0);
1703
1831
  }
1832
+ function numericValue2(value) {
1833
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1834
+ }
1704
1835
  function parseObject(value) {
1705
1836
  if (value && typeof value === "object" && !Array.isArray(value)) return value;
1706
1837
  try {
@@ -1716,13 +1847,20 @@ function asRecords(value) {
1716
1847
 
1717
1848
  // src/output/table-output.ts
1718
1849
  function location(evidence) {
1850
+ const selected = isRecord(evidence.selectedHandler) ? evidence.selectedHandler : void 0;
1851
+ const selectedFile = selected?.sourceFile;
1852
+ const selectedLine = selected?.sourceLine;
1853
+ if (selectedFile || selectedLine)
1854
+ return `${String(selectedFile ?? "")}:${String(selectedLine ?? "")}`;
1719
1855
  const file = evidence.file ?? evidence.sourceFile ?? evidence.handlerSourceFile ?? evidence.operationSourceFile ?? evidence.registrationSourceFile;
1720
1856
  const line = evidence.line ?? evidence.sourceLine ?? evidence.handlerSourceLine ?? evidence.operationSourceLine ?? evidence.registrationSourceLine;
1721
1857
  if (file || line) return `${String(file ?? "")}:${String(line ?? "")}`;
1722
1858
  const candidates = evidence.candidates;
1859
+ if (Array.isArray(candidates) && candidates.some((candidate) => isRecord(candidate) && candidate.methodId !== void 0)) return ":";
1723
1860
  if (Array.isArray(candidates) && candidates.length > 0) {
1724
- const first = candidates[0];
1725
- return `${String(first.sourceFile ?? "")}:${String(first.sourceLine ?? "")}`;
1861
+ const first = candidates.find(isRecord);
1862
+ if (first)
1863
+ return `${String(first.sourceFile ?? "")}:${String(first.sourceLine ?? "")}`;
1726
1864
  }
1727
1865
  return ":";
1728
1866
  }
@@ -1766,8 +1904,10 @@ function hintLines(evidence) {
1766
1904
  const hints = suggestions.flatMap((item) => isRecord(item) && typeof item.cli === "string" ? [item.cli] : []);
1767
1905
  const unique = [...new Set(hints)];
1768
1906
  const shown = unique.slice(0, 3).map((hint) => `try ${hint}`);
1769
- if (unique.length > shown.length)
1770
- shown.push(`... ${unique.length - shown.length} more hint(s) available in JSON`);
1907
+ const omitted = numberValue(evidence.omittedImplementationHintSuggestionCount);
1908
+ const remaining = Math.max(0, unique.length - shown.length) + omitted;
1909
+ if (remaining > 0)
1910
+ shown.push(`... ${remaining} additional hint(s) omitted; use a scoped --implementation-hint`);
1771
1911
  return [...dynamicLines, ...shown];
1772
1912
  }
1773
1913
  function dynamicHintLines(evidence) {
@@ -1857,8 +1997,12 @@ function compactMessage(diagnostic) {
1857
1997
  }
1858
1998
  function suggestedHintLines(diagnostic) {
1859
1999
  const direct = cliHints(diagnostic.suggestedHints);
1860
- if (direct.length > 0) return cappedHints(direct);
1861
- return cappedHints(cliHintsFromSuggestions(diagnostic.implementationHintSuggestions));
2000
+ const omitted = numericValue3(diagnostic.omittedImplementationHintSuggestionCount);
2001
+ if (direct.length > 0) return cappedHints(direct, omitted);
2002
+ return cappedHints(
2003
+ cliHintsFromSuggestions(diagnostic.implementationHintSuggestions),
2004
+ omitted
2005
+ );
1862
2006
  }
1863
2007
  function cliHints(value) {
1864
2008
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -1873,12 +2017,17 @@ function cliHintsFromSuggestions(value) {
1873
2017
  function isRecord2(value) {
1874
2018
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1875
2019
  }
1876
- function cappedHints(hints) {
2020
+ function cappedHints(hints, omitted) {
1877
2021
  const unique = [...new Set(hints)];
1878
2022
  const shown = unique.slice(0, 3);
1879
- if (unique.length > shown.length) shown.push(`... ${unique.length - shown.length} more hint(s) available in --format json`);
2023
+ const remaining = Math.max(0, unique.length - shown.length) + omitted;
2024
+ if (remaining > 0)
2025
+ shown.push(`... ${remaining} additional hint(s) omitted; use a scoped --implementation-hint`);
1880
2026
  return shown;
1881
2027
  }
2028
+ function numericValue3(value) {
2029
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
2030
+ }
1882
2031
  function cleanDoctorMessage() {
1883
2032
  return `${pc.green("No diagnostics recorded")}
1884
2033
  `;