@saptools/service-flow 0.1.20 → 0.1.21
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 +6 -0
- package/README.md +10 -2
- package/TECHNICAL-NOTE.md +7 -0
- package/dist/{chunk-EKLZJJW7.js → chunk-TSMDIKML.js} +83 -26
- package/dist/chunk-TSMDIKML.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/dist/chunk-EKLZJJW7.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.21
|
|
4
|
+
|
|
5
|
+
- Propagate helper-returned connected clients from function declarations, arrow-function variables, function-expression variables, named export lists, and aliased exports into caller destructuring and transaction aliases.
|
|
6
|
+
- Preserve conservative object-return analysis by binding only returned properties backed by local `cds.connect.to(...)` client variables, while ignoring unrelated metadata fields.
|
|
7
|
+
- Document contextual implementation selection evidence and strict doctor checks for operation-path-only remote actions.
|
|
8
|
+
|
|
3
9
|
## 0.1.20
|
|
4
10
|
|
|
5
11
|
- Preserve service-binding evidence when helpers return connected clients inside object properties and callers destructure or create simple transaction aliases.
|
package/README.md
CHANGED
|
@@ -227,7 +227,15 @@ service-flow list calls --workspace /path/to/workspace --repo facade-service --o
|
|
|
227
227
|
- Service bindings are matched to outbound calls by repository, source file, and
|
|
228
228
|
variable name to avoid false cross-file matches. If a helper-returned client is
|
|
229
229
|
not linked, export the helper from a relative import target and ensure it returns
|
|
230
|
-
`cds.connect.to(...)` directly or
|
|
230
|
+
`cds.connect.to(...)` directly or returns an object property backed by a local
|
|
231
|
+
connected-client variable. Supported helper shapes include function declarations,
|
|
232
|
+
arrow-function variables, function-expression variables, named export lists, and
|
|
233
|
+
aliased exports such as `export { connectCatalog as createCatalogClient }`.
|
|
234
|
+
Shorthand returns like `return { client }` and explicit returns like
|
|
235
|
+
`return { serviceClient: client }` are followed only when the returned value is
|
|
236
|
+
a concrete client. Trace evidence includes the caller variable, returned
|
|
237
|
+
property, imported helper, source file, exported symbol, placeholders, and
|
|
238
|
+
transaction alias steps.
|
|
231
239
|
- `SELECT.one.from(Entity)`, `SELECT.from(Entity)`, `INSERT.into(Entity)`,
|
|
232
240
|
`UPDATE(Entity)`, and `DELETE.from(Entity)` are indexed as local database
|
|
233
241
|
query entities when statically knowable.
|
|
@@ -410,7 +418,7 @@ Run `service-flow index` after source, CDS, package metadata, or helper-package
|
|
|
410
418
|
<details>
|
|
411
419
|
<summary><b>Why is an expected call unresolved?</b></summary>
|
|
412
420
|
|
|
413
|
-
Check `service-flow doctor`, then inspect the facts with `service-flow list services`, `service-flow list operations`, and `service-flow list calls`. Dynamic destinations may need `--var key=value`, and
|
|
421
|
+
Check `service-flow doctor`, then inspect the facts with `service-flow list services`, `service-flow list operations`, and `service-flow list calls`. Dynamic destinations may need `--var key=value`; the key is the full trimmed expression inside `${...}` and is matched literally without JavaScript evaluation. Operation-path-only ambiguous remote actions usually mean the call had no service binding id; inspect `list calls`, `inspect operation`, and `doctor --strict` to determine whether helper-return propagation or wrapper support is missing. Contextual implementation selection only continues into a handler when static evidence such as caller repository, resolved service path, destination/alias expression, dependency edges, registration package, and local service ownership makes exactly one candidate stronger; ties remain ambiguous with reasons.
|
|
414
422
|
Default doctor output is intended to focus on actionable indexing or trace-impacting issues; use `--strict` when you need exhaustive model-shape diagnostics for entity-only or extension-heavy CDS models. Strict mode also reports normalized OData invocation ambiguity and remote-action target quality, including whether unresolved unknown/dynamic paths are semantic instead of numeric call ids.
|
|
415
423
|
|
|
416
424
|
</details>
|
package/TECHNICAL-NOTE.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Service Flow Resolution Notes
|
|
2
2
|
|
|
3
|
+
## 0.1.21 helper-return propagation and contextual trace notes
|
|
4
|
+
|
|
5
|
+
- Helper-return binding analysis uses the same returned-object scanner for `function` declarations, `async function` declarations, arrow-function variables, async arrow-function variables, and function-expression variables. Named export lists and aliases are resolved, so `export { connectCatalog as createCatalogClient }` can be destructured by callers without losing evidence.
|
|
6
|
+
- Returned object properties are bound only when their value is a local variable initialized from `cds.connect.to(...)`. Shorthand properties and explicit property assignments are supported; unrelated strings, codes, and metadata fields are ignored. Destructuring renames and `.tx()` aliases append helper-chain evidence instead of replacing the original binding evidence.
|
|
7
|
+
- Trace-time contextual implementation selection may use caller repository, runtime-resolved service path, destination or alias expression, package dependency evidence, handler and registration packages, and local service ownership to continue from an ambiguous operation edge into one handler. If candidates tie or no static signal is strong enough, trace keeps the ambiguous edge and reports the tie or unresolved reason.
|
|
8
|
+
- `doctor --strict` is the intended place for broad regression aggregates, including helper-return coverage, contextual implementation ambiguity, and remote actions that have an operation path but no service binding id.
|
|
9
|
+
|
|
3
10
|
## 0.1.20 helper object clients, expression placeholders, and remote-action target notes
|
|
4
11
|
|
|
5
12
|
- Helper-return binding analysis now follows concrete `cds.connect.to(...)` clients returned through object shorthand or explicit properties. Callers that destructure those properties, rename them, or assign a simple `.tx()` transaction alias keep helper-chain evidence including caller variable, returned property, helper source, destination expression, service-path expression, and placeholders. Arbitrary object returns are ignored.
|
|
@@ -900,6 +900,8 @@ function collectReturnedObjectBindings(fn) {
|
|
|
900
900
|
const bindings = /* @__PURE__ */ new Map();
|
|
901
901
|
const returns = /* @__PURE__ */ new Map();
|
|
902
902
|
function visit(node) {
|
|
903
|
+
if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
|
|
904
|
+
return;
|
|
903
905
|
if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
|
|
904
906
|
const fact = findConnectInExpression(node.initializer);
|
|
905
907
|
if (fact) bindings.set(node.name.text, fact);
|
|
@@ -923,6 +925,35 @@ function collectReturnedObjectBindings(fn) {
|
|
|
923
925
|
}
|
|
924
926
|
return out;
|
|
925
927
|
}
|
|
928
|
+
function functionLikeInitializer(expr) {
|
|
929
|
+
if (!expr) return void 0;
|
|
930
|
+
if (ts5.isArrowFunction(expr) || ts5.isFunctionExpression(expr)) return expr;
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
function directReturnConnectFact(fn) {
|
|
934
|
+
const localBindings = /* @__PURE__ */ new Map();
|
|
935
|
+
let returned;
|
|
936
|
+
function visit(node) {
|
|
937
|
+
if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
|
|
938
|
+
return;
|
|
939
|
+
if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
|
|
940
|
+
const fact = findConnectInExpression(node.initializer);
|
|
941
|
+
if (fact) localBindings.set(node.name.text, fact);
|
|
942
|
+
}
|
|
943
|
+
if (!returned && ts5.isReturnStatement(node) && node.expression)
|
|
944
|
+
returned = node.expression;
|
|
945
|
+
if (!returned) ts5.forEachChild(node, visit);
|
|
946
|
+
}
|
|
947
|
+
visit(fn);
|
|
948
|
+
if (!returned) return void 0;
|
|
949
|
+
if (ts5.isIdentifier(returned)) return localBindings.get(returned.text);
|
|
950
|
+
return findConnectInExpression(returned);
|
|
951
|
+
}
|
|
952
|
+
function directConnectFactFromFunctionLike(fn) {
|
|
953
|
+
if (ts5.isArrowFunction(fn) && fn.body && !ts5.isBlock(fn.body))
|
|
954
|
+
return findConnectInExpression(fn.body);
|
|
955
|
+
return directReturnConnectFact(fn);
|
|
956
|
+
}
|
|
926
957
|
function exportedLocalNames(sf) {
|
|
927
958
|
const exports = /* @__PURE__ */ new Map();
|
|
928
959
|
for (const stmt of sf.statements) {
|
|
@@ -951,12 +982,7 @@ async function helperBindings(repoPath, filePath) {
|
|
|
951
982
|
const factsByLocal = /* @__PURE__ */ new Map();
|
|
952
983
|
for (const stmt of sf.statements) {
|
|
953
984
|
if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
|
|
954
|
-
|
|
955
|
-
stmt.forEachChild(function visit(node) {
|
|
956
|
-
if (!fact && ts5.isReturnStatement(node) && node.expression)
|
|
957
|
-
fact = findConnectInExpression(node.expression);
|
|
958
|
-
if (!fact) ts5.forEachChild(node, visit);
|
|
959
|
-
});
|
|
985
|
+
const fact = directConnectFactFromFunctionLike(stmt);
|
|
960
986
|
if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf4(sf, stmt) });
|
|
961
987
|
for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(stmt))
|
|
962
988
|
factsByLocal.set(`${stmt.name.text}#${returnedProperty}`, { ...objectFact, returnedProperty, sourceLine: lineOf4(sf, stmt) });
|
|
@@ -964,6 +990,22 @@ async function helperBindings(repoPath, filePath) {
|
|
|
964
990
|
if (ts5.isVariableStatement(stmt))
|
|
965
991
|
for (const decl of stmt.declarationList.declarations) {
|
|
966
992
|
if (!ts5.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
993
|
+
const helper = functionLikeInitializer(decl.initializer);
|
|
994
|
+
if (helper) {
|
|
995
|
+
const directReturn = directConnectFactFromFunctionLike(helper);
|
|
996
|
+
if (directReturn)
|
|
997
|
+
factsByLocal.set(decl.name.text, {
|
|
998
|
+
...directReturn,
|
|
999
|
+
sourceLine: lineOf4(sourceFileAst, decl)
|
|
1000
|
+
});
|
|
1001
|
+
for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(helper))
|
|
1002
|
+
factsByLocal.set(`${decl.name.text}#${returnedProperty}`, {
|
|
1003
|
+
...objectFact,
|
|
1004
|
+
returnedProperty,
|
|
1005
|
+
sourceLine: lineOf4(sourceFileAst, decl)
|
|
1006
|
+
});
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
967
1009
|
const fact = findConnectInExpression(decl.initializer);
|
|
968
1010
|
if (fact)
|
|
969
1011
|
factsByLocal.set(decl.name.text, {
|
|
@@ -1002,22 +1044,36 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1002
1044
|
const classHelpers = collectClassHelpers(sourceFileAst);
|
|
1003
1045
|
const localObjectHelpers = /* @__PURE__ */ new Map();
|
|
1004
1046
|
for (const stmt of sourceFileAst.statements) {
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1047
|
+
if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
|
|
1048
|
+
const rows2 = [];
|
|
1049
|
+
for (const [returnedProperty, fact] of collectReturnedObjectBindings(stmt))
|
|
1050
|
+
rows2.push({ ...fact, exportedName: stmt.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
|
|
1051
|
+
if (rows2.length > 0) localObjectHelpers.set(stmt.name.text, rows2);
|
|
1052
|
+
}
|
|
1053
|
+
if (ts5.isVariableStatement(stmt)) {
|
|
1054
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
1055
|
+
if (!ts5.isIdentifier(decl.name)) continue;
|
|
1056
|
+
const helper = functionLikeInitializer(decl.initializer);
|
|
1057
|
+
if (!helper) continue;
|
|
1058
|
+
const rows2 = [];
|
|
1059
|
+
for (const [returnedProperty, fact] of collectReturnedObjectBindings(helper))
|
|
1060
|
+
rows2.push({ ...fact, exportedName: decl.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, decl) });
|
|
1061
|
+
if (rows2.length > 0) localObjectHelpers.set(decl.name.text, rows2);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1010
1064
|
}
|
|
1011
|
-
async function
|
|
1065
|
+
async function importedHelpers(localName) {
|
|
1012
1066
|
const imp = imports.find((i) => i.localName === localName && i.sourceFile);
|
|
1013
|
-
if (!imp?.sourceFile) return
|
|
1067
|
+
if (!imp?.sourceFile) return [];
|
|
1014
1068
|
if (!helperCache.has(imp.sourceFile))
|
|
1015
1069
|
helperCache.set(
|
|
1016
1070
|
imp.sourceFile,
|
|
1017
1071
|
await helperBindings(repoPath, imp.sourceFile)
|
|
1018
1072
|
);
|
|
1019
|
-
|
|
1020
|
-
|
|
1073
|
+
return (helperCache.get(imp.sourceFile) ?? []).filter((h) => h.exportedName === imp.exportedName).map((helper) => ({ imp, helper }));
|
|
1074
|
+
}
|
|
1075
|
+
async function importedHelper(localName) {
|
|
1076
|
+
return (await importedHelpers(localName)).find((row) => !row.helper.returnedProperty);
|
|
1021
1077
|
}
|
|
1022
1078
|
async function recordVariable(decl) {
|
|
1023
1079
|
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
@@ -1057,23 +1113,24 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1057
1113
|
});
|
|
1058
1114
|
}
|
|
1059
1115
|
}
|
|
1060
|
-
async function
|
|
1061
|
-
if (!ts5.isIdentifier(call.expression)) return
|
|
1062
|
-
const local = localObjectHelpers.get(call.expression.text)
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
return resolved ? { helper: resolved.helper, imp: resolved.imp } : void 0;
|
|
1116
|
+
async function helpersForCall(call) {
|
|
1117
|
+
if (!ts5.isIdentifier(call.expression)) return [];
|
|
1118
|
+
const local = localObjectHelpers.get(call.expression.text) ?? [];
|
|
1119
|
+
const imported = await importedHelpers(call.expression.text);
|
|
1120
|
+
return [...local.map((helper) => ({ helper })), ...imported];
|
|
1066
1121
|
}
|
|
1067
1122
|
async function recordDestructuredHelper(decl) {
|
|
1068
1123
|
if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
1069
1124
|
const call = unwrapCall(decl.initializer);
|
|
1070
1125
|
if (!call) return;
|
|
1071
|
-
const
|
|
1072
|
-
if (
|
|
1126
|
+
const helpers = await helpersForCall(call);
|
|
1127
|
+
if (helpers.length === 0) return;
|
|
1073
1128
|
for (const el of decl.name.elements) {
|
|
1074
1129
|
if (!ts5.isIdentifier(el.name)) continue;
|
|
1075
1130
|
const propertyName = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1076
|
-
|
|
1131
|
+
const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
|
|
1132
|
+
if (matches.length !== 1) continue;
|
|
1133
|
+
const resolved = matches[0];
|
|
1077
1134
|
out.push({
|
|
1078
1135
|
variableName: el.name.text,
|
|
1079
1136
|
alias: resolved.helper.alias,
|
|
@@ -1084,7 +1141,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1084
1141
|
placeholders: resolved.helper.placeholders,
|
|
1085
1142
|
sourceFile: normalizePath(filePath),
|
|
1086
1143
|
sourceLine: lineOf4(sourceFileAst, decl),
|
|
1087
|
-
helperChain: [{ callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName, importSource: resolved.imp?.sourceFile, helperSourceFile: resolved.helper.sourceFile, helperSourceLine: resolved.helper.sourceLine }]
|
|
1144
|
+
helperChain: [{ callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName, importSource: resolved.imp?.sourceFile, exportedSymbol: resolved.imp?.exportedName, helperSourceFile: resolved.helper.sourceFile, helperSourceLine: resolved.helper.sourceLine }]
|
|
1088
1145
|
});
|
|
1089
1146
|
}
|
|
1090
1147
|
}
|
|
@@ -2221,4 +2278,4 @@ export {
|
|
|
2221
2278
|
linkWorkspace,
|
|
2222
2279
|
trace
|
|
2223
2280
|
};
|
|
2224
|
-
//# sourceMappingURL=chunk-
|
|
2281
|
+
//# sourceMappingURL=chunk-TSMDIKML.js.map
|