@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,443 @@
1
+ import { posix } from 'node:path';
2
+ import type { SymbolCallFact } from '../types.js';
3
+ import type { Db } from './connection.js';
4
+
5
+ export interface RelativeSymbolCallResolution {
6
+ id: number | null;
7
+ status: 'resolved' | 'ambiguous' | 'unresolved';
8
+ reason: string | null;
9
+ strategy: string;
10
+ candidateCount: number;
11
+ eligibleCandidateCount: number;
12
+ candidateSetComplete: boolean;
13
+ resolvedModulePath?: string;
14
+ }
15
+
16
+ interface TargetRow {
17
+ id: number;
18
+ kind: string;
19
+ sourceFile: string;
20
+ exported: boolean;
21
+ evidenceJson: string | null;
22
+ }
23
+
24
+ interface RelativeModuleScope {
25
+ paths: Set<string>;
26
+ ambiguous: boolean;
27
+ }
28
+
29
+ interface MappingTarget {
30
+ specifier?: string;
31
+ }
32
+
33
+ interface MappedExecutableRows {
34
+ rows: TargetRow[];
35
+ ambiguous: boolean;
36
+ }
37
+
38
+ const stripExtension = (value: string): string =>
39
+ value.replace(/\.(?:ts|tsx|js|jsx|cds)$/, '');
40
+
41
+ function record(value: unknown): Record<string, unknown> | undefined {
42
+ return value && typeof value === 'object' && !Array.isArray(value)
43
+ ? value as Record<string, unknown>
44
+ : undefined;
45
+ }
46
+
47
+ function parsedRecord(value: string | null): Record<string, unknown> | undefined {
48
+ if (value === null) return undefined;
49
+ try {
50
+ return record(JSON.parse(value) as unknown);
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }
55
+
56
+ function targetRows(rows: Array<Record<string, unknown>>): TargetRow[] {
57
+ return rows.flatMap((row) =>
58
+ typeof row.id === 'number' && typeof row.kind === 'string'
59
+ && typeof row.sourceFile === 'string'
60
+ && typeof row.exported === 'number'
61
+ ? [{
62
+ id: row.id,
63
+ kind: row.kind,
64
+ sourceFile: row.sourceFile,
65
+ exported: row.exported === 1,
66
+ evidenceJson: typeof row.evidenceJson === 'string'
67
+ ? row.evidenceJson
68
+ : null,
69
+ }]
70
+ : []);
71
+ }
72
+
73
+ function relativeTargets(sourceFile: string, specifier: string): Set<string> {
74
+ const joined = stripExtension(posix.normalize(
75
+ posix.join(posix.dirname(sourceFile), specifier),
76
+ ));
77
+ return new Set([joined, `${joined}/index`]);
78
+ }
79
+
80
+ function relativeModuleScope(
81
+ db: Db,
82
+ repoId: number,
83
+ sourceFile: string,
84
+ specifier: string,
85
+ ): RelativeModuleScope {
86
+ const requested = relativeTargets(sourceFile, specifier);
87
+ const rows = db.prepare(`SELECT relative_path relativePath FROM files
88
+ WHERE repo_id=? ORDER BY relative_path COLLATE BINARY`).all(repoId);
89
+ const matched = rows.flatMap((row) =>
90
+ typeof row.relativePath === 'string'
91
+ && requested.has(stripExtension(row.relativePath))
92
+ ? [stripExtension(row.relativePath)] : []);
93
+ return {
94
+ paths: new Set(matched),
95
+ ambiguous: matched.length > 1,
96
+ };
97
+ }
98
+
99
+ function scopedRows(
100
+ rows: readonly TargetRow[],
101
+ paths: ReadonlySet<string>,
102
+ ): TargetRow[] {
103
+ return rows.filter((row) => paths.has(stripExtension(row.sourceFile)));
104
+ }
105
+
106
+ function bodyEligible(row: TargetRow): boolean {
107
+ const evidence = parsedRecord(row.evidenceJson);
108
+ const direct = record(evidence?.executableBodyEligibility);
109
+ const surface = record(evidence?.packagePublicSurface);
110
+ const packageBody = record(surface?.bodyEligibility);
111
+ const body = direct ?? packageBody;
112
+ return body?.eligible === true && body.reason === 'body_present';
113
+ }
114
+
115
+ function relativeBinding(
116
+ call: SymbolCallFact,
117
+ ): Record<string, unknown> | undefined {
118
+ const binding = record(call.evidence.importBinding);
119
+ return binding?.moduleKind === 'relative'
120
+ && binding.rawModuleSpecifier === call.importSource
121
+ ? binding
122
+ : undefined;
123
+ }
124
+
125
+ function importedHandlerMember(
126
+ call: SymbolCallFact,
127
+ binding = relativeBinding(call),
128
+ ): boolean {
129
+ return call.callRole === 'event_subscribe_handler'
130
+ && binding?.referenceShape === 'static_member'
131
+ && typeof binding.importedName === 'string'
132
+ && binding.requestedPublicName === call.calleeLocalName;
133
+ }
134
+
135
+ function handlerMemberPubliclyCarried(
136
+ row: TargetRow,
137
+ binding: Record<string, unknown>,
138
+ ): boolean {
139
+ if (row.exported) return true;
140
+ const evidence = parsedRecord(row.evidenceJson);
141
+ return evidence?.source === 'exported_class_instance_member'
142
+ && evidence.exportedClass === binding.importedName;
143
+ }
144
+
145
+ function requiresPublicClassMember(
146
+ relation: unknown,
147
+ call: SymbolCallFact,
148
+ binding: Record<string, unknown>,
149
+ ): boolean {
150
+ return relation === 'class_instance_method'
151
+ || importedHandlerMember(call, binding);
152
+ }
153
+
154
+ function resolved(
155
+ row: TargetRow,
156
+ strategy: string,
157
+ candidateCount: number,
158
+ resolvedModulePath: string,
159
+ ): RelativeSymbolCallResolution {
160
+ return {
161
+ id: row.id,
162
+ status: 'resolved',
163
+ reason: null,
164
+ strategy,
165
+ candidateCount,
166
+ eligibleCandidateCount: 1,
167
+ candidateSetComplete: true,
168
+ resolvedModulePath,
169
+ };
170
+ }
171
+
172
+ function unresolved(
173
+ strategy: string,
174
+ reason: string,
175
+ candidateCount: number,
176
+ eligibleCandidateCount = 0,
177
+ candidateSetComplete = true,
178
+ ): RelativeSymbolCallResolution {
179
+ return {
180
+ id: null,
181
+ status: 'unresolved',
182
+ reason,
183
+ strategy,
184
+ candidateCount,
185
+ eligibleCandidateCount,
186
+ candidateSetComplete,
187
+ };
188
+ }
189
+
190
+ function ambiguous(
191
+ strategy: string,
192
+ reason: string,
193
+ candidateCount: number,
194
+ eligibleCandidateCount: number,
195
+ ): RelativeSymbolCallResolution {
196
+ return {
197
+ id: null,
198
+ status: 'ambiguous',
199
+ reason,
200
+ strategy,
201
+ candidateCount,
202
+ eligibleCandidateCount,
203
+ candidateSetComplete: true,
204
+ };
205
+ }
206
+
207
+ function uniqueRows(rows: readonly TargetRow[]): TargetRow[] {
208
+ return [...new Map(rows.map((row) => [row.id, row])).values()]
209
+ .sort((left, right) => left.id - right.id);
210
+ }
211
+
212
+ function executableResolution(
213
+ rows: readonly TargetRow[],
214
+ scoped: readonly TargetRow[],
215
+ modulePath: string | undefined,
216
+ strategy: string,
217
+ ambiguousReason: string,
218
+ ): RelativeSymbolCallResolution {
219
+ const eligible = uniqueRows(scoped.filter(bodyEligible));
220
+ if (eligible.length === 1 && eligible[0] && modulePath)
221
+ return resolved(eligible[0], strategy, rows.length, modulePath);
222
+ if (eligible.length > 1) return ambiguous(
223
+ strategy, ambiguousReason, rows.length, eligible.length,
224
+ );
225
+ return unresolved(
226
+ strategy,
227
+ scoped.length > 0
228
+ ? 'relative_import_requested_module_has_no_executable_body'
229
+ : 'relative_import_requested_module_has_no_target',
230
+ rows.length,
231
+ );
232
+ }
233
+
234
+ function qualifiedRows(
235
+ db: Db,
236
+ repoId: number,
237
+ call: SymbolCallFact,
238
+ ): TargetRow[] {
239
+ return targetRows(db.prepare(`SELECT id,kind,source_file sourceFile,
240
+ exported,evidence_json evidenceJson FROM symbols
241
+ WHERE repo_id=? AND source_file<>? AND qualified_name=?
242
+ ORDER BY id`).all(repoId, call.sourceFile, call.calleeLocalName));
243
+ }
244
+
245
+ function exportedRows(
246
+ db: Db,
247
+ repoId: number,
248
+ call: SymbolCallFact,
249
+ ): TargetRow[] {
250
+ return targetRows(db.prepare(`SELECT id,kind,source_file sourceFile,
251
+ exported,evidence_json evidenceJson FROM symbols
252
+ WHERE repo_id=? AND source_file<>? AND exported=1
253
+ AND (exported_name=? OR name=? OR qualified_name=?)
254
+ ORDER BY id`).all(
255
+ repoId, call.sourceFile,
256
+ call.calleeLocalName, call.calleeLocalName, call.calleeLocalName,
257
+ ));
258
+ }
259
+
260
+ function proxyRows(
261
+ db: Db,
262
+ repoId: number,
263
+ call: SymbolCallFact,
264
+ ): TargetRow[] {
265
+ return targetRows(db.prepare(`SELECT id,kind,source_file sourceFile,
266
+ exported,evidence_json evidenceJson FROM symbols
267
+ WHERE repo_id=? AND source_file<>?
268
+ AND (exported_name=? OR name=? OR qualified_name=?)
269
+ ORDER BY id`).all(
270
+ repoId, call.sourceFile,
271
+ call.calleeLocalName, call.calleeLocalName, call.calleeLocalName,
272
+ ));
273
+ }
274
+
275
+ function mappingTarget(row: TargetRow): MappingTarget | undefined {
276
+ const evidence = parsedRecord(row.evidenceJson);
277
+ if (row.kind !== 'object_alias'
278
+ || evidence?.source !== 'exported_object_shorthand') return undefined;
279
+ const specifier = evidence.targetImportSource;
280
+ return typeof specifier === 'string' ? { specifier } : {};
281
+ }
282
+
283
+ function mappingTargetScope(
284
+ db: Db,
285
+ repoId: number,
286
+ row: TargetRow,
287
+ ): RelativeModuleScope | undefined {
288
+ const target = mappingTarget(row);
289
+ if (!target) return undefined;
290
+ return target.specifier
291
+ ? relativeModuleScope(db, repoId, row.sourceFile, target.specifier)
292
+ : { paths: new Set([stripExtension(row.sourceFile)]), ambiguous: false };
293
+ }
294
+
295
+ function mappedExecutableRows(
296
+ db: Db,
297
+ repoId: number,
298
+ rows: readonly TargetRow[],
299
+ aliases: readonly TargetRow[],
300
+ ): MappedExecutableRows {
301
+ const scopes = aliases.flatMap((alias) => {
302
+ const scope = mappingTargetScope(db, repoId, alias);
303
+ return scope ? [scope] : [];
304
+ });
305
+ if (scopes.some((scope) => scope.ambiguous))
306
+ return { rows: [], ambiguous: true };
307
+ const mapped = scopes.flatMap((scope) => {
308
+ return rows.filter((row) =>
309
+ row.kind !== 'object_alias' && bodyEligible(row)
310
+ && scope.paths.has(stripExtension(row.sourceFile)));
311
+ });
312
+ return { rows: uniqueRows(mapped), ambiguous: false };
313
+ }
314
+
315
+ function proxyResolution(
316
+ db: Db,
317
+ repoId: number,
318
+ rows: readonly TargetRow[],
319
+ scope: RelativeModuleScope,
320
+ ): RelativeSymbolCallResolution {
321
+ const scoped = scopedRows(rows, scope.paths);
322
+ const aliases = scoped.filter((row) => mappingTarget(row));
323
+ const mapped = mappedExecutableRows(db, repoId, rows, aliases);
324
+ if (mapped.ambiguous) return unresolved(
325
+ 'proxy_member_exported_object_map',
326
+ 'relative_import_module_resolution_ambiguous',
327
+ rows.length, 0, false,
328
+ );
329
+ const direct = scoped.filter((row) =>
330
+ row.kind !== 'object_alias' && row.exported && bodyEligible(row));
331
+ const eligible = uniqueRows([
332
+ ...direct,
333
+ ...mapped.rows,
334
+ ]);
335
+ const modulePath = [...scope.paths][0];
336
+ const strategy = aliases.length > 0
337
+ ? 'proxy_member_exported_object_map'
338
+ : 'relative_import_path_disambiguated';
339
+ if (eligible.length === 1 && eligible[0] && modulePath)
340
+ return resolved(eligible[0], strategy, rows.length, modulePath);
341
+ if (eligible.length > 1) return ambiguous(
342
+ 'proxy_member_no_global_name_fallback',
343
+ 'multiple_proxy_targets_in_requested_module',
344
+ rows.length,
345
+ eligible.length,
346
+ );
347
+ return unresolved(
348
+ 'proxy_member_no_global_name_fallback',
349
+ scoped.length > 0
350
+ ? 'relative_import_requested_module_has_no_executable_body'
351
+ : 'relative_import_requested_module_has_no_target',
352
+ rows.length,
353
+ );
354
+ }
355
+
356
+ function resolutionRows(
357
+ db: Db,
358
+ repoId: number,
359
+ call: SymbolCallFact,
360
+ relation: unknown,
361
+ ): TargetRow[] {
362
+ if (relation === 'class_instance_method' || isAccessorCall(relation, call)
363
+ || importedHandlerMember(call))
364
+ return qualifiedRows(
365
+ db, repoId, call,
366
+ );
367
+ if (relation === 'relative_import_proxy_member') return proxyRows(
368
+ db, repoId, call,
369
+ );
370
+ return exportedRows(db, repoId, call);
371
+ }
372
+
373
+ function isAccessorCall(relation: unknown, call: SymbolCallFact): boolean {
374
+ return relation === 'relative_import'
375
+ && call.calleeExpression.includes('().')
376
+ && String(call.calleeLocalName).includes('.');
377
+ }
378
+
379
+ function strategyFor(
380
+ relation: unknown,
381
+ call: SymbolCallFact,
382
+ candidateCount: number,
383
+ ): string {
384
+ if (relation === 'class_instance_method')
385
+ return 'relative_import_class_instance_method';
386
+ if (relation === 'relative_import_namespace_member')
387
+ return 'relative_import_namespace_member';
388
+ if (isAccessorCall(relation, call))
389
+ return 'relative_import_static_accessor_instance_method';
390
+ return candidateCount > 1
391
+ ? 'relative_import_path_disambiguated'
392
+ : 'relative_import_exported_exact';
393
+ }
394
+
395
+ function ambiguousReasonFor(
396
+ relation: unknown,
397
+ call: SymbolCallFact,
398
+ ): string {
399
+ if (relation === 'class_instance_method')
400
+ return 'multiple_relative_class_targets_in_requested_module';
401
+ if (relation === 'relative_import_namespace_member')
402
+ return 'multiple_namespace_targets_in_requested_module';
403
+ return isAccessorCall(relation, call)
404
+ ? 'multiple_accessor_targets_in_requested_module'
405
+ : 'multiple_exported_targets_in_requested_module';
406
+ }
407
+
408
+ export function resolveRelativeSymbolCall(
409
+ db: Db,
410
+ repoId: number,
411
+ call: SymbolCallFact,
412
+ relation: unknown,
413
+ ): RelativeSymbolCallResolution | undefined {
414
+ if (!call.importSource?.startsWith('.')) return undefined;
415
+ const rows = resolutionRows(db, repoId, call, relation);
416
+ const binding = relativeBinding(call);
417
+ const strategy = strategyFor(relation, call, rows.length);
418
+ if (!binding) return unresolved(
419
+ strategy, 'relative_import_provenance_missing', rows.length,
420
+ );
421
+ if (binding.typeOnly === true) return unresolved(
422
+ strategy, 'relative_import_type_only', rows.length,
423
+ );
424
+ const moduleScope = relativeModuleScope(
425
+ db, repoId, call.sourceFile, call.importSource,
426
+ );
427
+ if (moduleScope.ambiguous) return unresolved(
428
+ strategy, 'relative_import_module_resolution_ambiguous',
429
+ rows.length, 0, false,
430
+ );
431
+ if (relation === 'relative_import_proxy_member')
432
+ return proxyResolution(db, repoId, rows, moduleScope);
433
+ const moduleRows = scopedRows(rows, moduleScope.paths);
434
+ const scoped = binding && requiresPublicClassMember(
435
+ relation, call, binding,
436
+ )
437
+ ? moduleRows.filter((row) => handlerMemberPubliclyCarried(row, binding))
438
+ : moduleRows;
439
+ return executableResolution(
440
+ rows, scoped, [...moduleScope.paths][0],
441
+ strategy, ambiguousReasonFor(relation, call),
442
+ );
443
+ }