@saptools/service-flow 0.1.62 → 0.1.64

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.63
4
+
5
+ - Stopped recording CAP runtime lifecycle activity on the `cds` facade (`bootstrap`, `loaded`, `connect`, `serving`, `served`, `listening`, and `shutdown`) and supported-receiver `on('error')` hooks as asynchronous domain-event facts or derived event edges, while preserving custom facade events, lifecycle-named service events, domain messaging events, error emissions, and synthetic event-registration symbols.
6
+ - Made object-form `send({ method, ... })` calls with a dynamic method default to `POST` with bounded `dynamicMethodDefaulted` parser evidence instead of storing a property name or expression as the HTTP verb, without a schema, linker, trace-engine, or output-shape change.
7
+
3
8
  ## 0.1.62
4
9
 
5
10
  - Assigned every distinct Mermaid trace/graph node its own stable per-render identifier instead of truncating normalized endpoint strings to 60 characters, preventing nodes with long shared prefixes from collapsing into one.
@@ -1637,11 +1637,40 @@ function readExports(repoPath, filePath, seen, context) {
1637
1637
 
1638
1638
  // src/utils/redaction.ts
1639
1639
  var SENSITIVE = /authorization|cookie|token|secret|password|key|credential/i;
1640
+ var SENSITIVE_KEYWORD = /authorization|cookie|token|secret|password|key|credential/gi;
1641
+ function isWhitespace(value) {
1642
+ return value !== void 0 && /\s/u.test(value);
1643
+ }
1644
+ function redactionSpan(text, start, keyword) {
1645
+ let cursor = start + keyword.length;
1646
+ while (isWhitespace(text[cursor])) cursor += 1;
1647
+ if (text[cursor] !== ":" && text[cursor] !== "=") return void 0;
1648
+ cursor += 1;
1649
+ while (isWhitespace(text[cursor])) cursor += 1;
1650
+ const candidateQuote = text[cursor];
1651
+ const quote = candidateQuote === "'" || candidateQuote === '"' || candidateQuote === "`" ? candidateQuote : void 0;
1652
+ if (quote !== void 0) cursor += 1;
1653
+ const valueStart = cursor;
1654
+ while (cursor < text.length && !/[,'"`}\s]/u.test(text[cursor] ?? "")) cursor += 1;
1655
+ if (cursor === valueStart) return void 0;
1656
+ if (quote !== void 0) {
1657
+ if (text[cursor] !== quote) return void 0;
1658
+ cursor += 1;
1659
+ }
1660
+ return { start, end: cursor, keyword };
1661
+ }
1640
1662
  function redactText(text) {
1641
- return text.replace(
1642
- /(authorization|cookie|token|secret|password|key|credential)\s*[:=]\s*(['"`]?)[^,'"`}\s]+\2/gi,
1643
- "$1: [REDACTED]"
1644
- );
1663
+ let cursor = 0;
1664
+ let output = "";
1665
+ for (const match of text.matchAll(SENSITIVE_KEYWORD)) {
1666
+ const start = match.index ?? 0;
1667
+ if (start < cursor) continue;
1668
+ const span = redactionSpan(text, start, match[0]);
1669
+ if (span === void 0) continue;
1670
+ output += text.slice(cursor, span.start) + `${span.keyword}: [REDACTED]`;
1671
+ cursor = span.end;
1672
+ }
1673
+ return output + text.slice(cursor);
1645
1674
  }
1646
1675
  function redactValue(value) {
1647
1676
  if (Array.isArray(value)) return value.map(redactValue);
@@ -1665,6 +1694,31 @@ import ts5 from "typescript";
1665
1694
  import fs7 from "fs/promises";
1666
1695
  import path8 from "path";
1667
1696
  import ts4 from "typescript";
1697
+
1698
+ // src/utils/001-placeholders.ts
1699
+ function scanPlaceholders(value) {
1700
+ const input = value ?? "";
1701
+ const spans = [];
1702
+ let cursor = 0;
1703
+ while (cursor < input.length) {
1704
+ const start = input.indexOf("${", cursor);
1705
+ if (start < 0) break;
1706
+ const closingBrace = input.indexOf("}", start + 2);
1707
+ if (closingBrace < 0) break;
1708
+ spans.push({
1709
+ start,
1710
+ end: closingBrace + 1,
1711
+ key: input.slice(start + 2, closingBrace)
1712
+ });
1713
+ cursor = closingBrace + 1;
1714
+ }
1715
+ return spans;
1716
+ }
1717
+ function extractPlaceholderKeys(value) {
1718
+ return scanPlaceholders(value).map((span) => span.key.trim()).filter(Boolean);
1719
+ }
1720
+
1721
+ // src/parsers/service-binding-parser-helpers.ts
1668
1722
  function lineOf3(sf, node) {
1669
1723
  return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
1670
1724
  }
@@ -1675,7 +1729,7 @@ function stringValue2(node) {
1675
1729
  return node.getText();
1676
1730
  }
1677
1731
  function placeholders(value) {
1678
- return [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
1732
+ return extractPlaceholderKeys(value);
1679
1733
  }
1680
1734
  function connectFactFromCall(call) {
1681
1735
  const expr = call.expression;
@@ -3691,13 +3745,15 @@ function literalText(expr) {
3691
3745
  if (isStringLike(expr)) return expr.text;
3692
3746
  return void 0;
3693
3747
  }
3694
- function objectPropertyText(object, key) {
3695
- const prop = object.properties.find(
3696
- (property) => ts10.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts10.isShorthandPropertyAssignment(property) && property.name.text === key
3697
- );
3698
- if (!prop) return void 0;
3699
- return ts10.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
3700
- }
3748
+ var CDS_LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
3749
+ "bootstrap",
3750
+ "loaded",
3751
+ "connect",
3752
+ "serving",
3753
+ "served",
3754
+ "listening",
3755
+ "shutdown"
3756
+ ]);
3701
3757
  function objectPropertyIsShorthand(object, key) {
3702
3758
  return object.properties.some((property) => ts10.isShorthandPropertyAssignment(property) && property.name.text === key);
3703
3759
  }
@@ -3980,7 +4036,10 @@ function classifyOutboundCallsInSource(source, filePath) {
3980
4036
  if (objectArg && ts10.isObjectLiteralExpression(objectArg)) {
3981
4037
  const receiver = receiverName(expr.expression);
3982
4038
  const queryExpression = propertyInitializer(objectArg, "query");
3983
- const method = stripQuotes(resolveExpression(propertyInitializer(objectArg, "method"), node, "literal").value ?? objectPropertyText(objectArg, "method") ?? "POST");
4039
+ const methodExpression = propertyInitializer(objectArg, "method");
4040
+ const methodResolution = resolveExpression(methodExpression, node, "literal");
4041
+ const method = stripQuotes(methodResolution.value ?? "POST");
4042
+ const dynamicMethodDefaulted = Boolean(methodExpression && methodResolution.value === void 0);
3984
4043
  const pathExpr = propertyInitializer(objectArg, "path") ?? propertyInitializer(objectArg, "event");
3985
4044
  const pathAnalysis = analyzeOperationPath(pathExpr, node, method);
3986
4045
  const op = pathExpr ? operationPathExpression(pathAnalysis) ?? pathExpr.getText(source) : void 0;
@@ -3992,7 +4051,7 @@ function classifyOutboundCallsInSource(source, filePath) {
3992
4051
  const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
3993
4052
  const queryEntity = queryExpression ? queryEntityFromAst(queryExpression, initializers) : isODataQueryRead ? intent.entitySegment : void 0;
3994
4053
  const unresolvedReason = queryExpression ? queryEntity ? void 0 : queryWarning(queryExpression.getText(source)) : pathExpr ? pathUnresolvedReason(pathAnalysis) : void 0;
3995
- 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 : void 0, rawPathExpression: pathAnalysis.rawExpression, literalPathSource: literalPathSource(pathAnalysis), odataPathIntent: operationPathExpr ? intent : void 0, pathAnalysis, staticPathCandidates: legacyPathCandidates(pathAnalysis), parserWarning: unresolvedReason });
4054
+ 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 : void 0, rawPathExpression: pathAnalysis.rawExpression, literalPathSource: literalPathSource(pathAnalysis), odataPathIntent: operationPathExpr ? intent : void 0, pathAnalysis, staticPathCandidates: legacyPathCandidates(pathAnalysis), parserWarning: unresolvedReason, ...dynamicMethodDefaulted ? { dynamicMethodDefaulted: true } : {} });
3996
4055
  } else {
3997
4056
  const receiver = receiverName(expr.expression);
3998
4057
  const rootReceiver = rootReceiverName(expr.expression);
@@ -4034,7 +4093,10 @@ function classifyOutboundCallsInSource(source, filePath) {
4034
4093
  const rootReceiver = rootReceiverName(expr.expression);
4035
4094
  if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
4036
4095
  const eventName = literalText(node.arguments[0]);
4037
- 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" });
4096
+ const effectiveReceiver = rootReceiver ?? receiver;
4097
+ const lifecycleHook = effectiveReceiver === "cds" && CDS_LIFECYCLE_EVENTS.has(eventName ?? "");
4098
+ const errorHook = expr.name.text === "on" && eventName === "error";
4099
+ 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" });
4038
4100
  }
4039
4101
  } else {
4040
4102
  const external = externalHttpEvidence(node, source);
@@ -4144,12 +4206,11 @@ function serviceOperationCall(node, aliases) {
4144
4206
  }
4145
4207
 
4146
4208
  // src/linker/dynamic-edge-resolver.ts
4147
- var PLACEHOLDER = /\$\{([^}]*)\}/g;
4148
4209
  function applyVariables(template, vars) {
4149
4210
  return substituteVariables(template, vars).effective;
4150
4211
  }
4151
4212
  function extractPlaceholders(template) {
4152
- return [...(template ?? "").matchAll(PLACEHOLDER)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
4213
+ return extractPlaceholderKeys(template);
4153
4214
  }
4154
4215
  function matchRuntimeTemplate(template, concrete) {
4155
4216
  if (!template || !concrete) return void 0;
@@ -4172,10 +4233,15 @@ function substituteVariables(template, vars) {
4172
4233
  const placeholders3 = [...new Set(extractPlaceholders(template))];
4173
4234
  const supplied = placeholders3.filter((key) => Object.hasOwn(vars, key));
4174
4235
  const missing = placeholders3.filter((key) => !Object.hasOwn(vars, key));
4175
- const effective = template.replace(PLACEHOLDER, (_m, key) => {
4176
- const trimmed = key.trim();
4177
- return Object.hasOwn(vars, trimmed) ? vars[trimmed] ?? "" : `\${${trimmed}}`;
4178
- });
4236
+ let lastIndex = 0;
4237
+ let effective = "";
4238
+ for (const span of scanPlaceholders(template)) {
4239
+ const trimmed = span.key.trim();
4240
+ const replacement = Object.hasOwn(vars, trimmed) ? vars[trimmed] ?? "" : `\${${trimmed}}`;
4241
+ effective += template.slice(lastIndex, span.start) + replacement;
4242
+ lastIndex = span.end;
4243
+ }
4244
+ effective += template.slice(lastIndex);
4179
4245
  return {
4180
4246
  original: template,
4181
4247
  effective,
@@ -4188,10 +4254,10 @@ function substituteVariables(template, vars) {
4188
4254
  function runtimeTemplatePattern(template) {
4189
4255
  let pattern = "";
4190
4256
  let lastIndex = 0;
4191
- for (const match of template.matchAll(PLACEHOLDER)) {
4192
- pattern += escapeRegex(template.slice(lastIndex, match.index));
4257
+ for (const span of scanPlaceholders(template)) {
4258
+ pattern += escapeRegex(template.slice(lastIndex, span.start));
4193
4259
  pattern += "([^/]+?)";
4194
- lastIndex = (match.index ?? 0) + match[0].length;
4260
+ lastIndex = span.end;
4195
4261
  }
4196
4262
  return `${pattern}${escapeRegex(template.slice(lastIndex))}`;
4197
4263
  }
@@ -5179,7 +5245,7 @@ function operationMatches(candidate, operationPath) {
5179
5245
  return candidate.operationPath === names.path || candidate.operationPath === names.simplePath || candidate.operationName === names.name || candidate.operationName === names.simpleName;
5180
5246
  }
5181
5247
  function resolveOperation(db, signals, workspaceId) {
5182
- const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath].flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim())).filter(Boolean);
5248
+ const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath].flatMap(extractPlaceholderKeys);
5183
5249
  if (missing.length > 0)
5184
5250
  return {
5185
5251
  status: "dynamic",
@@ -5525,7 +5591,7 @@ function compactCandidateScores(candidates2) {
5525
5591
  }));
5526
5592
  }
5527
5593
  function placeholderKeys(values) {
5528
- const keys = values.flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim()).filter(Boolean));
5594
+ const keys = values.flatMap(extractPlaceholderKeys);
5529
5595
  return [...new Set(keys)].sort();
5530
5596
  }
5531
5597
  function parseJson(value) {
@@ -5803,7 +5869,7 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
5803
5869
  }
5804
5870
  if (isRemoteEntityCall) {
5805
5871
  const target = buildRemoteQueryTarget({ queryEntity: intent.entitySegment ?? call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
5806
- const entityKind = callType.replace("remote_entity_", "remote_entity_");
5872
+ const entityKind = callType;
5807
5873
  db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_ACCESSES_REMOTE_ENTITY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence, remoteEntityAccess: entityKind }), 0, generation);
5808
5874
  return { status: "terminal", callType };
5809
5875
  }
@@ -6507,17 +6573,18 @@ function proposalForIdentity(candidate, template, identity, sourceKind, npmPacka
6507
6573
  }];
6508
6574
  }
6509
6575
  function matchIdentityTemplate(template, identity, npmPackage) {
6510
- const matches2 = [...template.matchAll(/\$\{([^}]*)\}/g)];
6511
- if (matches2.length !== 1 || !matches2[0]?.[1]) return void 0;
6512
- const placeholder = matches2[0][0];
6513
- const sentinel = "dynamicplaceholdertoken";
6576
+ const matches2 = scanPlaceholders(template);
6577
+ const matchSpan = matches2[0];
6578
+ if (matches2.length !== 1 || !matchSpan?.key) return void 0;
6579
+ const placeholder = template.slice(matchSpan.start, matchSpan.end);
6580
+ const sentinel = "dynamic_placeholder_token";
6514
6581
  const normalizedTemplate = normalizeIdentity(template.replace(placeholder, sentinel));
6515
6582
  const [prefix, suffix, extra] = normalizedTemplate.split(sentinel);
6516
6583
  if (!prefix || !suffix || extra !== void 0) return void 0;
6517
6584
  const normalizedIdentity = normalizeIdentity(identity, npmPackage);
6518
6585
  const match = new RegExp(`^${escapeRegex2(prefix)}([a-z0-9]+)${escapeRegex2(suffix)}$`).exec(normalizedIdentity);
6519
6586
  if (!match?.[1]) return void 0;
6520
- return { key: matches2[0][1].trim(), value: match[1], normalizedIdentity };
6587
+ return { key: matchSpan.key.trim(), value: match[1], normalizedIdentity };
6521
6588
  }
6522
6589
  function competingIdentityKeys(proposals) {
6523
6590
  const owners = /* @__PURE__ */ new Map();
@@ -6622,7 +6689,7 @@ function routeImplementationEvidence(db, operationId) {
6622
6689
  }
6623
6690
  function normalizeIdentity(value, npmPackage = false) {
6624
6691
  const unscoped = npmPackage && /^@[^/]+\/[^/]+$/.test(value) ? value.slice(value.indexOf("/") + 1) : value;
6625
- return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
6692
+ return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|(?<!_)_+$/g, "");
6626
6693
  }
6627
6694
  function stringValue7(value) {
6628
6695
  return typeof value === "string" ? value : void 0;
@@ -8959,4 +9026,4 @@ export {
8959
9026
  selectorRepoAmbiguousDiagnostic,
8960
9027
  trace
8961
9028
  };
8962
- //# sourceMappingURL=chunk-BGD7UYJN.js.map
9029
+ //# sourceMappingURL=chunk-TBH33OYC.js.map