@saptools/service-flow 0.1.69 → 0.1.72
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 +17 -0
- package/README.md +9 -5
- package/TECHNICAL-NOTE.md +17 -0
- package/dist/{chunk-3N3B5KHV.js → chunk-Z6D433R5.js} +8766 -6546
- package/dist/chunk-Z6D433R5.js.map +1 -0
- package/dist/cli.js +703 -160
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +53 -14
- package/dist/index.js +2 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor-event-quality.ts +371 -0
- 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 +9 -11
- package/src/cli.ts +6 -6
- package/src/db/{009-binding-fact-semantics.ts → binding-fact-semantics.ts} +2 -2
- package/src/db/{000-call-fact-repository.ts → call-fact-repository.ts} +12 -9
- package/src/db/{003-current-fact-semantics.ts → current-fact-semantics.ts} +8 -8
- package/src/db/event-fact-semantics.ts +347 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/{002-fact-json-inventory.ts → fact-json-inventory.ts} +16 -0
- package/src/db/{001-fact-lifecycle.ts → fact-lifecycle.ts} +3 -3
- package/src/db/migrations.ts +12 -1
- 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} +80 -1
- package/src/db/{008-relative-fact-semantics.ts → relative-fact-semantics.ts} +3 -3
- package/src/db/{006-relative-symbol-resolution.ts → relative-symbol-resolution.ts} +1 -1
- package/src/db/repositories.ts +31 -5
- package/src/db/{005-schema-structure.ts → schema-structure.ts} +41 -1
- package/src/db/schema.ts +6 -2
- package/src/db/{011-symbol-call-semantics.ts → symbol-call-semantics.ts} +1 -1
- package/src/index.ts +3 -3
- package/src/indexer/repository-indexer.ts +50 -11
- package/src/indexer/workspace-indexer.ts +3 -3
- package/src/linker/{007-call-edge-insertion.ts → call-edge-insertion.ts} +2 -2
- package/src/linker/{002-call-evidence.ts → call-evidence.ts} +2 -2
- package/src/linker/cross-repo-linker.ts +30 -8
- package/src/linker/dynamic-edge-resolver.ts +1 -1
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +161 -0
- package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts} +124 -20
- package/src/linker/{006-event-template-link.ts → event-template-link.ts} +40 -6
- 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/package-event-constant-resolver.ts +298 -0
- 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/output/table-output.ts +13 -1
- package/src/parsers/{006-binding-identity.ts → binding-identity.ts} +3 -3
- package/src/parsers/{011-binding-lexical-scope.ts → binding-lexical-scope.ts} +1 -1
- package/src/parsers/{021-binding-visibility.ts → binding-visibility.ts} +1 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +327 -0
- package/src/parsers/event-call-analysis.ts +242 -0
- package/src/parsers/event-environment-reference.ts +231 -0
- package/src/parsers/event-loop-registration.ts +132 -0
- package/src/parsers/event-name-import-resolution.ts +243 -0
- package/src/parsers/event-receiver-analysis.ts +404 -0
- package/src/parsers/{005-event-subscription-facts.ts → event-subscription-facts.ts} +6 -2
- package/src/parsers/generated-constants-parser.ts +80 -14
- 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/{023-outbound-call-classifier.ts → outbound-call-classifier.ts} +30 -127
- package/src/parsers/outbound-call-parser.ts +15 -3
- package/src/parsers/{022-outbound-expression-analysis.ts → outbound-expression-analysis.ts} +4 -4
- 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} +2 -2
- 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} +32 -11
- package/src/parsers/string-constant-lookups.ts +358 -0
- 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 +6 -6
- package/src/trace/{021-compact-decision-normalization.ts → compact-decision-normalization.ts} +2 -2
- package/src/trace/{020-compact-field-projection.ts → compact-field-projection.ts} +3 -3
- package/src/trace/{024-compact-observation-decision.ts → compact-observation-decision.ts} +3 -3
- package/src/trace/{016-compact-projector.ts → compact-projector.ts} +4 -4
- 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/{030-event-runtime-resolution.ts → event-runtime-resolution.ts} +26 -5
- package/src/trace/event-shape-candidate-trace.ts +172 -0
- package/src/trace/{011-event-subscriber-traversal.ts → event-subscriber-traversal.ts} +20 -8
- package/src/trace/evidence.ts +9 -2
- package/src/trace/implementation-hints.ts +2 -2
- package/src/trace/{005-implementation-selection.ts → implementation-selection.ts} +2 -2
- package/src/trace/{031-local-call-expansion.ts → local-call-expansion.ts} +3 -3
- package/src/trace/{009-selected-handler-provenance.ts → selected-handler-provenance.ts} +1 -1
- package/src/trace/selectors.ts +1 -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} +2 -2
- package/src/trace/{019-trace-edge-semantics.ts → trace-edge-semantics.ts} +3 -3
- 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} +3 -3
- package/src/trace/{027-trace-scope-execution.ts → trace-scope-execution.ts} +35 -43
- 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} +5 -5
- package/src/trace/{010-traversal-scope.ts → traversal-scope.ts} +1 -1
- package/src/types.ts +17 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-3N3B5KHV.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/cli/{001-index-summary.ts → index-summary.ts} +0 -0
- /package/src/db/{014-binding-helper-provenance.ts → binding-helper-provenance.ts} +0 -0
- /package/src/db/{012-binding-reference-proof.ts → binding-reference-proof.ts} +0 -0
- /package/src/db/{013-index-publication-failure.ts → index-publication-failure.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/{000-direct-query-execution.ts → direct-query-execution.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/{014-compact-contract.ts → compact-contract.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/{007-implementation-start-diagnostic.ts → implementation-start-diagnostic.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,10 +1,13 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
2
|
import { linkHelperPackages } from './helper-package-linker.js';
|
|
3
|
-
import { linkImplementations as linkCanonicalImplementations } from './
|
|
4
|
-
import { linkPackageImportSymbolCalls } from './
|
|
5
|
-
import { linkEventSubscriptionHandlers } from './
|
|
6
|
-
import { insertCallEdge } from './
|
|
7
|
-
import {
|
|
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 { linkEventShapeCandidates } from './event-shape-candidate-linker.js';
|
|
8
|
+
import { assertWorkspaceLinkable } from '../db/fact-lifecycle.js';
|
|
9
|
+
import { linkPackageEventConstants } from
|
|
10
|
+
'./package-event-constant-resolver.js';
|
|
8
11
|
export interface LinkWorkspaceResult {
|
|
9
12
|
edgeCount: number;
|
|
10
13
|
unresolvedCount: number;
|
|
@@ -23,11 +26,13 @@ export interface LinkWorkspaceResult {
|
|
|
23
26
|
subscriptionHandlerAmbiguousCount: number;
|
|
24
27
|
subscriptionHandlerUnresolvedCount: number;
|
|
25
28
|
subscriptionHandlerMissingAssociationCount: number;
|
|
29
|
+
eventShapeCandidateCount: number;
|
|
26
30
|
}
|
|
27
31
|
export function linkWorkspace(db: Db, workspaceId: number, vars: Record<string, string> = {}): LinkWorkspaceResult {
|
|
28
32
|
return db.transaction(() => {
|
|
29
33
|
assertWorkspaceLinkable(db, workspaceId);
|
|
30
34
|
linkPackageImportSymbolCalls(db, workspaceId);
|
|
35
|
+
linkPackageEventConstants(db, workspaceId);
|
|
31
36
|
assertWorkspaceLinkable(db, workspaceId, 'terminal');
|
|
32
37
|
const generation = nextGraphGeneration(db, workspaceId);
|
|
33
38
|
db.prepare('DELETE FROM graph_edges WHERE workspace_id=?').run(workspaceId);
|
|
@@ -35,10 +40,13 @@ export function linkWorkspace(db: Db, workspaceId: number, vars: Record<string,
|
|
|
35
40
|
const subscriptions = linkEventSubscriptionHandlers(
|
|
36
41
|
db, workspaceId, generation, vars,
|
|
37
42
|
);
|
|
43
|
+
const eventShapes = linkEventShapeCandidates(
|
|
44
|
+
db, workspaceId, generation,
|
|
45
|
+
);
|
|
38
46
|
const impl = linkCanonicalImplementations(db, workspaceId, generation);
|
|
39
47
|
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
40
48
|
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
41
|
-
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount + subscriptions.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount, subscriptionHandlerResolvedCount: subscriptions.resolvedCount, subscriptionHandlerAmbiguousCount: subscriptions.ambiguousCount, subscriptionHandlerUnresolvedCount: subscriptions.unresolvedCount, subscriptionHandlerMissingAssociationCount: subscriptions.missingAssociationCount };
|
|
49
|
+
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount + subscriptions.edgeCount + eventShapes.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount, subscriptionHandlerResolvedCount: subscriptions.resolvedCount, subscriptionHandlerAmbiguousCount: subscriptions.ambiguousCount, subscriptionHandlerUnresolvedCount: subscriptions.unresolvedCount, subscriptionHandlerMissingAssociationCount: subscriptions.missingAssociationCount, eventShapeCandidateCount: eventShapes.edgeCount };
|
|
42
50
|
});
|
|
43
51
|
}
|
|
44
52
|
function nextGraphGeneration(db: Db, workspaceId: number): number {
|
|
@@ -54,7 +62,8 @@ type CallLinkSummary = Omit<LinkWorkspaceResult,
|
|
|
54
62
|
| 'subscriptionHandlerResolvedCount'
|
|
55
63
|
| 'subscriptionHandlerAmbiguousCount'
|
|
56
64
|
| 'subscriptionHandlerUnresolvedCount'
|
|
57
|
-
| 'subscriptionHandlerMissingAssociationCount'
|
|
65
|
+
| 'subscriptionHandlerMissingAssociationCount'
|
|
66
|
+
| 'eventShapeCandidateCount'>;
|
|
58
67
|
|
|
59
68
|
function linkCalls(db: Db, workspaceId: number, vars: Record<string, string>, generation: number): CallLinkSummary {
|
|
60
69
|
let edgeCount = 0;
|
|
@@ -65,7 +74,20 @@ function linkCalls(db: Db, workspaceId: number, vars: Record<string, string>, ge
|
|
|
65
74
|
let ambiguousCount = 0;
|
|
66
75
|
let dynamicCount = 0;
|
|
67
76
|
let terminalCount = 0;
|
|
68
|
-
const calls = db.prepare(`SELECT c.*,r.name repoName,
|
|
77
|
+
const calls = db.prepare(`SELECT c.*,r.name repoName,
|
|
78
|
+
r.environment_declarations_json environmentDeclarationsJson,
|
|
79
|
+
b.id selectedBindingId,b.alias,b.alias_expr aliasExpr,
|
|
80
|
+
b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,
|
|
81
|
+
b.is_dynamic isDynamic,b.placeholders_json placeholdersJson,
|
|
82
|
+
b.source_file bindingSourceFile,b.source_line bindingSourceLine,
|
|
83
|
+
b.helper_chain_json helperChainJson,req.service_path requireServicePath,
|
|
84
|
+
req.destination requireDestination
|
|
85
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
86
|
+
LEFT JOIN service_bindings b ON b.id=c.service_binding_id
|
|
87
|
+
LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias
|
|
88
|
+
WHERE r.workspace_id=?`).all(
|
|
89
|
+
workspaceId,
|
|
90
|
+
) as Array<Record<string, unknown>>;
|
|
69
91
|
for (const call of calls) {
|
|
70
92
|
const result = insertCallEdge(db, workspaceId, call, vars, generation);
|
|
71
93
|
edgeCount += 1;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
applyEventEnvironmentTransforms,
|
|
4
|
+
type EventEnvironmentReference,
|
|
5
|
+
} from '../parsers/event-environment-reference.js';
|
|
6
|
+
import {
|
|
7
|
+
parseEnvironmentDeclarationsFact,
|
|
8
|
+
type EnvironmentDeclarationsFact,
|
|
9
|
+
} from '../parsers/environment-declarations.js';
|
|
10
|
+
import {
|
|
11
|
+
eventTemplateVariables,
|
|
12
|
+
parseEventSkeletonFact,
|
|
13
|
+
} from '../utils/event-skeleton.js';
|
|
14
|
+
|
|
15
|
+
export interface EventEnvironmentResolution {
|
|
16
|
+
status: 'resolved' | 'ambiguous' | 'unresolved' | 'not_applicable';
|
|
17
|
+
variables: Record<string, string>;
|
|
18
|
+
reason?: string;
|
|
19
|
+
provenance: Array<Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SubscriptionEnvironmentTarget {
|
|
23
|
+
consumerRepoId?: number;
|
|
24
|
+
consumerRepoName?: string;
|
|
25
|
+
resolution: EventEnvironmentResolution;
|
|
26
|
+
collisionCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
30
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
31
|
+
? value as Record<string, unknown> : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parsedJson(value: unknown): unknown {
|
|
35
|
+
if (typeof value !== 'string') return value;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(value) as unknown;
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function bindingEnvironmentValues(
|
|
44
|
+
binding: EventEnvironmentReference,
|
|
45
|
+
fact: EnvironmentDeclarationsFact,
|
|
46
|
+
): string[] {
|
|
47
|
+
if (!binding.environmentKey) return [];
|
|
48
|
+
return [...new Set(fact.declarations
|
|
49
|
+
.filter((item) => item.key === binding.environmentKey)
|
|
50
|
+
.map((item) => item.value))];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function bindingResolution(
|
|
54
|
+
binding: EventEnvironmentReference,
|
|
55
|
+
fact: EnvironmentDeclarationsFact,
|
|
56
|
+
): { value?: string; reason?: string; ambiguous?: boolean } {
|
|
57
|
+
if (binding.status === 'refused')
|
|
58
|
+
return { reason: binding.reason ?? 'event_environment_reference_refused' };
|
|
59
|
+
if (fact.status === 'incomplete') return {
|
|
60
|
+
reason: 'event_environment_declarations_incomplete',
|
|
61
|
+
};
|
|
62
|
+
const values = bindingEnvironmentValues(binding, fact);
|
|
63
|
+
if (fact.status === 'ambiguous' || values.length > 1) return {
|
|
64
|
+
reason: 'event_environment_declaration_ambiguous', ambiguous: true,
|
|
65
|
+
};
|
|
66
|
+
const value = values[0];
|
|
67
|
+
if (value === undefined)
|
|
68
|
+
return { reason: 'event_environment_declaration_missing' };
|
|
69
|
+
return {
|
|
70
|
+
value: applyEventEnvironmentTransforms(value, binding.transforms),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function provenance(
|
|
75
|
+
binding: EventEnvironmentReference,
|
|
76
|
+
fact: EnvironmentDeclarationsFact,
|
|
77
|
+
): Record<string, unknown> {
|
|
78
|
+
const rank = new Map([
|
|
79
|
+
['env_declaration_mta', 0],
|
|
80
|
+
['env_declaration_manifest', 1],
|
|
81
|
+
['env_declaration_dotenv', 2],
|
|
82
|
+
['env_declaration_dev', 3],
|
|
83
|
+
]);
|
|
84
|
+
const declaration = fact.declarations.filter((item) =>
|
|
85
|
+
item.key === binding.environmentKey).sort((left, right) =>
|
|
86
|
+
(rank.get(left.provenance) ?? 99) - (rank.get(right.provenance) ?? 99)
|
|
87
|
+
|| (left.sourceFile < right.sourceFile
|
|
88
|
+
? -1 : left.sourceFile > right.sourceFile ? 1 : 0)
|
|
89
|
+
|| left.startOffset - right.startOffset)[0];
|
|
90
|
+
return {
|
|
91
|
+
sourceKey: binding.sourceKey,
|
|
92
|
+
environmentKey: binding.environmentKey,
|
|
93
|
+
transforms: binding.transforms,
|
|
94
|
+
declarationProvenance: declaration?.provenance,
|
|
95
|
+
declarationSourceFile: declaration?.sourceFile,
|
|
96
|
+
declarationStartOffset: declaration?.startOffset,
|
|
97
|
+
declarationEndOffset: declaration?.endOffset,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function resolveEventEnvironment(
|
|
102
|
+
skeletonValue: unknown,
|
|
103
|
+
environmentValue: unknown,
|
|
104
|
+
variables: Record<string, string>,
|
|
105
|
+
): EventEnvironmentResolution {
|
|
106
|
+
const skeleton = parseEventSkeletonFact(skeletonValue);
|
|
107
|
+
if (!skeleton || skeleton.environmentBindings.length === 0) return {
|
|
108
|
+
status: 'not_applicable',
|
|
109
|
+
variables: eventTemplateVariables(skeleton, variables),
|
|
110
|
+
provenance: [],
|
|
111
|
+
};
|
|
112
|
+
const fact = parseEnvironmentDeclarationsFact(environmentValue);
|
|
113
|
+
if (!fact) return {
|
|
114
|
+
status: 'unresolved', variables,
|
|
115
|
+
reason: 'event_environment_declarations_invalid', provenance: [],
|
|
116
|
+
};
|
|
117
|
+
const expanded = eventTemplateVariables(skeleton, variables);
|
|
118
|
+
const outcomes = skeleton.environmentBindings.map((binding) =>
|
|
119
|
+
Object.hasOwn(expanded, binding.sourceKey)
|
|
120
|
+
? {} : bindingResolution(binding, fact));
|
|
121
|
+
for (let index = 0; index < outcomes.length; index += 1) {
|
|
122
|
+
const binding = skeleton.environmentBindings[index];
|
|
123
|
+
const outcome = outcomes[index];
|
|
124
|
+
if (binding?.sourceKey && outcome?.value !== undefined
|
|
125
|
+
&& !Object.hasOwn(expanded, binding.sourceKey))
|
|
126
|
+
expanded[binding.sourceKey] = outcome.value;
|
|
127
|
+
}
|
|
128
|
+
const ambiguous = outcomes.find((outcome) => outcome.ambiguous);
|
|
129
|
+
const unresolved = outcomes.find((outcome) => outcome.reason);
|
|
130
|
+
return {
|
|
131
|
+
status: ambiguous ? 'ambiguous'
|
|
132
|
+
: unresolved ? 'unresolved' : 'resolved',
|
|
133
|
+
variables: expanded,
|
|
134
|
+
reason: ambiguous?.reason ?? unresolved?.reason,
|
|
135
|
+
provenance: skeleton.environmentBindings.map((binding) =>
|
|
136
|
+
provenance(binding, fact)),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function dependencies(value: unknown): Record<string, string> {
|
|
141
|
+
const parsed = record(parsedJson(value));
|
|
142
|
+
if (!parsed) return {};
|
|
143
|
+
return Object.fromEntries(Object.entries(parsed).flatMap(([key, item]) =>
|
|
144
|
+
typeof item === 'string' ? [[key, item]] : []));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function consumerRows(
|
|
148
|
+
db: Db,
|
|
149
|
+
workspaceId: number,
|
|
150
|
+
packageName: string,
|
|
151
|
+
): Array<Record<string, unknown>> {
|
|
152
|
+
return db.prepare(`SELECT id,name,dependencies_json dependenciesJson,
|
|
153
|
+
environment_declarations_json environmentJson
|
|
154
|
+
FROM repositories WHERE workspace_id=?
|
|
155
|
+
ORDER BY name COLLATE BINARY,id`).all(workspaceId)
|
|
156
|
+
.filter((row) => Object.hasOwn(
|
|
157
|
+
dependencies(row.dependenciesJson), packageName,
|
|
158
|
+
));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function collisionCounts(
|
|
162
|
+
targets: readonly SubscriptionEnvironmentTarget[],
|
|
163
|
+
): Map<string, number> {
|
|
164
|
+
const counts = new Map<string, number>();
|
|
165
|
+
for (const target of targets) {
|
|
166
|
+
const values = Object.entries(target.resolution.variables)
|
|
167
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
168
|
+
const key = JSON.stringify(values);
|
|
169
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
170
|
+
}
|
|
171
|
+
return counts;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function subscriptionEnvironmentTargets(
|
|
175
|
+
db: Db,
|
|
176
|
+
workspaceId: number,
|
|
177
|
+
packageName: string | undefined,
|
|
178
|
+
skeletonValue: unknown,
|
|
179
|
+
ownEnvironmentValue: unknown,
|
|
180
|
+
variables: Record<string, string>,
|
|
181
|
+
): SubscriptionEnvironmentTarget[] {
|
|
182
|
+
const skeleton = parseEventSkeletonFact(skeletonValue);
|
|
183
|
+
if (!skeleton || skeleton.environmentBindings.length === 0
|
|
184
|
+
|| !packageName) return [{
|
|
185
|
+
resolution: resolveEventEnvironment(
|
|
186
|
+
skeletonValue, ownEnvironmentValue, variables,
|
|
187
|
+
),
|
|
188
|
+
collisionCount: 1,
|
|
189
|
+
}];
|
|
190
|
+
const rows = consumerRows(db, workspaceId, packageName);
|
|
191
|
+
const provisional = rows.map((row): SubscriptionEnvironmentTarget => ({
|
|
192
|
+
consumerRepoId: Number(row.id),
|
|
193
|
+
consumerRepoName: String(row.name),
|
|
194
|
+
resolution: resolveEventEnvironment(
|
|
195
|
+
skeletonValue, row.environmentJson, variables,
|
|
196
|
+
),
|
|
197
|
+
collisionCount: 1,
|
|
198
|
+
}));
|
|
199
|
+
if (provisional.length === 0) return [{
|
|
200
|
+
resolution: resolveEventEnvironment(
|
|
201
|
+
skeletonValue, ownEnvironmentValue, variables,
|
|
202
|
+
),
|
|
203
|
+
collisionCount: 1,
|
|
204
|
+
}];
|
|
205
|
+
const counts = collisionCounts(provisional);
|
|
206
|
+
return provisional.map((target) => {
|
|
207
|
+
const key = JSON.stringify(Object.entries(target.resolution.variables)
|
|
208
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
|
|
209
|
+
return { ...target, collisionCount: counts.get(key) ?? 1 };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import { parseEventSkeletonFact } from '../utils/event-skeleton.js';
|
|
3
|
+
|
|
4
|
+
export interface EventShapeCandidateLinkSummary {
|
|
5
|
+
edgeCount: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface EventShapeRow extends Record<string, unknown> {
|
|
9
|
+
id: number;
|
|
10
|
+
repoId: number;
|
|
11
|
+
repoName: string;
|
|
12
|
+
signature: string;
|
|
13
|
+
skeletonJson: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface SubscriberAssociation extends Record<string, unknown> {
|
|
17
|
+
graphEdgeId: number;
|
|
18
|
+
subscribeCallId: number;
|
|
19
|
+
targetKind: string;
|
|
20
|
+
targetId: string;
|
|
21
|
+
status: string;
|
|
22
|
+
evidenceJson: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function eventRows(
|
|
26
|
+
db: Db,
|
|
27
|
+
workspaceId: number,
|
|
28
|
+
callType: 'async_emit' | 'async_subscribe',
|
|
29
|
+
): EventShapeRow[] {
|
|
30
|
+
return db.prepare(`SELECT c.id,c.repo_id repoId,r.name repoName,
|
|
31
|
+
c.event_skeleton_signature signature,
|
|
32
|
+
c.event_skeleton_json skeletonJson
|
|
33
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
34
|
+
WHERE r.workspace_id=? AND c.call_type=?
|
|
35
|
+
AND c.event_skeleton_signature IS NOT NULL
|
|
36
|
+
AND c.event_skeleton_json IS NOT NULL
|
|
37
|
+
ORDER BY c.event_skeleton_signature COLLATE BINARY,
|
|
38
|
+
r.name COLLATE BINARY,r.id,c.source_file COLLATE BINARY,
|
|
39
|
+
c.call_site_start_offset,c.call_site_end_offset,c.id`).all(
|
|
40
|
+
workspaceId, callType,
|
|
41
|
+
) as unknown as EventShapeRow[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function associations(
|
|
45
|
+
db: Db,
|
|
46
|
+
workspaceId: number,
|
|
47
|
+
generation: number,
|
|
48
|
+
): SubscriberAssociation[] {
|
|
49
|
+
return db.prepare(`SELECT id graphEdgeId,
|
|
50
|
+
CAST(json_extract(evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
51
|
+
subscribeCallId,
|
|
52
|
+
to_kind targetKind,to_id targetId,status,evidence_json evidenceJson
|
|
53
|
+
FROM graph_edges
|
|
54
|
+
WHERE workspace_id=? AND generation=?
|
|
55
|
+
AND edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
56
|
+
AND to_kind='symbol'
|
|
57
|
+
ORDER BY subscribeCallId,id`).all(
|
|
58
|
+
workspaceId, generation,
|
|
59
|
+
) as unknown as SubscriberAssociation[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parsedEvidence(value: string): Record<string, unknown> {
|
|
63
|
+
try {
|
|
64
|
+
const parsed: unknown = JSON.parse(value);
|
|
65
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
66
|
+
? parsed as Record<string, unknown> : {};
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function candidateEligible(
|
|
73
|
+
emit: EventShapeRow,
|
|
74
|
+
subscribe: EventShapeRow,
|
|
75
|
+
): boolean {
|
|
76
|
+
if (emit.signature !== subscribe.signature) return false;
|
|
77
|
+
const left = parseEventSkeletonFact(emit.skeletonJson);
|
|
78
|
+
const right = parseEventSkeletonFact(subscribe.skeletonJson);
|
|
79
|
+
return Boolean(left?.candidateEligible && right?.candidateEligible
|
|
80
|
+
&& left.holeCount === right.holeCount
|
|
81
|
+
&& JSON.stringify(left.literalSpans) === JSON.stringify(right.literalSpans));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function candidateEvidence(
|
|
85
|
+
emit: EventShapeRow,
|
|
86
|
+
subscribe: EventShapeRow,
|
|
87
|
+
association: SubscriberAssociation,
|
|
88
|
+
): Record<string, unknown> {
|
|
89
|
+
const evidence = parsedEvidence(association.evidenceJson);
|
|
90
|
+
return {
|
|
91
|
+
publishCallId: emit.id,
|
|
92
|
+
subscribeCallId: subscribe.id,
|
|
93
|
+
eventSkeletonSignature: emit.signature,
|
|
94
|
+
dispatchScope: 'workspace_event_name_only',
|
|
95
|
+
dispatchCertainty: 'skeleton_equivalent',
|
|
96
|
+
subscriptionRepositoryId: subscribe.repoId,
|
|
97
|
+
subscriptionRepositoryName: subscribe.repoName,
|
|
98
|
+
subscriptionConsumerRepositoryId:
|
|
99
|
+
evidence.subscriptionConsumerRepositoryId,
|
|
100
|
+
subscriptionConsumerRepositoryName:
|
|
101
|
+
evidence.subscriptionConsumerRepositoryName,
|
|
102
|
+
handlerSymbolId: Number(association.targetId),
|
|
103
|
+
associationGraphEdgeId: association.graphEdgeId,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function insertCandidate(
|
|
108
|
+
db: Db,
|
|
109
|
+
workspaceId: number,
|
|
110
|
+
generation: number,
|
|
111
|
+
emit: EventShapeRow,
|
|
112
|
+
subscribe: EventShapeRow,
|
|
113
|
+
association: SubscriberAssociation,
|
|
114
|
+
): void {
|
|
115
|
+
db.prepare(`INSERT INTO graph_edges(
|
|
116
|
+
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
117
|
+
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
118
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
119
|
+
workspaceId,
|
|
120
|
+
'EVENT_SHAPE_CANDIDATE_SUBSCRIBER',
|
|
121
|
+
'dynamic',
|
|
122
|
+
'call',
|
|
123
|
+
String(emit.id),
|
|
124
|
+
association.targetKind,
|
|
125
|
+
association.targetId,
|
|
126
|
+
0.3,
|
|
127
|
+
JSON.stringify(candidateEvidence(
|
|
128
|
+
emit, subscribe, association,
|
|
129
|
+
)),
|
|
130
|
+
1,
|
|
131
|
+
'event_skeleton_equivalent_non_authoritative',
|
|
132
|
+
generation,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function linkEventShapeCandidates(
|
|
137
|
+
db: Db,
|
|
138
|
+
workspaceId: number,
|
|
139
|
+
generation: number,
|
|
140
|
+
): EventShapeCandidateLinkSummary {
|
|
141
|
+
const emits = eventRows(db, workspaceId, 'async_emit');
|
|
142
|
+
const subscriptions = eventRows(db, workspaceId, 'async_subscribe');
|
|
143
|
+
const bySubscription = new Map<number, SubscriberAssociation[]>();
|
|
144
|
+
for (const association of associations(db, workspaceId, generation))
|
|
145
|
+
bySubscription.set(association.subscribeCallId, [
|
|
146
|
+
...(bySubscription.get(association.subscribeCallId) ?? []),
|
|
147
|
+
association,
|
|
148
|
+
]);
|
|
149
|
+
let edgeCount = 0;
|
|
150
|
+
for (const emit of emits)
|
|
151
|
+
for (const subscription of subscriptions) {
|
|
152
|
+
if (!candidateEligible(emit, subscription)) continue;
|
|
153
|
+
for (const association of bySubscription.get(subscription.id) ?? []) {
|
|
154
|
+
insertCandidate(
|
|
155
|
+
db, workspaceId, generation, emit, subscription, association,
|
|
156
|
+
);
|
|
157
|
+
edgeCount += 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { edgeCount };
|
|
161
|
+
}
|
package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts}
RENAMED
|
@@ -2,7 +2,12 @@ import type { Db } from '../db/connection.js';
|
|
|
2
2
|
import {
|
|
3
3
|
linkEventTemplate,
|
|
4
4
|
type LinkedEventTemplate,
|
|
5
|
-
} from './
|
|
5
|
+
} from './event-template-link.js';
|
|
6
|
+
import {
|
|
7
|
+
subscriptionEnvironmentTargets,
|
|
8
|
+
type SubscriptionEnvironmentTarget,
|
|
9
|
+
} from './event-environment-link.js';
|
|
10
|
+
import { parseEventSkeletonFact } from '../utils/event-skeleton.js';
|
|
6
11
|
|
|
7
12
|
export interface SubscriptionHandlerLinkSummary {
|
|
8
13
|
edgeCount: number;
|
|
@@ -25,6 +30,9 @@ interface SubscriptionRow {
|
|
|
25
30
|
endOffset?: number | null;
|
|
26
31
|
confidence: number;
|
|
27
32
|
unresolvedReason?: string | null;
|
|
33
|
+
packageName?: string | null;
|
|
34
|
+
environmentJson?: string | null;
|
|
35
|
+
eventSkeletonJson?: string | null;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
interface HandlerCallRow {
|
|
@@ -63,7 +71,10 @@ function subscriptionRows(db: Db, workspaceId: number): SubscriptionRow[] {
|
|
|
63
71
|
c.source_symbol_id sourceSymbolId,c.event_name_expr eventName,
|
|
64
72
|
c.source_file sourceFile,c.source_line sourceLine,
|
|
65
73
|
c.call_site_start_offset startOffset,c.call_site_end_offset endOffset,
|
|
66
|
-
c.confidence,c.unresolved_reason unresolvedReason
|
|
74
|
+
c.confidence,c.unresolved_reason unresolvedReason,
|
|
75
|
+
c.event_skeleton_json eventSkeletonJson,
|
|
76
|
+
r.package_name packageName,
|
|
77
|
+
r.environment_declarations_json environmentJson
|
|
67
78
|
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
68
79
|
WHERE r.workspace_id=? AND c.call_type='async_subscribe'
|
|
69
80
|
AND json_extract(c.evidence_json,'$.handlerReferenceStatus')
|
|
@@ -208,11 +219,18 @@ function evidenceFor(
|
|
|
208
219
|
subscription: SubscriptionRow,
|
|
209
220
|
association: HandlerAssociation,
|
|
210
221
|
event: LinkedEventTemplate,
|
|
222
|
+
environment?: SubscriptionEnvironmentTarget,
|
|
211
223
|
): Record<string, unknown> {
|
|
212
224
|
const call: Partial<HandlerCallRow> = association.call ?? {};
|
|
213
225
|
const symbolCallReason = boundedSymbolCallReason(call.unresolvedReason);
|
|
226
|
+
const environmentAmbiguous = environment?.resolution.status === 'ambiguous'
|
|
227
|
+
|| Number(environment?.collisionCount ?? 1) > 1;
|
|
228
|
+
const resolutionStatus = event.isDynamic
|
|
229
|
+
? 'unresolved'
|
|
230
|
+
: environmentAmbiguous ? 'ambiguous' : association.status;
|
|
214
231
|
return {
|
|
215
232
|
eventName: subscription.eventName,
|
|
233
|
+
...(eventSkeletonEvidence(subscription.eventSkeletonJson)),
|
|
216
234
|
...(event.substitution.placeholders.length > 0 ? {
|
|
217
235
|
effectiveEventName: event.targetId,
|
|
218
236
|
eventTemplateResolution: event.substitution,
|
|
@@ -226,6 +244,18 @@ function evidenceFor(
|
|
|
226
244
|
factOrigin: association.factOrigin ?? call.factOrigin,
|
|
227
245
|
repositoryId: subscription.repoId,
|
|
228
246
|
repositoryName: subscription.repoName,
|
|
247
|
+
subscriptionConsumerRepositoryId: environment?.consumerRepoId,
|
|
248
|
+
subscriptionConsumerRepositoryName: environment?.consumerRepoName,
|
|
249
|
+
dispatchCertainty: environment?.resolution.status === 'resolved'
|
|
250
|
+
? 'environment_declaration_exact' : 'static_name_only',
|
|
251
|
+
...(environment?.resolution.status === 'not_applicable' ? {} : {
|
|
252
|
+
eventEnvironmentResolution: {
|
|
253
|
+
status: environment?.resolution.status,
|
|
254
|
+
reason: environment?.resolution.reason,
|
|
255
|
+
provenance: environment?.resolution.provenance,
|
|
256
|
+
collisionCount: environment?.collisionCount,
|
|
257
|
+
},
|
|
258
|
+
}),
|
|
229
259
|
sourceFile: subscription.sourceFile,
|
|
230
260
|
sourceLine: subscription.sourceLine,
|
|
231
261
|
callSiteStartOffset: subscription.startOffset,
|
|
@@ -237,14 +267,23 @@ function evidenceFor(
|
|
|
237
267
|
associationStatus: association.status,
|
|
238
268
|
symbolCallResolutionStatus:
|
|
239
269
|
association.symbolCallResolutionStatus ?? call.status,
|
|
240
|
-
resolutionStatus
|
|
270
|
+
resolutionStatus,
|
|
241
271
|
resolutionStrategy: call.strategy,
|
|
242
272
|
candidateCount: call.candidateCount,
|
|
243
|
-
reasonCode:
|
|
273
|
+
reasonCode: environmentAmbiguous
|
|
274
|
+
? environment?.resolution.reason ?? 'event_environment_value_collision'
|
|
275
|
+
: association.reasonCode,
|
|
244
276
|
...symbolCallReason,
|
|
245
277
|
};
|
|
246
278
|
}
|
|
247
279
|
|
|
280
|
+
function eventSkeletonEvidence(
|
|
281
|
+
value: string | null | undefined,
|
|
282
|
+
): Record<string, unknown> {
|
|
283
|
+
const skeleton = parseEventSkeletonFact(value);
|
|
284
|
+
return skeleton ? { eventSkeleton: skeleton } : {};
|
|
285
|
+
}
|
|
286
|
+
|
|
248
287
|
function boundedSymbolCallReason(
|
|
249
288
|
reason: string | null | undefined,
|
|
250
289
|
): Record<string, unknown> {
|
|
@@ -264,10 +303,20 @@ function insertAssociationEdge(
|
|
|
264
303
|
subscription: SubscriptionRow,
|
|
265
304
|
association: HandlerAssociation,
|
|
266
305
|
event: LinkedEventTemplate,
|
|
306
|
+
environment?: SubscriptionEnvironmentTarget,
|
|
267
307
|
): void {
|
|
268
|
-
const
|
|
308
|
+
const environmentAmbiguous = environment?.resolution.status === 'ambiguous'
|
|
309
|
+
|| Number(environment?.collisionCount ?? 1) > 1;
|
|
310
|
+
const status = event.isDynamic
|
|
311
|
+
? 'unresolved'
|
|
312
|
+
: environmentAmbiguous ? 'ambiguous' : association.status;
|
|
269
313
|
const reason = event.isDynamic
|
|
270
|
-
?
|
|
314
|
+
? event.substitution.missing.length > 0
|
|
315
|
+
? 'event_template_variables_missing'
|
|
316
|
+
: event.unresolvedReason ?? 'event_name_unsupported_constant_expression'
|
|
317
|
+
: environmentAmbiguous
|
|
318
|
+
? environment?.resolution.reason
|
|
319
|
+
?? 'event_environment_value_collision'
|
|
271
320
|
: association.reasonCode ?? association.call?.unresolvedReason ?? null;
|
|
272
321
|
db.prepare(`INSERT INTO graph_edges(
|
|
273
322
|
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
@@ -281,13 +330,68 @@ function insertAssociationEdge(
|
|
|
281
330
|
association.toKind,
|
|
282
331
|
association.toId,
|
|
283
332
|
association.call?.confidence ?? subscription.confidence,
|
|
284
|
-
JSON.stringify(evidenceFor(
|
|
333
|
+
JSON.stringify(evidenceFor(
|
|
334
|
+
subscription, association, event, environment,
|
|
335
|
+
)),
|
|
285
336
|
event.isDynamic ? 1 : 0,
|
|
286
337
|
reason,
|
|
287
338
|
generation,
|
|
288
339
|
);
|
|
289
340
|
}
|
|
290
341
|
|
|
342
|
+
function linkedSubscriptionEvents(
|
|
343
|
+
db: Db,
|
|
344
|
+
workspaceId: number,
|
|
345
|
+
subscription: SubscriptionRow,
|
|
346
|
+
variables: Record<string, string>,
|
|
347
|
+
): Array<{
|
|
348
|
+
event: LinkedEventTemplate;
|
|
349
|
+
environment: SubscriptionEnvironmentTarget;
|
|
350
|
+
}> {
|
|
351
|
+
const skeleton = parseEventSkeletonFact(subscription.eventSkeletonJson);
|
|
352
|
+
return subscriptionEnvironmentTargets(
|
|
353
|
+
db,
|
|
354
|
+
workspaceId,
|
|
355
|
+
subscription.packageName ?? undefined,
|
|
356
|
+
subscription.eventSkeletonJson,
|
|
357
|
+
subscription.environmentJson,
|
|
358
|
+
variables,
|
|
359
|
+
).map((environment) => ({
|
|
360
|
+
environment,
|
|
361
|
+
event: linkEventTemplate(
|
|
362
|
+
subscription.eventName,
|
|
363
|
+
environment.resolution.variables,
|
|
364
|
+
subscription.unresolvedReason ?? undefined,
|
|
365
|
+
skeleton,
|
|
366
|
+
),
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function targetStatus(
|
|
371
|
+
association: HandlerAssociation,
|
|
372
|
+
event: LinkedEventTemplate,
|
|
373
|
+
environment: SubscriptionEnvironmentTarget,
|
|
374
|
+
): 'resolved' | 'ambiguous' | 'unresolved' {
|
|
375
|
+
if (event.isDynamic || association.status === 'unresolved')
|
|
376
|
+
return 'unresolved';
|
|
377
|
+
if (association.status === 'ambiguous'
|
|
378
|
+
|| environment.resolution.status === 'ambiguous'
|
|
379
|
+
|| environment.collisionCount > 1) return 'ambiguous';
|
|
380
|
+
return 'resolved';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function incrementSummary(
|
|
384
|
+
summary: SubscriptionHandlerLinkSummary,
|
|
385
|
+
status: 'resolved' | 'ambiguous' | 'unresolved',
|
|
386
|
+
missing: boolean,
|
|
387
|
+
): void {
|
|
388
|
+
summary.edgeCount += 1;
|
|
389
|
+
summary.resolvedCount += status === 'resolved' ? 1 : 0;
|
|
390
|
+
summary.ambiguousCount += status === 'ambiguous' ? 1 : 0;
|
|
391
|
+
summary.unresolvedCount += status === 'unresolved' ? 1 : 0;
|
|
392
|
+
summary.missingAssociationCount += missing ? 1 : 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
291
395
|
export function linkEventSubscriptionHandlers(
|
|
292
396
|
db: Db,
|
|
293
397
|
workspaceId: number,
|
|
@@ -302,22 +406,22 @@ export function linkEventSubscriptionHandlers(
|
|
|
302
406
|
missingAssociationCount: 0,
|
|
303
407
|
};
|
|
304
408
|
for (const subscription of subscriptionRows(db, workspaceId)) {
|
|
305
|
-
const event = linkEventTemplate(
|
|
306
|
-
subscription.eventName, variables,
|
|
307
|
-
subscription.unresolvedReason ?? undefined,
|
|
308
|
-
);
|
|
309
409
|
const association = associationFor(
|
|
310
410
|
subscription, roleSiteRows(db, subscription),
|
|
311
411
|
);
|
|
312
|
-
|
|
313
|
-
db, workspaceId,
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
412
|
+
for (const target of linkedSubscriptionEvents(
|
|
413
|
+
db, workspaceId, subscription, variables,
|
|
414
|
+
)) {
|
|
415
|
+
insertAssociationEdge(
|
|
416
|
+
db, workspaceId, generation, subscription, association,
|
|
417
|
+
target.event, target.environment,
|
|
418
|
+
);
|
|
419
|
+
incrementSummary(
|
|
420
|
+
summary,
|
|
421
|
+
targetStatus(association, target.event, target.environment),
|
|
422
|
+
association.missing,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
321
425
|
}
|
|
322
426
|
return summary;
|
|
323
427
|
}
|