@saptools/service-flow 0.1.51 → 0.1.52

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 (40) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +10 -5
  3. package/TECHNICAL-NOTE.md +8 -4
  4. package/dist/{chunk-YZJKE5UX.js → chunk-PTLDSHRC.js} +2412 -553
  5. package/dist/chunk-PTLDSHRC.js.map +1 -0
  6. package/dist/cli.js +280 -506
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +45 -7
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/000-clean.ts +82 -0
  12. package/src/cli.ts +75 -57
  13. package/src/db/connection.ts +1 -1
  14. package/src/db/migrations.ts +5 -3
  15. package/src/db/repositories.ts +120 -22
  16. package/src/db/schema.ts +7 -2
  17. package/src/indexer/repository-indexer.ts +57 -29
  18. package/src/indexer/workspace-indexer.ts +84 -6
  19. package/src/linker/cross-repo-linker.ts +13 -1
  20. package/src/output/table-output.ts +29 -3
  21. package/src/parsers/cds-parser.ts +8 -2
  22. package/src/parsers/decorator-parser.ts +382 -48
  23. package/src/parsers/handler-registration-parser.ts +38 -21
  24. package/src/parsers/imported-wrapper-parser.ts +18 -5
  25. package/src/parsers/outbound-call-parser.ts +25 -8
  26. package/src/parsers/package-json-parser.ts +36 -11
  27. package/src/parsers/service-binding-parser-helpers.ts +8 -1
  28. package/src/parsers/service-binding-parser.ts +6 -3
  29. package/src/parsers/symbol-parser.ts +13 -3
  30. package/src/parsers/ts-project.ts +54 -0
  31. package/src/trace/000-dynamic-target-types.ts +84 -0
  32. package/src/trace/001-dynamic-identity.ts +280 -0
  33. package/src/trace/002-trace-diagnostics.ts +54 -0
  34. package/src/trace/003-dynamic-references.ts +82 -0
  35. package/src/trace/dynamic-targets.ts +459 -229
  36. package/src/trace/evidence.ts +305 -46
  37. package/src/trace/selectors.ts +483 -0
  38. package/src/trace/trace-engine.ts +90 -83
  39. package/src/types.ts +27 -1
  40. package/dist/chunk-YZJKE5UX.js.map +0 -1
@@ -1,15 +1,27 @@
1
1
  import type { Db } from '../db/connection.js';
2
+ import { reposByName } from '../db/repositories.js';
2
3
  import { extractPlaceholders } from '../linker/dynamic-edge-resolver.js';
3
4
  import { normalizeODataOperationInvocationPath } from '../linker/odata-path-normalizer.js';
4
5
  import { resolveOperation } from '../linker/service-resolver.js';
5
6
  import type { ImplementationHint, TraceEdge, TraceOptions, TraceResult, TraceStart } from '../types.js';
6
- import { baseTraceEvidence, edgeTarget, runtimeResolution, runtimeVariableDiagnostic, type TraceGraphRow } from './evidence.js';
7
+ import { baseTraceEvidence, edgeTarget, runtimeNoCandidateDiagnostics, runtimeResolution, runtimeVariableDiagnostic, type TraceGraphRow } from './evidence.js';
7
8
  import { dynamicCandidateBranches } from './dynamic-branches.js';
9
+ import {
10
+ loadTraceDiagnostics,
11
+ prependTraceDiagnostic,
12
+ } from './002-trace-diagnostics.js';
8
13
  import { implementationHintDiagnostic, selectImplementation, type ImplementationSelection } from './implementation-hints.js';
9
- import { ambiguousStartDiagnostic } from './selectors.js';
14
+ import {
15
+ ambiguousStartDiagnostic,
16
+ selectorNotFoundDiagnostic,
17
+ selectorRepoAmbiguousDiagnostic,
18
+ selectorRepoNotFoundDiagnostic,
19
+ sourceScopeForSelector,
20
+ } from './selectors.js';
10
21
  interface RepoRef {
11
22
  id: number;
12
23
  name: string;
24
+ packageName?: string;
13
25
  }
14
26
  interface StartScope {
15
27
  repo?: RepoRef;
@@ -78,13 +90,14 @@ function normalizeOperation(value: string | undefined): string | undefined {
78
90
  function positiveDepth(value: number): number {
79
91
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
80
92
  }
81
- function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart, hintOptions: ImplementationHintOptions): { files?: Set<string>; symbols?: Set<number>; repoId?: number; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
93
+ function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart, hintOptions: ImplementationHintOptions, workspaceId?: number): { files?: Set<string>; symbols?: Set<number>; repoId?: number; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
82
94
  const requested = normalizeOperation(start.operationPath ?? start.operation);
83
95
  if (!requested) return undefined;
84
96
  const rows = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
85
97
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
86
- WHERE (? IS NULL OR r.id=?) AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
87
- ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith('/') ? requested : `/${requested}`) as Array<Record<string, unknown>>;
98
+ WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR r.id=?)
99
+ AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
100
+ ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(workspaceId, workspaceId, repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith('/') ? requested : `/${requested}`) as Array<Record<string, unknown>>;
88
101
  if (rows.length === 0) return undefined;
89
102
  const repoCount = new Set(rows.map((row) => String(row.repoName))).size;
90
103
  const serviceCount = new Set(rows.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
@@ -126,60 +139,33 @@ function sourceFilesForStart(
126
139
  db: Db,
127
140
  repoId: number | undefined,
128
141
  start: TraceStart,
129
- ): { files?: Set<string>; symbols?: Set<number>; repoId?: number } | undefined {
130
- const handler = start.handler;
131
- const operation = normalizeOperation(start.operation ?? start.operationPath);
132
- if (!handler && !operation) return undefined;
133
- const rows = db
134
- .prepare(
135
- `SELECT DISTINCT hc.source_file sourceFile,s.id symbolId
136
- FROM handler_classes hc LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name
137
- WHERE (? IS NULL OR hc.repo_id=?) AND (? IS NULL OR hc.class_name=? OR hm.method_name=?)
138
- AND (? IS NULL OR hm.decorator_value=? OR hm.method_name=?)
139
- AND (? IS NULL OR EXISTS (SELECT 1 FROM cds_services s JOIN cds_operations o ON o.service_id=s.id WHERE s.repo_id=hc.repo_id AND s.service_path=? AND (? IS NULL OR o.operation_path=? OR o.operation_name=? OR hm.decorator_value=? OR hm.method_name=?)))`,
140
- )
141
- .all(
142
- repoId,
143
- repoId,
144
- handler,
145
- handler,
146
- handler,
147
- operation,
148
- operation,
149
- operation,
150
- start.servicePath,
151
- start.servicePath,
152
- operation,
153
- operation,
154
- operation,
155
- operation,
156
- operation,
157
- ) as Array<{ sourceFile?: string; symbolId?: number }>;
158
- if (rows.length > 0) return { files: new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]), symbols: new Set(rows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
159
- if (start.servicePath && operation) {
160
- const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
161
- FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
162
- JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved' AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
163
- JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
164
- JOIN handler_classes hc ON hc.id=hm.handler_class_id
165
- LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
166
- WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation) as Array<{ sourceFile?: string; symbolId?: number }>;
167
- if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean) as string[]), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
168
- }
169
- return undefined;
170
- }
171
- function startScope(db: Db, start: TraceStart, hintOptions: ImplementationHintOptions): StartScope {
172
- const repo = start.repo
173
- ? (db
174
- .prepare(
175
- 'SELECT id,name FROM repositories WHERE name=? OR package_name=?',
176
- )
177
- .get(start.repo, start.repo) as RepoRef | undefined)
178
- : undefined;
179
- if (start.repo && !repo) return { repo, selectorMatched: false };
180
- const operationScope = operationStartScope(db, repo?.id, start, hintOptions);
142
+ workspaceId: number | undefined,
143
+ ): ReturnType<typeof sourceScopeForSelector> {
144
+ return sourceScopeForSelector(db, repoId, start, workspaceId);
145
+ }
146
+ function startScope(db: Db, start: TraceStart, hintOptions: ImplementationHintOptions, workspaceId?: number): StartScope {
147
+ const repos: RepoRef[] = start.repo
148
+ ? reposByName(db, start.repo, workspaceId).map((row) => ({
149
+ id: row.id,
150
+ name: row.name,
151
+ packageName: row.package_name ?? undefined,
152
+ }))
153
+ : [];
154
+ if (start.repo && repos.length === 0) return {
155
+ selectorMatched: false,
156
+ startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)],
157
+ };
158
+ if (start.repo && repos.length > 1) return {
159
+ selectorMatched: false,
160
+ startDiagnostics: [selectorRepoAmbiguousDiagnostic(start.repo, repos)],
161
+ };
162
+ const repo = repos[0];
163
+ const operationScope = operationStartScope(
164
+ db, repo?.id, start, hintOptions, workspaceId,
165
+ );
181
166
  const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === 'operation' || d.resolutionStage === 'implementation');
182
- const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
167
+ const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start, workspaceId);
168
+ const terminalSelectorScope = Boolean(sourceScope?.diagnostics?.length && !sourceScope.files);
183
169
  const sourceFiles = sourceScope?.files;
184
170
  const hasSelector = Boolean(
185
171
  start.handler ?? start.operation ?? start.operationPath ?? start.servicePath,
@@ -191,9 +177,12 @@ function startScope(db: Db, start: TraceStart, hintOptions: ImplementationHintOp
191
177
  executionRepoId: sourceScope?.repoId ?? repo?.id,
192
178
  sourceFiles,
193
179
  symbolIds: sourceScope?.symbols,
194
- selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== undefined),
180
+ selectorMatched: !terminalOperationScope && !terminalSelectorScope
181
+ && (!hasSelector || sourceFiles !== undefined),
195
182
  startOperationId: operationScope?.operationId,
196
- startDiagnostics: operationScope?.diagnostics,
183
+ startDiagnostics: operationScope?.diagnostics?.length
184
+ ? operationScope.diagnostics
185
+ : sourceScope?.diagnostics,
197
186
  };
198
187
  }
199
188
  function handlerFilesForOperation(db: Db, operationId: string): Set<string> {
@@ -211,8 +200,19 @@ function handlerFilesForOperation(db: Db, operationId: string): Set<string> {
211
200
  .prepare(
212
201
  `SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
213
202
  JOIN handler_methods hm ON hm.handler_class_id=hc.id
214
- LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
215
- WHERE hc.repo_id=? AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`,
203
+ LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
204
+ AND sym.source_file=hc.source_file
205
+ AND sym.qualified_name=hc.class_name || '.' || hm.method_name
206
+ AND sym.start_line=hm.source_line
207
+ WHERE hc.repo_id=?
208
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
209
+ CASE WHEN hm.decorator_kind='Event' THEN 'event'
210
+ WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
211
+ ELSE 'unsupported' END)='operation'
212
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
213
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On')
214
+ THEN 1 ELSE 0 END)=1
215
+ AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`,
216
216
  )
217
217
  .all(op.repoId, operation, operation, op.operationName) as Array<{
218
218
  sourceFile?: string;
@@ -230,7 +230,9 @@ function handlerMethodNode(db: Db, methodId: string): Record<string, unknown> |
230
230
  function implementationScope(db: Db, operationId: string): { repoId?: number; files: Set<string>; symbolId?: number; edge?: GraphRow } {
231
231
  const edge = implementationEdge(db, operationId);
232
232
  if (!edge || edge.status !== 'resolved') return { files: new Set(), edge };
233
- const row = db.prepare('SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?').get(edge.to_id) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
233
+ const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(edge.to_id) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
234
+ if (!row || typeof row.symbolId !== 'number')
235
+ return { repoId: row?.repoId, files: new Set(), edge };
234
236
  return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
235
237
  }
236
238
  function implementationMethodIdFromHint(edge: GraphRow | undefined, options: ImplementationHintOptions): ImplementationSelection {
@@ -257,8 +259,8 @@ function contextImplementationMethodId(edge: GraphRow | undefined, callerRepoId:
257
259
  return { blocksAutomatic: false, evidence: hinted.evidence.reason === 'no_scoped_hint_matched_edge' ? hinted.evidence : { status: 'tied', tieReason: scores.length > 1 ? 'duplicate_helper_implementation_candidates' : 'no_unique_materially_stronger_candidate', candidateScores: scores } };
258
260
  }
259
261
  function handlerScope(db: Db, methodId: string): { repoId?: number; files: Set<string>; symbolId?: number } | undefined {
260
- const row = db.prepare('SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?').get(methodId) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
261
- if (!row) return undefined;
262
+ const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(methodId) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
263
+ if (!row || typeof row.symbolId !== 'number') return undefined;
262
264
  return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
263
265
  }
264
266
 
@@ -491,22 +493,25 @@ export function trace(
491
493
  options: TraceOptions,
492
494
  ): TraceResult {
493
495
  const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
494
- const scope = startScope(db, start, hintOptions);
495
- const diagnostics = db
496
- .prepare(
497
- 'SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)',
498
- )
499
- .all(scope.repo?.id, scope.repo?.id) as Array<Record<string, unknown>>;
500
- const stale = db.prepare('SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?)').all(scope.repo?.id, scope.repo?.id) as Array<{ name?: string; reason?: string }>;
496
+ const scope = startScope(db, start, hintOptions, options.workspaceId);
497
+ const hasSelector = Boolean(start.repo || start.handler || start.operation
498
+ || start.operationPath || start.servicePath);
499
+ const diagnosticRepoId = scope.executionRepoId ?? scope.repo?.id;
500
+ const diagnostics = loadTraceDiagnostics(
501
+ db,
502
+ diagnosticRepoId,
503
+ !hasSelector,
504
+ options.workspaceId,
505
+ );
506
+ const stale = diagnosticRepoId !== undefined || !hasSelector
507
+ ? db.prepare('SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?) AND (? IS NULL OR workspace_id=?) ORDER BY name,id').all(diagnosticRepoId, diagnosticRepoId, options.workspaceId, options.workspaceId) as Array<{ name?: string; reason?: string }>
508
+ : [];
501
509
  for (const row of stale)
502
- diagnostics.unshift({ severity: 'warning', code: 'graph_stale', message: `Graph is stale for ${row.name ?? 'repository'}: ${row.reason ?? 'facts_changed'}. Run service-flow link.` });
503
- for (const diagnostic of scope.startDiagnostics ?? []) diagnostics.unshift(diagnostic);
510
+ prependTraceDiagnostic(diagnostics, { severity: 'warning', code: 'graph_stale', message: `Graph is stale for ${row.name ?? 'repository'}: ${row.reason ?? 'facts_changed'}. Run service-flow link.` });
511
+ for (const diagnostic of scope.startDiagnostics ?? [])
512
+ prependTraceDiagnostic(diagnostics, diagnostic);
504
513
  if (!scope.selectorMatched && !(scope.startDiagnostics?.length))
505
- diagnostics.unshift({
506
- severity: 'warning',
507
- code: 'trace_start_not_found',
508
- message: start.servicePath && !start.operation && !start.operationPath && !start.handler ? 'Service-only trace requires --operation or --path and will not broaden to the whole workspace' : 'No handler source matched the requested trace start selector',
509
- });
514
+ prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
510
515
  const maxDepth = positiveDepth(options.depth);
511
516
  const edges: TraceEdge[] = [];
512
517
  const nodes = new Map<string, Record<string, unknown>>();
@@ -539,9 +544,9 @@ export function trace(
539
544
  seenScopes.add(key);
540
545
  const calls = db
541
546
  .prepare(
542
- `SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) ORDER BY c.source_file,c.source_line`,
547
+ `SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR r.workspace_id=?) ORDER BY c.source_file,c.source_line`,
543
548
  )
544
- .all(current.repoId, current.repoId) as CallRow[];
549
+ .all(current.repoId, current.repoId, options.workspaceId, options.workspaceId) as CallRow[];
545
550
  const filtered = calls.filter(
546
551
  (c) =>
547
552
  (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) &&
@@ -623,7 +628,7 @@ export function trace(
623
628
  if (implementation.edge) {
624
629
  const implEvidence = JSON.parse(String(implementation.edge.evidence_json || '{}')) as Record<string, unknown>;
625
630
  const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence.implementationHintSuggestions);
626
- if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
631
+ if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
627
632
  const handlerNode = implementation.edge.status === 'resolved' ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
628
633
  const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
629
634
  if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
@@ -674,6 +679,8 @@ export function trace(
674
679
  }
675
680
  }
676
681
  const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
677
- if (runtimeDiagnostic) diagnostics.unshift(runtimeDiagnostic);
682
+ if (runtimeDiagnostic) prependTraceDiagnostic(diagnostics, runtimeDiagnostic);
683
+ for (const diagnostic of runtimeNoCandidateDiagnostics(edges))
684
+ prependTraceDiagnostic(diagnostics, diagnostic);
678
685
  return { start, nodes: [...nodes.values()], edges, diagnostics };
679
686
  }
package/src/types.ts CHANGED
@@ -95,14 +95,34 @@ export interface HandlerClassFact {
95
95
  sourceFile: string;
96
96
  sourceLine: number;
97
97
  methods: HandlerMethodFact[];
98
- }
98
+ hasHandlerDecorator?: boolean;
99
+ classDecoratorNames?: string[];
100
+ observedDecoratorNames?: string[];
101
+ unsupportedDecoratorNames?: string[];
102
+ }
103
+ export type HandlerMethodKind =
104
+ | 'operation'
105
+ | 'entity_lifecycle'
106
+ | 'event'
107
+ | 'unsupported_lifecycle'
108
+ | 'unsupported_decorator';
109
+ export type HandlerLifecyclePhase = 'on' | 'before' | 'after';
110
+ export type HandlerLifecycleEvent = 'CREATE' | 'READ' | 'UPDATE' | 'DELETE';
99
111
  export interface HandlerMethodFact {
100
112
  methodName: string;
101
113
  decoratorKind: string;
102
114
  decoratorValue?: string;
103
115
  decoratorRawExpression: string;
116
+ handlerKind?: HandlerMethodKind;
117
+ executable?: boolean;
118
+ lifecyclePhase?: HandlerLifecyclePhase;
119
+ lifecycleEvent?: HandlerLifecycleEvent;
104
120
  decoratorResolution: {
105
121
  rawExpression: string;
122
+ decoratorExpression?: string;
123
+ argumentExpression?: string;
124
+ resolvedDecoratorKind?: string;
125
+ decoratorImportSource?: string;
106
126
  resolvedValue?: string;
107
127
  resolutionKind:
108
128
  | 'literal'
@@ -110,8 +130,13 @@ export interface HandlerMethodFact {
110
130
  | 'enum_member'
111
131
  | 'const_object_property'
112
132
  | 'generated_constant_name'
133
+ | 'lifecycle_implicit'
113
134
  | 'unresolved';
114
135
  unresolvedReason?: string;
136
+ handlerKind?: HandlerMethodKind;
137
+ executable?: boolean;
138
+ lifecyclePhase?: HandlerLifecyclePhase;
139
+ lifecycleEvent?: HandlerLifecycleEvent;
115
140
  };
116
141
  sourceFile: string;
117
142
  sourceLine: number;
@@ -202,6 +227,7 @@ export interface ImplementationHint {
202
227
  export type DynamicMode = 'strict' | 'candidates' | 'infer';
203
228
  export interface TraceOptions {
204
229
  depth: number;
230
+ workspaceId?: number;
205
231
  vars?: Record<string, string>;
206
232
  includeExternal?: boolean;
207
233
  includeDb?: boolean;