@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,371 @@
1
+ import {
2
+ scanPlaceholderStructure,
3
+ type PlaceholderSpan,
4
+ } from '../utils/001-placeholders.js';
5
+
6
+ export type ODataPathStructureReason =
7
+ | 'path_parenthesis_is_unbalanced'
8
+ | 'path_parenthesis_suffix_is_invalid'
9
+ | 'path_placeholder_is_malformed'
10
+ | 'path_quote_is_unbalanced';
11
+
12
+ export interface ODataPathSegment {
13
+ readonly text: string;
14
+ readonly start: number;
15
+ readonly end: number;
16
+ }
17
+
18
+ export interface ODataPathStructure {
19
+ readonly status: 'valid' | 'malformed';
20
+ readonly reason?: ODataPathStructureReason;
21
+ readonly rawPath: string;
22
+ readonly pathWithoutQuery: string;
23
+ readonly queryString?: string;
24
+ readonly queryIndex?: number;
25
+ readonly placeholderSpans: readonly PlaceholderSpan[];
26
+ readonly placeholderKeys: string[];
27
+ readonly segments: readonly ODataPathSegment[];
28
+ readonly firstSegment?: ODataPathSegment;
29
+ readonly firstSegmentHead?: string;
30
+ readonly firstSegmentHeadRuntimeDependent: boolean;
31
+ readonly firstParenthesisOpen?: number;
32
+ readonly firstParenthesisClose?: number;
33
+ readonly firstParenthesisArguments?: string;
34
+ readonly firstParenthesisPlaceholderKeys: string[];
35
+ }
36
+
37
+ interface SyntaxScan {
38
+ readonly queryIndex?: number;
39
+ readonly separators: number[];
40
+ readonly firstOpen?: number;
41
+ readonly firstClose?: number;
42
+ readonly reason?: ODataPathStructureReason;
43
+ }
44
+
45
+ interface ParenthesisState {
46
+ readonly stack: number[];
47
+ firstOpen?: number;
48
+ firstClose?: number;
49
+ segmentIndex: number;
50
+ }
51
+
52
+ interface SyntaxCursor {
53
+ index: number;
54
+ spanIndex: number;
55
+ }
56
+
57
+ interface HeadEvidence {
58
+ readonly firstSegment?: ODataPathSegment;
59
+ readonly firstSegmentHead?: string;
60
+ readonly firstSegmentHeadRuntimeDependent: boolean;
61
+ }
62
+
63
+ interface ArgumentEvidence {
64
+ readonly firstParenthesisArguments?: string;
65
+ readonly firstParenthesisPlaceholderKeys: string[];
66
+ }
67
+
68
+ export function analyzeODataPathStructure(
69
+ path: string | undefined,
70
+ ): ODataPathStructure {
71
+ const rawPath = (path ?? '').trim();
72
+ const placeholders = scanPlaceholderStructure(rawPath);
73
+ if (placeholders.status === 'malformed')
74
+ return malformedStructure(rawPath, 'path_placeholder_is_malformed');
75
+ const syntax = scanSyntax(rawPath, placeholders.spans);
76
+ if (syntax.reason)
77
+ return malformedStructure(rawPath, syntax.reason, placeholders.spans);
78
+ return validStructure(rawPath, placeholders.spans, syntax);
79
+ }
80
+
81
+ function scanSyntax(
82
+ raw: string,
83
+ placeholders: readonly PlaceholderSpan[],
84
+ ): SyntaxScan {
85
+ const state: ParenthesisState = { stack: [], segmentIndex: 0 };
86
+ const cursor: SyntaxCursor = { index: 0, spanIndex: 0 };
87
+ const separators: number[] = [];
88
+ while (cursor.index < raw.length) {
89
+ const step = scanSyntaxStep(raw, placeholders, cursor, state, separators);
90
+ if (step.reason) return { separators, reason: step.reason };
91
+ if (step.query) {
92
+ const queryReason = validateQuerySyntax(
93
+ raw, cursor.index + 1, placeholders, cursor.spanIndex,
94
+ );
95
+ if (queryReason) return { separators, reason: queryReason };
96
+ return finalizedSyntax(raw, separators, state, cursor.index);
97
+ }
98
+ }
99
+ if (state.stack.length > 0)
100
+ return { separators, reason: 'path_parenthesis_is_unbalanced' };
101
+ return finalizedSyntax(raw, separators, state);
102
+ }
103
+
104
+ function validateQuerySyntax(
105
+ raw: string,
106
+ start: number,
107
+ placeholders: readonly PlaceholderSpan[],
108
+ initialSpanIndex: number,
109
+ ): ODataPathStructureReason | undefined {
110
+ const cursor: SyntaxCursor = { index: start, spanIndex: initialSpanIndex };
111
+ let parenthesisDepth = 0;
112
+ while (cursor.index < raw.length) {
113
+ if (skipPlaceholder(placeholders, cursor)) continue;
114
+ const char = raw[cursor.index];
115
+ if (["'", '"'].includes(char)) {
116
+ const end = quotedEnd(
117
+ raw, cursor.index, char, placeholders, cursor.spanIndex,
118
+ );
119
+ if (end === undefined) return 'path_quote_is_unbalanced';
120
+ cursor.index = end;
121
+ continue;
122
+ }
123
+ if (char === '(') parenthesisDepth += 1;
124
+ if (char === ')' && parenthesisDepth === 0)
125
+ return 'path_parenthesis_is_unbalanced';
126
+ if (char === ')') parenthesisDepth -= 1;
127
+ cursor.index += 1;
128
+ }
129
+ return parenthesisDepth === 0
130
+ ? undefined : 'path_parenthesis_is_unbalanced';
131
+ }
132
+
133
+ function scanSyntaxStep(
134
+ raw: string,
135
+ placeholders: readonly PlaceholderSpan[],
136
+ cursor: SyntaxCursor,
137
+ state: ParenthesisState,
138
+ separators: number[],
139
+ ): { query?: boolean; reason?: ODataPathStructureReason } {
140
+ if (skipPlaceholder(placeholders, cursor)) return {};
141
+ const char = raw[cursor.index];
142
+ if (char === "'" || char === '"') {
143
+ const end = quotedEnd(raw, cursor.index, char, placeholders, cursor.spanIndex);
144
+ if (end === undefined) return { reason: 'path_quote_is_unbalanced' };
145
+ cursor.index = end;
146
+ return {};
147
+ }
148
+ const result = consumePathDelimiter(raw, cursor.index, state, separators);
149
+ if (!result.query && !result.reason) cursor.index += 1;
150
+ return result;
151
+ }
152
+
153
+ function skipPlaceholder(
154
+ placeholders: readonly PlaceholderSpan[],
155
+ cursor: SyntaxCursor,
156
+ ): boolean {
157
+ while (placeholders[cursor.spanIndex]?.end
158
+ && (placeholders[cursor.spanIndex]?.end ?? 0) <= cursor.index)
159
+ cursor.spanIndex += 1;
160
+ const span = placeholders[cursor.spanIndex];
161
+ if (span?.start !== cursor.index) return false;
162
+ cursor.index = span.end;
163
+ cursor.spanIndex += 1;
164
+ return true;
165
+ }
166
+
167
+ function consumePathDelimiter(
168
+ raw: string,
169
+ index: number,
170
+ state: ParenthesisState,
171
+ separators: number[],
172
+ ): { query?: boolean; reason?: ODataPathStructureReason } {
173
+ const char = raw[index];
174
+ if (char === '(') return openParenthesis(state, index);
175
+ if (char === ')') return closeParenthesis(state, index);
176
+ if (char === '?') return state.stack.length === 0 ? { query: true } : {};
177
+ if (char === '/') addSeparator(state, separators, index);
178
+ return {};
179
+ }
180
+
181
+ function openParenthesis(
182
+ state: ParenthesisState,
183
+ index: number,
184
+ ): { reason?: ODataPathStructureReason } {
185
+ if (state.stack.length === 0 && state.segmentIndex === 0) {
186
+ if (state.firstOpen !== undefined)
187
+ return { reason: 'path_parenthesis_suffix_is_invalid' };
188
+ state.firstOpen = index;
189
+ }
190
+ state.stack.push(index);
191
+ return {};
192
+ }
193
+
194
+ function closeParenthesis(
195
+ state: ParenthesisState,
196
+ index: number,
197
+ ): { reason?: ODataPathStructureReason } {
198
+ if (state.stack.length === 0)
199
+ return { reason: 'path_parenthesis_is_unbalanced' };
200
+ state.stack.pop();
201
+ if (state.stack.length === 0 && state.segmentIndex === 0)
202
+ state.firstClose = index;
203
+ return {};
204
+ }
205
+
206
+ function addSeparator(
207
+ state: ParenthesisState,
208
+ separators: number[],
209
+ index: number,
210
+ ): void {
211
+ if (index === 0 || state.stack.length > 0) return;
212
+ separators.push(index);
213
+ state.segmentIndex += 1;
214
+ }
215
+
216
+ function finalizedSyntax(
217
+ raw: string,
218
+ separators: number[],
219
+ state: ParenthesisState,
220
+ queryIndex?: number,
221
+ ): SyntaxScan {
222
+ if (state.stack.length > 0)
223
+ return { separators, reason: 'path_parenthesis_is_unbalanced' };
224
+ const firstEnd = separators[0] ?? queryIndex ?? raw.length;
225
+ if (state.firstClose !== undefined
226
+ && raw.slice(state.firstClose + 1, firstEnd).trim())
227
+ return { separators, reason: 'path_parenthesis_suffix_is_invalid' };
228
+ return {
229
+ queryIndex, separators,
230
+ firstOpen: state.firstOpen, firstClose: state.firstClose,
231
+ };
232
+ }
233
+
234
+ function quotedEnd(
235
+ raw: string,
236
+ start: number,
237
+ quote: string,
238
+ placeholders: readonly PlaceholderSpan[],
239
+ initialSpanIndex: number,
240
+ ): number | undefined {
241
+ let spanIndex = initialSpanIndex;
242
+ let index = start + 1;
243
+ while (index < raw.length) {
244
+ const span = placeholders[spanIndex];
245
+ if (span?.start === index) {
246
+ index = span.end;
247
+ spanIndex += 1;
248
+ continue;
249
+ }
250
+ if (raw[index] === '\\') {
251
+ index += 2;
252
+ continue;
253
+ }
254
+ if (raw[index] !== quote) {
255
+ index += 1;
256
+ continue;
257
+ }
258
+ if (raw[index + 1] === quote) {
259
+ index += 2;
260
+ continue;
261
+ }
262
+ return index + 1;
263
+ }
264
+ return undefined;
265
+ }
266
+
267
+ function validStructure(
268
+ rawPath: string,
269
+ spans: readonly PlaceholderSpan[],
270
+ syntax: SyntaxScan,
271
+ ): ODataPathStructure {
272
+ const pathEnd = syntax.queryIndex ?? rawPath.length;
273
+ const pathWithoutQuery = rawPath.slice(0, pathEnd);
274
+ const segments = pathSegments(rawPath, pathEnd, syntax.separators);
275
+ const head = headEvidence(rawPath, segments, spans, syntax.firstOpen);
276
+ const argumentsResult = argumentEvidence(rawPath, spans, syntax);
277
+ return {
278
+ status: 'valid', rawPath, pathWithoutQuery,
279
+ queryString: queryString(rawPath, syntax.queryIndex),
280
+ queryIndex: syntax.queryIndex,
281
+ placeholderSpans: spans,
282
+ placeholderKeys: uniqueKeys(spans),
283
+ segments,
284
+ ...head,
285
+ firstParenthesisOpen: syntax.firstOpen,
286
+ firstParenthesisClose: syntax.firstClose,
287
+ ...argumentsResult,
288
+ };
289
+ }
290
+
291
+ function headEvidence(
292
+ raw: string,
293
+ segments: readonly ODataPathSegment[],
294
+ spans: readonly PlaceholderSpan[],
295
+ firstOpen: number | undefined,
296
+ ): HeadEvidence {
297
+ const firstSegment = segments[0];
298
+ if (!firstSegment)
299
+ return { firstSegmentHeadRuntimeDependent: false };
300
+ const end = firstOpen ?? firstSegment.end;
301
+ const value = raw.slice(firstSegment.start, end).trim();
302
+ return {
303
+ firstSegment,
304
+ firstSegmentHead: value || undefined,
305
+ firstSegmentHeadRuntimeDependent: spans.some((span) =>
306
+ overlaps(span, firstSegment.start, end)),
307
+ };
308
+ }
309
+
310
+ function argumentEvidence(
311
+ raw: string,
312
+ spans: readonly PlaceholderSpan[],
313
+ syntax: SyntaxScan,
314
+ ): ArgumentEvidence {
315
+ if (syntax.firstOpen === undefined || syntax.firstClose === undefined)
316
+ return { firstParenthesisPlaceholderKeys: [] };
317
+ const start = syntax.firstOpen + 1;
318
+ const end = syntax.firstClose;
319
+ return {
320
+ firstParenthesisArguments: raw.slice(start, end),
321
+ firstParenthesisPlaceholderKeys: uniqueKeys(spans.filter((span) =>
322
+ span.start >= start && span.end <= end)),
323
+ };
324
+ }
325
+
326
+ function queryString(raw: string, queryIndex: number | undefined): string | undefined {
327
+ return queryIndex === undefined ? undefined : raw.slice(queryIndex + 1);
328
+ }
329
+
330
+ function pathSegments(
331
+ raw: string,
332
+ pathEnd: number,
333
+ separators: number[],
334
+ ): ODataPathSegment[] {
335
+ const start = raw.startsWith('/') ? 1 : 0;
336
+ const boundaries = separators.filter((value) => value >= start && value < pathEnd);
337
+ const ends = [...boundaries, pathEnd];
338
+ let current = start;
339
+ const segments: ODataPathSegment[] = [];
340
+ for (const end of ends) {
341
+ const text = raw.slice(current, end);
342
+ if (text) segments.push({ text, start: current, end });
343
+ current = end + 1;
344
+ }
345
+ return segments;
346
+ }
347
+
348
+ function malformedStructure(
349
+ rawPath: string,
350
+ reason: ODataPathStructureReason,
351
+ spans: readonly PlaceholderSpan[] = [],
352
+ ): ODataPathStructure {
353
+ return {
354
+ status: 'malformed', reason, rawPath, pathWithoutQuery: rawPath,
355
+ placeholderSpans: spans, placeholderKeys: [],
356
+ segments: [], firstSegmentHeadRuntimeDependent: false,
357
+ firstParenthesisPlaceholderKeys: [],
358
+ };
359
+ }
360
+
361
+ function overlaps(
362
+ span: PlaceholderSpan,
363
+ start: number,
364
+ end: number,
365
+ ): boolean {
366
+ return span.start < end && span.end > start;
367
+ }
368
+
369
+ function uniqueKeys(spans: readonly PlaceholderSpan[]): string[] {
370
+ return [...new Set(spans.map((span) => span.key.trim()).filter(Boolean))];
371
+ }
@@ -37,10 +37,11 @@ export interface LinkWorkspaceResult {
37
37
  export function linkWorkspace(db: Db, workspaceId: number, vars: Record<string, string> = {}): LinkWorkspaceResult {
38
38
  return db.transaction(() => {
39
39
  assertWorkspaceLinkable(db, workspaceId);
40
+ linkPackageImportSymbolCalls(db, workspaceId);
41
+ assertWorkspaceLinkable(db, workspaceId, 'terminal');
40
42
  const generation = nextGraphGeneration(db, workspaceId);
41
43
  db.prepare('DELETE FROM graph_edges WHERE workspace_id=?').run(workspaceId);
42
44
  const deps = linkHelperPackages(db, workspaceId, generation);
43
- linkPackageImportSymbolCalls(db, workspaceId);
44
45
  const subscriptions = linkEventSubscriptionHandlers(
45
46
  db, workspaceId, generation,
46
47
  );