@saptools/service-flow 0.1.70 → 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 +13 -0
- package/README.md +9 -5
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-GSLFY6J2.js → chunk-Z6D433R5.js} +8696 -6476
- package/dist/chunk-Z6D433R5.js.map +1 -0
- package/dist/cli.js +685 -142
- 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/doctor.ts +4 -6
- package/src/cli.ts +1 -1
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -5
- package/src/db/event-fact-semantics.ts +347 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/migrations.ts +12 -1
- package/src/db/package-target-invalidation.ts +79 -0
- package/src/db/repositories.ts +28 -2
- package/src/db/schema-structure.ts +41 -1
- package/src/db/schema.ts +6 -2
- package/src/indexer/repository-indexer.ts +45 -6
- package/src/linker/cross-repo-linker.ts +25 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +161 -0
- package/src/linker/event-subscription-handler-linker.ts +123 -19
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +298 -0
- package/src/output/table-output.ts +13 -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/event-subscription-facts.ts +4 -0
- package/src/parsers/generated-constants-parser.ts +80 -14
- package/src/parsers/outbound-call-classifier.ts +27 -124
- package/src/parsers/outbound-call-parser.ts +13 -1
- package/src/parsers/outbound-expression-analysis.ts +2 -2
- package/src/parsers/stable-local-value.ts +30 -9
- package/src/parsers/string-constant-lookups.ts +358 -0
- package/src/trace/event-runtime-resolution.ts +24 -3
- package/src/trace/event-shape-candidate-trace.ts +172 -0
- package/src/trace/event-subscriber-traversal.ts +19 -7
- package/src/trace/evidence.ts +7 -0
- package/src/trace/trace-scope-execution.ts +21 -29
- package/src/types.ts +17 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-GSLFY6J2.js.map +0 -1
|
@@ -12,7 +12,8 @@ const requiredColumns = {
|
|
|
12
12
|
repositories: [
|
|
13
13
|
'id', 'workspace_id', 'name', 'absolute_path', 'relative_path',
|
|
14
14
|
'package_name', 'package_version', 'dependencies_json',
|
|
15
|
-
'package_public_surface_json', '
|
|
15
|
+
'package_public_surface_json', 'environment_declarations_json',
|
|
16
|
+
'kind', 'is_git_repo', 'last_indexed_at',
|
|
16
17
|
'index_status', 'error_count', 'fingerprint', 'fact_generation',
|
|
17
18
|
'graph_generation', 'graph_stale_reason', 'graph_stale_at',
|
|
18
19
|
'fact_analyzer_version',
|
|
@@ -66,6 +67,7 @@ const requiredColumns = {
|
|
|
66
67
|
outbound_calls: [
|
|
67
68
|
'id', 'repo_id', 'source_symbol_id', 'service_binding_id', 'call_type',
|
|
68
69
|
'method', 'operation_path_expr', 'query_entity', 'event_name_expr',
|
|
70
|
+
'event_skeleton_signature', 'event_skeleton_json',
|
|
69
71
|
'payload_summary', 'source_file', 'source_line',
|
|
70
72
|
'call_site_start_offset', 'call_site_end_offset', 'evidence_json',
|
|
71
73
|
'confidence', 'unresolved_reason', 'local_service_name',
|
|
@@ -79,6 +81,13 @@ const requiredColumns = {
|
|
|
79
81
|
'call_site_end_offset', 'call_role', 'status', 'evidence_json',
|
|
80
82
|
'confidence', 'unresolved_reason',
|
|
81
83
|
],
|
|
84
|
+
generated_constants: [
|
|
85
|
+
'id', 'repo_id', 'source_file', 'source_line', 'name',
|
|
86
|
+
'container_name', 'member_name', 'value', 'constant_kind', 'exported',
|
|
87
|
+
'stable', 'resolution_status', 'unresolved_reason',
|
|
88
|
+
'declaration_start_offset', 'declaration_end_offset',
|
|
89
|
+
'value_start_offset', 'value_end_offset',
|
|
90
|
+
],
|
|
82
91
|
graph_edges: [
|
|
83
92
|
'id', 'workspace_id', 'edge_type', 'status', 'from_kind', 'from_id',
|
|
84
93
|
'to_kind', 'to_id', 'confidence', 'evidence_json', 'is_dynamic',
|
|
@@ -167,6 +176,33 @@ function bindingIndexInvalid(
|
|
|
167
176
|
&& metadata?.unique === unique && metadata.partial === partial ? 0 : 1;
|
|
168
177
|
}
|
|
169
178
|
|
|
179
|
+
function tableIndexInvalid(
|
|
180
|
+
db: Db,
|
|
181
|
+
table: string,
|
|
182
|
+
name: string,
|
|
183
|
+
columns: readonly string[],
|
|
184
|
+
unique: number,
|
|
185
|
+
): number {
|
|
186
|
+
const metadata = db.prepare(`PRAGMA index_list(${table})`)
|
|
187
|
+
.all().find((item) => item.name === name);
|
|
188
|
+
return metadata?.unique === unique
|
|
189
|
+
&& indexColumns(db, name).join('\0') === columns.join('\0') ? 0 : 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function eventSurfaceIndexesInvalid(db: Db): number {
|
|
193
|
+
return tableIndexInvalid(
|
|
194
|
+
db, 'outbound_calls', 'idx_outbound_event_skeleton',
|
|
195
|
+
['event_skeleton_signature', 'call_type', 'repo_id'], 0,
|
|
196
|
+
) + tableIndexInvalid(
|
|
197
|
+
db, 'generated_constants', 'idx_generated_constant_name',
|
|
198
|
+
['repo_id', 'source_file', 'name'], 0,
|
|
199
|
+
) + tableIndexInvalid(
|
|
200
|
+
db, 'generated_constants', 'uq_generated_constant_site',
|
|
201
|
+
['repo_id', 'source_file', 'name', 'declaration_start_offset',
|
|
202
|
+
'declaration_end_offset'], 1,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
170
206
|
function exactBindingIndexInvalid(db: Db): number {
|
|
171
207
|
const row = db.prepare(`SELECT sql FROM sqlite_master
|
|
172
208
|
WHERE type='index' AND name='uq_service_binding_exact_site'`).get();
|
|
@@ -197,5 +233,9 @@ export function invalidSchemaStructureCategories(
|
|
|
197
233
|
exactBindingIndexInvalid(db)
|
|
198
234
|
+ bindingIndexInvalid(db, 'idx_service_binding_site', 0, 0),
|
|
199
235
|
),
|
|
236
|
+
...category(
|
|
237
|
+
'schema_event_surface_index_invalid',
|
|
238
|
+
eventSurfaceIndexesInvalid(db),
|
|
239
|
+
),
|
|
200
240
|
];
|
|
201
241
|
}
|
package/src/db/schema.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const schemaTablesSql = `
|
|
2
2
|
CREATE TABLE IF NOT EXISTS workspaces (id INTEGER PRIMARY KEY, root_path TEXT UNIQUE NOT NULL, db_path TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
3
|
-
CREATE TABLE IF NOT EXISTS repositories (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, name TEXT NOT NULL, absolute_path TEXT NOT NULL, relative_path TEXT NOT NULL, package_name TEXT, package_version TEXT, dependencies_json TEXT DEFAULT '{}', package_public_surface_json TEXT, kind TEXT NOT NULL, is_git_repo INTEGER NOT NULL, last_indexed_at TEXT, index_status TEXT DEFAULT 'pending', error_count INTEGER DEFAULT 0, fingerprint TEXT, fact_generation INTEGER NOT NULL DEFAULT 0, graph_generation INTEGER NOT NULL DEFAULT 0, graph_stale_reason TEXT, graph_stale_at TEXT, fact_analyzer_version TEXT, UNIQUE(workspace_id, absolute_path), FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
3
|
+
CREATE TABLE IF NOT EXISTS repositories (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, name TEXT NOT NULL, absolute_path TEXT NOT NULL, relative_path TEXT NOT NULL, package_name TEXT, package_version TEXT, dependencies_json TEXT DEFAULT '{}', package_public_surface_json TEXT, environment_declarations_json TEXT DEFAULT '{"schema":"service-flow/environment-declarations@1","status":"not_applicable","reason":null,"recordCap":32,"total":0,"shown":0,"omitted":0,"declarations":[]}', kind TEXT NOT NULL, is_git_repo INTEGER NOT NULL, last_indexed_at TEXT, index_status TEXT DEFAULT 'pending', error_count INTEGER DEFAULT 0, fingerprint TEXT, fact_generation INTEGER NOT NULL DEFAULT 0, graph_generation INTEGER NOT NULL DEFAULT 0, graph_stale_reason TEXT, graph_stale_at TEXT, fact_analyzer_version TEXT, UNIQUE(workspace_id, absolute_path), FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
4
4
|
CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, relative_path TEXT NOT NULL, extension TEXT NOT NULL, sha256 TEXT NOT NULL, size_bytes INTEGER NOT NULL, last_indexed_at TEXT NOT NULL, UNIQUE(repo_id, relative_path), FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
5
5
|
CREATE TABLE IF NOT EXISTS cds_requires (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, alias TEXT NOT NULL, kind TEXT, model TEXT, destination TEXT, service_path TEXT, request_timeout INTEGER, raw_json TEXT NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
6
6
|
CREATE TABLE IF NOT EXISTS cds_services (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, namespace TEXT, service_name TEXT NOT NULL, qualified_name TEXT NOT NULL, service_path TEXT NOT NULL, is_extend INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, extension_local_ref TEXT, extension_imported_symbol TEXT, extension_local_alias TEXT, extension_module_specifier TEXT, extension_import_kind TEXT, extension_base_service_id INTEGER, extension_base_status TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(extension_base_service_id) REFERENCES cds_services(id) ON DELETE SET NULL);
|
|
@@ -10,7 +10,8 @@ CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTE
|
|
|
10
10
|
CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, decorator_resolution_json TEXT NOT NULL DEFAULT '{}', source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
|
|
11
11
|
CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, class_name TEXT, import_source TEXT, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE SET NULL);
|
|
12
12
|
CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, binding_site_start_offset INTEGER, binding_site_end_offset INTEGER, owner_resolution TEXT NOT NULL DEFAULT 'legacy_unknown' CHECK(owner_resolution IN ('owned_exact','ownerless_file_scope','legacy_unknown')), helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
13
|
-
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
13
|
+
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, event_skeleton_signature TEXT, event_skeleton_json TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
14
|
+
CREATE TABLE IF NOT EXISTS generated_constants (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, name TEXT NOT NULL, container_name TEXT, member_name TEXT, value TEXT, constant_kind TEXT NOT NULL, exported INTEGER NOT NULL, stable INTEGER NOT NULL, resolution_status TEXT NOT NULL CHECK(resolution_status IN ('resolved','refused')), unresolved_reason TEXT, declaration_start_offset INTEGER NOT NULL, declaration_end_offset INTEGER NOT NULL, value_start_offset INTEGER NOT NULL, value_end_offset INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
14
15
|
CREATE TABLE IF NOT EXISTS symbol_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, caller_symbol_id INTEGER NOT NULL, callee_symbol_id INTEGER, callee_expression TEXT NOT NULL, import_source TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, call_role TEXT NOT NULL DEFAULT 'legacy_unknown', status TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, unresolved_reason TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(caller_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE, FOREIGN KEY(callee_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
15
16
|
CREATE TABLE IF NOT EXISTS graph_edges (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, edge_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'unresolved', from_kind TEXT NOT NULL, from_id TEXT NOT NULL, to_kind TEXT NOT NULL, to_id TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, is_dynamic INTEGER NOT NULL, unresolved_reason TEXT, generation INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
16
17
|
CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, status TEXT NOT NULL, repo_count INTEGER NOT NULL, file_count INTEGER NOT NULL, diagnostic_count INTEGER NOT NULL, error_message TEXT, owner_pid INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
@@ -24,6 +25,9 @@ CREATE INDEX IF NOT EXISTS idx_extension_import ON cds_services(extension_module
|
|
|
24
25
|
CREATE INDEX IF NOT EXISTS idx_operation_name ON cds_operations(operation_name, operation_path);
|
|
25
26
|
CREATE INDEX IF NOT EXISTS idx_operation_base ON cds_operations(base_operation_id);
|
|
26
27
|
CREATE INDEX IF NOT EXISTS idx_calls_repo ON outbound_calls(repo_id, call_type);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_outbound_event_skeleton ON outbound_calls(event_skeleton_signature,call_type,repo_id);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_generated_constant_name ON generated_constants(repo_id,source_file,name);
|
|
30
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_generated_constant_site ON generated_constants(repo_id,source_file,name,declaration_start_offset,declaration_end_offset);
|
|
27
31
|
CREATE INDEX IF NOT EXISTS idx_symbol_calls_caller ON symbol_calls(repo_id, caller_symbol_id);
|
|
28
32
|
CREATE INDEX IF NOT EXISTS idx_outbound_call_site ON outbound_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_type);
|
|
29
33
|
CREATE INDEX IF NOT EXISTS idx_symbol_call_site_role ON symbol_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_role);
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
insertCalls,
|
|
8
8
|
insertExecutableSymbols,
|
|
9
9
|
insertHandler,
|
|
10
|
+
insertGeneratedConstants,
|
|
10
11
|
handlerMethodIsExecutable,
|
|
11
12
|
insertRegistrations,
|
|
12
13
|
insertSymbolCalls,
|
|
@@ -23,6 +24,9 @@ import {
|
|
|
23
24
|
parseOutboundCalls,
|
|
24
25
|
} from '../parsers/outbound-call-parser.js';
|
|
25
26
|
import { parseExecutableSymbols } from '../parsers/symbol-parser.js';
|
|
27
|
+
import {
|
|
28
|
+
generatedConstantFacts,
|
|
29
|
+
} from '../parsers/generated-constants-parser.js';
|
|
26
30
|
import {
|
|
27
31
|
loadPackageJsonSnapshot,
|
|
28
32
|
} from '../parsers/package-json-parser.js';
|
|
@@ -54,7 +58,19 @@ import {
|
|
|
54
58
|
type RepositorySourceContext,
|
|
55
59
|
type SourceContextInstrumentation,
|
|
56
60
|
} from '../parsers/ts-project.js';
|
|
57
|
-
import
|
|
61
|
+
import {
|
|
62
|
+
createImportedEventNameResolver,
|
|
63
|
+
} from '../parsers/event-name-import-resolution.js';
|
|
64
|
+
import {
|
|
65
|
+
collectEnvironmentDeclarations,
|
|
66
|
+
type EnvironmentDeclarationsFact,
|
|
67
|
+
} from '../parsers/environment-declarations.js';
|
|
68
|
+
import {
|
|
69
|
+
createEventEnvironmentReferenceResolver,
|
|
70
|
+
} from '../parsers/event-environment-reference.js';
|
|
71
|
+
import { invalidateEventSurfaceFacts } from
|
|
72
|
+
'../db/event-surface-invalidation.js';
|
|
73
|
+
import type { CdsServiceFact, GeneratedConstantFact, HandlerClassFact, HandlerRegistrationFact, OutboundCallFact, PackageFacts, ServiceBindingFact, ExecutableSymbolFact, SymbolCallFact } from '../types.js';
|
|
58
74
|
export interface IndexRepoResult {
|
|
59
75
|
fileCount: number;
|
|
60
76
|
diagnosticCount: number;
|
|
@@ -68,6 +84,7 @@ interface ParsedFacts {
|
|
|
68
84
|
calls: OutboundCallFact[];
|
|
69
85
|
symbols: ExecutableSymbolFact[];
|
|
70
86
|
symbolCalls: SymbolCallFact[];
|
|
87
|
+
generatedConstants: GeneratedConstantFact[];
|
|
71
88
|
fileRecords: Array<{ relativePath: string; extension: string; sha256: string; sizeBytes: number }>;
|
|
72
89
|
}
|
|
73
90
|
export interface PreparedRepositoryIndex extends IndexRepoResult {
|
|
@@ -77,6 +94,7 @@ export interface PreparedRepositoryIndex extends IndexRepoResult {
|
|
|
77
94
|
kind?: string;
|
|
78
95
|
parsed?: ParsedFacts;
|
|
79
96
|
packagePublicSurface?: PackagePublicSurfaceFact;
|
|
97
|
+
environmentDeclarations?: EnvironmentDeclarationsFact;
|
|
80
98
|
}
|
|
81
99
|
export async function indexRepository(
|
|
82
100
|
db: Db,
|
|
@@ -139,6 +157,7 @@ export async function prepareRepositoryIndex(
|
|
|
139
157
|
kind: await classifyRepository(repo.absolute_path, packageFacts),
|
|
140
158
|
parsed,
|
|
141
159
|
packagePublicSurface: packageSurface.surface,
|
|
160
|
+
environmentDeclarations: collectEnvironmentDeclarations(sources),
|
|
142
161
|
fileCount: sourceFiles.length,
|
|
143
162
|
diagnosticCount: parsed.handlers.filter((handler) =>
|
|
144
163
|
handler.hasHandlerDecorator
|
|
@@ -154,20 +173,27 @@ export function publishPreparedRepositoryIndex(
|
|
|
154
173
|
): void {
|
|
155
174
|
if (prepared.skipped) return;
|
|
156
175
|
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint
|
|
157
|
-
|| !prepared.kind || !prepared.packagePublicSurface
|
|
176
|
+
|| !prepared.kind || !prepared.packagePublicSurface
|
|
177
|
+
|| !prepared.environmentDeclarations)
|
|
158
178
|
throw new Error('Prepared repository index is missing publication facts');
|
|
159
179
|
const now = new Date().toISOString();
|
|
160
180
|
const repoId = prepared.repo.id;
|
|
181
|
+
const environmentJson = JSON.stringify(prepared.environmentDeclarations);
|
|
161
182
|
invalidatePackageTargetFacts(
|
|
162
183
|
db, repoId, prepared.packageFacts.packageName, invalidations,
|
|
163
184
|
);
|
|
185
|
+
invalidateEventSurfaceFacts(
|
|
186
|
+
db, repoId, prepared.parsed.calls, environmentJson,
|
|
187
|
+
);
|
|
164
188
|
db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
|
|
165
|
-
dependencies_json=?,package_public_surface_json=?,
|
|
189
|
+
dependencies_json=?,package_public_surface_json=?,
|
|
190
|
+
environment_declarations_json=?,kind=?,index_status=?
|
|
166
191
|
WHERE id=?`).run(
|
|
167
192
|
prepared.packageFacts.packageName,
|
|
168
193
|
prepared.packageFacts.packageVersion,
|
|
169
194
|
JSON.stringify(prepared.packageFacts.dependencies),
|
|
170
195
|
JSON.stringify(prepared.packagePublicSurface),
|
|
196
|
+
environmentJson,
|
|
171
197
|
prepared.kind,
|
|
172
198
|
'indexing',
|
|
173
199
|
repoId,
|
|
@@ -183,6 +209,7 @@ export function publishPreparedRepositoryIndex(
|
|
|
183
209
|
insertRegistrations(db, repoId, prepared.parsed.registrations);
|
|
184
210
|
insertBindings(db, repoId, prepared.parsed.bindings);
|
|
185
211
|
insertCalls(db, repoId, prepared.parsed.calls);
|
|
212
|
+
insertGeneratedConstants(db, repoId, prepared.parsed.generatedConstants);
|
|
186
213
|
db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(now, prepared.fingerprint, now, ANALYZER_VERSION, repoId);
|
|
187
214
|
}
|
|
188
215
|
|
|
@@ -243,14 +270,21 @@ async function parseAllSourceFacts(
|
|
|
243
270
|
root: string,
|
|
244
271
|
sources: RepositorySourceContext,
|
|
245
272
|
): Promise<ParsedFacts> {
|
|
246
|
-
const facts: ParsedFacts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], fileRecords: [] };
|
|
273
|
+
const facts: ParsedFacts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], generatedConstants: [], fileRecords: [] };
|
|
247
274
|
for (const snapshot of sources.entries()) {
|
|
248
275
|
const file = snapshot.filePath;
|
|
249
276
|
facts.fileRecords.push({ relativePath: normalizePath(file), extension: path.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
|
|
250
277
|
if (file.endsWith('.cds')) facts.services.push(...(await parseCdsFile(root, file, sources)));
|
|
251
278
|
if (/\.[jt]s$/.test(file)) {
|
|
252
279
|
const source = snapshot.sourceFile();
|
|
253
|
-
|
|
280
|
+
facts.generatedConstants.push(...generatedConstantFacts(source, file));
|
|
281
|
+
const classified = classifyOutboundCallsInSource(source, file, {
|
|
282
|
+
importedEventNameResolver: createImportedEventNameResolver(
|
|
283
|
+
sources, source, file,
|
|
284
|
+
),
|
|
285
|
+
eventEnvironmentReferenceResolver:
|
|
286
|
+
createEventEnvironmentReferenceResolver(sources, source, file),
|
|
287
|
+
});
|
|
254
288
|
facts.handlers.push(...(await parseDecorators(root, file, sources)));
|
|
255
289
|
facts.registrations.push(...(await parseHandlerRegistrations(root, file, sources)));
|
|
256
290
|
const bindings = await parseServiceBindings(root, file, sources);
|
|
@@ -280,12 +314,17 @@ async function findSourceFiles(root: string): Promise<string[]> {
|
|
|
280
314
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
281
315
|
if (e.isDirectory()) {
|
|
282
316
|
if (!['node_modules', 'dist', 'gen', 'coverage', '.git'].includes(e.name)) await walk(path.join(dir, e.name), rel);
|
|
283
|
-
} else if (
|
|
317
|
+
} else if (isRepositoryFactInput(e.name)
|
|
318
|
+
&& !isDefaultTestFile(rel)) out.push(rel);
|
|
284
319
|
}
|
|
285
320
|
}
|
|
286
321
|
await walk(root);
|
|
287
322
|
return out.sort();
|
|
288
323
|
}
|
|
324
|
+
function isRepositoryFactInput(name: string): boolean {
|
|
325
|
+
return /\.(cds|ts|js)$/.test(name)
|
|
326
|
+
|| ['nodemon.json', '.env', 'mta.yaml', 'manifest.yml'].includes(name);
|
|
327
|
+
}
|
|
289
328
|
function isDefaultTestFile(relativeFile: string): boolean {
|
|
290
329
|
const parts = relativeFile.split('/');
|
|
291
330
|
if (parts.some((part) => ['test', 'tests', '__tests__'].includes(part))) return true;
|
|
@@ -4,7 +4,10 @@ import { linkImplementations as linkCanonicalImplementations } from './implement
|
|
|
4
4
|
import { linkPackageImportSymbolCalls } from './package-import-symbol-resolver.js';
|
|
5
5
|
import { linkEventSubscriptionHandlers } from './event-subscription-handler-linker.js';
|
|
6
6
|
import { insertCallEdge } from './call-edge-insertion.js';
|
|
7
|
+
import { linkEventShapeCandidates } from './event-shape-candidate-linker.js';
|
|
7
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
|
+
}
|