@saptools/service-flow 0.1.52 → 0.1.53
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 +9 -12
- package/TECHNICAL-NOTE.md +11 -3
- package/dist/{chunk-PTLDSHRC.js → chunk-LFH7C46B.js} +2214 -1044
- package/dist/chunk-LFH7C46B.js.map +1 -0
- package/dist/cli.js +157 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-doctor-projection.ts +140 -0
- package/src/cli/doctor.ts +20 -3
- package/src/db/repositories.ts +10 -2
- package/src/linker/000-implementation-candidates.ts +641 -0
- package/src/linker/001-implementation-evidence-projection.ts +119 -0
- package/src/linker/002-call-evidence.ts +226 -0
- package/src/linker/cross-repo-linker.ts +27 -453
- package/src/linker/dynamic-edge-resolver.ts +35 -0
- package/src/linker/external-http-target.ts +24 -3
- package/src/linker/helper-package-linker.ts +18 -2
- package/src/linker/service-resolver.ts +45 -2
- package/src/output/doctor-output.ts +13 -4
- package/src/output/table-output.ts +4 -2
- package/src/trace/000-dynamic-target-types.ts +12 -0
- package/src/trace/001-dynamic-identity.ts +45 -17
- package/src/trace/003-dynamic-references.ts +236 -35
- package/src/trace/004-dynamic-candidate-sources.ts +155 -0
- package/src/trace/005-implementation-selection.ts +187 -0
- package/src/trace/006-contextual-projection.ts +30 -0
- package/src/trace/007-implementation-start-diagnostic.ts +61 -0
- package/src/trace/dynamic-targets.ts +205 -159
- package/src/trace/evidence.ts +35 -9
- package/src/trace/implementation-hints.ts +148 -8
- package/src/trace/selectors.ts +74 -9
- package/src/trace/trace-engine.ts +39 -52
- package/src/utils/000-bounded-projection.ts +161 -0
- package/dist/chunk-PTLDSHRC.js.map +0 -1
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
projectBounded,
|
|
3
|
+
stableProjectionValue,
|
|
4
|
+
type BoundedProjection,
|
|
5
|
+
} from '../utils/000-bounded-projection.js';
|
|
6
|
+
|
|
7
|
+
type Diagnostic = Record<string, unknown>;
|
|
8
|
+
|
|
9
|
+
const boundedDoctorArrayKeys = new Set([
|
|
10
|
+
'candidates',
|
|
11
|
+
'candidateScores',
|
|
12
|
+
'candidateFamilies',
|
|
13
|
+
'candidateEvidence',
|
|
14
|
+
'candidatePaths',
|
|
15
|
+
'candidateRawPaths',
|
|
16
|
+
'candidateNormalizedOperationPaths',
|
|
17
|
+
'normalizedCandidateOperations',
|
|
18
|
+
'candidateLiterals',
|
|
19
|
+
'bindingCandidates',
|
|
20
|
+
'bindingAlternatives',
|
|
21
|
+
'registrations',
|
|
22
|
+
'implementationHintSuggestions',
|
|
23
|
+
'selectableImplementationRepositories',
|
|
24
|
+
'matchedHints',
|
|
25
|
+
'candidateSuggestions',
|
|
26
|
+
'dynamicTargetCandidates',
|
|
27
|
+
'dynamicTargetCandidateSuggestions',
|
|
28
|
+
'rejectedCandidates',
|
|
29
|
+
'suggestedVarSets',
|
|
30
|
+
'copyableExamples',
|
|
31
|
+
'examples',
|
|
32
|
+
'expandedExamples',
|
|
33
|
+
'selectorSuggestions',
|
|
34
|
+
'serviceSuggestions',
|
|
35
|
+
'repositories',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export function boundDoctorDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
|
|
39
|
+
return diagnostics.map(boundDoctorDiagnostic);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function boundDoctorDiagnostic(diagnostic: Diagnostic): Diagnostic {
|
|
43
|
+
const bounded = boundDoctorValue(diagnostic);
|
|
44
|
+
return isDiagnostic(bounded) ? bounded : {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function boundDoctorValue(value: unknown): unknown {
|
|
48
|
+
if (Array.isArray(value)) return value.map(boundDoctorValue);
|
|
49
|
+
if (!isDiagnostic(value)) return value;
|
|
50
|
+
const input = value;
|
|
51
|
+
const output: Diagnostic = {};
|
|
52
|
+
for (const [key, child] of Object.entries(input)) {
|
|
53
|
+
if (!Array.isArray(child) || !boundedDoctorArrayKeys.has(key)) {
|
|
54
|
+
output[key] = boundDoctorValue(child);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const projection = projectBounded(child.map(boundDoctorValue), compareDiagnostic);
|
|
58
|
+
output[key] = projection.items;
|
|
59
|
+
addProjectionMetadata(output, input, key, projection);
|
|
60
|
+
}
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isDiagnostic(value: unknown): value is Diagnostic {
|
|
65
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function addProjectionMetadata(
|
|
69
|
+
output: Diagnostic,
|
|
70
|
+
input: Diagnostic,
|
|
71
|
+
key: string,
|
|
72
|
+
projection: BoundedProjection<unknown>,
|
|
73
|
+
): void {
|
|
74
|
+
const names = projectionNames(key);
|
|
75
|
+
const total = Math.max(
|
|
76
|
+
numericValue(input[names.total]),
|
|
77
|
+
projection.totalCount,
|
|
78
|
+
siblingCollectionCount(input, key),
|
|
79
|
+
);
|
|
80
|
+
output[names.total] = total;
|
|
81
|
+
output[names.shown] = projection.shownCount;
|
|
82
|
+
output[names.omitted] = Math.max(0, total - projection.shownCount);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function siblingCollectionCount(input: Diagnostic, key: string): number {
|
|
86
|
+
if (key !== 'examples' || !Array.isArray(input.expandedExamples)) return 0;
|
|
87
|
+
return input.expandedExamples.length;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function projectionNames(key: string): { total: string; shown: string; omitted: string } {
|
|
91
|
+
const stem = projectionStem(key);
|
|
92
|
+
return {
|
|
93
|
+
total: `${stem}Count`,
|
|
94
|
+
shown: `shown${upperFirst(stem)}Count`,
|
|
95
|
+
omitted: `omitted${upperFirst(stem)}Count`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function projectionStem(key: string): string {
|
|
100
|
+
const stems: Record<string, string> = {
|
|
101
|
+
candidates: 'candidate',
|
|
102
|
+
candidateScores: 'candidateScore',
|
|
103
|
+
candidateFamilies: 'candidateFamily',
|
|
104
|
+
candidateEvidence: 'candidateEvidence',
|
|
105
|
+
candidatePaths: 'candidatePath',
|
|
106
|
+
candidateRawPaths: 'candidateRawPath',
|
|
107
|
+
candidateNormalizedOperationPaths: 'candidateNormalizedOperationPath',
|
|
108
|
+
normalizedCandidateOperations: 'normalizedCandidateOperation',
|
|
109
|
+
candidateLiterals: 'candidateLiteral',
|
|
110
|
+
bindingCandidates: 'bindingCandidate',
|
|
111
|
+
bindingAlternatives: 'bindingAlternative',
|
|
112
|
+
registrations: 'registration',
|
|
113
|
+
implementationHintSuggestions: 'implementationHintSuggestion',
|
|
114
|
+
selectableImplementationRepositories: 'selectableImplementationRepository',
|
|
115
|
+
matchedHints: 'matchedHint',
|
|
116
|
+
candidateSuggestions: 'candidateSuggestion',
|
|
117
|
+
dynamicTargetCandidates: 'dynamicTargetCandidate',
|
|
118
|
+
dynamicTargetCandidateSuggestions: 'dynamicTargetCandidateSuggestion',
|
|
119
|
+
rejectedCandidates: 'rejectedCandidate',
|
|
120
|
+
suggestedVarSets: 'suggestedVarSet',
|
|
121
|
+
copyableExamples: 'copyableExample',
|
|
122
|
+
examples: 'example',
|
|
123
|
+
expandedExamples: 'expandedExample',
|
|
124
|
+
selectorSuggestions: 'selectorSuggestion',
|
|
125
|
+
serviceSuggestions: 'serviceSuggestion',
|
|
126
|
+
};
|
|
127
|
+
return stems[key] ?? 'repository';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function compareDiagnostic(left: unknown, right: unknown): number {
|
|
131
|
+
return stableProjectionValue(left).localeCompare(stableProjectionValue(right));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function numericValue(value: unknown): number {
|
|
135
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function upperFirst(value: string): string {
|
|
139
|
+
return value ? `${value[0]?.toUpperCase() ?? ''}${value.slice(1)}` : value;
|
|
140
|
+
}
|
package/src/cli/doctor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
2
|
import { classifyODataPathIntent, normalizeODataOperationInvocationPath } from '../linker/odata-path-normalizer.js';
|
|
3
3
|
import { implementationHintSuggestions } from '../trace/implementation-hints.js';
|
|
4
|
+
import { boundDoctorDiagnostics } from './001-doctor-projection.js';
|
|
4
5
|
import { ANALYZER_VERSION } from '../version.js';
|
|
5
6
|
|
|
6
7
|
type Diagnostic = Record<string, unknown>;
|
|
@@ -15,7 +16,7 @@ export function linkUpgradeWarnings(db: Db): Diagnostic[] {
|
|
|
15
16
|
|
|
16
17
|
export function doctorDiagnostics(db: Db, strict: boolean, options: DoctorOptions = {}): Diagnostic[] {
|
|
17
18
|
const diagnostics = db.prepare('SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id').all() as Diagnostic[];
|
|
18
|
-
return [
|
|
19
|
+
return boundDoctorDiagnostics([
|
|
19
20
|
...diagnostics,
|
|
20
21
|
...healthDiagnostics(db, strict),
|
|
21
22
|
...remoteTargetWithoutImplementationDiagnostics(db, strict, Boolean(options.detail)),
|
|
@@ -23,7 +24,7 @@ export function doctorDiagnostics(db: Db, strict: boolean, options: DoctorOption
|
|
|
23
24
|
...schemaDriftDiagnostics(db, strict),
|
|
24
25
|
...analyzerVersionDiagnostics(db, strict),
|
|
25
26
|
...parserQualityDiagnostics(db, strict, options),
|
|
26
|
-
];
|
|
27
|
+
]);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function healthDiagnostics(db: Db, strict: boolean): Diagnostic[] {
|
|
@@ -252,6 +253,10 @@ function addImplementationCategory(grouped: Map<string, Diagnostic & { count: nu
|
|
|
252
253
|
const current = grouped.get(key) ?? { category, baseOperation, reason, candidateFamily: family, count: 0, servicePaths: [], examples: [] };
|
|
253
254
|
const hintSuggestions = implementationSuggestions(evidence);
|
|
254
255
|
const candidates = asRecords(evidence.candidates);
|
|
256
|
+
const candidateCount = Math.max(
|
|
257
|
+
numericValue(evidence.candidateCount),
|
|
258
|
+
candidates.length,
|
|
259
|
+
);
|
|
255
260
|
current.count += 1;
|
|
256
261
|
current.servicePaths.push(String(row.servicePath ?? evidence.servicePath ?? ''));
|
|
257
262
|
current.examples.push({
|
|
@@ -259,7 +264,15 @@ function addImplementationCategory(grouped: Map<string, Diagnostic & { count: nu
|
|
|
259
264
|
operation: row.operationName,
|
|
260
265
|
status: row.status,
|
|
261
266
|
reason: row.unresolvedReason,
|
|
262
|
-
candidateCount
|
|
267
|
+
candidateCount,
|
|
268
|
+
shownCandidateCount: Math.min(
|
|
269
|
+
candidateCount,
|
|
270
|
+
numericValue(evidence.shownCandidateCount) || candidates.length,
|
|
271
|
+
),
|
|
272
|
+
omittedCandidateCount: Math.max(
|
|
273
|
+
0,
|
|
274
|
+
candidateCount - (numericValue(evidence.shownCandidateCount) || candidates.length),
|
|
275
|
+
),
|
|
263
276
|
candidateEvidence: candidates.slice(0, 3),
|
|
264
277
|
implementationHintSuggestions: hintSuggestions,
|
|
265
278
|
});
|
|
@@ -655,6 +668,10 @@ function candidateCount(value: unknown): number {
|
|
|
655
668
|
return Number(parseObject(value).candidateCount ?? 0);
|
|
656
669
|
}
|
|
657
670
|
|
|
671
|
+
function numericValue(value: unknown): number {
|
|
672
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
673
|
+
}
|
|
674
|
+
|
|
658
675
|
function parseObject(value: unknown): Diagnostic {
|
|
659
676
|
if (value && typeof value === 'object' && !Array.isArray(value)) return value as Diagnostic;
|
|
660
677
|
try {
|
package/src/db/repositories.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Db } from './connection.js';
|
|
2
|
+
import { projectBounded } from '../utils/000-bounded-projection.js';
|
|
2
3
|
import type {
|
|
3
4
|
CdsRequire,
|
|
4
5
|
CdsServiceFact,
|
|
@@ -596,12 +597,19 @@ function bindingEvidence(
|
|
|
596
597
|
candidates: BindingCandidate[],
|
|
597
598
|
selected?: BindingCandidate,
|
|
598
599
|
): Record<string, unknown> {
|
|
600
|
+
const projection = projectBounded(candidates, (left, right) =>
|
|
601
|
+
Number(right.id === selected?.id) - Number(left.id === selected?.id)
|
|
602
|
+
|| left.sourceFile.localeCompare(right.sourceFile)
|
|
603
|
+
|| left.sourceLine - right.sourceLine
|
|
604
|
+
|| left.id - right.id);
|
|
599
605
|
return {
|
|
600
606
|
status,
|
|
601
|
-
candidateCount:
|
|
607
|
+
candidateCount: projection.totalCount,
|
|
608
|
+
shownCandidateCount: projection.shownCount,
|
|
609
|
+
omittedCandidateCount: projection.omittedCount,
|
|
602
610
|
selectedBindingId: selected?.id,
|
|
603
611
|
sourceOrderRule: 'binding_source_line_must_not_follow_call',
|
|
604
|
-
candidates:
|
|
612
|
+
candidates: projection.items.map((candidate) => ({
|
|
605
613
|
bindingId: candidate.id,
|
|
606
614
|
symbolId: candidate.symbolId,
|
|
607
615
|
variableName: candidate.variableName,
|