@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
|
@@ -22,6 +22,17 @@ export interface OperationResolution {
|
|
|
22
22
|
candidates: OperationTarget[];
|
|
23
23
|
reasons: string[];
|
|
24
24
|
}
|
|
25
|
+
interface OperationSignals {
|
|
26
|
+
servicePath?: string;
|
|
27
|
+
alias?: string;
|
|
28
|
+
destination?: string;
|
|
29
|
+
operationPath?: string;
|
|
30
|
+
serviceName?: string;
|
|
31
|
+
repoId?: number;
|
|
32
|
+
hasExplicitOverride?: boolean;
|
|
33
|
+
isDynamic?: boolean;
|
|
34
|
+
localServiceLookup?: string;
|
|
35
|
+
}
|
|
25
36
|
function rows(
|
|
26
37
|
db: Db,
|
|
27
38
|
operationPath: string,
|
|
@@ -60,31 +71,30 @@ function operationMatches(candidate: OperationTarget, operationPath: string | un
|
|
|
60
71
|
}
|
|
61
72
|
export function resolveOperation(
|
|
62
73
|
db: Db,
|
|
63
|
-
signals:
|
|
64
|
-
servicePath?: string;
|
|
65
|
-
alias?: string;
|
|
66
|
-
destination?: string;
|
|
67
|
-
operationPath?: string;
|
|
68
|
-
serviceName?: string;
|
|
69
|
-
repoId?: number;
|
|
70
|
-
hasExplicitOverride?: boolean;
|
|
71
|
-
isDynamic?: boolean;
|
|
72
|
-
localServiceLookup?: string;
|
|
73
|
-
},
|
|
74
|
+
signals: OperationSignals,
|
|
74
75
|
workspaceId?: number,
|
|
75
76
|
): OperationResolution {
|
|
77
|
+
const early = earlyOperationResolution(db, signals, workspaceId);
|
|
78
|
+
if (early) return early;
|
|
79
|
+
const selected = selectOperationCandidates(db, signals, workspaceId);
|
|
80
|
+
if (!Array.isArray(selected)) return selected;
|
|
81
|
+
const candidates = scoreOperationCandidates(selected, signals);
|
|
82
|
+
return finalOperationResolution(candidates, signals);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function earlyOperationResolution(
|
|
86
|
+
db: Db,
|
|
87
|
+
signals: OperationSignals,
|
|
88
|
+
workspaceId: number | undefined,
|
|
89
|
+
): OperationResolution | undefined {
|
|
76
90
|
const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath]
|
|
77
91
|
.flatMap(extractPlaceholderKeys);
|
|
78
92
|
if (missing.length > 0)
|
|
79
93
|
return {
|
|
80
94
|
status: 'dynamic',
|
|
81
|
-
candidates:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
score: 0.2,
|
|
85
|
-
reasons: ['operation_path_match'],
|
|
86
|
-
}))
|
|
87
|
-
: [],
|
|
95
|
+
candidates: dynamicOperationCandidates(
|
|
96
|
+
db, signals.operationPath, workspaceId,
|
|
97
|
+
),
|
|
88
98
|
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`),
|
|
89
99
|
};
|
|
90
100
|
if (!signals.operationPath)
|
|
@@ -93,72 +103,184 @@ export function resolveOperation(
|
|
|
93
103
|
candidates: [],
|
|
94
104
|
reasons: ['missing_operation_path'],
|
|
95
105
|
};
|
|
96
|
-
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function dynamicOperationCandidates(
|
|
110
|
+
db: Db,
|
|
111
|
+
operationPath: string | undefined,
|
|
112
|
+
workspaceId: number | undefined,
|
|
113
|
+
): OperationTarget[] {
|
|
114
|
+
if (!operationPath || extractPlaceholderKeys(operationPath).length > 0)
|
|
115
|
+
return [];
|
|
116
|
+
return rows(db, operationPath, workspaceId).map((candidate) => ({
|
|
117
|
+
...candidate,
|
|
118
|
+
score: 0.2,
|
|
119
|
+
reasons: ['operation_path_match'],
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function selectOperationCandidates(
|
|
124
|
+
db: Db,
|
|
125
|
+
signals: OperationSignals,
|
|
126
|
+
workspaceId?: number,
|
|
127
|
+
): OperationTarget[] | OperationResolution {
|
|
128
|
+
const operationPath = signals.operationPath ?? '';
|
|
129
|
+
const allCandidates = rows(db, operationPath, workspaceId).map((c) => ({
|
|
97
130
|
...c,
|
|
98
131
|
score: 0.2,
|
|
99
132
|
reasons: ['operation_path_match'],
|
|
100
133
|
}));
|
|
101
134
|
let candidates = allCandidates.filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId));
|
|
102
|
-
if (candidates.length === 0 && signals.repoId !== undefined
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
135
|
+
if (candidates.length === 0 && signals.repoId !== undefined
|
|
136
|
+
&& signals.serviceName)
|
|
137
|
+
candidates = implementationContextCandidates(
|
|
138
|
+
db, allCandidates, signals.repoId, signals.serviceName,
|
|
139
|
+
);
|
|
140
|
+
if (candidates.length === 0 && signals.repoId !== undefined
|
|
141
|
+
&& signals.serviceName)
|
|
142
|
+
return missingOwnedCandidate(allCandidates, signals.serviceName);
|
|
111
143
|
if (candidates.length === 0)
|
|
112
144
|
return {
|
|
113
145
|
status: 'unresolved',
|
|
114
146
|
candidates: [],
|
|
115
147
|
reasons: ['no_operation_candidates'],
|
|
116
148
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
149
|
+
return candidates;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function missingOwnedCandidate(
|
|
153
|
+
candidates: OperationTarget[],
|
|
154
|
+
serviceName: string,
|
|
155
|
+
): OperationResolution {
|
|
156
|
+
const matching = candidates.filter((candidate) =>
|
|
157
|
+
serviceMatches(candidate, serviceName));
|
|
158
|
+
return {
|
|
159
|
+
status: 'unresolved',
|
|
160
|
+
candidates: matching,
|
|
161
|
+
reasons: matching.length > 0
|
|
162
|
+
? ['local_service_candidate_without_caller_ownership']
|
|
163
|
+
: ['no_operation_candidates'],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function scoreOperationCandidates(
|
|
168
|
+
candidates: OperationTarget[],
|
|
169
|
+
signals: OperationSignals,
|
|
170
|
+
): OperationTarget[] {
|
|
124
171
|
for (const c of candidates) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
c.score -= 0.1;
|
|
131
|
-
c.reasons.push('service_path_mismatch');
|
|
132
|
-
}
|
|
133
|
-
if (signals.serviceName) {
|
|
134
|
-
const simple = signals.serviceName.split('.').at(-1) ?? signals.serviceName;
|
|
135
|
-
if (c.qualifiedName === signals.serviceName) {
|
|
136
|
-
c.score += 0.8;
|
|
137
|
-
c.reasons.push('exact_local_qualified_service_name');
|
|
138
|
-
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
139
|
-
c.score += 0.75;
|
|
140
|
-
c.reasons.push('exact_local_simple_service_name');
|
|
141
|
-
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
142
|
-
c.score += 0.7;
|
|
143
|
-
c.reasons.push('exact_local_service_path');
|
|
144
|
-
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
145
|
-
c.score += candidates.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
146
|
-
c.reasons.push('suffix_local_service_path');
|
|
147
|
-
} else c.reasons.push('local_service_name_mismatch');
|
|
148
|
-
}
|
|
149
|
-
if (signals.hasExplicitOverride) {
|
|
150
|
-
c.score += 0.2;
|
|
151
|
-
c.reasons.push(signals.repoId !== undefined ? 'explicit_local_service_call' : 'explicit_dynamic_override');
|
|
152
|
-
}
|
|
153
|
-
if (signals.repoId !== undefined && candidates.length === 1 && signals.serviceName && c.reasons.includes('local_service_name_mismatch') && operationMatches(c, signals.operationPath)) {
|
|
154
|
-
c.score = Math.max(c.score, 0.9);
|
|
155
|
-
c.reasons.push('same_repo_unique_operation_path_with_lookup_mismatch');
|
|
156
|
-
}
|
|
172
|
+
applyServicePathScore(c, signals.servicePath);
|
|
173
|
+
applyServiceNameScore(c, signals.serviceName, candidates);
|
|
174
|
+
applyOverrideScore(c, signals);
|
|
175
|
+
applyUniquePathRecovery(c, candidates, signals);
|
|
176
|
+
c.score = Math.max(0, Math.min(1, c.score));
|
|
157
177
|
}
|
|
158
|
-
for (const c of candidates) c.score = Math.max(0, Math.min(1, c.score));
|
|
159
178
|
candidates.sort(
|
|
160
|
-
(a, b) => b.score - a.score || a.repoName
|
|
179
|
+
(a, b) => b.score - a.score || compareBinary(a.repoName, b.repoName),
|
|
180
|
+
);
|
|
181
|
+
return candidates;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function applyServicePathScore(
|
|
185
|
+
candidate: OperationTarget,
|
|
186
|
+
servicePath: string | undefined,
|
|
187
|
+
): void {
|
|
188
|
+
if (!servicePath) return;
|
|
189
|
+
const exact = candidate.servicePath === servicePath;
|
|
190
|
+
candidate.score += exact ? 0.75 : -0.1;
|
|
191
|
+
candidate.reasons.push(exact ? 'exact_service_path' : 'service_path_mismatch');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function serviceNameScore(
|
|
195
|
+
candidate: OperationTarget,
|
|
196
|
+
serviceName: string,
|
|
197
|
+
candidates: readonly OperationTarget[],
|
|
198
|
+
): { score: number; reason: string } {
|
|
199
|
+
const simple = serviceName.split('.').at(-1) ?? serviceName;
|
|
200
|
+
if (candidate.qualifiedName === serviceName)
|
|
201
|
+
return { score: 0.8, reason: 'exact_local_qualified_service_name' };
|
|
202
|
+
if (candidate.serviceName === serviceName || candidate.serviceName === simple)
|
|
203
|
+
return { score: 0.75, reason: 'exact_local_simple_service_name' };
|
|
204
|
+
if ([serviceName, `/${serviceName}`, `/${simple}`]
|
|
205
|
+
.includes(candidate.servicePath))
|
|
206
|
+
return { score: 0.7, reason: 'exact_local_service_path' };
|
|
207
|
+
if (candidate.servicePath.endsWith(`/${simple}`)) return {
|
|
208
|
+
score: candidates.filter((item) =>
|
|
209
|
+
item.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2,
|
|
210
|
+
reason: 'suffix_local_service_path',
|
|
211
|
+
};
|
|
212
|
+
return { score: 0, reason: 'local_service_name_mismatch' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function applyServiceNameScore(
|
|
216
|
+
candidate: OperationTarget,
|
|
217
|
+
serviceName: string | undefined,
|
|
218
|
+
candidates: readonly OperationTarget[],
|
|
219
|
+
): void {
|
|
220
|
+
if (!serviceName) return;
|
|
221
|
+
const signal = serviceNameScore(candidate, serviceName, candidates);
|
|
222
|
+
candidate.score += signal.score;
|
|
223
|
+
candidate.reasons.push(signal.reason);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function applyOverrideScore(
|
|
227
|
+
candidate: OperationTarget,
|
|
228
|
+
signals: OperationSignals,
|
|
229
|
+
): void {
|
|
230
|
+
if (!signals.hasExplicitOverride) return;
|
|
231
|
+
candidate.score += 0.2;
|
|
232
|
+
candidate.reasons.push(signals.repoId !== undefined
|
|
233
|
+
? 'explicit_local_service_call' : 'explicit_dynamic_override');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function applyUniquePathRecovery(
|
|
237
|
+
candidate: OperationTarget,
|
|
238
|
+
candidates: readonly OperationTarget[],
|
|
239
|
+
signals: OperationSignals,
|
|
240
|
+
): void {
|
|
241
|
+
if (signals.repoId === undefined || candidates.length !== 1
|
|
242
|
+
|| !signals.serviceName
|
|
243
|
+
|| !candidate.reasons.includes('local_service_name_mismatch')
|
|
244
|
+
|| !operationMatches(candidate, signals.operationPath)) return;
|
|
245
|
+
candidate.score = Math.max(candidate.score, 0.9);
|
|
246
|
+
candidate.reasons.push(
|
|
247
|
+
'same_repo_unique_operation_path_with_lookup_mismatch',
|
|
161
248
|
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function strongSignal(signals: OperationSignals): boolean {
|
|
252
|
+
return Boolean(signals.servicePath || signals.serviceName || signals.alias
|
|
253
|
+
|| signals.destination || signals.hasExplicitOverride);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function targetSignalMatches(
|
|
257
|
+
candidate: OperationTarget,
|
|
258
|
+
signals: OperationSignals,
|
|
259
|
+
): boolean {
|
|
260
|
+
if (candidate.servicePath === signals.servicePath) return true;
|
|
261
|
+
if (!signals.serviceName) return false;
|
|
262
|
+
return !candidate.reasons.includes('local_service_name_mismatch')
|
|
263
|
+
|| candidate.reasons.includes(
|
|
264
|
+
'same_repo_unique_operation_path_with_lookup_mismatch',
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function bestCandidateResolves(
|
|
269
|
+
best: OperationTarget | undefined,
|
|
270
|
+
second: OperationTarget | undefined,
|
|
271
|
+
signals: OperationSignals,
|
|
272
|
+
): best is OperationTarget {
|
|
273
|
+
if (!best) return false;
|
|
274
|
+
return best.score >= 0.9
|
|
275
|
+
&& targetSignalMatches(best, signals)
|
|
276
|
+
&& operationMatches(best, signals.operationPath)
|
|
277
|
+
&& (!second || best.score - second.score >= 0.25);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function finalOperationResolution(
|
|
281
|
+
candidates: OperationTarget[],
|
|
282
|
+
signals: OperationSignals,
|
|
283
|
+
): OperationResolution {
|
|
162
284
|
const best = candidates[0];
|
|
163
285
|
const second = candidates[1];
|
|
164
286
|
if (signals.isDynamic && !signals.hasExplicitOverride && !signals.servicePath)
|
|
@@ -167,19 +289,13 @@ export function resolveOperation(
|
|
|
167
289
|
candidates,
|
|
168
290
|
reasons: ['dynamic_target_without_override'],
|
|
169
291
|
};
|
|
170
|
-
if (!
|
|
292
|
+
if (!strongSignal(signals))
|
|
171
293
|
return {
|
|
172
294
|
status: candidates.length > 1 ? 'ambiguous' : 'unresolved',
|
|
173
295
|
candidates,
|
|
174
296
|
reasons: ['operation_path_only_has_no_strong_target_signal'],
|
|
175
297
|
};
|
|
176
|
-
if (
|
|
177
|
-
best &&
|
|
178
|
-
best.score >= 0.9 &&
|
|
179
|
-
(best.servicePath === signals.servicePath || Boolean(signals.serviceName && (!best.reasons.includes('local_service_name_mismatch') || best.reasons.includes('same_repo_unique_operation_path_with_lookup_mismatch')))) &&
|
|
180
|
-
operationMatches(best, signals.operationPath) &&
|
|
181
|
-
(!second || best.score - second.score >= 0.25)
|
|
182
|
-
)
|
|
298
|
+
if (bestCandidateResolves(best, second, signals))
|
|
183
299
|
return {
|
|
184
300
|
status: 'resolved',
|
|
185
301
|
target: best,
|
|
@@ -192,6 +308,10 @@ export function resolveOperation(
|
|
|
192
308
|
reasons: ['candidate_score_below_resolution_threshold'],
|
|
193
309
|
};
|
|
194
310
|
}
|
|
311
|
+
|
|
312
|
+
function compareBinary(left: string, right: string): number {
|
|
313
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
314
|
+
}
|
|
195
315
|
function serviceMatches(candidate: OperationTarget, serviceName: string | undefined): boolean {
|
|
196
316
|
if (!serviceName) return false;
|
|
197
317
|
const simple = serviceName.split('.').at(-1) ?? serviceName;
|
|
@@ -43,6 +43,17 @@ export function queryBuilderRoot(
|
|
|
43
43
|
: undefined;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export function chainIncludesForUpdate(node: ts.Expression): boolean {
|
|
47
|
+
let current = unwrapQueryExpression(node);
|
|
48
|
+
while (ts.isCallExpression(current)
|
|
49
|
+
&& ts.isPropertyAccessExpression(current.expression)) {
|
|
50
|
+
if (isCapQueryBuilderRootName(expressionName(current.expression))) break;
|
|
51
|
+
if (current.expression.name.text === 'forUpdate') return true;
|
|
52
|
+
current = unwrapQueryExpression(current.expression.expression);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
export function directQueryBuilderStatement(
|
|
47
58
|
node: ts.CallExpression,
|
|
48
59
|
): DirectQueryBuilderStatement | undefined {
|