@saptools/service-flow 0.1.67 → 0.1.69
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 +20 -0
- package/README.md +27 -9
- package/TECHNICAL-NOTE.md +36 -0
- package/dist/chunk-3N3B5KHV.js +19596 -0
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +2645 -521
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +67 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +499 -340
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +699 -0
- package/src/db/004-package-target-invalidation.ts +183 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +464 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +210 -0
- package/src/db/009-binding-fact-semantics.ts +352 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +268 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +130 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +135 -13
- package/src/indexer/workspace-indexer.ts +237 -34
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +34 -11
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +4 -166
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +348 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +306 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +168 -0
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +162 -512
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +513 -352
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +15 -17
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +122 -38
- package/src/trace/021-compact-decision-normalization.ts +171 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +81 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +336 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/trace/trace-engine.ts +122 -624
- package/src/types.ts +57 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js +0 -12151
- 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
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
substituteVariables,
|
|
4
|
+
type RuntimeSubstitution,
|
|
5
|
+
} from './dynamic-edge-resolver.js';
|
|
6
|
+
|
|
7
|
+
export interface LinkedEventTemplate {
|
|
8
|
+
targetId: string;
|
|
9
|
+
targetKind: 'event' | 'event_candidate';
|
|
10
|
+
status: 'terminal' | 'dynamic';
|
|
11
|
+
isDynamic: boolean;
|
|
12
|
+
unresolvedReason?: string;
|
|
13
|
+
substitution: RuntimeSubstitution;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function linkEventTemplate(
|
|
17
|
+
template: string,
|
|
18
|
+
variables: Record<string, string>,
|
|
19
|
+
parserReason?: string,
|
|
20
|
+
): LinkedEventTemplate {
|
|
21
|
+
const substitution = substituteVariables(template, variables);
|
|
22
|
+
const missing = substitution.missing.length > 0;
|
|
23
|
+
const unsupportedDynamic = substitution.placeholders.length === 0
|
|
24
|
+
&& parserReason !== undefined;
|
|
25
|
+
const dynamic = missing || unsupportedDynamic;
|
|
26
|
+
return {
|
|
27
|
+
targetId: dynamic
|
|
28
|
+
? `Event: ${substitution.effective ?? template}`
|
|
29
|
+
: substitution.effective ?? template,
|
|
30
|
+
targetKind: dynamic ? 'event_candidate' : 'event',
|
|
31
|
+
status: dynamic ? 'dynamic' : 'terminal',
|
|
32
|
+
isDynamic: dynamic,
|
|
33
|
+
unresolvedReason: missing
|
|
34
|
+
? `Dynamic target requires runtime variable overrides: ${
|
|
35
|
+
substitution.missing.join(', ')}`
|
|
36
|
+
: unsupportedDynamic ? parserReason : undefined,
|
|
37
|
+
substitution,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function insertEventCallEdge(
|
|
42
|
+
db: Db,
|
|
43
|
+
workspaceId: number,
|
|
44
|
+
generation: number,
|
|
45
|
+
call: Record<string, unknown>,
|
|
46
|
+
variables: Record<string, string>,
|
|
47
|
+
evidence: Record<string, unknown>,
|
|
48
|
+
): { status: string; callType: string } {
|
|
49
|
+
const callType = String(call.call_type);
|
|
50
|
+
const event = linkEventTemplate(
|
|
51
|
+
String(call.event_name_expr ?? ''), variables,
|
|
52
|
+
typeof call.unresolved_reason === 'string'
|
|
53
|
+
? call.unresolved_reason : undefined,
|
|
54
|
+
);
|
|
55
|
+
const edgeType = event.isDynamic
|
|
56
|
+
? 'DYNAMIC_EDGE_CANDIDATE'
|
|
57
|
+
: callType === 'async_emit'
|
|
58
|
+
? 'HANDLER_EMITS_EVENT' : 'EVENT_CONSUMED_BY_HANDLER';
|
|
59
|
+
const eventEvidence = event.substitution.placeholders.length > 0
|
|
60
|
+
? { ...evidence, eventTemplateResolution: event.substitution }
|
|
61
|
+
: evidence;
|
|
62
|
+
db.prepare(`INSERT INTO graph_edges(
|
|
63
|
+
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
64
|
+
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
65
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
66
|
+
workspaceId, edgeType, event.status, 'call', String(call.id),
|
|
67
|
+
event.targetKind, event.targetId, Number(call.confidence ?? 0.2),
|
|
68
|
+
JSON.stringify(eventEvidence), event.isDynamic ? 1 : 0,
|
|
69
|
+
event.unresolvedReason ?? null, generation,
|
|
70
|
+
);
|
|
71
|
+
return { status: event.status, callType };
|
|
72
|
+
}
|