@saptools/service-flow 0.1.49 → 0.1.50
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 +7 -0
- package/README.md +1 -1
- package/dist/{chunk-XOROZHW4.js → chunk-52OUS3MO.js} +303 -109
- package/dist/chunk-52OUS3MO.js.map +1 -0
- package/dist/cli.js +67 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +59 -1
- package/src/db/migrations.ts +4 -1
- package/src/db/repositories.ts +2 -1
- package/src/db/schema.ts +1 -1
- package/src/linker/cross-repo-linker.ts +102 -5
- package/src/linker/operation-decorator-normalizer.ts +3 -3
- package/src/parsers/decorator-parser.ts +128 -22
- package/src/trace/selectors.ts +36 -0
- package/src/trace/trace-engine.ts +1 -20
- package/src/types.ts +12 -0
- package/dist/chunk-XOROZHW4.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -14,8 +14,9 @@ import {
|
|
|
14
14
|
parseOutboundCalls,
|
|
15
15
|
parsePackageJson,
|
|
16
16
|
parseServiceBindings,
|
|
17
|
+
parseVars,
|
|
17
18
|
trace
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-52OUS3MO.js";
|
|
19
20
|
|
|
20
21
|
// src/cli.ts
|
|
21
22
|
import { Command } from "commander";
|
|
@@ -101,7 +102,7 @@ CREATE TABLE IF NOT EXISTS cds_services (id INTEGER PRIMARY KEY, repo_id INTEGER
|
|
|
101
102
|
CREATE TABLE IF NOT EXISTS cds_operations (id INTEGER PRIMARY KEY, service_id INTEGER NOT NULL, operation_type TEXT NOT NULL, operation_name TEXT NOT NULL, operation_path TEXT NOT NULL, params_json TEXT NOT NULL, return_type TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, provenance TEXT NOT NULL DEFAULT 'direct', base_operation_id INTEGER, FOREIGN KEY(service_id) REFERENCES cds_services(id) ON DELETE CASCADE, FOREIGN KEY(base_operation_id) REFERENCES cds_operations(id) ON DELETE SET NULL);
|
|
102
103
|
CREATE TABLE IF NOT EXISTS symbols (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, file_id INTEGER, kind TEXT NOT NULL, name TEXT NOT NULL, qualified_name TEXT NOT NULL, exported INTEGER NOT NULL, start_line INTEGER NOT NULL, end_line INTEGER NOT NULL, start_offset INTEGER, end_offset INTEGER, source_file TEXT, exported_name TEXT, evidence_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE CASCADE);
|
|
103
104
|
CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, class_name TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
104
|
-
CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
|
|
105
|
+
CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, decorator_resolution_json TEXT NOT NULL DEFAULT '{}', source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
|
|
105
106
|
CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, class_name TEXT, import_source TEXT, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE SET NULL);
|
|
106
107
|
CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
107
108
|
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
@@ -120,8 +121,11 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, rep
|
|
|
120
121
|
`;
|
|
121
122
|
|
|
122
123
|
// src/db/migrations.ts
|
|
123
|
-
var CURRENT_SCHEMA_VERSION =
|
|
124
|
+
var CURRENT_SCHEMA_VERSION = 10;
|
|
124
125
|
var columns = {
|
|
126
|
+
handler_methods: [
|
|
127
|
+
{ name: "decorator_resolution_json", ddl: "ALTER TABLE handler_methods ADD COLUMN decorator_resolution_json TEXT NOT NULL DEFAULT '{}'" }
|
|
128
|
+
],
|
|
125
129
|
service_bindings: [
|
|
126
130
|
{ name: "helper_chain_json", ddl: "ALTER TABLE service_bindings ADD COLUMN helper_chain_json TEXT" },
|
|
127
131
|
{ name: "alias_expr", ddl: "ALTER TABLE service_bindings ADD COLUMN alias_expr TEXT" }
|
|
@@ -210,7 +214,7 @@ function migrate(db) {
|
|
|
210
214
|
// package.json
|
|
211
215
|
var package_default = {
|
|
212
216
|
name: "@saptools/service-flow",
|
|
213
|
-
version: "0.1.
|
|
217
|
+
version: "0.1.50",
|
|
214
218
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
215
219
|
type: "module",
|
|
216
220
|
publishConfig: {
|
|
@@ -518,7 +522,7 @@ function insertHandler(db, repoId, h) {
|
|
|
518
522
|
).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
|
|
519
523
|
);
|
|
520
524
|
const stmt = db.prepare(
|
|
521
|
-
"INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,source_file,source_line) VALUES(
|
|
525
|
+
"INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
|
|
522
526
|
);
|
|
523
527
|
for (const m of h.methods)
|
|
524
528
|
stmt.run(
|
|
@@ -527,6 +531,7 @@ function insertHandler(db, repoId, h) {
|
|
|
527
531
|
m.decoratorKind,
|
|
528
532
|
m.decoratorValue,
|
|
529
533
|
m.decoratorRawExpression,
|
|
534
|
+
JSON.stringify(m.decoratorResolution),
|
|
530
535
|
m.sourceFile,
|
|
531
536
|
m.sourceLine
|
|
532
537
|
);
|
|
@@ -1569,6 +1574,8 @@ function parserQualityDiagnostics(db, strict, options) {
|
|
|
1569
1574
|
classInstanceNoiseQuality(db),
|
|
1570
1575
|
contextualBindingPropagationQuality(db),
|
|
1571
1576
|
serviceBindingQuality(db, Boolean(options.detail)),
|
|
1577
|
+
decoratorResolutionQuality(db),
|
|
1578
|
+
handlerRegistrationPairingQuality(db),
|
|
1572
1579
|
nestedThisReceiverQuality(db),
|
|
1573
1580
|
wrapperPathPropagationQuality(db),
|
|
1574
1581
|
remoteQueryTargetQuality(db),
|
|
@@ -1659,9 +1666,18 @@ function addImplementationCategory(grouped, row) {
|
|
|
1659
1666
|
const key = [category, baseOperation, reason, family].join("\0");
|
|
1660
1667
|
const current = grouped.get(key) ?? { category, baseOperation, reason, candidateFamily: family, count: 0, servicePaths: [], examples: [] };
|
|
1661
1668
|
const hintSuggestions = implementationSuggestions(evidence);
|
|
1669
|
+
const candidates = asRecords(evidence.candidates);
|
|
1662
1670
|
current.count += 1;
|
|
1663
1671
|
current.servicePaths.push(String(row.servicePath ?? evidence.servicePath ?? ""));
|
|
1664
|
-
current.examples.push({
|
|
1672
|
+
current.examples.push({
|
|
1673
|
+
servicePath: row.servicePath,
|
|
1674
|
+
operation: row.operationName,
|
|
1675
|
+
status: row.status,
|
|
1676
|
+
reason: row.unresolvedReason,
|
|
1677
|
+
candidateCount: candidates.length,
|
|
1678
|
+
candidateEvidence: candidates.slice(0, 3),
|
|
1679
|
+
implementationHintSuggestions: hintSuggestions
|
|
1680
|
+
});
|
|
1665
1681
|
grouped.set(key, current);
|
|
1666
1682
|
}
|
|
1667
1683
|
function implementationSuggestions(evidence) {
|
|
@@ -1840,6 +1856,51 @@ function bindingCategoryAction(category) {
|
|
|
1840
1856
|
return "Use named or destructured parameters on an indexed helper symbol.";
|
|
1841
1857
|
return "Add a direct CAP client binding or statically provable helper-return binding.";
|
|
1842
1858
|
}
|
|
1859
|
+
function decoratorResolutionQuality(db) {
|
|
1860
|
+
const aggregate = db.prepare(`SELECT
|
|
1861
|
+
SUM(CASE WHEN json_extract(decorator_resolution_json,'$.resolutionKind')
|
|
1862
|
+
IN ('const_identifier','enum_member','const_object_property','generated_constant_name') THEN 1 ELSE 0 END) resolvedFromConstants,
|
|
1863
|
+
SUM(CASE WHEN json_extract(decorator_resolution_json,'$.resolutionKind')
|
|
1864
|
+
='unresolved' THEN 1 ELSE 0 END) unresolvedExpressions
|
|
1865
|
+
FROM handler_methods`).get();
|
|
1866
|
+
const unresolved = Number(aggregate.unresolvedExpressions ?? 0);
|
|
1867
|
+
const examples = db.prepare(`SELECT hm.method_name methodName,
|
|
1868
|
+
hm.decorator_raw_expression rawExpression,
|
|
1869
|
+
json_extract(hm.decorator_resolution_json,'$.unresolvedReason') unresolvedReason,
|
|
1870
|
+
hm.source_file sourceFile,hm.source_line sourceLine
|
|
1871
|
+
FROM handler_methods hm
|
|
1872
|
+
WHERE json_extract(hm.decorator_resolution_json,'$.resolutionKind')='unresolved'
|
|
1873
|
+
ORDER BY hm.source_file,hm.source_line LIMIT 5`).all();
|
|
1874
|
+
return {
|
|
1875
|
+
severity: unresolved > 0 ? "warning" : "info",
|
|
1876
|
+
code: "strict_decorator_resolution_quality",
|
|
1877
|
+
message: "Handler decorator string-resolution aggregate",
|
|
1878
|
+
resolvedFromConstants: Number(aggregate.resolvedFromConstants ?? 0),
|
|
1879
|
+
unresolvedExpressions: unresolved,
|
|
1880
|
+
unresolvedExamples: examples
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
function handlerRegistrationPairingQuality(db) {
|
|
1884
|
+
const mismatch = db.prepare(`SELECT COUNT(*) count
|
|
1885
|
+
FROM handler_registrations hr
|
|
1886
|
+
JOIN handler_classes hc ON hc.id=hr.handler_class_id
|
|
1887
|
+
WHERE hr.handler_class_id IS NOT NULL
|
|
1888
|
+
AND (hc.repo_id<>hr.repo_id OR hc.class_name<>hr.class_name)`).get();
|
|
1889
|
+
const prevented = db.prepare(`SELECT COUNT(*) count
|
|
1890
|
+
FROM handler_registrations hr
|
|
1891
|
+
JOIN handler_classes exactClass ON exactClass.id=hr.handler_class_id
|
|
1892
|
+
JOIN handler_classes otherClass ON otherClass.class_name=hr.class_name
|
|
1893
|
+
AND otherClass.repo_id<>hr.repo_id
|
|
1894
|
+
WHERE hr.handler_class_id IS NOT NULL AND hr.import_source IS NOT NULL`).get();
|
|
1895
|
+
const mismatched = Number(mismatch.count ?? 0);
|
|
1896
|
+
return {
|
|
1897
|
+
severity: mismatched > 0 ? "error" : "info",
|
|
1898
|
+
code: "strict_handler_registration_pairing_quality",
|
|
1899
|
+
message: "Handler registration and class ownership aggregate",
|
|
1900
|
+
mismatchedExactRegistrations: mismatched,
|
|
1901
|
+
preventedSyntheticCrossRepositoryPairs: Number(prevented.count ?? 0)
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1843
1904
|
function wrapperPathPropagationQuality(db) {
|
|
1844
1905
|
const examples = db.prepare("SELECT source_file sourceFile,source_line sourceLine,json_extract(evidence_json,'$.receiver') receiverName,json_extract(evidence_json,'$.operationPathExpression') pathIdentifier FROM outbound_calls WHERE call_type='remote_action' AND unresolved_reason='dynamic_operation_path_identifier' ORDER BY source_file,source_line LIMIT 5").all();
|
|
1845
1906
|
return { severity: examples.length > 0 ? "warning" : "info", code: "strict_wrapper_path_propagation_quality", message: "Dynamic path sends where send({ path }) used a path identifier", dynamicPathIdentifierCalls: examples.length, examples };
|
|
@@ -1964,16 +2025,6 @@ function asRecords(value) {
|
|
|
1964
2025
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
1965
2026
|
}
|
|
1966
2027
|
|
|
1967
|
-
// src/trace/selectors.ts
|
|
1968
|
-
function parseVars(values) {
|
|
1969
|
-
const out = {};
|
|
1970
|
-
for (const value of values ?? []) {
|
|
1971
|
-
const [key, ...rest] = value.split("=");
|
|
1972
|
-
if (key && rest.length > 0) out[key] = rest.join("=");
|
|
1973
|
-
}
|
|
1974
|
-
return out;
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
2028
|
// src/output/table-output.ts
|
|
1978
2029
|
function location(evidence) {
|
|
1979
2030
|
const file = evidence.file ?? evidence.sourceFile ?? evidence.handlerSourceFile ?? evidence.operationSourceFile ?? evidence.registrationSourceFile;
|