@saptools/service-flow 0.1.67 → 0.1.68

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 (80) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +20 -8
  3. package/TECHNICAL-NOTE.md +27 -0
  4. package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
  5. package/dist/chunk-AEM4JY22.js.map +1 -0
  6. package/dist/cli.js +2325 -413
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +61 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/003-doctor-package-resolution.ts +68 -0
  12. package/src/cli/doctor.ts +25 -14
  13. package/src/db/000-call-fact-repository.ts +475 -342
  14. package/src/db/001-fact-lifecycle.ts +60 -30
  15. package/src/db/002-fact-json-inventory.ts +169 -0
  16. package/src/db/003-current-fact-semantics.ts +698 -0
  17. package/src/db/004-package-target-invalidation.ts +173 -0
  18. package/src/db/005-schema-structure.ts +201 -0
  19. package/src/db/006-relative-symbol-resolution.ts +443 -0
  20. package/src/db/007-package-fact-semantics.ts +573 -0
  21. package/src/db/008-relative-fact-semantics.ts +207 -0
  22. package/src/db/009-binding-fact-semantics.ts +347 -0
  23. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  24. package/src/db/011-symbol-call-semantics.ts +144 -0
  25. package/src/db/012-binding-reference-proof.ts +264 -0
  26. package/src/db/migrations.ts +16 -3
  27. package/src/db/repositories.ts +113 -6
  28. package/src/db/schema.ts +4 -2
  29. package/src/index.ts +12 -0
  30. package/src/indexer/repository-indexer.ts +72 -11
  31. package/src/indexer/workspace-indexer.ts +123 -32
  32. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  33. package/src/linker/004-event-subscription-handler-linker.ts +2 -0
  34. package/src/linker/005-odata-path-structure.ts +371 -0
  35. package/src/linker/cross-repo-linker.ts +2 -1
  36. package/src/linker/odata-path-normalizer.ts +273 -180
  37. package/src/linker/service-resolver.ts +197 -77
  38. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  39. package/src/parsers/003-package-public-surface.ts +661 -0
  40. package/src/parsers/004-fact-identity.ts +108 -0
  41. package/src/parsers/005-event-subscription-facts.ts +281 -0
  42. package/src/parsers/006-binding-identity.ts +343 -0
  43. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  44. package/src/parsers/008-package-surface-publication.ts +82 -0
  45. package/src/parsers/009-symbol-call-facts.ts +528 -0
  46. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  47. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  48. package/src/parsers/012-package-fact-contract.ts +306 -0
  49. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  50. package/src/parsers/014-service-binding-helper-flow.ts +240 -0
  51. package/src/parsers/015-service-binding-collector.ts +693 -0
  52. package/src/parsers/016-local-symbol-reference.ts +261 -0
  53. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  54. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  55. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  56. package/src/parsers/020-stable-local-value.ts +217 -0
  57. package/src/parsers/021-binding-visibility.ts +152 -0
  58. package/src/parsers/operation-path-analysis.ts +6 -1
  59. package/src/parsers/outbound-call-parser.ts +19 -6
  60. package/src/parsers/package-json-parser.ts +45 -3
  61. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  62. package/src/parsers/service-binding-parser.ts +147 -597
  63. package/src/parsers/symbol-parser.ts +482 -353
  64. package/src/trace/002-trace-diagnostics.ts +36 -1
  65. package/src/trace/016-compact-projector.ts +16 -17
  66. package/src/trace/020-compact-field-projection.ts +48 -37
  67. package/src/trace/021-compact-decision-normalization.ts +105 -0
  68. package/src/trace/022-trace-fact-preflight.ts +21 -0
  69. package/src/trace/023-nested-event-scopes.ts +23 -0
  70. package/src/trace/024-compact-observation-decision.ts +76 -0
  71. package/src/trace/025-trace-implementation-scope.ts +123 -0
  72. package/src/trace/026-trace-start-scope.ts +335 -0
  73. package/src/trace/027-trace-scope-execution.ts +566 -0
  74. package/src/trace/028-trace-operation-execution.ts +336 -0
  75. package/src/trace/029-trace-start-implementation.ts +172 -0
  76. package/src/trace/trace-engine.ts +122 -624
  77. package/src/types.ts +56 -0
  78. package/src/utils/001-placeholders.ts +188 -10
  79. package/src/version.ts +1 -1
  80. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -18,16 +18,33 @@ import { classifyRepository } from '../discovery/classify-repository.js';
18
18
  import { parseCdsFile } from '../parsers/cds-parser.js';
19
19
  import { parseDecorators } from '../parsers/decorator-parser.js';
20
20
  import { parseHandlerRegistrations } from '../parsers/handler-registration-parser.js';
21
- import { parseOutboundCalls } from '../parsers/outbound-call-parser.js';
21
+ import {
22
+ classifyOutboundCallsInSource,
23
+ parseOutboundCalls,
24
+ } from '../parsers/outbound-call-parser.js';
22
25
  import { parseExecutableSymbols } from '../parsers/symbol-parser.js';
23
26
  import {
24
27
  loadPackageJsonSnapshot,
25
28
  } from '../parsers/package-json-parser.js';
26
29
  import { parseServiceBindings } from '../parsers/service-binding-parser.js';
30
+ import { reconcileSourceFacts } from '../parsers/007-source-fact-reconciliation.js';
31
+ import {
32
+ analyzeRepositoryPackageSurface,
33
+ mergePackageSymbolEvidence,
34
+ } from '../parsers/008-package-surface-publication.js';
35
+ import type {
36
+ PackagePublicSurfaceFact,
37
+ } from '../parsers/003-package-public-surface.js';
27
38
  import { normalizePath } from '../utils/path-utils.js';
28
39
  import { errorMessage } from '../utils/diagnostics.js';
29
40
  import { sha256Text } from '../utils/hashing.js';
30
41
  import { ANALYZER_VERSION } from '../version.js';
42
+ import {
43
+ createPackageInvalidationBatch,
44
+ finalizePackageTargetInvalidations,
45
+ invalidatePackageTargetFacts,
46
+ type PackageInvalidationBatch,
47
+ } from '../db/004-package-target-invalidation.js';
31
48
  import {
32
49
  loadRepositorySourceContext,
33
50
  type RepositorySourceContext,
@@ -55,6 +72,7 @@ export interface PreparedRepositoryIndex extends IndexRepoResult {
55
72
  fingerprint?: string;
56
73
  kind?: string;
57
74
  parsed?: ParsedFacts;
75
+ packagePublicSurface?: PackagePublicSurfaceFact;
58
76
  }
59
77
  export async function indexRepository(
60
78
  db: Db,
@@ -64,7 +82,11 @@ export async function indexRepository(
64
82
  ): Promise<IndexRepoResult> {
65
83
  try {
66
84
  const prepared = await prepareRepositoryIndex(repo, force, instrumentation);
67
- if (!prepared.skipped) db.transaction(() => publishPreparedRepositoryIndex(db, prepared));
85
+ if (!prepared.skipped) db.transaction(() => {
86
+ const batch = createPackageInvalidationBatch([prepared.repo.id]);
87
+ publishPreparedRepositoryIndex(db, prepared, batch);
88
+ finalizePackageTargetInvalidations(db, batch);
89
+ });
68
90
  return { fileCount: prepared.fileCount, diagnosticCount: prepared.diagnosticCount, skipped: prepared.skipped };
69
91
  } catch (error) {
70
92
  recordIndexFailure(db, repo.id, error);
@@ -89,13 +111,21 @@ export async function prepareRepositoryIndex(
89
111
  sources, packageFacts, packageSnapshot.rawText,
90
112
  );
91
113
  if (!force && repo.fingerprint === fingerprint) return { repo, fileCount: 0, diagnosticCount: 0, skipped: true };
92
- const parsed = await parseAllSourceFacts(repo.absolute_path, sources);
114
+ const parsedFacts = await parseAllSourceFacts(repo.absolute_path, sources);
115
+ const packageSurface = analyzeRepositoryPackageSurface(
116
+ packageFacts, packageSnapshot.manifest, sources,
117
+ );
118
+ const parsed = {
119
+ ...parsedFacts,
120
+ symbols: mergePackageSymbolEvidence(parsedFacts.symbols, packageSurface),
121
+ };
93
122
  return {
94
123
  repo,
95
124
  packageFacts,
96
125
  fingerprint,
97
126
  kind: await classifyRepository(repo.absolute_path, packageFacts),
98
127
  parsed,
128
+ packagePublicSurface: packageSurface.surface,
99
129
  fileCount: sourceFiles.length,
100
130
  diagnosticCount: parsed.handlers.filter((handler) =>
101
131
  handler.hasHandlerDecorator
@@ -104,12 +134,31 @@ export async function prepareRepositoryIndex(
104
134
  skipped: false,
105
135
  };
106
136
  }
107
- export function publishPreparedRepositoryIndex(db: Db, prepared: PreparedRepositoryIndex): void {
137
+ export function publishPreparedRepositoryIndex(
138
+ db: Db,
139
+ prepared: PreparedRepositoryIndex,
140
+ invalidations: PackageInvalidationBatch,
141
+ ): void {
108
142
  if (prepared.skipped) return;
109
- if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind) throw new Error('Prepared repository index is missing publication facts');
143
+ if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint
144
+ || !prepared.kind || !prepared.packagePublicSurface)
145
+ throw new Error('Prepared repository index is missing publication facts');
110
146
  const now = new Date().toISOString();
111
147
  const repoId = prepared.repo.id;
112
- db.prepare('UPDATE repositories SET package_name=?, package_version=?, dependencies_json=?, kind=?, index_status=? WHERE id=?').run(prepared.packageFacts.packageName, prepared.packageFacts.packageVersion, JSON.stringify(prepared.packageFacts.dependencies), prepared.kind, 'indexing', repoId);
148
+ invalidatePackageTargetFacts(
149
+ db, repoId, prepared.packageFacts.packageName, invalidations,
150
+ );
151
+ db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
152
+ dependencies_json=?,package_public_surface_json=?,kind=?,index_status=?
153
+ WHERE id=?`).run(
154
+ prepared.packageFacts.packageName,
155
+ prepared.packageFacts.packageVersion,
156
+ JSON.stringify(prepared.packageFacts.dependencies),
157
+ JSON.stringify(prepared.packagePublicSurface),
158
+ prepared.kind,
159
+ 'indexing',
160
+ repoId,
161
+ );
113
162
  clearRepoFacts(db, repoId);
114
163
  insertRequires(db, repoId, prepared.packageFacts.cdsRequires);
115
164
  const fileStmt = db.prepare('INSERT INTO files(repo_id,relative_path,extension,sha256,size_bytes,last_indexed_at) VALUES(?,?,?,?,?,?) ON CONFLICT(repo_id,relative_path) DO UPDATE SET sha256=excluded.sha256,size_bytes=excluded.size_bytes,last_indexed_at=excluded.last_indexed_at');
@@ -139,13 +188,25 @@ async function parseAllSourceFacts(
139
188
  facts.fileRecords.push({ relativePath: normalizePath(file), extension: path.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
140
189
  if (file.endsWith('.cds')) facts.services.push(...(await parseCdsFile(root, file, sources)));
141
190
  if (/\.[jt]s$/.test(file)) {
191
+ const source = snapshot.sourceFile();
192
+ const classified = classifyOutboundCallsInSource(source, file);
142
193
  facts.handlers.push(...(await parseDecorators(root, file, sources)));
143
194
  facts.registrations.push(...(await parseHandlerRegistrations(root, file, sources)));
144
- facts.bindings.push(...(await parseServiceBindings(root, file, sources)));
145
- const symbolFacts = await parseExecutableSymbols(root, file, sources);
146
- facts.symbols.push(...symbolFacts.symbols);
147
- facts.symbolCalls.push(...symbolFacts.calls);
148
- facts.calls.push(...(await parseOutboundCalls(root, file, sources)));
195
+ const bindings = await parseServiceBindings(root, file, sources);
196
+ const symbolFacts = await parseExecutableSymbols(
197
+ root, file, sources, classified,
198
+ );
199
+ const outboundCalls = await parseOutboundCalls(
200
+ root, file, sources, classified, bindings,
201
+ );
202
+ const reconciled = reconcileSourceFacts(
203
+ source, classified, bindings, outboundCalls,
204
+ symbolFacts.symbols, symbolFacts.calls,
205
+ );
206
+ facts.bindings.push(...reconciled.bindings);
207
+ facts.symbols.push(...reconciled.symbols);
208
+ facts.symbolCalls.push(...reconciled.symbolCalls);
209
+ facts.calls.push(...reconciled.outboundCalls);
149
210
  }
150
211
  }
151
212
  return facts;
@@ -3,6 +3,10 @@ import { listRepositories, reposByName } from '../db/repositories.js';
3
3
  import { errorMessage } from '../utils/diagnostics.js';
4
4
  import { prepareRepositoryIndex, publishPreparedRepositoryIndex, recordIndexFailure, type PreparedRepositoryIndex } from './repository-indexer.js';
5
5
  import { materializeCdsExtensionOperations } from './cds-extension-resolver.js';
6
+ import {
7
+ createPackageInvalidationBatch,
8
+ finalizePackageTargetInvalidations,
9
+ } from '../db/004-package-target-invalidation.js';
6
10
  // Ownerless rows predate PID coordination; this matches doctor's stale-run threshold without taking over a recent legacy writer.
7
11
  const LEGACY_OWNER_RECOVERY_MS = 60 * 60 * 1_000;
8
12
  type RunningIndexRow = Record<string, unknown>;
@@ -81,41 +85,128 @@ export async function indexWorkspace(
81
85
  workspaceId: number,
82
86
  options: { repo?: string; force: boolean; injectDerivedMaterializationFailure?: boolean },
83
87
  ): Promise<{ repoCount: number; indexedCount: number; skippedCount: number; fileCount: number; diagnosticCount: number }> {
84
- const repos = options.repo
85
- ? reposByName(db, options.repo, workspaceId)
86
- : listRepositories(db, workspaceId);
87
- if (options.repo && repos.length === 0)
88
- throw new Error(`selector_repo_not_found: no indexed repository matched ${options.repo}.`);
89
- if (options.repo && repos.length > 1)
90
- throw new Error(`selector_repo_ambiguous: repository selector ${options.repo} matched ${repos.length} repositories; use a unique repository name.`);
88
+ const repos = selectedRepositories(db, workspaceId, options.repo);
91
89
  const runId = claimIndexRun(db, workspaceId, repos.length);
92
- let fileCount = 0;
93
- let diagnosticCount = 0;
94
- let skippedCount = 0;
95
- const preparedRows: PreparedRepositoryIndex[] = [];
96
- let activeRepoId: number | undefined;
90
+ const state: PreparationState = {
91
+ fileCount: 0, diagnosticCount: 0, skippedCount: 0, rows: [],
92
+ };
97
93
  try {
98
- for (const repo of repos) {
99
- activeRepoId = repo.id;
100
- const result = await prepareRepositoryIndex(repo, options.force);
101
- preparedRows.push(result);
102
- fileCount += result.fileCount;
103
- diagnosticCount += result.diagnosticCount;
104
- skippedCount += result.skipped ? 1 : 0;
105
- }
106
- db.transaction(() => {
107
- for (const row of preparedRows) {
108
- activeRepoId = row.repo.id;
109
- publishPreparedRepositoryIndex(db, row);
110
- }
111
- if (options.injectDerivedMaterializationFailure) throw new Error('Injected derived materialization failure');
112
- materializeCdsExtensionOperations(db, workspaceId);
113
- db.prepare("UPDATE index_runs SET finished_at=?, status='success', file_count=?, diagnostic_count=? WHERE id=?").run(new Date().toISOString(), fileCount, diagnosticCount, runId);
114
- });
115
- return { repoCount: repos.length, indexedCount: repos.length - skippedCount, skippedCount, fileCount, diagnosticCount };
94
+ await prepareRepositories(repos, options.force, state);
95
+ publishPreparedRows(db, workspaceId, options, runId, state);
96
+ return indexSummary(repos.length, state);
116
97
  } catch (error) {
117
- db.prepare("UPDATE index_runs SET finished_at=?, status='failed', file_count=?, diagnostic_count=?, error_message=? WHERE id=?").run(new Date().toISOString(), fileCount, diagnosticCount + 1, errorMessage(error), runId);
118
- if (activeRepoId && preparedRows.length < repos.length) recordIndexFailure(db, activeRepoId, error);
98
+ finishFailedRun(db, runId, state, error);
99
+ if (state.activeRepoId && state.rows.length < repos.length)
100
+ recordIndexFailure(db, state.activeRepoId, error);
119
101
  throw error;
120
102
  }
121
103
  }
104
+
105
+ type IndexRepository = ReturnType<typeof listRepositories>[number];
106
+ interface PreparationState {
107
+ fileCount: number;
108
+ diagnosticCount: number;
109
+ skippedCount: number;
110
+ rows: PreparedRepositoryIndex[];
111
+ activeRepoId?: number;
112
+ }
113
+
114
+ function selectedRepositories(
115
+ db: Db,
116
+ workspaceId: number,
117
+ repoName: string | undefined,
118
+ ): IndexRepository[] {
119
+ const repos = repoName
120
+ ? reposByName(db, repoName, workspaceId)
121
+ : listRepositories(db, workspaceId);
122
+ if (repoName && repos.length === 0)
123
+ throw new Error(
124
+ `selector_repo_not_found: no indexed repository matched ${repoName}.`,
125
+ );
126
+ if (repoName && repos.length > 1)
127
+ throw new Error(
128
+ `selector_repo_ambiguous: repository selector ${repoName} matched ${repos.length} repositories; use a unique repository name.`,
129
+ );
130
+ return repos;
131
+ }
132
+
133
+ async function prepareRepositories(
134
+ repos: readonly IndexRepository[],
135
+ force: boolean,
136
+ state: PreparationState,
137
+ ): Promise<void> {
138
+ for (const repo of repos) {
139
+ state.activeRepoId = repo.id;
140
+ const result = await prepareRepositoryIndex(repo, force);
141
+ state.rows.push(result);
142
+ state.fileCount += result.fileCount;
143
+ state.diagnosticCount += result.diagnosticCount;
144
+ state.skippedCount += result.skipped ? 1 : 0;
145
+ }
146
+ }
147
+
148
+ function publishPreparedRows(
149
+ db: Db,
150
+ workspaceId: number,
151
+ options: { injectDerivedMaterializationFailure?: boolean },
152
+ runId: number,
153
+ state: PreparationState,
154
+ ): void {
155
+ db.transaction(() => {
156
+ const invalidations = createPackageInvalidationBatch(
157
+ state.rows.filter((row) => !row.skipped).map((row) => row.repo.id),
158
+ );
159
+ for (const row of state.rows) {
160
+ state.activeRepoId = row.repo.id;
161
+ publishPreparedRepositoryIndex(db, row, invalidations);
162
+ }
163
+ if (options.injectDerivedMaterializationFailure)
164
+ throw new Error('Injected derived materialization failure');
165
+ materializeCdsExtensionOperations(db, workspaceId);
166
+ finalizePackageTargetInvalidations(db, invalidations);
167
+ finishSuccessfulRun(db, runId, state);
168
+ });
169
+ }
170
+
171
+ function finishSuccessfulRun(
172
+ db: Db,
173
+ runId: number,
174
+ state: PreparationState,
175
+ ): void {
176
+ db.prepare(`UPDATE index_runs SET finished_at=?,status='success',
177
+ file_count=?,diagnostic_count=? WHERE id=?`).run(
178
+ new Date().toISOString(), state.fileCount, state.diagnosticCount, runId,
179
+ );
180
+ }
181
+
182
+ function finishFailedRun(
183
+ db: Db,
184
+ runId: number,
185
+ state: PreparationState,
186
+ error: unknown,
187
+ ): void {
188
+ db.prepare(`UPDATE index_runs SET finished_at=?,status='failed',
189
+ file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
190
+ new Date().toISOString(), state.fileCount, state.diagnosticCount + 1,
191
+ errorMessage(error), runId,
192
+ );
193
+ }
194
+
195
+ function indexSummary(
196
+ repoCount: number,
197
+ state: PreparationState,
198
+ ): {
199
+ repoCount: number;
200
+ indexedCount: number;
201
+ skippedCount: number;
202
+ fileCount: number;
203
+ diagnosticCount: number;
204
+ } {
205
+ return {
206
+ repoCount,
207
+ indexedCount: repoCount - state.skippedCount,
208
+ skippedCount: state.skippedCount,
209
+ fileCount: state.fileCount,
210
+ diagnosticCount: state.diagnosticCount,
211
+ };
212
+ }