@saptools/service-flow 0.1.70 → 0.1.73
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 +21 -0
- package/README.md +23 -6
- package/TECHNICAL-NOTE.md +24 -2
- package/dist/{chunk-GSLFY6J2.js → chunk-32WOTGTS.js} +12061 -9288
- package/dist/chunk-32WOTGTS.js.map +1 -0
- package/dist/cli.js +854 -161
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +54 -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 +446 -0
- package/src/cli/doctor.ts +4 -6
- package/src/cli.ts +35 -13
- package/src/config/workspace-config.ts +15 -0
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -29
- package/src/db/event-fact-semantics.ts +546 -0
- package/src/db/event-site-semantics.ts +62 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/fact-lifecycle.ts +70 -12
- package/src/db/migrations.ts +15 -1
- package/src/db/package-target-invalidation.ts +80 -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 +93 -10
- package/src/indexer/workspace-indexer.ts +13 -3
- package/src/linker/cross-repo-linker.ts +27 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +204 -0
- package/src/linker/event-subscription-handler-linker.ts +220 -25
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +302 -0
- package/src/output/repository-inspection.ts +11 -0
- package/src/output/table-output.ts +13 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +404 -0
- package/src/parsers/event-call-analysis.ts +370 -0
- package/src/parsers/event-environment-reference.ts +242 -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 +394 -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 +42 -10
- package/src/parsers/string-constant-lookups.ts +408 -0
- package/src/trace/edge-target.ts +65 -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 +90 -8
- package/src/trace/evidence.ts +7 -28
- package/src/trace/trace-scope-execution.ts +22 -30
- package/src/types.ts +19 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-GSLFY6J2.js.map +0 -1
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createWorkspaceConfig,
|
|
5
5
|
loadWorkspaceConfig,
|
|
6
6
|
saveWorkspaceConfig,
|
|
7
|
+
type WorkspaceConfig,
|
|
7
8
|
} from './config/workspace-config.js';
|
|
8
9
|
import { openDatabase, openReadOnlyDatabase, type Db } from './db/connection.js';
|
|
9
10
|
import {
|
|
@@ -20,6 +21,7 @@ import { classifyRepository } from './discovery/classify-repository.js';
|
|
|
20
21
|
import { indexWorkspace } from './indexer/workspace-indexer.js';
|
|
21
22
|
import { linkWorkspace } from './linker/cross-repo-linker.js';
|
|
22
23
|
import { doctorDiagnostics, linkUpgradeWarnings } from './cli/doctor.js';
|
|
24
|
+
import { factLifecycleDiagnostic } from './db/fact-lifecycle.js';
|
|
23
25
|
import { trace } from './trace/trace-engine.js';
|
|
24
26
|
import { compactTrace } from './trace/compact-trace.js';
|
|
25
27
|
import {
|
|
@@ -33,6 +35,9 @@ import { renderDoctorDiagnostics } from './output/doctor-output.js';
|
|
|
33
35
|
import { renderMermaid } from './output/mermaid-output.js';
|
|
34
36
|
import { createStdoutWriter } from './output/stdout-policy.js';
|
|
35
37
|
import { renderCompactJson } from './output/compact-json-output.js';
|
|
38
|
+
import {
|
|
39
|
+
projectRepositoryInspection,
|
|
40
|
+
} from './output/repository-inspection.js';
|
|
36
41
|
import { VERSION } from './version.js';
|
|
37
42
|
import type {
|
|
38
43
|
DynamicMode,
|
|
@@ -122,6 +127,7 @@ async function withWorkspace<T>(
|
|
|
122
127
|
db: ReturnType<typeof openDatabase>,
|
|
123
128
|
workspaceId: number,
|
|
124
129
|
rootPath: string,
|
|
130
|
+
config: WorkspaceConfig,
|
|
125
131
|
) => Promise<T> | T,
|
|
126
132
|
): Promise<T> {
|
|
127
133
|
const config = await loadWorkspaceConfig(workspace);
|
|
@@ -130,7 +136,7 @@ async function withWorkspace<T>(
|
|
|
130
136
|
const row = getWorkspace(db, config.rootPath);
|
|
131
137
|
const workspaceId =
|
|
132
138
|
row?.id ?? upsertWorkspace(db, config.rootPath, config.dbPath);
|
|
133
|
-
return await fn(db, workspaceId, config.rootPath);
|
|
139
|
+
return await fn(db, workspaceId, config.rootPath, config);
|
|
134
140
|
} finally {
|
|
135
141
|
db.close();
|
|
136
142
|
}
|
|
@@ -275,10 +281,11 @@ function registerIndexCommand(program: Command): void {
|
|
|
275
281
|
.option('--force')
|
|
276
282
|
.action(
|
|
277
283
|
(opts: { workspace?: string; repo?: string; force?: boolean }) =>
|
|
278
|
-
void withWorkspace(opts.workspace, async (db, workspaceId) => {
|
|
284
|
+
void withWorkspace(opts.workspace, async (db, workspaceId, _root, config) => {
|
|
279
285
|
const r = await indexWorkspace(db, workspaceId, {
|
|
280
286
|
repo: opts.repo,
|
|
281
287
|
force: Boolean(opts.force),
|
|
288
|
+
eventEnvironmentKeys: config.eventEnvironmentKeys,
|
|
282
289
|
});
|
|
283
290
|
const outcome = indexCommandOutcome(r);
|
|
284
291
|
writeStdout(outcome.stdout);
|
|
@@ -298,7 +305,7 @@ function registerLinkCommand(program: Command): void {
|
|
|
298
305
|
const r = linkWorkspace(db, workspaceId);
|
|
299
306
|
const upgradeWarnings = linkUpgradeWarnings(db, workspaceId);
|
|
300
307
|
writeStdout(
|
|
301
|
-
`${upgradeWarnings.length ? `Warnings: ${upgradeWarnings.map((item) => String(item.code)).join(', ')}. Run service-flow doctor --strict for remediation.\n` : ''}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved, ${r.subscriptionHandlerResolvedCount} subscription handlers resolved, ${r.subscriptionHandlerAmbiguousCount} subscription handlers ambiguous, ${r.subscriptionHandlerUnresolvedCount} subscription handlers unresolved, ${r.subscriptionHandlerMissingAssociationCount} subscription handler associations missing\n`,
|
|
308
|
+
`${upgradeWarnings.length ? `Warnings: ${upgradeWarnings.map((item) => String(item.code)).join(', ')}. Run service-flow doctor --strict for remediation.\n` : ''}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved, ${r.subscriptionHandlerResolvedCount} subscription handlers resolved, ${r.subscriptionHandlerAmbiguousCount} subscription handlers ambiguous, ${r.subscriptionHandlerUnresolvedCount} subscription handlers unresolved, ${r.subscriptionHandlerMissingAssociationCount} subscription handler associations missing, ${r.eventShapeCandidateCount} event shape candidates, ${r.eventShapeCandidateOmittedCount} event shape candidates omitted by the link cap\n`,
|
|
302
309
|
);
|
|
303
310
|
}).catch(fail),
|
|
304
311
|
);
|
|
@@ -329,22 +336,23 @@ function registerTraceCommand(program: Command): void {
|
|
|
329
336
|
function listRepositoriesCommand(
|
|
330
337
|
opts: { workspace?: string },
|
|
331
338
|
): Promise<void> {
|
|
332
|
-
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) =>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
),
|
|
339
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
340
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
341
|
+
writeStdout(renderJson(
|
|
342
|
+
listRepositories(db, workspaceId).map((repo) => ({
|
|
343
|
+
name: repo.name,
|
|
344
|
+
kind: repo.kind,
|
|
345
|
+
packageName: repo.package_name,
|
|
346
|
+
})),
|
|
341
347
|
));
|
|
348
|
+
});
|
|
342
349
|
}
|
|
343
350
|
|
|
344
351
|
function listServicesCommand(
|
|
345
352
|
opts: { workspace?: string; repo?: string },
|
|
346
353
|
): Promise<void> {
|
|
347
354
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
355
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
348
356
|
const selection = opts.repo
|
|
349
357
|
? selectRepository(db, opts.repo, workspaceId) : {};
|
|
350
358
|
if (selection.diagnostic) {
|
|
@@ -363,6 +371,7 @@ function listOperationsCommand(
|
|
|
363
371
|
opts: { workspace?: string; repo?: string; service?: string },
|
|
364
372
|
): Promise<void> {
|
|
365
373
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
374
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
366
375
|
const selection = opts.repo
|
|
367
376
|
? selectRepository(db, opts.repo, workspaceId) : {};
|
|
368
377
|
if (selection.diagnostic) {
|
|
@@ -383,6 +392,7 @@ function listCallsCommand(
|
|
|
383
392
|
opts: { workspace?: string; repo?: string; operation?: string },
|
|
384
393
|
): Promise<void> {
|
|
385
394
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
395
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
386
396
|
const selection = opts.repo
|
|
387
397
|
? selectRepository(db, opts.repo, workspaceId) : {};
|
|
388
398
|
if (selection.diagnostic) {
|
|
@@ -460,9 +470,12 @@ function inspectRepositoryCommand(
|
|
|
460
470
|
opts: { workspace?: string },
|
|
461
471
|
): Promise<void> {
|
|
462
472
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
473
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
463
474
|
const selection = selectRepository(db, name, workspaceId);
|
|
464
475
|
writeStdout(renderJson(
|
|
465
|
-
selection.repo
|
|
476
|
+
selection.repo
|
|
477
|
+
? projectRepositoryInspection(selection.repo)
|
|
478
|
+
: selection.diagnostic ?? { error: 'repo not found' },
|
|
466
479
|
));
|
|
467
480
|
});
|
|
468
481
|
}
|
|
@@ -472,6 +485,7 @@ function inspectOperationCommand(
|
|
|
472
485
|
opts: { workspace?: string },
|
|
473
486
|
): Promise<void> {
|
|
474
487
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
488
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
475
489
|
const rows = db.prepare(
|
|
476
490
|
'SELECT o.* 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_name=? OR o.operation_path=?)',
|
|
477
491
|
).all(workspaceId, selector, selector);
|
|
@@ -479,6 +493,14 @@ function inspectOperationCommand(
|
|
|
479
493
|
});
|
|
480
494
|
}
|
|
481
495
|
|
|
496
|
+
function writeLifecycleBlock(db: Db, workspaceId: number): boolean {
|
|
497
|
+
const diagnostic = factLifecycleDiagnostic(db, workspaceId);
|
|
498
|
+
if (!diagnostic) return false;
|
|
499
|
+
writeStdout(renderJson([diagnostic]));
|
|
500
|
+
process.exitCode = 1;
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
482
504
|
function registerInspectCommands(program: Command): void {
|
|
483
505
|
const inspect = program.command('inspect');
|
|
484
506
|
inspect
|
|
@@ -7,10 +7,24 @@ import {
|
|
|
7
7
|
DEFAULT_DB_FILE,
|
|
8
8
|
DEFAULT_IGNORES,
|
|
9
9
|
} from './defaults.js';
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_EVENT_ENVIRONMENT_KEYS,
|
|
12
|
+
EVENT_ENVIRONMENT_KEY_CAP,
|
|
13
|
+
normalizeEventEnvironmentKeys,
|
|
14
|
+
validEventEnvironmentKey,
|
|
15
|
+
} from '../parsers/environment-declarations.js';
|
|
16
|
+
const environmentKeys = z.array(
|
|
17
|
+
z.string().refine(validEventEnvironmentKey),
|
|
18
|
+
).min(1).max(EVENT_ENVIRONMENT_KEY_CAP).transform(
|
|
19
|
+
normalizeEventEnvironmentKeys,
|
|
20
|
+
);
|
|
10
21
|
const schema = z.object({
|
|
11
22
|
rootPath: z.string(),
|
|
12
23
|
dbPath: z.string(),
|
|
13
24
|
ignore: z.array(z.string()),
|
|
25
|
+
eventEnvironmentKeys: environmentKeys.default(
|
|
26
|
+
[...DEFAULT_EVENT_ENVIRONMENT_KEYS],
|
|
27
|
+
),
|
|
14
28
|
createdAt: z.string(),
|
|
15
29
|
updatedAt: z.string(),
|
|
16
30
|
});
|
|
@@ -55,6 +69,7 @@ export function createWorkspaceConfig(
|
|
|
55
69
|
rootPath: root,
|
|
56
70
|
dbPath: path.resolve(dbPath ?? defaultDbPath(root)),
|
|
57
71
|
ignore,
|
|
72
|
+
eventEnvironmentKeys: [...DEFAULT_EVENT_ENVIRONMENT_KEYS],
|
|
58
73
|
createdAt: now,
|
|
59
74
|
updatedAt: now,
|
|
60
75
|
};
|
|
@@ -400,11 +400,12 @@ export function insertCalls(
|
|
|
400
400
|
function outboundCallInsertStatement(db: Db): Statement {
|
|
401
401
|
return db.prepare(`INSERT INTO outbound_calls(
|
|
402
402
|
repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,
|
|
403
|
-
event_name_expr,
|
|
403
|
+
event_name_expr,event_skeleton_signature,event_skeleton_json,
|
|
404
|
+
payload_summary,source_file,source_line,call_site_start_offset,
|
|
404
405
|
call_site_end_offset,confidence,unresolved_reason,local_service_name,
|
|
405
406
|
local_service_lookup,alias_chain_json,evidence_json,external_target_kind,
|
|
406
407
|
external_target_id,external_target_label,external_target_dynamic,service_binding_id
|
|
407
|
-
) VALUES(
|
|
408
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
|
408
409
|
}
|
|
409
410
|
|
|
410
411
|
function insertOutboundCall(
|
|
@@ -424,7 +425,9 @@ function insertOutboundCall(
|
|
|
424
425
|
stmt.run(
|
|
425
426
|
repoId, sourceSymbolId,
|
|
426
427
|
call.callType, call.method, call.operationPathExpr, call.queryEntity,
|
|
427
|
-
call.eventNameExpr, call.
|
|
428
|
+
call.eventNameExpr, call.eventSkeleton?.signature ?? null,
|
|
429
|
+
call.eventSkeleton ? JSON.stringify(call.eventSkeleton) : null,
|
|
430
|
+
call.payloadSummary, call.sourceFile, call.sourceLine,
|
|
428
431
|
call.callSiteStartOffset, call.callSiteEndOffset, call.confidence,
|
|
429
432
|
call.unresolvedReason,
|
|
430
433
|
call.localServiceName, call.localServiceLookup,
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
invalidBindingFactCategories,
|
|
12
12
|
} from './binding-fact-semantics.js';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
} from './
|
|
13
|
+
import { invalidSymbolFactCategories } from './symbol-call-semantics.js';
|
|
14
|
+
import { invalidEventFactCategories } from './event-fact-semantics.js';
|
|
15
|
+
import { eventSiteCategories } from './event-site-semantics.js';
|
|
16
16
|
|
|
17
17
|
export type PackageFactPhase = 'pre_package' | 'terminal';
|
|
18
18
|
|
|
@@ -258,31 +258,6 @@ function duplicateSymbolCallCategories(
|
|
|
258
258
|
return category('symbol_call_site_duplicate', duplicate);
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
function eventNameCategories(
|
|
262
|
-
db: Db,
|
|
263
|
-
workspaceId?: number,
|
|
264
|
-
): FactSemanticCategoryCount[] {
|
|
265
|
-
const invalid = count(db, `SELECT COUNT(*) count
|
|
266
|
-
FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
|
|
267
|
-
WHERE ${currentRepositoryPredicate()}
|
|
268
|
-
AND fact.call_type IN ('async_emit','async_subscribe')
|
|
269
|
-
AND (typeof(fact.event_name_expr)<>'text'
|
|
270
|
-
OR length(fact.event_name_expr)=0)`, workspaceId);
|
|
271
|
-
const duplicate = count(db, `SELECT COUNT(*) count FROM (
|
|
272
|
-
SELECT fact.repo_id,fact.source_file,fact.call_site_start_offset,
|
|
273
|
-
fact.call_site_end_offset,COUNT(*) duplicate_count
|
|
274
|
-
FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
|
|
275
|
-
WHERE ${currentRepositoryPredicate()}
|
|
276
|
-
AND fact.call_type='async_subscribe'
|
|
277
|
-
GROUP BY fact.repo_id,fact.source_file,fact.call_site_start_offset,
|
|
278
|
-
fact.call_site_end_offset HAVING COUNT(*)<>1
|
|
279
|
-
)`, workspaceId);
|
|
280
|
-
return [
|
|
281
|
-
...category('event_name_invalid', invalid),
|
|
282
|
-
...category('async_subscription_site_duplicate', duplicate),
|
|
283
|
-
];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
261
|
function outboundOwnerCount(db: Db, workspaceId?: number): number {
|
|
287
262
|
return count(db, `WITH eligible AS (
|
|
288
263
|
SELECT fact.id fact_id,s.id symbol_id,
|
|
@@ -685,7 +660,8 @@ export function invalidFactSemanticCategories(
|
|
|
685
660
|
...operationCategories(db, workspaceId),
|
|
686
661
|
...callSpanCategories(db, workspaceId),
|
|
687
662
|
...duplicateSymbolCallCategories(db, workspaceId),
|
|
688
|
-
...
|
|
663
|
+
...eventSiteCategories(db, workspaceId),
|
|
664
|
+
...invalidEventFactCategories(db, workspaceId, phase),
|
|
689
665
|
...ownerCategories(db, workspaceId),
|
|
690
666
|
...bindingCategories(db, workspaceId),
|
|
691
667
|
...invalidBindingFactCategories(db, workspaceId),
|