@saptools/service-flow 0.1.51 → 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 +13 -0
- package/README.md +16 -14
- package/TECHNICAL-NOTE.md +18 -6
- package/dist/{chunk-YZJKE5UX.js → chunk-LFH7C46B.js} +4290 -1261
- package/dist/chunk-LFH7C46B.js.map +1 -0
- package/dist/cli.js +435 -515
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +45 -7
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/000-clean.ts +82 -0
- package/src/cli/001-doctor-projection.ts +140 -0
- package/src/cli/doctor.ts +20 -3
- package/src/cli.ts +75 -57
- package/src/db/connection.ts +1 -1
- package/src/db/migrations.ts +5 -3
- package/src/db/repositories.ts +130 -24
- package/src/db/schema.ts +7 -2
- package/src/indexer/repository-indexer.ts +57 -29
- package/src/indexer/workspace-indexer.ts +84 -6
- 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 -441
- 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 +33 -5
- package/src/parsers/cds-parser.ts +8 -2
- package/src/parsers/decorator-parser.ts +382 -48
- package/src/parsers/handler-registration-parser.ts +38 -21
- package/src/parsers/imported-wrapper-parser.ts +18 -5
- package/src/parsers/outbound-call-parser.ts +25 -8
- package/src/parsers/package-json-parser.ts +36 -11
- package/src/parsers/service-binding-parser-helpers.ts +8 -1
- package/src/parsers/service-binding-parser.ts +6 -3
- package/src/parsers/symbol-parser.ts +13 -3
- package/src/parsers/ts-project.ts +54 -0
- package/src/trace/000-dynamic-target-types.ts +96 -0
- package/src/trace/001-dynamic-identity.ts +308 -0
- package/src/trace/002-trace-diagnostics.ts +54 -0
- package/src/trace/003-dynamic-references.ts +283 -0
- 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 +582 -306
- package/src/trace/evidence.ts +331 -46
- package/src/trace/implementation-hints.ts +148 -8
- package/src/trace/selectors.ts +551 -3
- package/src/trace/trace-engine.ts +129 -135
- package/src/types.ts +27 -1
- package/src/utils/000-bounded-projection.ts +161 -0
- package/dist/chunk-YZJKE5UX.js.map +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export const DEFAULT_EVIDENCE_CANDIDATE_LIMIT = 5;
|
|
2
|
+
|
|
3
|
+
export interface BoundedProjection<T> {
|
|
4
|
+
totalCount: number;
|
|
5
|
+
shownCount: number;
|
|
6
|
+
omittedCount: number;
|
|
7
|
+
items: T[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function projectBounded<T>(
|
|
11
|
+
values: readonly T[],
|
|
12
|
+
compare: (left: T, right: T) => number,
|
|
13
|
+
limit = DEFAULT_EVIDENCE_CANDIDATE_LIMIT,
|
|
14
|
+
): BoundedProjection<T> {
|
|
15
|
+
const normalizedLimit = positiveLimit(limit);
|
|
16
|
+
const sorted = [...values].sort(compare);
|
|
17
|
+
const items = sorted.slice(0, normalizedLimit);
|
|
18
|
+
return {
|
|
19
|
+
totalCount: sorted.length,
|
|
20
|
+
shownCount: items.length,
|
|
21
|
+
omittedCount: Math.max(0, sorted.length - items.length),
|
|
22
|
+
items,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function positiveLimit(value: number | undefined): number {
|
|
27
|
+
return Number.isFinite(value) && Number(value) > 0
|
|
28
|
+
? Math.floor(Number(value))
|
|
29
|
+
: DEFAULT_EVIDENCE_CANDIDATE_LIMIT;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const candidateLikeCollections = new Set([
|
|
33
|
+
'candidates',
|
|
34
|
+
'candidateScores',
|
|
35
|
+
'candidateFamilies',
|
|
36
|
+
'candidateEvidence',
|
|
37
|
+
'candidatePaths',
|
|
38
|
+
'candidateRawPaths',
|
|
39
|
+
'candidateNormalizedOperationPaths',
|
|
40
|
+
'normalizedCandidateOperations',
|
|
41
|
+
'candidateLiterals',
|
|
42
|
+
'bindingCandidates',
|
|
43
|
+
'bindingAlternatives',
|
|
44
|
+
'implementationHintSuggestions',
|
|
45
|
+
'selectableImplementationRepositories',
|
|
46
|
+
'matchedHints',
|
|
47
|
+
'candidateSuggestions',
|
|
48
|
+
'dynamicTargetCandidates',
|
|
49
|
+
'dynamicTargetCandidateSuggestions',
|
|
50
|
+
'rejectedCandidates',
|
|
51
|
+
'suggestedVarSets',
|
|
52
|
+
'copyableExamples',
|
|
53
|
+
'selectorSuggestions',
|
|
54
|
+
'serviceSuggestions',
|
|
55
|
+
'repositories',
|
|
56
|
+
'examples',
|
|
57
|
+
'expandedExamples',
|
|
58
|
+
'registrations',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parser facts are retained in their tables; graph evidence only carries a
|
|
63
|
+
* deterministic explanation. This prevents nested parser alternatives from
|
|
64
|
+
* bypassing the graph evidence cap while leaving canonical facts queryable.
|
|
65
|
+
*/
|
|
66
|
+
export function boundCandidateLikeEvidence(
|
|
67
|
+
evidence: Record<string, unknown>,
|
|
68
|
+
limit = DEFAULT_EVIDENCE_CANDIDATE_LIMIT,
|
|
69
|
+
): Record<string, unknown> {
|
|
70
|
+
const output: Record<string, unknown> = {};
|
|
71
|
+
for (const [key, value] of Object.entries(evidence)) {
|
|
72
|
+
if (!Array.isArray(value) || !candidateLikeCollections.has(key)) {
|
|
73
|
+
output[key] = boundNestedEvidence(value, limit);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const projection = projectBounded(value, compareEvidenceValue, limit);
|
|
77
|
+
output[key] = projection.items.map((item) => boundNestedEvidence(item, limit));
|
|
78
|
+
addCollectionCounts(output, evidence, key, projection);
|
|
79
|
+
}
|
|
80
|
+
return output;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function boundNestedEvidence(value: unknown, limit: number): unknown {
|
|
84
|
+
if (Array.isArray(value)) return value.map((item) => boundNestedEvidence(item, limit));
|
|
85
|
+
if (!isEvidenceRecord(value)) return value;
|
|
86
|
+
return boundCandidateLikeEvidence(value, limit);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isEvidenceRecord(value: unknown): value is Record<string, unknown> {
|
|
90
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function addCollectionCounts(
|
|
94
|
+
output: Record<string, unknown>,
|
|
95
|
+
input: Record<string, unknown>,
|
|
96
|
+
key: string,
|
|
97
|
+
projection: BoundedProjection<unknown>,
|
|
98
|
+
): void {
|
|
99
|
+
const stem = collectionStem(key);
|
|
100
|
+
const countName = `${stem}Count`;
|
|
101
|
+
const shownName = `shown${upperFirst(stem)}Count`;
|
|
102
|
+
const omittedName = `omitted${upperFirst(stem)}Count`;
|
|
103
|
+
const total = Math.max(numericValue(input[countName]), projection.totalCount);
|
|
104
|
+
output[countName] = total;
|
|
105
|
+
output[shownName] = projection.shownCount;
|
|
106
|
+
output[omittedName] = Math.max(0, total - projection.shownCount);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectionStem(key: string): string {
|
|
110
|
+
const stems: Record<string, string> = {
|
|
111
|
+
candidates: 'candidate',
|
|
112
|
+
candidateScores: 'candidateScore',
|
|
113
|
+
candidateFamilies: 'candidateFamily',
|
|
114
|
+
candidateEvidence: 'candidateEvidence',
|
|
115
|
+
candidatePaths: 'candidatePath',
|
|
116
|
+
candidateRawPaths: 'candidateRawPath',
|
|
117
|
+
candidateNormalizedOperationPaths: 'candidateNormalizedOperationPath',
|
|
118
|
+
normalizedCandidateOperations: 'normalizedCandidateOperation',
|
|
119
|
+
candidateLiterals: 'candidateLiteral',
|
|
120
|
+
bindingCandidates: 'bindingCandidate',
|
|
121
|
+
bindingAlternatives: 'bindingAlternative',
|
|
122
|
+
implementationHintSuggestions: 'implementationHintSuggestion',
|
|
123
|
+
selectableImplementationRepositories: 'selectableImplementationRepository',
|
|
124
|
+
matchedHints: 'matchedHint',
|
|
125
|
+
candidateSuggestions: 'candidateSuggestion',
|
|
126
|
+
dynamicTargetCandidates: 'dynamicTargetCandidate',
|
|
127
|
+
dynamicTargetCandidateSuggestions: 'dynamicTargetCandidateSuggestion',
|
|
128
|
+
rejectedCandidates: 'rejectedCandidate',
|
|
129
|
+
suggestedVarSets: 'suggestedVarSet',
|
|
130
|
+
copyableExamples: 'copyableExample',
|
|
131
|
+
selectorSuggestions: 'selectorSuggestion',
|
|
132
|
+
serviceSuggestions: 'serviceSuggestion',
|
|
133
|
+
repositories: 'repository',
|
|
134
|
+
examples: 'example',
|
|
135
|
+
expandedExamples: 'expandedExample',
|
|
136
|
+
registrations: 'registration',
|
|
137
|
+
};
|
|
138
|
+
return stems[key] ?? 'candidate';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function compareEvidenceValue(left: unknown, right: unknown): number {
|
|
142
|
+
return stableProjectionValue(left).localeCompare(stableProjectionValue(right));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function stableProjectionValue(value: unknown): string {
|
|
146
|
+
if (Array.isArray(value))
|
|
147
|
+
return `[${value.map(stableProjectionValue).join(',')}]`;
|
|
148
|
+
if (isEvidenceRecord(value)) {
|
|
149
|
+
return `{${Object.keys(value).sort().map((key) =>
|
|
150
|
+
`${JSON.stringify(key)}:${stableProjectionValue(value[key])}`).join(',')}}`;
|
|
151
|
+
}
|
|
152
|
+
return JSON.stringify(value) ?? '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function numericValue(value: unknown): number {
|
|
156
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function upperFirst(value: string): string {
|
|
160
|
+
return value ? `${value[0]?.toUpperCase() ?? ''}${value.slice(1)}` : value;
|
|
161
|
+
}
|