@saptools/service-flow 0.1.67 → 0.1.68

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +20 -8
  3. package/TECHNICAL-NOTE.md +27 -0
  4. package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
  5. package/dist/chunk-AEM4JY22.js.map +1 -0
  6. package/dist/cli.js +2325 -413
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +61 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/003-doctor-package-resolution.ts +68 -0
  12. package/src/cli/doctor.ts +25 -14
  13. package/src/db/000-call-fact-repository.ts +475 -342
  14. package/src/db/001-fact-lifecycle.ts +60 -30
  15. package/src/db/002-fact-json-inventory.ts +169 -0
  16. package/src/db/003-current-fact-semantics.ts +698 -0
  17. package/src/db/004-package-target-invalidation.ts +173 -0
  18. package/src/db/005-schema-structure.ts +201 -0
  19. package/src/db/006-relative-symbol-resolution.ts +443 -0
  20. package/src/db/007-package-fact-semantics.ts +573 -0
  21. package/src/db/008-relative-fact-semantics.ts +207 -0
  22. package/src/db/009-binding-fact-semantics.ts +347 -0
  23. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  24. package/src/db/011-symbol-call-semantics.ts +144 -0
  25. package/src/db/012-binding-reference-proof.ts +264 -0
  26. package/src/db/migrations.ts +16 -3
  27. package/src/db/repositories.ts +113 -6
  28. package/src/db/schema.ts +4 -2
  29. package/src/index.ts +12 -0
  30. package/src/indexer/repository-indexer.ts +72 -11
  31. package/src/indexer/workspace-indexer.ts +123 -32
  32. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  33. package/src/linker/004-event-subscription-handler-linker.ts +2 -0
  34. package/src/linker/005-odata-path-structure.ts +371 -0
  35. package/src/linker/cross-repo-linker.ts +2 -1
  36. package/src/linker/odata-path-normalizer.ts +273 -180
  37. package/src/linker/service-resolver.ts +197 -77
  38. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  39. package/src/parsers/003-package-public-surface.ts +661 -0
  40. package/src/parsers/004-fact-identity.ts +108 -0
  41. package/src/parsers/005-event-subscription-facts.ts +281 -0
  42. package/src/parsers/006-binding-identity.ts +343 -0
  43. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  44. package/src/parsers/008-package-surface-publication.ts +82 -0
  45. package/src/parsers/009-symbol-call-facts.ts +528 -0
  46. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  47. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  48. package/src/parsers/012-package-fact-contract.ts +306 -0
  49. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  50. package/src/parsers/014-service-binding-helper-flow.ts +240 -0
  51. package/src/parsers/015-service-binding-collector.ts +693 -0
  52. package/src/parsers/016-local-symbol-reference.ts +261 -0
  53. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  54. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  55. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  56. package/src/parsers/020-stable-local-value.ts +217 -0
  57. package/src/parsers/021-binding-visibility.ts +152 -0
  58. package/src/parsers/operation-path-analysis.ts +6 -1
  59. package/src/parsers/outbound-call-parser.ts +19 -6
  60. package/src/parsers/package-json-parser.ts +45 -3
  61. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  62. package/src/parsers/service-binding-parser.ts +147 -597
  63. package/src/parsers/symbol-parser.ts +482 -353
  64. package/src/trace/002-trace-diagnostics.ts +36 -1
  65. package/src/trace/016-compact-projector.ts +16 -17
  66. package/src/trace/020-compact-field-projection.ts +48 -37
  67. package/src/trace/021-compact-decision-normalization.ts +105 -0
  68. package/src/trace/022-trace-fact-preflight.ts +21 -0
  69. package/src/trace/023-nested-event-scopes.ts +23 -0
  70. package/src/trace/024-compact-observation-decision.ts +76 -0
  71. package/src/trace/025-trace-implementation-scope.ts +123 -0
  72. package/src/trace/026-trace-start-scope.ts +335 -0
  73. package/src/trace/027-trace-scope-execution.ts +566 -0
  74. package/src/trace/028-trace-operation-execution.ts +336 -0
  75. package/src/trace/029-trace-start-implementation.ts +172 -0
  76. package/src/trace/trace-engine.ts +122 -624
  77. package/src/types.ts +56 -0
  78. package/src/utils/001-placeholders.ts +188 -10
  79. package/src/version.ts +1 -1
  80. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -0,0 +1,108 @@
1
+ import type { ExecutableSymbolFact } from '../types.js';
2
+
3
+ export const executableOwnerKinds = [
4
+ 'event_registration',
5
+ 'callback',
6
+ 'method',
7
+ 'object_method',
8
+ 'function',
9
+ ] as const;
10
+
11
+ type ExecutableOwnerKind = typeof executableOwnerKinds[number];
12
+
13
+ export interface OwnerCandidate {
14
+ kind: string;
15
+ qualifiedName: string;
16
+ startOffset: number;
17
+ endOffset: number;
18
+ }
19
+
20
+ export interface OwnerSelection<T extends OwnerCandidate> {
21
+ status: 'resolved' | 'none' | 'ambiguous';
22
+ owner?: T;
23
+ eligibleCount: number;
24
+ }
25
+
26
+ const kindRank = new Map<ExecutableOwnerKind, number>(
27
+ executableOwnerKinds.map((kind, index) => [kind, index]),
28
+ );
29
+
30
+ export function binaryCompare(left: string, right: string): number {
31
+ return left < right ? -1 : left > right ? 1 : 0;
32
+ }
33
+
34
+ export function containsHalfOpen(
35
+ containerStart: number,
36
+ containerEnd: number,
37
+ childStart: number,
38
+ childEnd: number,
39
+ ): boolean {
40
+ return containerStart <= childStart && containerEnd >= childEnd;
41
+ }
42
+
43
+ export function isExecutableOwnerKind(kind: string): kind is ExecutableOwnerKind {
44
+ return kindRank.has(kind as ExecutableOwnerKind);
45
+ }
46
+
47
+ function ownerIdentity(candidate: OwnerCandidate): string {
48
+ return [
49
+ candidate.kind,
50
+ candidate.startOffset,
51
+ candidate.endOffset,
52
+ candidate.qualifiedName,
53
+ ].join('\u0000');
54
+ }
55
+
56
+ function compareOwners(left: OwnerCandidate, right: OwnerCandidate): number {
57
+ return (left.endOffset - left.startOffset) - (right.endOffset - right.startOffset)
58
+ || (kindRank.get(left.kind as ExecutableOwnerKind) ?? 99)
59
+ - (kindRank.get(right.kind as ExecutableOwnerKind) ?? 99)
60
+ || left.startOffset - right.startOffset
61
+ || left.endOffset - right.endOffset
62
+ || binaryCompare(left.qualifiedName, right.qualifiedName);
63
+ }
64
+
65
+ function exactRegistration<T extends OwnerCandidate>(
66
+ candidates: T[],
67
+ callStart: number,
68
+ callEnd: number,
69
+ ): T[] {
70
+ return candidates.filter((candidate) =>
71
+ candidate.kind === 'event_registration'
72
+ && candidate.startOffset === callStart
73
+ && candidate.endOffset === callEnd);
74
+ }
75
+
76
+ export function selectCallOwner<T extends OwnerCandidate>(
77
+ candidates: readonly T[],
78
+ callStart: number,
79
+ callEnd: number,
80
+ preferExactRegistration = false,
81
+ ): OwnerSelection<T> {
82
+ const contained = candidates.filter((candidate) =>
83
+ isExecutableOwnerKind(candidate.kind)
84
+ && containsHalfOpen(
85
+ candidate.startOffset, candidate.endOffset, callStart, callEnd,
86
+ ));
87
+ const eligible = preferExactRegistration
88
+ ? exactRegistration(contained, callStart, callEnd)
89
+ : contained;
90
+ const pool = eligible;
91
+ if (pool.length === 0) return { status: 'none', eligibleCount: 0 };
92
+ const ordered = [...pool].sort(compareOwners);
93
+ const first = ordered[0];
94
+ if (!first) return { status: 'none', eligibleCount: 0 };
95
+ const duplicates = ordered.filter((candidate) =>
96
+ ownerIdentity(candidate) === ownerIdentity(first));
97
+ if (duplicates.length > 1)
98
+ return { status: 'ambiguous', eligibleCount: pool.length };
99
+ return { status: 'resolved', owner: first, eligibleCount: pool.length };
100
+ }
101
+
102
+ export function executableSymbolCandidates(
103
+ symbols: readonly ExecutableSymbolFact[],
104
+ sourceFile: string,
105
+ ): ExecutableSymbolFact[] {
106
+ return symbols.filter((symbol) =>
107
+ symbol.sourceFile === sourceFile && isExecutableOwnerKind(symbol.kind));
108
+ }
@@ -0,0 +1,281 @@
1
+ import ts from 'typescript';
2
+ import type {
3
+ ExecutableSymbolFact,
4
+ HandlerReferenceStatus,
5
+ SymbolCallFact,
6
+ } from '../types.js';
7
+ import {
8
+ collectSymbolImportBindings,
9
+ symbolImportReference,
10
+ type SymbolImportBinding,
11
+ type SymbolImportReference,
12
+ } from './002-symbol-import-bindings.js';
13
+ import type { ClassifiedOutboundCall } from './outbound-call-parser.js';
14
+ import {
15
+ localSymbolTarget,
16
+ type LocalSymbolTargetIdentity,
17
+ } from './016-local-symbol-reference.js';
18
+
19
+ interface HandlerTarget {
20
+ calleeExpression: string;
21
+ calleeLocalName: string;
22
+ importSource?: string;
23
+ relation: string;
24
+ referenceShape: string;
25
+ wrapperFunction?: string;
26
+ importBinding?: SymbolImportReference;
27
+ localTargetIdentity?: LocalSymbolTargetIdentity;
28
+ }
29
+
30
+ interface HandlerClassification {
31
+ status: HandlerReferenceStatus;
32
+ reason?: string;
33
+ referenceShape: string;
34
+ target?: HandlerTarget;
35
+ }
36
+
37
+ function lineOf(source: ts.SourceFile, node: ts.Node): number {
38
+ return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
39
+ }
40
+
41
+ function lineAt(source: ts.SourceFile, position: number): number {
42
+ return source.getLineAndCharacterOfPosition(position).line + 1;
43
+ }
44
+
45
+ function importRelation(binding: SymbolImportReference): string {
46
+ if (binding.moduleKind === 'package') return 'package_import';
47
+ return binding.referenceShape === 'namespace_member'
48
+ ? 'relative_import_namespace_member'
49
+ : 'relative_import';
50
+ }
51
+
52
+ function directTarget(
53
+ expression: ts.Expression,
54
+ source: ts.SourceFile,
55
+ imports: readonly SymbolImportBinding[],
56
+ symbols: readonly ExecutableSymbolFact[],
57
+ ): HandlerTarget | undefined {
58
+ const imported = symbolImportReference(expression, imports);
59
+ if (imported) return importedTarget(expression, source, imported);
60
+ if (ts.isIdentifier(expression)) {
61
+ const exact = localSymbolTarget(expression, source, symbols);
62
+ return {
63
+ calleeExpression: expression.text,
64
+ calleeLocalName: expression.text,
65
+ relation: exact
66
+ ? 'indexed_local_symbol'
67
+ : 'indexed_local_symbol_unproven',
68
+ referenceShape: 'identifier',
69
+ localTargetIdentity: exact,
70
+ };
71
+ }
72
+ return propertyTarget(expression, source, symbols);
73
+ }
74
+
75
+ function importedTarget(
76
+ expression: ts.Expression,
77
+ source: ts.SourceFile,
78
+ binding: SymbolImportReference,
79
+ ): HandlerTarget {
80
+ return {
81
+ calleeExpression: expression.getText(source),
82
+ calleeLocalName: binding.requestedPublicName,
83
+ importSource: binding.rawModuleSpecifier,
84
+ relation: importRelation(binding),
85
+ referenceShape: binding.referenceShape,
86
+ importBinding: binding,
87
+ };
88
+ }
89
+
90
+ function propertyTarget(
91
+ expression: ts.Expression,
92
+ source: ts.SourceFile,
93
+ symbols: readonly ExecutableSymbolFact[],
94
+ ): HandlerTarget | undefined {
95
+ if (!ts.isPropertyAccessExpression(expression) || expression.questionDotToken
96
+ || !ts.isIdentifier(expression.expression)) return undefined;
97
+ const exact = localSymbolTarget(expression, source, symbols);
98
+ return {
99
+ calleeExpression: expression.getText(source),
100
+ calleeLocalName: `${expression.expression.text}.${expression.name.text}`,
101
+ relation: exact
102
+ ? 'indexed_local_symbol'
103
+ : 'indexed_local_symbol_unproven',
104
+ referenceShape: 'static_member',
105
+ localTargetIdentity: exact,
106
+ };
107
+ }
108
+
109
+ function unsupportedClassification(
110
+ status: Exclude<HandlerReferenceStatus, 'role_required'>,
111
+ reason: string,
112
+ referenceShape: string,
113
+ ): HandlerClassification {
114
+ return { status, reason, referenceShape };
115
+ }
116
+
117
+ export function classifyHandlerReference(
118
+ expression: ts.Expression | undefined,
119
+ source: ts.SourceFile,
120
+ imports = collectSymbolImportBindings(source),
121
+ symbols: readonly ExecutableSymbolFact[] = [],
122
+ ): HandlerClassification {
123
+ if (!expression)
124
+ return unsupportedClassification(
125
+ 'missing_argument', 'handler_argument_missing', 'missing',
126
+ );
127
+ if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression))
128
+ return unsupportedClassification(
129
+ 'unsupported_inline', 'inline_handler_body_not_indexed', 'inline_callback',
130
+ );
131
+ const direct = directTarget(expression, source, imports, symbols);
132
+ if (direct)
133
+ return { status: 'role_required', referenceShape: direct.referenceShape, target: direct };
134
+ if (!ts.isCallExpression(expression))
135
+ return unsupportedClassification(
136
+ 'unsupported_reference_shape', 'handler_reference_shape_unsupported',
137
+ 'unsupported_expression',
138
+ );
139
+ return classifyWrapperReference(expression, source, imports, symbols);
140
+ }
141
+
142
+ function classifyWrapperReference(
143
+ expression: ts.CallExpression,
144
+ source: ts.SourceFile,
145
+ imports: readonly SymbolImportBinding[],
146
+ symbols: readonly ExecutableSymbolFact[],
147
+ ): HandlerClassification {
148
+ if (expression.questionDotToken || expression.arguments.length !== 1)
149
+ return unsupportedClassification(
150
+ 'unsupported_wrapper', 'wrapper_requires_one_reference', 'wrapper_call',
151
+ );
152
+ const inner = expression.arguments[0];
153
+ const target = inner
154
+ ? directTarget(inner, source, imports, symbols)
155
+ : undefined;
156
+ if (!target)
157
+ return unsupportedClassification(
158
+ 'unsupported_wrapper', 'wrapper_reference_shape_unsupported', 'wrapper_call',
159
+ );
160
+ return {
161
+ status: 'role_required',
162
+ referenceShape: `wrapped_${target.referenceShape}`,
163
+ target: { ...target, wrapperFunction: expression.expression.getText(source) },
164
+ };
165
+ }
166
+
167
+ function eventSymbol(
168
+ source: ts.SourceFile,
169
+ classified: ClassifiedOutboundCall,
170
+ ): ExecutableSymbolFact {
171
+ const node = classified.node;
172
+ const eventName = classified.fact.eventNameExpr ?? '';
173
+ const line = lineOf(source, node);
174
+ const safeName = eventName.replace(/[^A-Za-z0-9_$-]/g, '_');
175
+ const name = `event:${safeName}:${line}`;
176
+ const receiver = classified.fact.evidence?.receiver;
177
+ return {
178
+ kind: 'event_registration',
179
+ localName: name,
180
+ qualifiedName: `module:${classified.fact.sourceFile}#${name}`,
181
+ sourceFile: classified.fact.sourceFile,
182
+ startLine: line,
183
+ endLine: lineAt(source, node.getEnd() - 1),
184
+ startOffset: node.getStart(source),
185
+ endOffset: node.getEnd(),
186
+ exported: false,
187
+ importExportEvidence: {
188
+ source: 'synthetic_event_registration',
189
+ eventName,
190
+ registrationLine: line,
191
+ receiver: typeof receiver === 'string' ? receiver : undefined,
192
+ },
193
+ };
194
+ }
195
+
196
+ function eventCall(
197
+ classified: ClassifiedOutboundCall,
198
+ owner: ExecutableSymbolFact,
199
+ classification: HandlerClassification,
200
+ ): SymbolCallFact | undefined {
201
+ const target = classification.target;
202
+ if (!target) return undefined;
203
+ return {
204
+ callerQualifiedName: owner.qualifiedName,
205
+ calleeExpression: target.calleeExpression,
206
+ calleeLocalName: target.calleeLocalName,
207
+ importSource: target.importSource,
208
+ sourceFile: classified.fact.sourceFile,
209
+ sourceLine: classified.fact.sourceLine,
210
+ callSiteStartOffset: classified.fact.callSiteStartOffset,
211
+ callSiteEndOffset: classified.fact.callSiteEndOffset,
212
+ callRole: 'event_subscribe_handler',
213
+ evidence: {
214
+ relation: target.relation,
215
+ caller: owner.qualifiedName,
216
+ targetName: target.calleeLocalName,
217
+ referenceShape: classification.referenceShape,
218
+ ...(target.importBinding ? { importBinding: target.importBinding } : {}),
219
+ ...(target.localTargetIdentity
220
+ ? { localTargetIdentity: target.localTargetIdentity }
221
+ : {}),
222
+ ...(target.wrapperFunction ? { wrapperFunction: target.wrapperFunction } : {}),
223
+ factOrigin: 'event_subscribe_handler_reference',
224
+ },
225
+ };
226
+ }
227
+
228
+ function enrichSubscription(
229
+ source: ts.SourceFile,
230
+ classified: ClassifiedOutboundCall,
231
+ symbols: readonly ExecutableSymbolFact[],
232
+ ): { classified: ClassifiedOutboundCall; classification: HandlerClassification } {
233
+ const classification = classifyHandlerReference(
234
+ classified.node.arguments[1], source, undefined, symbols,
235
+ );
236
+ const evidence = {
237
+ ...(classified.fact.evidence ?? {}),
238
+ handlerReferenceStatus: classification.status,
239
+ ...(classification.reason
240
+ ? { handlerReferenceReason: classification.reason }
241
+ : {}),
242
+ handlerReferenceShape: classification.referenceShape,
243
+ };
244
+ return {
245
+ classified: { ...classified, fact: { ...classified.fact, evidence } },
246
+ classification,
247
+ };
248
+ }
249
+
250
+ export function reconcileEventSubscriptions(
251
+ source: ts.SourceFile,
252
+ classifications: readonly ClassifiedOutboundCall[],
253
+ symbols: readonly ExecutableSymbolFact[],
254
+ calls: readonly SymbolCallFact[],
255
+ ): {
256
+ classifications: ClassifiedOutboundCall[];
257
+ symbols: ExecutableSymbolFact[];
258
+ calls: SymbolCallFact[];
259
+ } {
260
+ const subscriptions = classifications.filter(
261
+ (item) => item.fact.callType === 'async_subscribe',
262
+ ).map((item) => enrichSubscription(source, item, symbols));
263
+ const eventSymbols = subscriptions.map((item) => eventSymbol(source, item.classified));
264
+ const eventCalls = subscriptions.flatMap((item, index) => {
265
+ const owner = eventSymbols[index];
266
+ const call = owner
267
+ ? eventCall(item.classified, owner, item.classification)
268
+ : undefined;
269
+ return call ? [call] : [];
270
+ });
271
+ return {
272
+ classifications: classifications.map((item) =>
273
+ subscriptions.find((candidate) => candidate.classified.node === item.node)
274
+ ?.classified ?? item),
275
+ symbols: [...symbols.filter((item) => item.kind !== 'event_registration'), ...eventSymbols],
276
+ calls: [
277
+ ...calls.filter((item) => item.callRole !== 'event_subscribe_handler'),
278
+ ...eventCalls,
279
+ ],
280
+ };
281
+ }