@saptools/service-flow 0.1.25 → 0.1.27

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.27
4
+
5
+ - Enrich trace-time contextual service-client bindings from package-level CAP `cds.requires` aliases so helper-internal sends resolve through require-derived service paths and destinations.
6
+ - Preserve contextual binding attempt evidence for unresolved helper sends, including effective service path, destination, candidate counts, and resolution reasons.
7
+ - Refine strict doctor diagnostics for contextual helper sends to distinguish resolved contextual opportunities, missing `cds.requires` rows, require-backed unresolved sends, remaining runtime variables, and workspaces with no contextual opportunity.
8
+ - Expand neutral CAP coverage for positional helper arguments, destructured object helper parameters, renamed properties, late assignments, table output, Mermaid output, and require-derived target operations.
9
+
10
+ ## 0.1.26
11
+
12
+ - Resolve helper-internal remote sends contextually in trace output when service clients are passed through positional arguments or one-level destructured object parameters.
13
+ - Add auditable contextual binding evidence for propagated helper client receivers, including caller argument, caller object property, callee parameter, callee receiver, and propagation source.
14
+ - Keep nested `this.<property>.<method>()` symbol-call resolution conservative unless explicit same-file or relative-import helper instance evidence exists.
15
+ - Update strict doctor diagnostics for trace-time contextual propagation opportunities and nested `this` receiver quality signals.
16
+
3
17
  ## 0.1.25
4
18
 
5
19
  - Make class-instance symbol-call indexing conservative so only same-file and relatively imported helper classes are traced, while built-in collection, date, URL, error, typed-array, promise, and abort-controller instances are ignored.
@@ -2266,21 +2266,33 @@ function receiverFromEvidence(value) {
2266
2266
  const evidence = parseEvidence(value);
2267
2267
  return typeof evidence.receiver === "string" ? evidence.receiver : void 0;
2268
2268
  }
2269
+ function hasDynamicPlaceholder(value) {
2270
+ return extractPlaceholders(value).length > 0;
2271
+ }
2272
+ function enrichBinding(row) {
2273
+ const effectiveServicePath = row.servicePathExpr && !hasDynamicPlaceholder(row.servicePathExpr) ? row.servicePathExpr : !row.servicePathExpr ? row.requireServicePath : void 0;
2274
+ const effectiveDestination = row.destinationExpr && !hasDynamicPlaceholder(row.destinationExpr) ? row.destinationExpr : !row.destinationExpr ? row.requireDestination : void 0;
2275
+ return { ...row, effectiveServicePath, effectiveDestination };
2276
+ }
2269
2277
  function knownBindingsForCalls(db, calls) {
2270
2278
  const map = /* @__PURE__ */ new Map();
2271
2279
  for (const call of calls) {
2272
2280
  const receiver = receiverFromEvidence(call.evidence_json);
2273
2281
  const bindingId = Number(call.service_binding_id ?? 0);
2274
2282
  if (!receiver || !bindingId) continue;
2275
- const row = db.prepare("SELECT id,alias,alias_expr aliasExpr,destination_expr destinationExpr,service_path_expr servicePathExpr,source_file sourceFile,source_line sourceLine FROM service_bindings WHERE id=?").get(bindingId);
2276
- if (row) map.set(receiver, { ...row, bindingId, source: "local_service_binding", calleeReceiver: receiver });
2283
+ const row = db.prepare(`SELECT b.id,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
2284
+ FROM service_bindings b LEFT JOIN cds_requires req ON req.repo_id=b.repo_id AND req.alias=b.alias
2285
+ WHERE b.id=?`).get(bindingId);
2286
+ if (row) map.set(receiver, enrichBinding({ ...row, bindingId, source: "local_service_binding", calleeReceiver: receiver }));
2277
2287
  }
2278
2288
  return map;
2279
2289
  }
2280
2290
  function knownBindingsForScope(db, repoId, symbolIds, files) {
2281
2291
  const map = /* @__PURE__ */ new Map();
2282
2292
  if (repoId === void 0) return map;
2283
- const rows2 = db.prepare("SELECT id,variable_name variableName,alias,alias_expr aliasExpr,destination_expr destinationExpr,service_path_expr servicePathExpr,source_file sourceFile,source_line sourceLine FROM service_bindings WHERE repo_id=?").all(repoId);
2293
+ const rows2 = db.prepare(`SELECT b.id,b.variable_name variableName,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
2294
+ FROM service_bindings b LEFT JOIN cds_requires req ON req.repo_id=b.repo_id AND req.alias=b.alias
2295
+ WHERE b.repo_id=?`).all(repoId);
2284
2296
  for (const row of rows2) {
2285
2297
  if (!row.variableName) continue;
2286
2298
  if (files && !files.has(String(row.sourceFile))) continue;
@@ -2288,7 +2300,7 @@ function knownBindingsForScope(db, repoId, symbolIds, files) {
2288
2300
  const owner = db.prepare("SELECT id FROM symbols WHERE id IN (" + [...symbolIds].map(() => "?").join(",") + ") AND source_file=? AND start_line<=? AND end_line>=? LIMIT 1").get(...symbolIds, row.sourceFile, row.sourceLine, row.sourceLine);
2289
2301
  if (!owner) continue;
2290
2302
  }
2291
- map.set(row.variableName, { ...row, bindingId: Number(row.id), source: "local_service_binding", calleeReceiver: row.variableName });
2303
+ map.set(row.variableName, enrichBinding({ ...row, bindingId: Number(row.id), source: "local_service_binding", calleeReceiver: row.variableName }));
2292
2304
  }
2293
2305
  return map;
2294
2306
  }
@@ -2324,10 +2336,13 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
2324
2336
  function contextualRuntimeResolution(db, call, binding, workspaceId) {
2325
2337
  if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
2326
2338
  const op = normalizeODataOperationInvocationPathForTrace(String(call.operation_path_expr));
2327
- const resolution = resolveOperation(db, { servicePath: binding.servicePathExpr, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination: binding.destinationExpr, hasExplicitOverride: true, isDynamic: false }, workspaceId);
2328
- const evidence = { contextualServiceBindingSelected: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, bindingSourceFile: binding.sourceFile, bindingSourceLine: binding.sourceLine }, operationPath: op, servicePath: binding.servicePathExpr, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination: binding.destinationExpr, contextualResolutionStatus: resolution.status, candidates: resolution.candidates, resolutionReasons: resolution.reasons };
2329
- if (!resolution.target) return { evidence, unresolvedReason: resolution.status === "ambiguous" ? "Ambiguous contextual operation candidates" : "No contextual operation candidate matched" };
2330
- return { row: { id: -Number(call.id), edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION", from_id: String(call.id), to_kind: "operation", to_id: String(resolution.target.operationId), confidence: resolution.target.score, evidence_json: JSON.stringify(evidence), status: "resolved" }, evidence: { ...evidence, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName }, unresolvedReason: void 0 };
2339
+ const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
2340
+ const destination = binding.effectiveDestination ?? binding.destinationExpr ?? binding.requireDestination;
2341
+ const resolution = resolveOperation(db, { servicePath, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination, hasExplicitOverride: true, isDynamic: false }, workspaceId);
2342
+ const evidence = { contextualServiceBindingAttempted: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, bindingSourceFile: binding.sourceFile, bindingSourceLine: binding.sourceLine, alias: binding.alias, aliasExpr: binding.aliasExpr, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination }, operationPath: op, servicePath, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination, contextualResolutionStatus: resolution.status, contextualCandidateCount: resolution.candidates.length, candidates: resolution.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
2343
+ if (!resolution.target) return { evidence, unresolvedReason: resolution.status === "ambiguous" ? "Ambiguous contextual operation candidates" : resolution.status === "dynamic" ? `Dynamic contextual target is missing runtime variables: ${resolution.reasons.join(", ")}` : "No contextual operation candidate matched" };
2344
+ const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
2345
+ return { row: { id: -Number(call.id), edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION", from_id: String(call.id), to_kind: "operation", to_id: String(resolution.target.operationId), confidence: resolution.target.score, evidence_json: JSON.stringify(resolvedEvidence), status: "resolved" }, evidence: resolvedEvidence, unresolvedReason: void 0 };
2331
2346
  }
2332
2347
  function normalizeODataOperationInvocationPathForTrace(raw) {
2333
2348
  return raw.startsWith("/") ? raw : `/${raw}`;
@@ -2388,7 +2403,8 @@ function trace(db, start, options) {
2388
2403
  while (queue.length > 0) {
2389
2404
  const current = queue.shift();
2390
2405
  if (!current || current.depth > maxDepth) continue;
2391
- const key = `${current.repoId ?? "*"}:${[...current.symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}`;
2406
+ const contextKey = [...(current.context ?? /* @__PURE__ */ new Map()).keys()].sort().join(",");
2407
+ const key = `${current.repoId ?? "*"}:${[...current.symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${contextKey}`;
2392
2408
  if (seenScopes.has(key)) continue;
2393
2409
  seenScopes.add(key);
2394
2410
  const calls = db.prepare(
@@ -2428,14 +2444,15 @@ function trace(db, start, options) {
2428
2444
  line: call.source_line,
2429
2445
  callType: call.call_type
2430
2446
  });
2431
- const contextual = contextualRuntimeResolution(db, call, (current.context ?? /* @__PURE__ */ new Map()).get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)));
2447
+ const contextual = contextualRuntimeResolution(db, call, callerBindings.get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)));
2432
2448
  const graphRows = contextual.row ? [contextual.row] : graph.get(Number(call.id)) ?? [];
2433
2449
  for (const row of graphRows) {
2434
2450
  if (seenEdges.has(Number(row.id))) continue;
2435
2451
  seenEdges.add(Number(row.id));
2436
- const rawEvidence = contextual.evidence && row.id < 0 ? contextual.evidence : JSON.parse(
2452
+ const persistedEvidence = JSON.parse(
2437
2453
  String(row.evidence_json || "{}")
2438
2454
  );
2455
+ const rawEvidence = contextual.evidence ? { ...persistedEvidence, ...contextual.evidence } : persistedEvidence;
2439
2456
  const effective = runtimeResolution(db, row, rawEvidence, options.vars);
2440
2457
  const evidence = effective.evidence;
2441
2458
  const effectiveRow = effective.row;
@@ -2530,4 +2547,4 @@ export {
2530
2547
  linkWorkspace,
2531
2548
  trace
2532
2549
  };
2533
- //# sourceMappingURL=chunk-HS746OHV.js.map
2550
+ //# sourceMappingURL=chunk-VDJTNOEI.js.map