@saptools/service-flow 0.1.62 → 0.1.63

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/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  stripQuotes,
18
18
  substituteVariables,
19
19
  trace
20
- } from "./chunk-BGD7UYJN.js";
20
+ } from "./chunk-EGBTHN7J.js";
21
21
 
22
22
  // src/parsers/generated-constants-parser.ts
23
23
  import fs from "fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saptools/service-flow",
3
- "version": "0.1.62",
3
+ "version": "0.1.63",
4
4
  "description": "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -76,13 +76,9 @@ function literalText(expr: ts.Expression | undefined): string | undefined {
76
76
  if (isStringLike(expr)) return expr.text;
77
77
  return undefined;
78
78
  }
79
- function objectPropertyText(object: ts.ObjectLiteralExpression, key: string): string | undefined {
80
- const prop = object.properties.find((property): property is ts.PropertyAssignment | ts.ShorthandPropertyAssignment =>
81
- (ts.isPropertyAssignment(property) && nameOfProperty(property.name) === key) || (ts.isShorthandPropertyAssignment(property) && property.name.text === key),
82
- );
83
- if (!prop) return undefined;
84
- return ts.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
85
- }
79
+ const CDS_LIFECYCLE_EVENTS = new Set([
80
+ 'bootstrap', 'loaded', 'connect', 'serving', 'served', 'listening', 'shutdown',
81
+ ]);
86
82
  function objectPropertyIsShorthand(object: ts.ObjectLiteralExpression, key: string): boolean {
87
83
  return object.properties.some((property) => ts.isShorthandPropertyAssignment(property) && property.name.text === key);
88
84
  }
@@ -376,7 +372,10 @@ export function classifyOutboundCallsInSource(source: ts.SourceFile, filePath: s
376
372
  if (objectArg && ts.isObjectLiteralExpression(objectArg)) {
377
373
  const receiver = receiverName(expr.expression);
378
374
  const queryExpression = propertyInitializer(objectArg, 'query');
379
- const method = stripQuotes(resolveExpression(propertyInitializer(objectArg, 'method'), node, 'literal').value ?? objectPropertyText(objectArg, 'method') ?? 'POST');
375
+ const methodExpression = propertyInitializer(objectArg, 'method');
376
+ const methodResolution = resolveExpression(methodExpression, node, 'literal');
377
+ const method = stripQuotes(methodResolution.value ?? 'POST');
378
+ const dynamicMethodDefaulted = Boolean(methodExpression && methodResolution.value === undefined);
380
379
  const pathExpr = propertyInitializer(objectArg, 'path') ?? propertyInitializer(objectArg, 'event');
381
380
  const pathAnalysis = analyzeOperationPath(pathExpr, node, method);
382
381
  const op = pathExpr ? operationPathExpression(pathAnalysis) ?? pathExpr.getText(source) : undefined;
@@ -392,7 +391,7 @@ export function classifyOutboundCallsInSource(source: ts.SourceFile, filePath: s
392
391
  const unresolvedReason = queryExpression
393
392
  ? queryEntity ? undefined : queryWarning(queryExpression.getText(source))
394
393
  : pathExpr ? pathUnresolvedReason(pathAnalysis) : undefined;
395
- add(node, { callType: queryExpression ? 'remote_query' : entityCallType ?? (isODataQueryRead ? 'remote_query' : 'remote_action'), serviceVariableName: receiver, method, operationPathExpr, queryEntity, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || queryExpression ? 0.8 : 0.4, unresolvedReason }, { receiver, classifier: 'service_client_send_object', operationPathExpression: shorthandPath ? op : undefined, rawPathExpression: pathAnalysis.rawExpression, literalPathSource: literalPathSource(pathAnalysis), odataPathIntent: operationPathExpr ? intent : undefined, pathAnalysis, staticPathCandidates: legacyPathCandidates(pathAnalysis), parserWarning: unresolvedReason });
394
+ add(node, { callType: queryExpression ? 'remote_query' : entityCallType ?? (isODataQueryRead ? 'remote_query' : 'remote_action'), serviceVariableName: receiver, method, operationPathExpr, queryEntity, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || queryExpression ? 0.8 : 0.4, unresolvedReason }, { receiver, classifier: 'service_client_send_object', operationPathExpression: shorthandPath ? op : undefined, rawPathExpression: pathAnalysis.rawExpression, literalPathSource: literalPathSource(pathAnalysis), odataPathIntent: operationPathExpr ? intent : undefined, pathAnalysis, staticPathCandidates: legacyPathCandidates(pathAnalysis), parserWarning: unresolvedReason, ...(dynamicMethodDefaulted ? { dynamicMethodDefaulted: true } : {}) });
396
395
  } else {
397
396
  const receiver = receiverName(expr.expression);
398
397
  const rootReceiver = rootReceiverName(expr.expression);
@@ -434,7 +433,11 @@ export function classifyOutboundCallsInSource(source: ts.SourceFile, filePath: s
434
433
  const rootReceiver = rootReceiverName(expr.expression);
435
434
  if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
436
435
  const eventName = literalText(node.arguments[0]);
437
- if (eventName) add(node, { callType: expr.name.text === 'on' ? 'async_subscribe' : 'async_emit', serviceVariableName: rootReceiver ?? receiver, eventNameExpr: eventName }, { receiver, rootReceiver, classifier: expr.name.text === 'on' ? 'cap_service_event_subscription' : 'cap_service_event_emit', receiverClassification: 'cap_evidence' });
436
+ const effectiveReceiver = rootReceiver ?? receiver;
437
+ const lifecycleHook = effectiveReceiver === 'cds'
438
+ && CDS_LIFECYCLE_EVENTS.has(eventName ?? '');
439
+ const errorHook = expr.name.text === 'on' && eventName === 'error';
440
+ if (eventName && !lifecycleHook && !errorHook) add(node, { callType: expr.name.text === 'on' ? 'async_subscribe' : 'async_emit', serviceVariableName: effectiveReceiver, eventNameExpr: eventName }, { receiver, rootReceiver, classifier: expr.name.text === 'on' ? 'cap_service_event_subscription' : 'cap_service_event_emit', receiverClassification: 'cap_evidence' });
438
441
  }
439
442
  } else {
440
443
  const external = externalHttpEvidence(node, source);