@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 +5 -0
- package/dist/{chunk-BGD7UYJN.js → chunk-TBH33OYC.js} +101 -34
- package/dist/chunk-TBH33OYC.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/linker/002-call-evidence.ts +2 -4
- package/src/linker/cross-repo-linker.ts +1 -1
- package/src/linker/dynamic-edge-resolver.ts +15 -12
- package/src/linker/service-resolver.ts +3 -1
- package/src/parsers/outbound-call-parser.ts +13 -10
- package/src/parsers/service-binding-parser-helpers.ts +2 -1
- package/src/trace/001-dynamic-identity.ts +8 -6
- package/src/utils/001-placeholders.ts +30 -0
- package/src/utils/redaction.ts +44 -4
- package/dist/chunk-BGD7UYJN.js.map +0 -1
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
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
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
|
|
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
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
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
|
|
4192
|
-
pattern += escapeRegex(template.slice(lastIndex,
|
|
4257
|
+
for (const span of scanPlaceholders(template)) {
|
|
4258
|
+
pattern += escapeRegex(template.slice(lastIndex, span.start));
|
|
4193
4259
|
pattern += "([^/]+?)";
|
|
4194
|
-
lastIndex =
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
const
|
|
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:
|
|
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-
|
|
9029
|
+
//# sourceMappingURL=chunk-TBH33OYC.js.map
|