@saptools/service-flow 0.1.68 → 0.1.70
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 +14 -0
- package/README.md +11 -5
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-AEM4JY22.js → chunk-GSLFY6J2.js} +6986 -5503
- package/dist/chunk-GSLFY6J2.js.map +1 -0
- package/dist/cli.js +334 -122
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/{002-doctor-lifecycle.ts → doctor-lifecycle.ts} +1 -1
- package/src/cli/{001-doctor-projection.ts → doctor-projection.ts} +1 -1
- package/src/cli/doctor.ts +5 -5
- package/src/cli/index-summary.ts +22 -0
- package/src/cli.ts +155 -91
- package/src/db/{009-binding-fact-semantics.ts → binding-fact-semantics.ts} +6 -1
- package/src/db/binding-helper-provenance.ts +17 -0
- package/src/db/{012-binding-reference-proof.ts → binding-reference-proof.ts} +4 -0
- package/src/db/{000-call-fact-repository.ts → call-fact-repository.ts} +49 -23
- package/src/db/{003-current-fact-semantics.ts → current-fact-semantics.ts} +5 -4
- package/src/db/{001-fact-lifecycle.ts → fact-lifecycle.ts} +3 -3
- package/src/db/index-publication-failure.ts +91 -0
- package/src/db/{007-package-fact-semantics.ts → package-fact-semantics.ts} +6 -6
- package/src/db/{010-package-symbol-surface-semantics.ts → package-symbol-surface-semantics.ts} +2 -2
- package/src/db/{004-package-target-invalidation.ts → package-target-invalidation.ts} +11 -1
- package/src/db/{008-relative-fact-semantics.ts → relative-fact-semantics.ts} +9 -6
- package/src/db/{006-relative-symbol-resolution.ts → relative-symbol-resolution.ts} +28 -7
- package/src/db/repositories.ts +24 -7
- package/src/db/{011-symbol-call-semantics.ts → symbol-call-semantics.ts} +1 -1
- package/src/index.ts +3 -3
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +70 -9
- package/src/indexer/workspace-indexer.ts +142 -30
- package/src/linker/call-edge-insertion.ts +568 -0
- package/src/linker/{002-call-evidence.ts → call-evidence.ts} +2 -2
- package/src/linker/cross-repo-linker.ts +6 -169
- package/src/linker/dynamic-edge-resolver.ts +1 -1
- package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts} +32 -11
- package/src/linker/event-template-link.ts +72 -0
- package/src/linker/external-http-target.ts +1 -1
- package/src/linker/helper-package-linker.ts +1 -1
- package/src/linker/{000-implementation-candidates.ts → implementation-candidates.ts} +1 -1
- package/src/linker/{001-implementation-evidence-projection.ts → implementation-evidence-projection.ts} +1 -1
- package/src/linker/odata-path-normalizer.ts +1 -1
- package/src/linker/{005-odata-path-structure.ts → odata-path-structure.ts} +1 -1
- package/src/linker/{003-package-import-symbol-resolver.ts → package-import-symbol-resolver.ts} +3 -3
- package/src/linker/service-resolver.ts +2 -2
- package/src/output/{001-compact-json-output.ts → compact-json-output.ts} +1 -1
- package/src/parsers/{006-binding-identity.ts → binding-identity.ts} +9 -4
- package/src/parsers/{011-binding-lexical-scope.ts → binding-lexical-scope.ts} +1 -1
- package/src/parsers/{021-binding-visibility.ts → binding-visibility.ts} +18 -2
- package/src/parsers/{000-direct-query-execution.ts → direct-query-execution.ts} +11 -0
- package/src/parsers/{005-event-subscription-facts.ts → event-subscription-facts.ts} +2 -2
- package/src/parsers/{016-local-symbol-reference.ts → local-symbol-reference.ts} +2 -2
- package/src/parsers/operation-path-analysis.ts +1 -1
- package/src/parsers/outbound-call-classifier.ts +692 -0
- package/src/parsers/outbound-call-parser.ts +146 -509
- package/src/parsers/outbound-expression-analysis.ts +700 -0
- package/src/parsers/{018-package-commonjs-syntax.ts → package-commonjs-syntax.ts} +1 -1
- package/src/parsers/{012-package-fact-contract.ts → package-fact-contract.ts} +3 -3
- package/src/parsers/{010-package-public-surface-analysis.ts → package-public-surface-analysis.ts} +2 -2
- package/src/parsers/{003-package-public-surface.ts → package-public-surface.ts} +3 -3
- package/src/parsers/{008-package-surface-publication.ts → package-surface-publication.ts} +1 -1
- package/src/parsers/{001-query-entity-resolution.ts → query-entity-resolution.ts} +1 -1
- package/src/parsers/{015-service-binding-collector.ts → service-binding-collector.ts} +3 -3
- package/src/parsers/{014-service-binding-helper-flow.ts → service-binding-helper-flow.ts} +72 -6
- package/src/parsers/service-binding-parser-helpers.ts +1 -1
- package/src/parsers/service-binding-parser.ts +2 -2
- package/src/parsers/{007-source-fact-reconciliation.ts → source-fact-reconciliation.ts} +3 -3
- package/src/parsers/{020-stable-local-value.ts → stable-local-value.ts} +2 -2
- package/src/parsers/{009-symbol-call-facts.ts → symbol-call-facts.ts} +3 -3
- package/src/parsers/{017-symbol-derived-contexts.ts → symbol-derived-contexts.ts} +2 -2
- package/src/parsers/symbol-parser.ts +43 -11
- package/src/trace/{014-compact-contract.ts → compact-contract.ts} +6 -0
- package/src/trace/{021-compact-decision-normalization.ts → compact-decision-normalization.ts} +77 -11
- package/src/trace/{020-compact-field-projection.ts → compact-field-projection.ts} +77 -4
- package/src/trace/{024-compact-observation-decision.ts → compact-observation-decision.ts} +10 -5
- package/src/trace/{016-compact-projector.ts → compact-projector.ts} +6 -7
- package/src/trace/{018-compact-trace.ts → compact-trace.ts} +3 -3
- package/src/trace/{006-contextual-projection.ts → contextual-projection.ts} +1 -1
- package/src/trace/{008-contextual-runtime-state.ts → contextual-runtime-state.ts} +1 -1
- package/src/trace/{001-dynamic-identity.ts → dynamic-identity.ts} +2 -2
- package/src/trace/{003-dynamic-references.ts → dynamic-references.ts} +2 -2
- package/src/trace/dynamic-targets.ts +6 -6
- package/src/trace/event-runtime-resolution.ts +151 -0
- package/src/trace/{011-event-subscriber-traversal.ts → event-subscriber-traversal.ts} +101 -9
- package/src/trace/evidence.ts +2 -2
- package/src/trace/implementation-hints.ts +10 -7
- package/src/trace/{005-implementation-selection.ts → implementation-selection.ts} +2 -2
- package/src/trace/{007-implementation-start-diagnostic.ts → implementation-start-diagnostic.ts} +1 -0
- package/src/trace/local-call-expansion.ts +37 -0
- package/src/trace/{009-selected-handler-provenance.ts → selected-handler-provenance.ts} +1 -1
- package/src/trace/selectors.ts +2 -1
- package/src/trace/{017-trace-context.ts → trace-context.ts} +2 -2
- package/src/trace/{015-trace-edge-recorder.ts → trace-edge-recorder.ts} +62 -5
- package/src/trace/{019-trace-edge-semantics.ts → trace-edge-semantics.ts} +9 -13
- package/src/trace/trace-engine.ts +10 -10
- package/src/trace/{022-trace-fact-preflight.ts → trace-fact-preflight.ts} +2 -2
- package/src/trace/{025-trace-implementation-scope.ts → trace-implementation-scope.ts} +1 -1
- package/src/trace/{028-trace-operation-execution.ts → trace-operation-execution.ts} +10 -10
- package/src/trace/{013-trace-root-scopes.ts → trace-root-scopes.ts} +5 -6
- package/src/trace/{027-trace-scope-execution.ts → trace-scope-execution.ts} +44 -44
- package/src/trace/{029-trace-start-implementation.ts → trace-start-implementation.ts} +7 -7
- package/src/trace/{026-trace-start-scope.ts → trace-start-scope.ts} +6 -5
- package/src/trace/{010-traversal-scope.ts → traversal-scope.ts} +1 -1
- package/src/types.ts +2 -1
- package/src/version.ts +1 -1
- package/dist/chunk-AEM4JY22.js.map +0 -1
- /package/src/cli/{000-clean.ts → clean.ts} +0 -0
- /package/src/cli/{003-doctor-package-resolution.ts → doctor-package-resolution.ts} +0 -0
- /package/src/db/{002-fact-json-inventory.ts → fact-json-inventory.ts} +0 -0
- /package/src/db/{005-schema-structure.ts → schema-structure.ts} +0 -0
- /package/src/output/{000-stdout-policy.ts → stdout-policy.ts} +0 -0
- /package/src/parsers/{019-binding-assignment-targets.ts → binding-assignment-targets.ts} +0 -0
- /package/src/parsers/{013-executable-body-eligibility.ts → executable-body-eligibility.ts} +0 -0
- /package/src/parsers/{004-fact-identity.ts → fact-identity.ts} +0 -0
- /package/src/parsers/{002-symbol-import-bindings.ts → symbol-import-bindings.ts} +0 -0
- /package/src/trace/{004-dynamic-candidate-sources.ts → dynamic-candidate-sources.ts} +0 -0
- /package/src/trace/{000-dynamic-target-types.ts → dynamic-target-types.ts} +0 -0
- /package/src/trace/{023-nested-event-scopes.ts → nested-event-scopes.ts} +0 -0
- /package/src/trace/{002-trace-diagnostics.ts → trace-diagnostics.ts} +0 -0
- /package/src/trace/{012-trace-graph-lookups.ts → trace-graph-lookups.ts} +0 -0
- /package/src/utils/{000-bounded-projection.ts → bounded-projection.ts} +0 -0
- /package/src/utils/{001-placeholders.ts → placeholders.ts} +0 -0
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
|
-
import { applyVariables } from './dynamic-edge-resolver.js';
|
|
3
|
-
import { classifyODataPathIntent, normalizeODataOperationInvocationPath } from './odata-path-normalizer.js';
|
|
4
|
-
import { buildRemoteQueryTarget } from './remote-query-target.js';
|
|
5
|
-
import { resolveOperation } from './service-resolver.js';
|
|
6
2
|
import { linkHelperPackages } from './helper-package-linker.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
objectJson,
|
|
13
|
-
objectValue,
|
|
14
|
-
} from './002-call-evidence.js';
|
|
15
|
-
import { linkPackageImportSymbolCalls } from './003-package-import-symbol-resolver.js';
|
|
16
|
-
import { linkEventSubscriptionHandlers } from './004-event-subscription-handler-linker.js';
|
|
17
|
-
import { assertWorkspaceLinkable } from '../db/001-fact-lifecycle.js';
|
|
3
|
+
import { linkImplementations as linkCanonicalImplementations } from './implementation-candidates.js';
|
|
4
|
+
import { linkPackageImportSymbolCalls } from './package-import-symbol-resolver.js';
|
|
5
|
+
import { linkEventSubscriptionHandlers } from './event-subscription-handler-linker.js';
|
|
6
|
+
import { insertCallEdge } from './call-edge-insertion.js';
|
|
7
|
+
import { assertWorkspaceLinkable } from '../db/fact-lifecycle.js';
|
|
18
8
|
export interface LinkWorkspaceResult {
|
|
19
9
|
edgeCount: number;
|
|
20
10
|
unresolvedCount: number;
|
|
@@ -43,7 +33,7 @@ export function linkWorkspace(db: Db, workspaceId: number, vars: Record<string,
|
|
|
43
33
|
db.prepare('DELETE FROM graph_edges WHERE workspace_id=?').run(workspaceId);
|
|
44
34
|
const deps = linkHelperPackages(db, workspaceId, generation);
|
|
45
35
|
const subscriptions = linkEventSubscriptionHandlers(
|
|
46
|
-
db, workspaceId, generation,
|
|
36
|
+
db, workspaceId, generation, vars,
|
|
47
37
|
);
|
|
48
38
|
const impl = linkCanonicalImplementations(db, workspaceId, generation);
|
|
49
39
|
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
@@ -89,156 +79,3 @@ function linkCalls(db: Db, workspaceId: number, vars: Record<string, string>, ge
|
|
|
89
79
|
}
|
|
90
80
|
return { edgeCount, unresolvedCount, resolvedCount, remoteResolvedCount, localResolvedCount, ambiguousCount, dynamicCount, terminalCount };
|
|
91
81
|
}
|
|
92
|
-
function insertCallEdge(db: Db, workspaceId: number, call: Record<string, unknown>, vars: Record<string, string>, generation: number): { status: string; callType: string } {
|
|
93
|
-
const callType = String(call.call_type);
|
|
94
|
-
const rawOp = applyVariables(String(call.operation_path_expr ?? ''), vars);
|
|
95
|
-
const intent = classifyODataPathIntent(rawOp, call.method as string | undefined);
|
|
96
|
-
const isEntityQueryIntent = ['entity_query', 'entity_key_read', 'entity_navigation_query'].includes(intent.kind);
|
|
97
|
-
const resolutionRawOp = callType === 'remote_query' && isEntityQueryIntent ? intent.pathWithoutQuery : rawOp;
|
|
98
|
-
const normalized = normalizeODataOperationInvocationPath(resolutionRawOp);
|
|
99
|
-
const op = normalized?.normalizedOperationPath ?? resolutionRawOp;
|
|
100
|
-
const servicePath = applyVariables((call.servicePathExpr as string | undefined) ?? (call.requireServicePath as string | undefined), vars);
|
|
101
|
-
const destination = (call.destinationExpr as string | undefined) ?? (call.requireDestination as string | undefined);
|
|
102
|
-
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
103
|
-
const isRemoteEntityCall = callType.startsWith('remote_entity_');
|
|
104
|
-
const indexedOperationCandidateCount = operationCandidateCount(db, workspaceId, op, intent.topLevelOperationName);
|
|
105
|
-
const credibleOperationSignal = Boolean(normalized?.wasInvocation) || (Boolean(intent.topLevelOperationNameCandidate) && indexedOperationCandidateCount > 0);
|
|
106
|
-
const strongEntitySignal = ['entity_media', 'entity_delete', 'entity_key_read', 'entity_navigation_query'].includes(intent.kind) || (intent.kind === 'entity_mutation' && (intent.hasEntityKeyPredicate || intent.hasNavigationSuffix));
|
|
107
|
-
const operationLikeRemoteEntity = isRemoteEntityCall && Boolean(op) && credibleOperationSignal && (!strongEntitySignal || indexedOperationCandidateCount > 0);
|
|
108
|
-
const isOperationCall = operationLikeRemoteEntity || ((callType === 'remote_action' || callType === 'local_service_call') || (callType === 'remote_query' && Boolean(op)));
|
|
109
|
-
const resolution = isOperationCall ? resolveOperation(db, { servicePath, operationPath: op, serviceName: call.local_service_name as string | undefined, repoId: callType === 'local_service_call' ? Number(call.repo_id) : undefined, alias: applyVariables((call.aliasExpr as string | undefined) ?? (call.alias as string | undefined), vars), destination: destination ? applyVariables(destination, vars) : undefined, isDynamic, hasExplicitOverride: Object.keys(vars).length > 0 || callType === 'local_service_call' }, workspaceId) : { status: 'unresolved' as const, candidates: [], reasons: [] };
|
|
110
|
-
const evidence: Record<string, unknown> = {
|
|
111
|
-
...linkedCallEvidence(
|
|
112
|
-
call,
|
|
113
|
-
resolution,
|
|
114
|
-
servicePath,
|
|
115
|
-
op,
|
|
116
|
-
destination ? applyVariables(destination, vars) : undefined,
|
|
117
|
-
normalized,
|
|
118
|
-
intent,
|
|
119
|
-
),
|
|
120
|
-
indexedOperationCandidateCount,
|
|
121
|
-
parserCallType: callType,
|
|
122
|
-
entityOperationPrecedence: operationPrecedence(
|
|
123
|
-
callType,
|
|
124
|
-
intent,
|
|
125
|
-
indexedOperationCandidateCount,
|
|
126
|
-
Boolean(resolution.target),
|
|
127
|
-
),
|
|
128
|
-
};
|
|
129
|
-
const pathAnalysis = objectValue(objectJson(call.evidence_json)?.pathAnalysis);
|
|
130
|
-
if (callType === 'remote_action' && pathAnalysis?.status === 'ambiguous') {
|
|
131
|
-
const candidatePaths = ambiguousPathCandidates(pathAnalysis);
|
|
132
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)').run(
|
|
133
|
-
workspaceId,
|
|
134
|
-
'UNRESOLVED_EDGE',
|
|
135
|
-
'ambiguous',
|
|
136
|
-
'call',
|
|
137
|
-
String(call.id),
|
|
138
|
-
'operation_candidates',
|
|
139
|
-
candidatePaths.items.join(','),
|
|
140
|
-
Number(call.confidence ?? 0.5),
|
|
141
|
-
JSON.stringify({
|
|
142
|
-
...evidence,
|
|
143
|
-
ambiguousOperationPathCandidateCount: candidatePaths.totalCount,
|
|
144
|
-
shownAmbiguousOperationPathCandidateCount: candidatePaths.shownCount,
|
|
145
|
-
omittedAmbiguousOperationPathCandidateCount: candidatePaths.omittedCount,
|
|
146
|
-
}),
|
|
147
|
-
0,
|
|
148
|
-
'Ambiguous operation path candidates require explicit disambiguation',
|
|
149
|
-
generation,
|
|
150
|
-
);
|
|
151
|
-
return { status: 'ambiguous', callType };
|
|
152
|
-
}
|
|
153
|
-
if (isRemoteEntityCall && (resolution.target || resolution.candidates.length > 0 || resolution.status === 'dynamic')) {
|
|
154
|
-
if (resolution.target) {
|
|
155
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, 'REMOTE_CALL_RESOLVES_TO_OPERATION', 'resolved', 'call', String(call.id), 'operation', String(resolution.target.operationId), resolution.target.score, JSON.stringify({ ...evidence, operationEntityPrecedence: 'indexed_operation_over_parser_entity' }), 0, generation);
|
|
156
|
-
return { status: 'resolved', callType };
|
|
157
|
-
}
|
|
158
|
-
const status = resolution.status === 'dynamic' ? 'dynamic' : resolution.status === 'ambiguous' ? 'ambiguous' : 'unresolved';
|
|
159
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, status === 'dynamic' ? 'DYNAMIC_EDGE_CANDIDATE' : 'UNRESOLVED_EDGE', status, 'call', String(call.id), 'operation_candidate', op ? `Remote action: ${op}` : 'Remote action: unknown path', Number(call.confidence ?? 0.2), JSON.stringify({ ...evidence, operationEntityPrecedence: resolution.candidates.length > 0 ? 'parser_entity_with_indexed_operation_candidates' : 'parser_entity_operation_candidate_without_indexed_match' }), status === 'dynamic' ? 1 : 0, unresolvedOperationReason(resolution), generation);
|
|
160
|
-
return { status, callType };
|
|
161
|
-
}
|
|
162
|
-
if (isRemoteEntityCall) {
|
|
163
|
-
const target = buildRemoteQueryTarget({ queryEntity: intent.entitySegment ?? call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : undefined, isDynamic, parserWarning: evidence.parserWarning });
|
|
164
|
-
const entityKind = callType;
|
|
165
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, 'HANDLER_ACCESSES_REMOTE_ENTITY', 'terminal', 'call', String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence, remoteEntityAccess: entityKind }), 0, generation);
|
|
166
|
-
return { status: 'terminal', callType };
|
|
167
|
-
}
|
|
168
|
-
if (callType === 'remote_query' && (isEntityQueryIntent || !op) && !resolution.target) {
|
|
169
|
-
const target = buildRemoteQueryTarget({ queryEntity: call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : undefined, isDynamic, parserWarning: evidence.parserWarning });
|
|
170
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, 'HANDLER_RUNS_REMOTE_QUERY', 'terminal', 'call', String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence }), 0, generation);
|
|
171
|
-
return { status: 'terminal', callType };
|
|
172
|
-
}
|
|
173
|
-
if (callType === 'local_service_call' && call.unresolved_reason === 'transport_client_method' && !resolution.target && resolution.candidates.length === 0) {
|
|
174
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, 'HANDLER_CALLS_TRANSPORT_METHOD', 'terminal', 'call', String(call.id), 'transport_method', String(op || 'transport_client_method'), Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, classification: 'transport_client_method' }), 0, generation);
|
|
175
|
-
return { status: 'terminal', callType };
|
|
176
|
-
}
|
|
177
|
-
if (resolution.target) {
|
|
178
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, callType === 'local_service_call' ? 'LOCAL_CALL_RESOLVES_TO_OPERATION' : 'REMOTE_CALL_RESOLVES_TO_OPERATION', 'resolved', 'call', String(call.id), 'operation', String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence), 0, generation);
|
|
179
|
-
return { status: 'resolved', callType };
|
|
180
|
-
}
|
|
181
|
-
const edgeType = callType === 'local_db_query' ? 'HANDLER_RUNS_DB_QUERY' : callType === 'external_http' ? 'HANDLER_CALLS_EXTERNAL_HTTP' : callType === 'async_emit' ? 'HANDLER_EMITS_EVENT' : callType === 'async_subscribe' ? 'EVENT_CONSUMED_BY_HANDLER' : resolution.status === 'dynamic' ? 'DYNAMIC_EDGE_CANDIDATE' : 'UNRESOLVED_EDGE';
|
|
182
|
-
const status = edgeType === 'DYNAMIC_EDGE_CANDIDATE' ? 'dynamic' : resolution.status === 'ambiguous' ? 'ambiguous' : edgeType === 'UNRESOLVED_EDGE' ? 'unresolved' : 'terminal';
|
|
183
|
-
const unresolvedReason = status === 'terminal' ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
|
|
184
|
-
const externalTarget = callType === 'external_http' ? externalHttpTarget(call) : undefined;
|
|
185
|
-
const targetKind = callType === 'local_db_query' ? 'db_entity' : callType.startsWith('async_') ? 'event' : callType === 'external_http' ? (externalTarget?.toKind ?? 'external_endpoint') : 'operation_candidate';
|
|
186
|
-
const targetId = callType === 'local_db_query' ? String(call.query_entity ?? 'unknown') : callType === 'remote_action' ? (op ? `Remote action: ${op}` : (call.unresolved_reason === 'dynamic_operation_path_identifier' ? 'Remote action: dynamic path' : 'Remote action: unknown path')) : callType === 'external_http' ? String(externalTarget?.toId ?? 'unknown') : String(call.event_name_expr ?? op ?? 'unknown');
|
|
187
|
-
const graphLevelDynamic = edgeType === 'DYNAMIC_EDGE_CANDIDATE' && resolution.status === 'dynamic';
|
|
188
|
-
const finalEvidence = externalTarget ? { ...evidence, externalTarget } : evidence;
|
|
189
|
-
db.prepare('INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)').run(workspaceId, edgeType, status, 'call', String(call.id), targetKind, targetId, Number(call.confidence ?? 0.2), JSON.stringify(finalEvidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
|
|
190
|
-
return { status, callType };
|
|
191
|
-
}
|
|
192
|
-
function operationCandidateCount(db: Db, workspaceId: number, operationPath: string | undefined, operationName: string | undefined): number {
|
|
193
|
-
if (!operationPath && !operationName) return 0;
|
|
194
|
-
const normalizedName = operationName ?? operationPath?.replace(/^\//, '').split('.').at(-1);
|
|
195
|
-
const row = db.prepare(`SELECT COUNT(*) count FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_path=? OR o.operation_path=? OR o.operation_name=?)`).get(workspaceId, operationPath, normalizedName ? `/${normalizedName}` : operationPath, normalizedName) as { count?: number } | undefined;
|
|
196
|
-
return Number(row?.count ?? 0);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function operationPrecedence(
|
|
200
|
-
callType: string,
|
|
201
|
-
intent: ReturnType<typeof classifyODataPathIntent>,
|
|
202
|
-
indexedOperationCandidateCount: number,
|
|
203
|
-
resolvedOperation: boolean,
|
|
204
|
-
): Record<string, unknown> {
|
|
205
|
-
if (resolvedOperation) {
|
|
206
|
-
return {
|
|
207
|
-
decision: 'operation',
|
|
208
|
-
reason: 'indexed_operation_with_strong_service_context',
|
|
209
|
-
indexedOperationCandidateCount,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (callType === 'remote_action' && intent.kind === 'operation_invocation') {
|
|
213
|
-
return {
|
|
214
|
-
decision: 'operation_candidate',
|
|
215
|
-
rejectionReason: indexedOperationCandidateCount > 0
|
|
216
|
-
? 'indexed_candidates_lack_unique_strong_service_context'
|
|
217
|
-
: 'no_indexed_operation_candidate',
|
|
218
|
-
indexedOperationCandidateCount,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
if (intent.kind.startsWith('entity_')) {
|
|
222
|
-
return {
|
|
223
|
-
decision: 'entity',
|
|
224
|
-
rejectionReason: indexedOperationCandidateCount > 0
|
|
225
|
-
? 'entity_shape_has_precedence_without_resolved_operation_context'
|
|
226
|
-
: 'entity_shape_has_no_indexed_operation_evidence',
|
|
227
|
-
indexedOperationCandidateCount,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
decision: 'unresolved',
|
|
232
|
-
rejectionReason: 'path_has_no_safe_entity_or_operation_precedence',
|
|
233
|
-
indexedOperationCandidateCount,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function unresolvedOperationReason(resolution: { candidates: unknown[]; status: string; reasons: string[] }): string {
|
|
238
|
-
if (resolution.status === 'dynamic') return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ['missing runtime variables']).join(', ')}`;
|
|
239
|
-
if (resolution.candidates.length === 0) return 'No indexed target operation matched';
|
|
240
|
-
if (resolution.reasons.includes('operation_path_only_has_no_strong_target_signal')) return 'Operation candidates found but no strong service signal is available';
|
|
241
|
-
if (resolution.reasons.includes('candidate_score_below_resolution_threshold')) return 'Operation candidates found but resolution score is below threshold';
|
|
242
|
-
if (resolution.status === 'ambiguous') return 'Ambiguous operation candidates require a strong service signal';
|
|
243
|
-
return 'Operation candidates found but resolution could not select a target';
|
|
244
|
-
}
|
package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts}
RENAMED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
linkEventTemplate,
|
|
4
|
+
type LinkedEventTemplate,
|
|
5
|
+
} from './event-template-link.js';
|
|
2
6
|
|
|
3
7
|
export interface SubscriptionHandlerLinkSummary {
|
|
4
8
|
edgeCount: number;
|
|
@@ -20,6 +24,7 @@ interface SubscriptionRow {
|
|
|
20
24
|
startOffset?: number | null;
|
|
21
25
|
endOffset?: number | null;
|
|
22
26
|
confidence: number;
|
|
27
|
+
unresolvedReason?: string | null;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
interface HandlerCallRow {
|
|
@@ -58,7 +63,7 @@ function subscriptionRows(db: Db, workspaceId: number): SubscriptionRow[] {
|
|
|
58
63
|
c.source_symbol_id sourceSymbolId,c.event_name_expr eventName,
|
|
59
64
|
c.source_file sourceFile,c.source_line sourceLine,
|
|
60
65
|
c.call_site_start_offset startOffset,c.call_site_end_offset endOffset,
|
|
61
|
-
c.confidence
|
|
66
|
+
c.confidence,c.unresolved_reason unresolvedReason
|
|
62
67
|
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
63
68
|
WHERE r.workspace_id=? AND c.call_type='async_subscribe'
|
|
64
69
|
AND json_extract(c.evidence_json,'$.handlerReferenceStatus')
|
|
@@ -202,11 +207,16 @@ function missingAssociation(
|
|
|
202
207
|
function evidenceFor(
|
|
203
208
|
subscription: SubscriptionRow,
|
|
204
209
|
association: HandlerAssociation,
|
|
210
|
+
event: LinkedEventTemplate,
|
|
205
211
|
): Record<string, unknown> {
|
|
206
212
|
const call: Partial<HandlerCallRow> = association.call ?? {};
|
|
207
213
|
const symbolCallReason = boundedSymbolCallReason(call.unresolvedReason);
|
|
208
214
|
return {
|
|
209
215
|
eventName: subscription.eventName,
|
|
216
|
+
...(event.substitution.placeholders.length > 0 ? {
|
|
217
|
+
effectiveEventName: event.targetId,
|
|
218
|
+
eventTemplateResolution: event.substitution,
|
|
219
|
+
} : {}),
|
|
210
220
|
associationBasis: 'exact_subscription_call_span',
|
|
211
221
|
dispatchScope: 'workspace_event_name_only',
|
|
212
222
|
subscribeCallId: subscription.id,
|
|
@@ -253,22 +263,27 @@ function insertAssociationEdge(
|
|
|
253
263
|
generation: number,
|
|
254
264
|
subscription: SubscriptionRow,
|
|
255
265
|
association: HandlerAssociation,
|
|
266
|
+
event: LinkedEventTemplate,
|
|
256
267
|
): void {
|
|
268
|
+
const status = event.isDynamic ? 'unresolved' : association.status;
|
|
269
|
+
const reason = event.isDynamic
|
|
270
|
+
? 'event_template_variables_missing'
|
|
271
|
+
: association.reasonCode ?? association.call?.unresolvedReason ?? null;
|
|
257
272
|
db.prepare(`INSERT INTO graph_edges(
|
|
258
273
|
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
259
274
|
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
260
275
|
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
261
276
|
workspaceId,
|
|
262
277
|
'EVENT_SUBSCRIPTION_HANDLED_BY',
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
278
|
+
status,
|
|
279
|
+
event.targetKind,
|
|
280
|
+
event.targetId,
|
|
266
281
|
association.toKind,
|
|
267
282
|
association.toId,
|
|
268
283
|
association.call?.confidence ?? subscription.confidence,
|
|
269
|
-
JSON.stringify(evidenceFor(subscription, association)),
|
|
270
|
-
0,
|
|
271
|
-
|
|
284
|
+
JSON.stringify(evidenceFor(subscription, association, event)),
|
|
285
|
+
event.isDynamic ? 1 : 0,
|
|
286
|
+
reason,
|
|
272
287
|
generation,
|
|
273
288
|
);
|
|
274
289
|
}
|
|
@@ -277,6 +292,7 @@ export function linkEventSubscriptionHandlers(
|
|
|
277
292
|
db: Db,
|
|
278
293
|
workspaceId: number,
|
|
279
294
|
generation: number,
|
|
295
|
+
variables: Record<string, string> = {},
|
|
280
296
|
): SubscriptionHandlerLinkSummary {
|
|
281
297
|
const summary: SubscriptionHandlerLinkSummary = {
|
|
282
298
|
edgeCount: 0,
|
|
@@ -286,16 +302,21 @@ export function linkEventSubscriptionHandlers(
|
|
|
286
302
|
missingAssociationCount: 0,
|
|
287
303
|
};
|
|
288
304
|
for (const subscription of subscriptionRows(db, workspaceId)) {
|
|
305
|
+
const event = linkEventTemplate(
|
|
306
|
+
subscription.eventName, variables,
|
|
307
|
+
subscription.unresolvedReason ?? undefined,
|
|
308
|
+
);
|
|
289
309
|
const association = associationFor(
|
|
290
310
|
subscription, roleSiteRows(db, subscription),
|
|
291
311
|
);
|
|
292
312
|
insertAssociationEdge(
|
|
293
|
-
db, workspaceId, generation, subscription, association,
|
|
313
|
+
db, workspaceId, generation, subscription, association, event,
|
|
294
314
|
);
|
|
315
|
+
const status = event.isDynamic ? 'unresolved' : association.status;
|
|
295
316
|
summary.edgeCount += 1;
|
|
296
|
-
summary.resolvedCount +=
|
|
297
|
-
summary.ambiguousCount +=
|
|
298
|
-
summary.unresolvedCount +=
|
|
317
|
+
summary.resolvedCount += status === 'resolved' ? 1 : 0;
|
|
318
|
+
summary.ambiguousCount += status === 'ambiguous' ? 1 : 0;
|
|
319
|
+
summary.unresolvedCount += status === 'unresolved' ? 1 : 0;
|
|
299
320
|
summary.missingAssociationCount += association.missing ? 1 : 0;
|
|
300
321
|
}
|
|
301
322
|
return summary;
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { projectBounded } from '../utils/
|
|
2
|
+
import { projectBounded } from '../utils/bounded-projection.js';
|
|
3
3
|
|
|
4
4
|
export type ExternalTargetKind = 'destination' | 'static_url' | 'url_expression' | 'unknown';
|
|
5
5
|
export interface ExternalHttpTarget { kind: ExternalTargetKind; toKind: 'external_destination' | 'external_endpoint'; toId: string; label: string; method?: string; dynamic: boolean; expression?: string; candidateLiteralCount?: number; shownCandidateLiteralCount?: number; omittedCandidateLiteralCount?: number; }
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
boundedImplementationTargetIds,
|
|
7
7
|
displayImplementationCandidates,
|
|
8
8
|
selectedHandlerProvenance,
|
|
9
|
-
} from './
|
|
9
|
+
} from './implementation-evidence-projection.js';
|
|
10
10
|
|
|
11
11
|
interface ImplementationCandidate extends Record<string, unknown> {
|
|
12
12
|
methodId: number;
|
package/src/linker/{003-package-import-symbol-resolver.ts → package-import-symbol-resolver.ts}
RENAMED
|
@@ -3,14 +3,14 @@ import type {
|
|
|
3
3
|
PackagePublicScope,
|
|
4
4
|
PackagePublicSurfaceFact,
|
|
5
5
|
PublicSurfaceTarget,
|
|
6
|
-
} from '../parsers/
|
|
6
|
+
} from '../parsers/package-public-surface.js';
|
|
7
7
|
import type {
|
|
8
8
|
SymbolImportReference,
|
|
9
|
-
} from '../parsers/
|
|
9
|
+
} from '../parsers/symbol-import-bindings.js';
|
|
10
10
|
import {
|
|
11
11
|
parsePackageImportReference,
|
|
12
12
|
parsePackagePublicSurfaceFact,
|
|
13
|
-
} from '../parsers/
|
|
13
|
+
} from '../parsers/package-fact-contract.js';
|
|
14
14
|
|
|
15
15
|
export interface PackageSymbolLinkSummary {
|
|
16
16
|
resolved: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
|
-
import { extractPlaceholderKeys } from '../utils/
|
|
3
|
-
import { canonicalImplementationEvidence } from './
|
|
2
|
+
import { extractPlaceholderKeys } from '../utils/placeholders.js';
|
|
3
|
+
import { canonicalImplementationEvidence } from './implementation-candidates.js';
|
|
4
4
|
export interface OperationTarget {
|
|
5
5
|
operationId: number;
|
|
6
6
|
repoName: string;
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
import {
|
|
10
10
|
executableSymbolCandidates,
|
|
11
11
|
selectCallOwner,
|
|
12
|
-
} from './
|
|
12
|
+
} from './fact-identity.js';
|
|
13
13
|
import {
|
|
14
14
|
bindingSite,
|
|
15
15
|
createBindingLexicalIndex,
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
type BindingLexicalIndex,
|
|
18
18
|
type BindingLexicalSite,
|
|
19
19
|
type VisibleBinding,
|
|
20
|
-
} from './
|
|
21
|
-
import { selectVisibleBinding } from './
|
|
20
|
+
} from './binding-lexical-scope.js';
|
|
21
|
+
import { selectVisibleBinding } from './binding-visibility.js';
|
|
22
22
|
|
|
23
23
|
const scopeChainCap = 16;
|
|
24
24
|
|
|
@@ -230,6 +230,9 @@ function resolvedReference(
|
|
|
230
230
|
chain: LexicalScopeFact[],
|
|
231
231
|
scopeIndex: number,
|
|
232
232
|
): ServiceBindingReference {
|
|
233
|
+
const helperReturn = selected.fact.helperChain?.some(
|
|
234
|
+
(step) => step.bindingOrigin === 'single_hop_helper_return',
|
|
235
|
+
) ?? false;
|
|
233
236
|
return {
|
|
234
237
|
status: 'resolved_exact',
|
|
235
238
|
variableName: call.serviceVariableName,
|
|
@@ -240,7 +243,9 @@ function resolvedReference(
|
|
|
240
243
|
? 'deterministic_reaching_assignment'
|
|
241
244
|
: selected.site.aliasSource
|
|
242
245
|
? 'lexical_alias_declaration'
|
|
243
|
-
:
|
|
246
|
+
: helperReturn
|
|
247
|
+
? 'single_hop_helper_return'
|
|
248
|
+
: 'lexical_declaration',
|
|
244
249
|
lexicalScopeChain: chain,
|
|
245
250
|
bindingScopeIndex: scopeIndex,
|
|
246
251
|
scopeChainTotal: chain.length,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type BindingLexicalSite,
|
|
11
11
|
type BindingSiteCandidate,
|
|
12
12
|
type VisibleBinding,
|
|
13
|
-
} from './
|
|
13
|
+
} from './binding-lexical-scope.js';
|
|
14
14
|
|
|
15
15
|
function reachingSites(
|
|
16
16
|
index: BindingLexicalIndex,
|
|
@@ -72,6 +72,20 @@ function candidatesAtSite<T>(
|
|
|
72
72
|
&& candidate.bindingSiteEndOffset === site.endOffset);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function helperCallDeclaration(site: BindingLexicalSite): boolean {
|
|
76
|
+
if (!ts.isVariableDeclaration(site.node) || !site.node.initializer)
|
|
77
|
+
return false;
|
|
78
|
+
let expression = site.node.initializer;
|
|
79
|
+
while (ts.isAwaitExpression(expression)
|
|
80
|
+
|| ts.isParenthesizedExpression(expression)
|
|
81
|
+
|| ts.isAsExpression(expression)
|
|
82
|
+
|| ts.isSatisfiesExpression(expression)
|
|
83
|
+
|| ts.isTypeAssertionExpression(expression))
|
|
84
|
+
expression = expression.expression;
|
|
85
|
+
return ts.isCallExpression(expression)
|
|
86
|
+
&& ts.isIdentifier(expression.expression);
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
function compatibleCaseClause(
|
|
76
90
|
site: BindingLexicalSite,
|
|
77
91
|
useNode: ts.Node,
|
|
@@ -108,7 +122,9 @@ function matchedBinding<T>(
|
|
|
108
122
|
status: 'unresolved',
|
|
109
123
|
reason: site.flow === 'assignment'
|
|
110
124
|
? 'unsupported_reaching_assignment'
|
|
111
|
-
:
|
|
125
|
+
: helperCallDeclaration(site)
|
|
126
|
+
? 'binding_flow_unsupported'
|
|
127
|
+
: 'binding_not_found',
|
|
112
128
|
};
|
|
113
129
|
if (matches.length > 1) return { status: 'ambiguous' };
|
|
114
130
|
return matches[0] ? { status: 'resolved', candidate: matches[0] } : undefined;
|
|
@@ -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 {
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
symbolImportReference,
|
|
10
10
|
type SymbolImportBinding,
|
|
11
11
|
type SymbolImportReference,
|
|
12
|
-
} from './
|
|
12
|
+
} from './symbol-import-bindings.js';
|
|
13
13
|
import type { ClassifiedOutboundCall } from './outbound-call-parser.js';
|
|
14
14
|
import {
|
|
15
15
|
localSymbolTarget,
|
|
16
16
|
type LocalSymbolTargetIdentity,
|
|
17
|
-
} from './
|
|
17
|
+
} from './local-symbol-reference.js';
|
|
18
18
|
|
|
19
19
|
interface HandlerTarget {
|
|
20
20
|
calleeExpression: string;
|
|
@@ -3,8 +3,8 @@ import type { ExecutableSymbolFact } from '../types.js';
|
|
|
3
3
|
import {
|
|
4
4
|
lexicalIdentifierDeclaration,
|
|
5
5
|
lexicalIdentifierDeclarations,
|
|
6
|
-
} from './
|
|
7
|
-
import { stableLocalValueReference } from './
|
|
6
|
+
} from './symbol-import-bindings.js';
|
|
7
|
+
import { stableLocalValueReference } from './stable-local-value.js';
|
|
8
8
|
|
|
9
9
|
export interface LocalSymbolTargetIdentity {
|
|
10
10
|
sourceFile: string;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
classifyODataPathIntent,
|
|
4
4
|
normalizeODataOperationInvocationPath,
|
|
5
5
|
} from '../linker/odata-path-normalizer.js';
|
|
6
|
-
import { analyzeODataPathStructure } from '../linker/
|
|
6
|
+
import { analyzeODataPathStructure } from '../linker/odata-path-structure.js';
|
|
7
7
|
|
|
8
8
|
export type OperationPathStatus = 'static' | 'ambiguous' | 'dynamic' | 'unknown';
|
|
9
9
|
|