@saptools/service-flow 0.1.65 → 0.1.67

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +67 -11
  3. package/TECHNICAL-NOTE.md +41 -1
  4. package/dist/{chunk-OONNRIDL.js → chunk-ZQABU7MR.js} +3764 -643
  5. package/dist/chunk-ZQABU7MR.js.map +1 -0
  6. package/dist/cli.js +158 -295
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +196 -1
  9. package/dist/index.js +7 -3
  10. package/dist/index.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/cli/002-doctor-lifecycle.ts +66 -0
  13. package/src/cli/doctor.ts +14 -27
  14. package/src/cli.ts +130 -102
  15. package/src/db/000-call-fact-repository.ts +537 -0
  16. package/src/db/001-fact-lifecycle.ts +111 -0
  17. package/src/db/migrations.ts +16 -1
  18. package/src/db/repositories.ts +1 -315
  19. package/src/db/schema.ts +4 -2
  20. package/src/index.ts +22 -0
  21. package/src/linker/004-event-subscription-handler-linker.ts +300 -0
  22. package/src/linker/cross-repo-linker.ts +23 -2
  23. package/src/output/001-compact-json-output.ts +6 -0
  24. package/src/parsers/imported-wrapper-parser.ts +4 -0
  25. package/src/parsers/outbound-call-parser.ts +11 -1
  26. package/src/parsers/symbol-parser.ts +7 -4
  27. package/src/trace/010-traversal-scope.ts +188 -0
  28. package/src/trace/011-event-subscriber-traversal.ts +366 -0
  29. package/src/trace/012-trace-graph-lookups.ts +74 -0
  30. package/src/trace/013-trace-root-scopes.ts +279 -0
  31. package/src/trace/014-compact-contract.ts +347 -0
  32. package/src/trace/015-trace-edge-recorder.ts +336 -0
  33. package/src/trace/016-compact-projector.ts +697 -0
  34. package/src/trace/017-trace-context.ts +378 -0
  35. package/src/trace/018-compact-trace.ts +87 -0
  36. package/src/trace/019-trace-edge-semantics.ts +328 -0
  37. package/src/trace/020-compact-field-projection.ts +387 -0
  38. package/src/trace/dynamic-branches.ts +35 -13
  39. package/src/trace/trace-engine.ts +271 -245
  40. package/src/types.ts +10 -0
  41. package/src/version.ts +1 -1
  42. package/dist/chunk-OONNRIDL.js.map +0 -1
package/src/cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Command } from 'commander';
1
+ import { Command, Option } from 'commander';
2
2
  import { DEFAULT_IGNORES } from './config/defaults.js';
3
3
  import {
4
4
  createWorkspaceConfig,
@@ -21,6 +21,7 @@ import { indexWorkspace } from './indexer/workspace-indexer.js';
21
21
  import { linkWorkspace } from './linker/cross-repo-linker.js';
22
22
  import { doctorDiagnostics, linkUpgradeWarnings } from './cli/doctor.js';
23
23
  import { trace } from './trace/trace-engine.js';
24
+ import { compactTrace } from './trace/018-compact-trace.js';
24
25
  import {
25
26
  parseVars,
26
27
  selectorRepoAmbiguousDiagnostic,
@@ -31,11 +32,55 @@ import { renderTraceJson, renderJson } from './output/json-output.js';
31
32
  import { renderDoctorDiagnostics } from './output/doctor-output.js';
32
33
  import { renderMermaid } from './output/mermaid-output.js';
33
34
  import { createStdoutWriter } from './output/000-stdout-policy.js';
35
+ import { renderCompactJson } from './output/001-compact-json-output.js';
34
36
  import { VERSION } from './version.js';
35
- import type { DynamicMode } from './types.js';
37
+ import type {
38
+ DynamicMode,
39
+ TraceOptions,
40
+ TraceResult,
41
+ TraceStart,
42
+ } from './types.js';
36
43
  import { cleanWorkspaceState } from './cli/000-clean.js';
37
44
 
38
45
  const stdout = createStdoutWriter(process.stdout, fail);
46
+ const TRACE_FORMATS = ['table', 'json', 'mermaid', 'compact-json'] as const;
47
+ const GRAPH_FORMATS = ['mermaid', 'json', 'compact-json'] as const;
48
+
49
+ type TraceFormat = (typeof TRACE_FORMATS)[number];
50
+ type GraphFormat = (typeof GRAPH_FORMATS)[number];
51
+
52
+ interface TraceCommandOptions {
53
+ workspace?: string;
54
+ repo?: string;
55
+ operation?: string;
56
+ service?: string;
57
+ path?: string;
58
+ handler?: string;
59
+ depth: string;
60
+ format: TraceFormat;
61
+ includeExternal?: boolean;
62
+ includeDb?: boolean;
63
+ includeAsync?: boolean;
64
+ implementationRepo?: string;
65
+ implementationHint: string[];
66
+ var: string[];
67
+ dynamicMode: string;
68
+ maxDynamicCandidates: string;
69
+ }
70
+
71
+ interface GraphCommandOptions {
72
+ workspace?: string;
73
+ repo?: string;
74
+ operation?: string;
75
+ service?: string;
76
+ path?: string;
77
+ format: GraphFormat;
78
+ implementationRepo?: string;
79
+ implementationHint: string[];
80
+ var: string[];
81
+ dynamicMode: string;
82
+ maxDynamicCandidates: string;
83
+ }
39
84
 
40
85
  function writeStdout(value: string): void {
41
86
  stdout.write(value);
@@ -127,6 +172,79 @@ function selectRepository(db: Db, selector: string, workspaceId?: number): {
127
172
  ),
128
173
  };
129
174
  }
175
+
176
+ function traceFormatOption(): Option {
177
+ return new Option('--format <format>', TRACE_FORMATS.join('|'))
178
+ .choices([...TRACE_FORMATS])
179
+ .default('table');
180
+ }
181
+
182
+ function graphFormatOption(): Option {
183
+ return new Option('--format <format>', GRAPH_FORMATS.join('|'))
184
+ .choices([...GRAPH_FORMATS])
185
+ .default('mermaid');
186
+ }
187
+
188
+ function writeTraceOutput(
189
+ db: Db,
190
+ start: TraceStart,
191
+ options: TraceOptions,
192
+ format: TraceFormat | GraphFormat,
193
+ ): void {
194
+ if (format === 'compact-json') {
195
+ writeStdout(renderCompactJson(compactTrace(db, start, options)));
196
+ return;
197
+ }
198
+ const result = trace(db, start, options);
199
+ writeStdout(renderDetailedTrace(result, format));
200
+ }
201
+
202
+ function renderDetailedTrace(
203
+ result: TraceResult,
204
+ format: Exclude<TraceFormat, 'compact-json'> | Exclude<GraphFormat, 'compact-json'>,
205
+ ): string {
206
+ if (format === 'json') return renderTraceJson(result);
207
+ if (format === 'mermaid') return renderMermaid(result);
208
+ return renderTraceTable(result);
209
+ }
210
+
211
+ function runTraceCommand(opts: TraceCommandOptions): Promise<void> {
212
+ return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
213
+ const start: TraceStart = {
214
+ repo: opts.repo, servicePath: opts.service, operation: opts.operation,
215
+ operationPath: opts.path, handler: opts.handler,
216
+ };
217
+ const options: TraceOptions = {
218
+ depth: Number(opts.depth), workspaceId, vars: parseVars(opts.var),
219
+ includeExternal: Boolean(opts.includeExternal),
220
+ includeDb: Boolean(opts.includeDb), includeAsync: Boolean(opts.includeAsync),
221
+ implementationRepo: opts.implementationRepo,
222
+ implementationHints: opts.implementationHint.map(parseImplementationHint),
223
+ dynamicMode: parseDynamicMode(opts.dynamicMode),
224
+ maxDynamicCandidates: parsePositiveInteger(opts.maxDynamicCandidates, 5),
225
+ };
226
+ writeTraceOutput(db, start, options, opts.format);
227
+ });
228
+ }
229
+
230
+ function runGraphCommand(opts: GraphCommandOptions): Promise<void> {
231
+ return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
232
+ const start: TraceStart = {
233
+ repo: opts.repo, operation: opts.operation, servicePath: opts.service,
234
+ operationPath: opts.path,
235
+ };
236
+ const options: TraceOptions = {
237
+ depth: 100, workspaceId, includeAsync: true, includeDb: true,
238
+ includeExternal: true, vars: parseVars(opts.var),
239
+ implementationRepo: opts.implementationRepo,
240
+ implementationHints: opts.implementationHint.map(parseImplementationHint),
241
+ dynamicMode: parseDynamicMode(opts.dynamicMode),
242
+ maxDynamicCandidates: parsePositiveInteger(opts.maxDynamicCandidates, 5),
243
+ };
244
+ writeTraceOutput(db, start, options, opts.format);
245
+ });
246
+ }
247
+
130
248
  export function createProgram(): Command {
131
249
  const program = new Command();
132
250
  program
@@ -169,9 +287,9 @@ export function createProgram(): Command {
169
287
  (opts: { workspace?: string }) =>
170
288
  void withWorkspace(opts.workspace, (db, workspaceId) => {
171
289
  const r = linkWorkspace(db, workspaceId);
172
- const upgradeWarnings = linkUpgradeWarnings(db);
290
+ const upgradeWarnings = linkUpgradeWarnings(db, workspaceId);
173
291
  writeStdout(
174
- `${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\n`,
292
+ `${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`,
175
293
  );
176
294
  }).catch(fail),
177
295
  );
@@ -184,7 +302,7 @@ export function createProgram(): Command {
184
302
  .option('--path <operationPath>')
185
303
  .option('--handler <name>')
186
304
  .option('--depth <n>', 'trace depth', '25')
187
- .option('--format <format>', 'table|json|mermaid', 'table')
305
+ .addOption(traceFormatOption())
188
306
  .option('--include-external')
189
307
  .option('--include-db')
190
308
  .option('--include-async')
@@ -193,57 +311,7 @@ export function createProgram(): Command {
193
311
  .option('--var <key=value>', 'dynamic variable', collect, [])
194
312
  .option('--dynamic-mode <mode>', 'strict|candidates|infer', 'strict')
195
313
  .option('--max-dynamic-candidates <n>', 'maximum dynamic candidates to show', '5')
196
- .action(
197
- (opts: {
198
- workspace?: string;
199
- repo?: string;
200
- operation?: string;
201
- service?: string;
202
- path?: string;
203
- handler?: string;
204
- depth: string;
205
- format: string;
206
- includeExternal?: boolean;
207
- includeDb?: boolean;
208
- includeAsync?: boolean;
209
- implementationRepo?: string;
210
- implementationHint: string[];
211
- var: string[];
212
- dynamicMode: string;
213
- maxDynamicCandidates: string;
214
- }) =>
215
- void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
216
- const result = trace(
217
- db,
218
- {
219
- repo: opts.repo,
220
- servicePath: opts.service,
221
- operation: opts.operation,
222
- operationPath: opts.path,
223
- handler: opts.handler,
224
- },
225
- {
226
- depth: Number(opts.depth),
227
- workspaceId,
228
- vars: parseVars(opts.var),
229
- includeExternal: Boolean(opts.includeExternal),
230
- includeDb: Boolean(opts.includeDb),
231
- includeAsync: Boolean(opts.includeAsync),
232
- implementationRepo: opts.implementationRepo,
233
- implementationHints: opts.implementationHint.map(parseImplementationHint),
234
- dynamicMode: parseDynamicMode(opts.dynamicMode),
235
- maxDynamicCandidates: parsePositiveInteger(opts.maxDynamicCandidates, 5),
236
- },
237
- );
238
- writeStdout(
239
- opts.format === 'json'
240
- ? renderTraceJson(result)
241
- : opts.format === 'mermaid'
242
- ? renderMermaid(result)
243
- : renderTraceTable(result),
244
- );
245
- }).catch(fail),
246
- );
314
+ .action((opts: TraceCommandOptions) => void runTraceCommand(opts).catch(fail));
247
315
  const list = program.command('list');
248
316
  list
249
317
  .command('repos')
@@ -348,55 +416,13 @@ export function createProgram(): Command {
348
416
  .option('--operation <name>')
349
417
  .option('--service <path>')
350
418
  .option('--path <operationPath>')
351
- .option('--format <format>', 'mermaid|json', 'mermaid')
419
+ .addOption(graphFormatOption())
352
420
  .option('--implementation-repo <name>')
353
421
  .option('--implementation-hint <scope>', 'scoped implementation hint', collect, [])
354
422
  .option('--var <key=value>', 'dynamic variable', collect, [])
355
423
  .option('--dynamic-mode <mode>', 'strict|candidates|infer', 'strict')
356
424
  .option('--max-dynamic-candidates <n>', 'maximum dynamic candidates to show', '5')
357
- .action(
358
- (opts: {
359
- workspace?: string;
360
- repo?: string;
361
- operation?: string;
362
- service?: string;
363
- path?: string;
364
- format: string;
365
- implementationRepo?: string;
366
- implementationHint: string[];
367
- var: string[];
368
- dynamicMode: string;
369
- maxDynamicCandidates: string;
370
- }) =>
371
- void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
372
- const result = trace(
373
- db,
374
- {
375
- repo: opts.repo,
376
- operation: opts.operation,
377
- servicePath: opts.service,
378
- operationPath: opts.path,
379
- },
380
- {
381
- depth: 100,
382
- workspaceId,
383
- includeAsync: true,
384
- includeDb: true,
385
- includeExternal: true,
386
- vars: parseVars(opts.var),
387
- implementationRepo: opts.implementationRepo,
388
- implementationHints: opts.implementationHint.map(parseImplementationHint),
389
- dynamicMode: parseDynamicMode(opts.dynamicMode),
390
- maxDynamicCandidates: parsePositiveInteger(opts.maxDynamicCandidates, 5),
391
- },
392
- );
393
- writeStdout(
394
- opts.format === 'json'
395
- ? renderTraceJson(result)
396
- : renderMermaid(result),
397
- );
398
- }).catch(fail),
399
- );
425
+ .action((opts: GraphCommandOptions) => void runGraphCommand(opts).catch(fail));
400
426
  const inspect = program.command('inspect');
401
427
  inspect
402
428
  .command('repo')
@@ -434,8 +460,10 @@ export function createProgram(): Command {
434
460
  .option('--format <format>', 'json|table')
435
461
  .action(
436
462
  (opts: { workspace?: string; strict?: boolean; detail?: boolean; format?: string }) =>
437
- void withReadOnlyWorkspace(opts.workspace, (db) => {
438
- const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), { detail: Boolean(opts.detail) });
463
+ void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
464
+ const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), {
465
+ detail: Boolean(opts.detail), workspaceId,
466
+ });
439
467
  writeStdout(renderDoctorDiagnostics(allDiagnostics, opts.format));
440
468
  }).catch(fail),
441
469
  );