@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ImplementationHint } from '../types.js';
|
|
2
|
+
import { projectBounded } from '../utils/000-bounded-projection.js';
|
|
2
3
|
|
|
3
4
|
interface Candidate {
|
|
4
5
|
accepted?: boolean;
|
|
@@ -25,6 +26,13 @@ export interface ImplementationSelection {
|
|
|
25
26
|
evidence: Record<string, unknown>;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export interface ImplementationHintSuggestionProjection {
|
|
30
|
+
suggestions: Array<Record<string, unknown>>;
|
|
31
|
+
suggestionCount: number;
|
|
32
|
+
shownSuggestionCount: number;
|
|
33
|
+
omittedSuggestionCount: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
export function parseImplementationHint(value: string): ImplementationHint {
|
|
29
37
|
const hint: Partial<ImplementationHint> = {};
|
|
30
38
|
for (const part of value.split(',')) {
|
|
@@ -40,8 +48,9 @@ export function selectImplementation(
|
|
|
40
48
|
rawEvidence: Record<string, unknown>,
|
|
41
49
|
hints: ImplementationHint[] | undefined,
|
|
42
50
|
legacyRepo: string | undefined,
|
|
51
|
+
canonicalEvidence?: Record<string, unknown>,
|
|
43
52
|
): ImplementationSelection {
|
|
44
|
-
const evidence = asEvidence(rawEvidence);
|
|
53
|
+
const evidence = asEvidence(canonicalEvidence ?? rawEvidence);
|
|
45
54
|
const scoped = hints ?? [];
|
|
46
55
|
const matchingHints = scoped.filter((hint) => hintMatchesEdge(hint, evidence));
|
|
47
56
|
if (matchingHints.length === 0) {
|
|
@@ -50,14 +59,18 @@ export function selectImplementation(
|
|
|
50
59
|
return { blocksAutomatic: false, evidence: { status: 'not_matched', reason, strategy: 'scoped_implementation_hint' } };
|
|
51
60
|
}
|
|
52
61
|
if (matchingHints.length > 1) {
|
|
62
|
+
const projection = projectBounded(matchingHints, compareHints);
|
|
53
63
|
return {
|
|
54
64
|
blocksAutomatic: true,
|
|
55
65
|
evidence: {
|
|
56
66
|
status: 'tied',
|
|
57
67
|
reason: 'multiple_scoped_hints_matched_edge',
|
|
58
68
|
strategy: 'scoped_implementation_hint',
|
|
59
|
-
matchedHints:
|
|
69
|
+
matchedHints: projection.items,
|
|
60
70
|
candidateCount: matchingHints.length,
|
|
71
|
+
matchedHintCount: projection.totalCount,
|
|
72
|
+
shownMatchedHintCount: projection.shownCount,
|
|
73
|
+
omittedMatchedHintCount: projection.omittedCount,
|
|
61
74
|
},
|
|
62
75
|
};
|
|
63
76
|
}
|
|
@@ -67,26 +80,48 @@ export function selectImplementation(
|
|
|
67
80
|
|
|
68
81
|
export function implementationHintDiagnostic(
|
|
69
82
|
selection: ImplementationSelection,
|
|
70
|
-
|
|
83
|
+
suggestionEvidence?: unknown,
|
|
71
84
|
): Record<string, unknown> | undefined {
|
|
72
85
|
if (!selection.blocksAutomatic || selection.methodId) return undefined;
|
|
86
|
+
const suggestions = projectedSuggestions(suggestionEvidence);
|
|
73
87
|
return {
|
|
74
88
|
severity: 'warning',
|
|
75
89
|
code: 'implementation_hint_mismatch',
|
|
76
90
|
message: 'Implementation hint did not select exactly one viable candidate',
|
|
77
91
|
hintStatus: selection.evidence.status,
|
|
78
92
|
candidateCount: selection.evidence.candidateCount,
|
|
79
|
-
implementationHintSuggestions:
|
|
93
|
+
implementationHintSuggestions: suggestions.suggestions.length > 0
|
|
94
|
+
? suggestions.suggestions
|
|
95
|
+
: undefined,
|
|
96
|
+
implementationHintSuggestionCount: suggestions.suggestionCount,
|
|
97
|
+
shownImplementationHintSuggestionCount: suggestions.shownSuggestionCount,
|
|
98
|
+
omittedImplementationHintSuggestionCount: suggestions.omittedSuggestionCount,
|
|
80
99
|
implementationSelection: selection.evidence,
|
|
81
100
|
};
|
|
82
101
|
}
|
|
83
102
|
|
|
84
103
|
export function implementationHintSuggestions(rawEvidence: Record<string, unknown>): Array<Record<string, unknown>> {
|
|
104
|
+
return implementationHintSuggestionProjection(rawEvidence).suggestions;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function implementationHintSuggestionProjection(
|
|
108
|
+
rawEvidence: Record<string, unknown>,
|
|
109
|
+
): ImplementationHintSuggestionProjection {
|
|
85
110
|
const evidence = asEvidence(rawEvidence);
|
|
86
111
|
const accepted = (evidence.candidates ?? []).filter((candidate) => candidate.accepted);
|
|
87
|
-
if (accepted.length < 2)
|
|
112
|
+
if (accepted.length < 2) {
|
|
113
|
+
return {
|
|
114
|
+
suggestions: [],
|
|
115
|
+
suggestionCount: 0,
|
|
116
|
+
shownSuggestionCount: 0,
|
|
117
|
+
omittedSuggestionCount: 0,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
88
120
|
const repos = selectableRepositories(accepted);
|
|
89
|
-
|
|
121
|
+
const repositoryProjection = projectBounded(
|
|
122
|
+
repos, (left, right) => left.localeCompare(right),
|
|
123
|
+
);
|
|
124
|
+
const suggestions = accepted
|
|
90
125
|
.flatMap((candidate) => {
|
|
91
126
|
const repo = candidate.handlerPackage?.name;
|
|
92
127
|
if (!repo || !repos.includes(repo)) return [];
|
|
@@ -96,12 +131,71 @@ export function implementationHintSuggestions(rawEvidence: Record<string, unknow
|
|
|
96
131
|
operationPath: hint.operationPath,
|
|
97
132
|
ambiguityReason: evidence.ambiguityReasons?.[0],
|
|
98
133
|
candidateFamily: hint.candidateFamily,
|
|
99
|
-
selectableImplementationRepositories:
|
|
134
|
+
selectableImplementationRepositories: repositoryProjection.items,
|
|
135
|
+
selectableImplementationRepositoryCount: repositoryProjection.totalCount,
|
|
136
|
+
shownSelectableImplementationRepositoryCount:
|
|
137
|
+
repositoryProjection.shownCount,
|
|
138
|
+
omittedSelectableImplementationRepositoryCount:
|
|
139
|
+
repositoryProjection.omittedCount,
|
|
100
140
|
implementationRepo: repo,
|
|
101
141
|
hint,
|
|
102
142
|
cli: `--implementation-hint ${hintString(hint)}`,
|
|
103
143
|
}];
|
|
104
144
|
});
|
|
145
|
+
const projection = projectBounded(suggestions, compareSuggestion);
|
|
146
|
+
return {
|
|
147
|
+
suggestions: projection.items,
|
|
148
|
+
suggestionCount: projection.totalCount,
|
|
149
|
+
shownSuggestionCount: projection.shownCount,
|
|
150
|
+
omittedSuggestionCount: projection.omittedCount,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function projectedSuggestions(value: unknown): ImplementationHintSuggestionProjection {
|
|
155
|
+
const evidence = objectRecord(value);
|
|
156
|
+
const values = Array.isArray(value)
|
|
157
|
+
? recordSuggestions(value)
|
|
158
|
+
: recordSuggestions(evidence.implementationHintSuggestions);
|
|
159
|
+
const projection = projectBounded(values, compareSuggestion);
|
|
160
|
+
const total = Math.max(
|
|
161
|
+
numericValue(evidence.implementationHintSuggestionCount),
|
|
162
|
+
projection.totalCount,
|
|
163
|
+
);
|
|
164
|
+
return {
|
|
165
|
+
suggestions: projection.items,
|
|
166
|
+
suggestionCount: total,
|
|
167
|
+
shownSuggestionCount: projection.shownCount,
|
|
168
|
+
omittedSuggestionCount: Math.max(0, total - projection.shownCount),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function compareHints(left: ImplementationHint, right: ImplementationHint): number {
|
|
173
|
+
return hintString(left).localeCompare(hintString(right));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function compareSuggestion(
|
|
177
|
+
left: Record<string, unknown>,
|
|
178
|
+
right: Record<string, unknown>,
|
|
179
|
+
): number {
|
|
180
|
+
return String(left.cli ?? '').localeCompare(String(right.cli ?? ''))
|
|
181
|
+
|| String(left.implementationRepo ?? '').localeCompare(
|
|
182
|
+
String(right.implementationRepo ?? ''),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function objectRecord(value: unknown): Record<string, unknown> {
|
|
187
|
+
return isRecord(value) ? value : {};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function recordSuggestions(value: unknown): Array<Record<string, unknown>> {
|
|
191
|
+
return Array.isArray(value)
|
|
192
|
+
? value.filter((item): item is Record<string, unknown> =>
|
|
193
|
+
Boolean(item && typeof item === 'object' && !Array.isArray(item)))
|
|
194
|
+
: [];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function numericValue(value: unknown): number {
|
|
198
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
105
199
|
}
|
|
106
200
|
|
|
107
201
|
function selectableRepositories(candidates: Candidate[]): string[] {
|
|
@@ -219,5 +313,51 @@ function legacyHint(implementationRepo: string): ImplementationHint {
|
|
|
219
313
|
}
|
|
220
314
|
|
|
221
315
|
function asEvidence(value: Record<string, unknown>): EdgeEvidence {
|
|
222
|
-
return
|
|
316
|
+
return {
|
|
317
|
+
servicePath: stringValue(value.servicePath),
|
|
318
|
+
operationPath: stringValue(value.operationPath),
|
|
319
|
+
ambiguityReasons: stringArray(value.ambiguityReasons),
|
|
320
|
+
candidateFamilies: candidateFamilies(value.candidateFamilies),
|
|
321
|
+
candidates: candidates(value.candidates),
|
|
322
|
+
modelPackage: packageValue(value.modelPackage),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function candidates(value: unknown): Candidate[] {
|
|
327
|
+
return recordSuggestions(value).map((candidate) => ({
|
|
328
|
+
accepted: candidate.accepted === true,
|
|
329
|
+
methodId: numericValue(candidate.methodId) || undefined,
|
|
330
|
+
sourceFile: stringValue(candidate.sourceFile),
|
|
331
|
+
handlerPackage: packageValue(candidate.handlerPackage),
|
|
332
|
+
modelPackage: packageValue(candidate.modelPackage),
|
|
333
|
+
servicePath: stringValue(candidate.servicePath),
|
|
334
|
+
operationPath: stringValue(candidate.operationPath),
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function candidateFamilies(value: unknown): Array<{ packageName?: string }> {
|
|
339
|
+
return recordSuggestions(value).map((family) => ({
|
|
340
|
+
packageName: stringValue(family.packageName),
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function packageValue(value: unknown): { name?: string; packageName?: string } | undefined {
|
|
345
|
+
const candidate = objectRecord(value);
|
|
346
|
+
const name = stringValue(candidate.name);
|
|
347
|
+
const packageName = stringValue(candidate.packageName);
|
|
348
|
+
return name || packageName ? { name, packageName } : undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function stringArray(value: unknown): string[] {
|
|
352
|
+
return Array.isArray(value)
|
|
353
|
+
? value.filter((item): item is string => typeof item === 'string')
|
|
354
|
+
: [];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function stringValue(value: unknown): string | undefined {
|
|
358
|
+
return typeof value === 'string' ? value : undefined;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
362
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
223
363
|
}
|