@saptools/service-flow 0.1.26 → 0.1.28

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
@@ -12,7 +12,7 @@ import {
12
12
  parsePackageJson,
13
13
  parseServiceBindings,
14
14
  trace
15
- } from "./chunk-Q3J2QDNX.js";
15
+ } from "./chunk-SWPGRM7Z.js";
16
16
 
17
17
  // src/cli.ts
18
18
  import { Command } from "commander";
@@ -188,7 +188,7 @@ function migrate(db) {
188
188
  // package.json
189
189
  var package_default = {
190
190
  name: "@saptools/service-flow",
191
- version: "0.1.26",
191
+ version: "0.1.28",
192
192
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
193
193
  type: "module",
194
194
  publishConfig: {
@@ -789,6 +789,41 @@ function bindingLocalName(name, initializer) {
789
789
  if (initializer && ts.isIdentifier(initializer)) return initializer.text;
790
790
  return void 0;
791
791
  }
792
+ function objectPatternAliases(pattern, parameter, source, lineNode) {
793
+ return pattern.elements.flatMap((element) => {
794
+ if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
795
+ const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
796
+ if (!property) return [];
797
+ const local = bindingLocalName(element.name, element.initializer);
798
+ return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line: lineOf(source, lineNode.getStart(source)) }] : [];
799
+ });
800
+ }
801
+ function parameterPropertyAliases(fn, source) {
802
+ const parameterNames = new Set(fn.parameters.flatMap((param) => ts.isIdentifier(param.name) ? [param.name.text] : []));
803
+ if (!fn.body || parameterNames.size === 0) return [];
804
+ const aliases = [];
805
+ const addFromAssignment = (left, right, node) => {
806
+ if (!ts.isObjectLiteralExpression(left) || !ts.isIdentifier(right) || !parameterNames.has(right.text)) return;
807
+ for (const prop of left.properties) {
808
+ if (!ts.isPropertyAssignment(prop)) continue;
809
+ const property = nameOf(prop.name);
810
+ if (property && ts.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf(source, node.getStart(source)) });
811
+ }
812
+ };
813
+ const visit = (node) => {
814
+ if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && node.initializer && ts.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
815
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) addFromAssignment(ts.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
816
+ ts.forEachChild(node, visit);
817
+ };
818
+ visit(fn.body);
819
+ const seen = /* @__PURE__ */ new Set();
820
+ return aliases.filter((alias) => {
821
+ const key = `${alias.parameter}.${alias.property}:${alias.local}`;
822
+ if (seen.has(key)) return false;
823
+ seen.add(key);
824
+ return true;
825
+ });
826
+ }
792
827
  function parameterBindings(params) {
793
828
  return params.flatMap((param, index) => {
794
829
  if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
@@ -826,7 +861,8 @@ async function parseExecutableSymbols(repoPath, filePath) {
826
861
  const bindings = isFunctionLike(node) ? parameterBindings(node.parameters) : void 0;
827
862
  const params = bindings?.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
828
863
  const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 ? "static_method" : "class_method", parameters: params } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
829
- const parameterEvidence = bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {};
864
+ const aliases = isFunctionLike(node) ? parameterPropertyAliases(node, source) : [];
865
+ const parameterEvidence = { ...bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {}, ...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {} };
830
866
  symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence ? { ...sourceEvidence, ...parameterEvidence } : bindings && bindings.length > 0 ? parameterEvidence : void 0 });
831
867
  };
832
868
  const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
@@ -1369,13 +1405,42 @@ function contextualBindingPropagationQuality(db) {
1369
1405
  WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind')='object_literal'
1370
1406
  AND json_extract(s.evidence_json,'$.parameterBindings[0].kind')='object_pattern'
1371
1407
  AND json_array_length(json_extract(sc.evidence_json,'$.callArguments[0].properties')) > json_array_length(json_extract(s.evidence_json,'$.parameterBindings[0].properties'))`).get();
1372
- const opportunities = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,'trace_time_contextual_binding_candidate' opportunity
1373
- FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1374
- WHERE c.call_type='remote_action' AND c.service_binding_id IS NULL AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL
1375
- AND (e.status IS NULL OR e.status!='resolved')
1408
+ const opportunities = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,b.alias bindingAlias,b.alias_expr bindingAliasExpr,b.service_path_expr servicePathExpr,b.destination_expr destinationExpr,req.service_path requireServicePath,req.destination requireDestination,COALESCE(e.status,'missing_edge') persistedStatus,
1409
+ CASE
1410
+ WHEN (b.alias_expr LIKE '%$%' OR b.service_path_expr LIKE '%$%' OR b.destination_expr LIKE '%$%') THEN 'runtime_variables_required'
1411
+ WHEN b.alias IS NOT NULL AND req.id IS NULL AND b.service_path_expr IS NULL THEN 'alias_without_matching_cds_requires'
1412
+ WHEN req.id IS NOT NULL AND COALESCE(e.status,'missing_edge')!='resolved' THEN 'cds_requires_present_but_persisted_resolution_unresolved'
1413
+ ELSE 'trace_time_contextual_binding_candidate'
1414
+ END contextualStatus
1415
+ FROM outbound_calls c
1416
+ LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1417
+ LEFT JOIN service_bindings b ON b.id=c.service_binding_id
1418
+ LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias
1419
+ WHERE c.call_type='remote_action' AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL
1420
+ AND (c.service_binding_id IS NULL OR e.status IS NULL OR e.status!='resolved')
1376
1421
  AND EXISTS (SELECT 1 FROM symbol_calls sc WHERE sc.status='resolved' AND sc.source_file=c.source_file)
1377
1422
  ORDER BY c.source_file,c.source_line LIMIT 8`).all();
1378
- return { severity: Number(missingMetadata.count ?? 0) + Number(destructuredUnmapped.count ?? 0) + opportunities.length > 0 ? "warning" : "info", code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation opportunities for trace-time helper resolution", localSymbolCallsWithServiceClientArguments: Number(serviceClientCalls.count ?? 0), calleeSymbolsMissingParameterMetadata: Number(missingMetadata.count ?? 0), destructuredObjectParametersPossiblyUnmapped: Number(destructuredUnmapped.count ?? 0), traceTimeContextualOpportunities: opportunities.length, examples: opportunities };
1423
+ const statusRows = db.prepare(`SELECT contextualStatus,COUNT(*) count FROM (
1424
+ SELECT CASE
1425
+ WHEN (b.alias_expr LIKE '%$%' OR b.service_path_expr LIKE '%$%' OR b.destination_expr LIKE '%$%') THEN 'runtime_variables_required'
1426
+ WHEN b.alias IS NOT NULL AND req.id IS NULL AND b.service_path_expr IS NULL THEN 'alias_without_matching_cds_requires'
1427
+ WHEN req.id IS NOT NULL AND COALESCE(e.status,'missing_edge')!='resolved' THEN 'cds_requires_present_but_persisted_resolution_unresolved'
1428
+ ELSE 'trace_time_contextual_binding_candidate'
1429
+ END contextualStatus
1430
+ FROM outbound_calls c
1431
+ LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1432
+ LEFT JOIN service_bindings b ON b.id=c.service_binding_id
1433
+ LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias
1434
+ WHERE c.call_type='remote_action' AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL
1435
+ AND (c.service_binding_id IS NULL OR e.status IS NULL OR e.status!='resolved')
1436
+ AND EXISTS (SELECT 1 FROM symbol_calls sc WHERE sc.status='resolved' AND sc.source_file=c.source_file)
1437
+ ) GROUP BY contextualStatus ORDER BY count DESC,contextualStatus`).all();
1438
+ const resolvedContextual = db.prepare(`SELECT COUNT(*) count FROM outbound_calls c JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT) WHERE c.call_type='remote_action' AND e.status='resolved' AND c.service_binding_id IS NOT NULL`).get();
1439
+ const totalOpportunities = statusRows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
1440
+ const actionableStatuses = /* @__PURE__ */ new Set(["alias_without_matching_cds_requires", "cds_requires_present_but_persisted_resolution_unresolved", "trace_time_contextual_binding_candidate"]);
1441
+ const actionableOpportunityCount = statusRows.reduce((sum, row) => actionableStatuses.has(String(row.contextualStatus)) ? sum + Number(row.count ?? 0) : sum, 0);
1442
+ const severity = Number(missingMetadata.count ?? 0) + Number(destructuredUnmapped.count ?? 0) + actionableOpportunityCount > 0 ? "warning" : "info";
1443
+ return { severity, code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation opportunities for trace-time helper resolution", localSymbolCallsWithServiceClientArguments: Number(serviceClientCalls.count ?? 0), calleeSymbolsMissingParameterMetadata: Number(missingMetadata.count ?? 0), destructuredObjectParametersPossiblyUnmapped: Number(destructuredUnmapped.count ?? 0), contextualHelperSendsResolvedDuringPersistedLink: Number(resolvedContextual.count ?? 0), traceTimeContextualOpportunities: totalOpportunities, traceTimeContextualOpportunityBreakdown: statusRows.length > 0 ? statusRows : [{ contextualStatus: "no_contextual_opportunity", count: 0 }], exampleCount: opportunities.length, examples: opportunities };
1379
1444
  }
1380
1445
  function nestedThisReceiverQuality(db) {
1381
1446
  const aggregate = db.prepare(`SELECT COUNT(*) total,