@saptools/service-flow 0.1.67 → 0.1.69

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 (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +27 -9
  3. package/TECHNICAL-NOTE.md +36 -0
  4. package/dist/chunk-3N3B5KHV.js +19596 -0
  5. package/dist/chunk-3N3B5KHV.js.map +1 -0
  6. package/dist/cli.js +2645 -521
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +67 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/001-index-summary.ts +22 -0
  12. package/src/cli/003-doctor-package-resolution.ts +68 -0
  13. package/src/cli/doctor.ts +25 -14
  14. package/src/cli.ts +151 -87
  15. package/src/db/000-call-fact-repository.ts +499 -340
  16. package/src/db/001-fact-lifecycle.ts +60 -30
  17. package/src/db/002-fact-json-inventory.ts +169 -0
  18. package/src/db/003-current-fact-semantics.ts +699 -0
  19. package/src/db/004-package-target-invalidation.ts +183 -0
  20. package/src/db/005-schema-structure.ts +201 -0
  21. package/src/db/006-relative-symbol-resolution.ts +464 -0
  22. package/src/db/007-package-fact-semantics.ts +573 -0
  23. package/src/db/008-relative-fact-semantics.ts +210 -0
  24. package/src/db/009-binding-fact-semantics.ts +352 -0
  25. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  26. package/src/db/011-symbol-call-semantics.ts +144 -0
  27. package/src/db/012-binding-reference-proof.ts +268 -0
  28. package/src/db/013-index-publication-failure.ts +91 -0
  29. package/src/db/014-binding-helper-provenance.ts +17 -0
  30. package/src/db/migrations.ts +16 -3
  31. package/src/db/repositories.ts +130 -6
  32. package/src/db/schema.ts +4 -2
  33. package/src/index.ts +12 -0
  34. package/src/indexer/cds-extension-resolver.ts +27 -3
  35. package/src/indexer/repository-indexer.ts +135 -13
  36. package/src/indexer/workspace-indexer.ts +237 -34
  37. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  38. package/src/linker/004-event-subscription-handler-linker.ts +34 -11
  39. package/src/linker/005-odata-path-structure.ts +371 -0
  40. package/src/linker/006-event-template-link.ts +72 -0
  41. package/src/linker/007-call-edge-insertion.ts +568 -0
  42. package/src/linker/cross-repo-linker.ts +4 -166
  43. package/src/linker/odata-path-normalizer.ts +273 -180
  44. package/src/linker/service-resolver.ts +197 -77
  45. package/src/parsers/000-direct-query-execution.ts +11 -0
  46. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  47. package/src/parsers/003-package-public-surface.ts +661 -0
  48. package/src/parsers/004-fact-identity.ts +108 -0
  49. package/src/parsers/005-event-subscription-facts.ts +281 -0
  50. package/src/parsers/006-binding-identity.ts +348 -0
  51. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  52. package/src/parsers/008-package-surface-publication.ts +82 -0
  53. package/src/parsers/009-symbol-call-facts.ts +528 -0
  54. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  55. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  56. package/src/parsers/012-package-fact-contract.ts +306 -0
  57. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  58. package/src/parsers/014-service-binding-helper-flow.ts +306 -0
  59. package/src/parsers/015-service-binding-collector.ts +693 -0
  60. package/src/parsers/016-local-symbol-reference.ts +261 -0
  61. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  62. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  63. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  64. package/src/parsers/020-stable-local-value.ts +217 -0
  65. package/src/parsers/021-binding-visibility.ts +168 -0
  66. package/src/parsers/022-outbound-expression-analysis.ts +700 -0
  67. package/src/parsers/023-outbound-call-classifier.ts +692 -0
  68. package/src/parsers/operation-path-analysis.ts +6 -1
  69. package/src/parsers/outbound-call-parser.ts +162 -512
  70. package/src/parsers/package-json-parser.ts +45 -3
  71. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  72. package/src/parsers/service-binding-parser.ts +147 -597
  73. package/src/parsers/symbol-parser.ts +513 -352
  74. package/src/trace/002-trace-diagnostics.ts +36 -1
  75. package/src/trace/007-implementation-start-diagnostic.ts +1 -0
  76. package/src/trace/011-event-subscriber-traversal.ts +100 -8
  77. package/src/trace/013-trace-root-scopes.ts +2 -3
  78. package/src/trace/014-compact-contract.ts +6 -0
  79. package/src/trace/015-trace-edge-recorder.ts +61 -4
  80. package/src/trace/016-compact-projector.ts +15 -17
  81. package/src/trace/019-trace-edge-semantics.ts +6 -10
  82. package/src/trace/020-compact-field-projection.ts +122 -38
  83. package/src/trace/021-compact-decision-normalization.ts +171 -0
  84. package/src/trace/022-trace-fact-preflight.ts +21 -0
  85. package/src/trace/023-nested-event-scopes.ts +23 -0
  86. package/src/trace/024-compact-observation-decision.ts +81 -0
  87. package/src/trace/025-trace-implementation-scope.ts +123 -0
  88. package/src/trace/026-trace-start-scope.ts +336 -0
  89. package/src/trace/027-trace-scope-execution.ts +566 -0
  90. package/src/trace/028-trace-operation-execution.ts +336 -0
  91. package/src/trace/029-trace-start-implementation.ts +172 -0
  92. package/src/trace/030-event-runtime-resolution.ts +151 -0
  93. package/src/trace/031-local-call-expansion.ts +37 -0
  94. package/src/trace/implementation-hints.ts +9 -6
  95. package/src/trace/selectors.ts +1 -0
  96. package/src/trace/trace-engine.ts +122 -624
  97. package/src/types.ts +57 -0
  98. package/src/utils/001-placeholders.ts +188 -10
  99. package/src/version.ts +1 -1
  100. package/dist/chunk-ZQABU7MR.js +0 -12151
  101. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -0,0 +1,183 @@
1
+ import type { Db } from './connection.js';
2
+ import { ANALYZER_VERSION } from '../version.js';
3
+ import {
4
+ parsePackageImportReference,
5
+ } from '../parsers/012-package-fact-contract.js';
6
+
7
+ interface PackageCallRow {
8
+ id: number;
9
+ repoId: number;
10
+ evidence: Record<string, unknown>;
11
+ }
12
+
13
+ export interface PackageInvalidationBatch {
14
+ publishingRepoIds: ReadonlySet<number>;
15
+ affectedCallerRepoIds: Set<number>;
16
+ affectedWorkspaceIds: Set<number>;
17
+ }
18
+
19
+ const resolverKeys = new Set([
20
+ 'candidateStrategy', 'candidateCount', 'eligibleCandidateCount',
21
+ 'selectedCandidateCount', 'candidateSetComplete', 'resolvedModulePath',
22
+ 'resolvedTargetRepositoryId', 'unresolvedReason',
23
+ 'targetRepositoryCandidateCount', 'targetRepositoryCandidates',
24
+ 'shownTargetRepositoryCandidateCount',
25
+ 'omittedTargetRepositoryCandidateCount', 'publicSurface',
26
+ ]);
27
+
28
+ function record(value: unknown): Record<string, unknown> | undefined {
29
+ return value && typeof value === 'object' && !Array.isArray(value)
30
+ ? value as Record<string, unknown> : undefined;
31
+ }
32
+
33
+ function parsedEvidence(value: unknown): Record<string, unknown> | undefined {
34
+ if (typeof value !== 'string') return undefined;
35
+ try {
36
+ return record(JSON.parse(value) as unknown);
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ }
41
+
42
+ function packageName(evidence: Record<string, unknown>): string | undefined {
43
+ return parsePackageImportReference(evidence.importBinding)
44
+ ?.requestedPackageName ?? undefined;
45
+ }
46
+
47
+ function packageCallEvidenceValid(
48
+ evidence: Record<string, unknown>,
49
+ ): boolean {
50
+ const binding = record(evidence.importBinding);
51
+ const classified = evidence.relation === 'package_import'
52
+ || binding?.moduleKind === 'package';
53
+ return !classified || packageName(evidence) !== undefined;
54
+ }
55
+
56
+ function currentCalls(
57
+ db: Db,
58
+ workspaceId: number,
59
+ targetRepoId: number,
60
+ ): PackageCallRow[] {
61
+ const rows = db.prepare(`SELECT sc.id,sc.repo_id repoId,
62
+ sc.evidence_json evidenceJson FROM symbol_calls sc
63
+ JOIN repositories r ON r.id=sc.repo_id
64
+ WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
65
+ ORDER BY sc.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
66
+ return rows.flatMap((row) => {
67
+ const evidence = parsedEvidence(row.evidenceJson);
68
+ if (!evidence || typeof row.id !== 'number'
69
+ || typeof row.repoId !== 'number')
70
+ throw new Error('invalid_current_package_import_evidence');
71
+ if (!packageCallEvidenceValid(evidence))
72
+ throw new Error('invalid_current_package_import_evidence');
73
+ return evidence.relation === 'package_import'
74
+ || record(evidence.importBinding)?.moduleKind === 'package'
75
+ ? [{ id: row.id, repoId: row.repoId, evidence }]
76
+ : [];
77
+ });
78
+ }
79
+
80
+ function pendingEvidence(evidence: Record<string, unknown>): string {
81
+ const parser = Object.fromEntries(Object.entries(evidence).filter(
82
+ ([key]) => !resolverKeys.has(key),
83
+ ));
84
+ if (!packageName(parser))
85
+ throw new Error('invalid_current_package_import_evidence');
86
+ return JSON.stringify({
87
+ ...parser,
88
+ candidateStrategy: 'package_import_pending',
89
+ candidateCount: 0,
90
+ eligibleCandidateCount: 0,
91
+ selectedCandidateCount: 0,
92
+ candidateSetComplete: false,
93
+ unresolvedReason: 'package_resolution_pending',
94
+ });
95
+ }
96
+
97
+ function targetWorkspace(
98
+ db: Db,
99
+ repoId: number,
100
+ ): { workspaceId: number; packageName?: string | null } {
101
+ const row = db.prepare(`SELECT workspace_id workspaceId,
102
+ package_name packageName FROM repositories WHERE id=?`).get(repoId);
103
+ if (typeof row?.workspaceId !== 'number')
104
+ throw new Error('Repository target is missing its workspace');
105
+ return {
106
+ workspaceId: row.workspaceId,
107
+ packageName: typeof row.packageName === 'string'
108
+ || row.packageName === null ? row.packageName : undefined,
109
+ };
110
+ }
111
+
112
+ function packageIdentityChanged(
113
+ previous: string | null | undefined,
114
+ next: string | undefined,
115
+ ): boolean {
116
+ const previousName = typeof previous === 'string' ? previous : null;
117
+ const nextName = typeof next === 'string' ? next : null;
118
+ return previousName !== nextName;
119
+ }
120
+
121
+ export function invalidatePackageTargetFacts(
122
+ db: Db,
123
+ targetRepoId: number,
124
+ newPackageName: string | undefined,
125
+ batch: PackageInvalidationBatch,
126
+ ): void {
127
+ const target = targetWorkspace(db, targetRepoId);
128
+ const names = new Set(
129
+ [target.packageName, newPackageName].filter(
130
+ (value): value is string => typeof value === 'string' && value.length > 0,
131
+ ),
132
+ );
133
+ if (names.size === 0) return;
134
+ const update = db.prepare(`UPDATE symbol_calls SET callee_symbol_id=NULL,
135
+ status='unresolved',unresolved_reason='package_resolution_pending',
136
+ evidence_json=? WHERE id=?`);
137
+ let matched = false;
138
+ for (const call of currentCalls(db, target.workspaceId, targetRepoId)) {
139
+ const requested = packageName(call.evidence);
140
+ if (!requested || !names.has(requested)) continue;
141
+ update.run(pendingEvidence(call.evidence), call.id);
142
+ batch.affectedCallerRepoIds.add(call.repoId);
143
+ matched = true;
144
+ }
145
+ if (matched || packageIdentityChanged(
146
+ target.packageName, newPackageName,
147
+ )) batch.affectedWorkspaceIds.add(target.workspaceId);
148
+ }
149
+
150
+ export function createPackageInvalidationBatch(
151
+ publishingRepoIds: readonly number[],
152
+ ): PackageInvalidationBatch {
153
+ return {
154
+ publishingRepoIds: new Set(publishingRepoIds),
155
+ affectedCallerRepoIds: new Set(),
156
+ affectedWorkspaceIds: new Set(),
157
+ };
158
+ }
159
+
160
+ export function mergePackageInvalidationEffects(
161
+ target: PackageInvalidationBatch,
162
+ source: PackageInvalidationBatch,
163
+ ): void {
164
+ for (const repoId of source.affectedCallerRepoIds)
165
+ target.affectedCallerRepoIds.add(repoId);
166
+ for (const workspaceId of source.affectedWorkspaceIds)
167
+ target.affectedWorkspaceIds.add(workspaceId);
168
+ }
169
+
170
+ export function finalizePackageTargetInvalidations(
171
+ db: Db,
172
+ batch: PackageInvalidationBatch,
173
+ ): void {
174
+ const increment = db.prepare(`UPDATE repositories
175
+ SET fact_generation=fact_generation+1 WHERE id=?`);
176
+ for (const repoId of batch.affectedCallerRepoIds)
177
+ if (!batch.publishingRepoIds.has(repoId)) increment.run(repoId);
178
+ const stale = db.prepare(`UPDATE repositories
179
+ SET graph_stale_reason='package_target_facts_changed',
180
+ graph_stale_at=datetime('now') WHERE workspace_id=?`);
181
+ for (const workspaceId of batch.affectedWorkspaceIds)
182
+ stale.run(workspaceId);
183
+ }
@@ -0,0 +1,201 @@
1
+ import type { Db } from './connection.js';
2
+
3
+ export interface SchemaStructureCategoryCount {
4
+ category: string;
5
+ count: number;
6
+ }
7
+
8
+ const requiredColumns = {
9
+ workspaces: [
10
+ 'id', 'root_path', 'db_path', 'created_at', 'updated_at',
11
+ ],
12
+ repositories: [
13
+ 'id', 'workspace_id', 'name', 'absolute_path', 'relative_path',
14
+ 'package_name', 'package_version', 'dependencies_json',
15
+ 'package_public_surface_json', 'kind', 'is_git_repo', 'last_indexed_at',
16
+ 'index_status', 'error_count', 'fingerprint', 'fact_generation',
17
+ 'graph_generation', 'graph_stale_reason', 'graph_stale_at',
18
+ 'fact_analyzer_version',
19
+ ],
20
+ files: [
21
+ 'id', 'repo_id', 'relative_path', 'extension', 'sha256', 'size_bytes',
22
+ 'last_indexed_at',
23
+ ],
24
+ cds_requires: [
25
+ 'id', 'repo_id', 'alias', 'kind', 'model', 'destination',
26
+ 'service_path', 'request_timeout', 'raw_json',
27
+ ],
28
+ cds_services: [
29
+ 'id', 'repo_id', 'namespace', 'service_name', 'qualified_name',
30
+ 'service_path', 'is_extend', 'source_file', 'source_line',
31
+ 'extension_local_ref', 'extension_imported_symbol',
32
+ 'extension_local_alias', 'extension_module_specifier',
33
+ 'extension_import_kind', 'extension_base_service_id',
34
+ 'extension_base_status',
35
+ ],
36
+ cds_operations: [
37
+ 'id', 'service_id', 'operation_type', 'operation_name', 'operation_path',
38
+ 'params_json', 'return_type', 'source_file', 'source_line', 'provenance',
39
+ 'base_operation_id',
40
+ ],
41
+ symbols: [
42
+ 'id', 'repo_id', 'file_id', 'kind', 'name', 'qualified_name',
43
+ 'exported', 'start_line', 'end_line', 'start_offset', 'end_offset',
44
+ 'source_file', 'exported_name', 'evidence_json',
45
+ ],
46
+ handler_classes: [
47
+ 'id', 'repo_id', 'symbol_id', 'class_name', 'source_file', 'source_line',
48
+ ],
49
+ handler_methods: [
50
+ 'id', 'handler_class_id', 'method_name', 'decorator_kind',
51
+ 'decorator_value', 'decorator_raw_expression',
52
+ 'decorator_resolution_json', 'source_file', 'source_line',
53
+ ],
54
+ handler_registrations: [
55
+ 'id', 'repo_id', 'handler_class_id', 'class_name', 'import_source',
56
+ 'registration_file', 'registration_line', 'registration_kind',
57
+ 'confidence',
58
+ ],
59
+ service_bindings: [
60
+ 'id', 'repo_id', 'symbol_id', 'variable_name', 'alias', 'alias_expr',
61
+ 'destination_expr', 'service_path_expr', 'is_dynamic',
62
+ 'placeholders_json', 'source_file', 'source_line',
63
+ 'binding_site_start_offset', 'binding_site_end_offset',
64
+ 'owner_resolution', 'helper_chain_json',
65
+ ],
66
+ outbound_calls: [
67
+ 'id', 'repo_id', 'source_symbol_id', 'service_binding_id', 'call_type',
68
+ 'method', 'operation_path_expr', 'query_entity', 'event_name_expr',
69
+ 'payload_summary', 'source_file', 'source_line',
70
+ 'call_site_start_offset', 'call_site_end_offset', 'evidence_json',
71
+ 'confidence', 'unresolved_reason', 'local_service_name',
72
+ 'local_service_lookup', 'alias_chain_json', 'external_target_kind',
73
+ 'external_target_id', 'external_target_label', 'external_target_dynamic',
74
+ ],
75
+ symbol_calls: [
76
+ 'id', 'repo_id', 'caller_symbol_id', 'callee_symbol_id',
77
+ 'callee_expression', 'import_source', 'source_file', 'source_line',
78
+ 'call_site_start_offset',
79
+ 'call_site_end_offset', 'call_role', 'status', 'evidence_json',
80
+ 'confidence', 'unresolved_reason',
81
+ ],
82
+ graph_edges: [
83
+ 'id', 'workspace_id', 'edge_type', 'status', 'from_kind', 'from_id',
84
+ 'to_kind', 'to_id', 'confidence', 'evidence_json', 'is_dynamic',
85
+ 'unresolved_reason', 'generation',
86
+ ],
87
+ index_runs: [
88
+ 'id', 'workspace_id', 'started_at', 'finished_at', 'status',
89
+ 'repo_count', 'file_count', 'diagnostic_count', 'error_message',
90
+ 'owner_pid',
91
+ ],
92
+ diagnostics: [
93
+ 'id', 'repo_id', 'file_id', 'severity', 'code', 'message',
94
+ 'source_file', 'source_line',
95
+ ],
96
+ search_index: ['kind', 'name', 'path', 'repo'],
97
+ } as const;
98
+
99
+ type RequiredTable = keyof typeof requiredColumns;
100
+
101
+ function tableNames(db: Db): Set<string> {
102
+ const rows = db.prepare(
103
+ "SELECT name FROM sqlite_master WHERE type='table'",
104
+ ).all();
105
+ return new Set(rows.flatMap((row) =>
106
+ typeof row.name === 'string' ? [row.name] : []));
107
+ }
108
+
109
+ function columnNames(db: Db, table: RequiredTable): Set<string> {
110
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
111
+ return new Set(rows.flatMap((row) =>
112
+ typeof row.name === 'string' ? [row.name] : []));
113
+ }
114
+
115
+ function missingTableCount(db: Db): number {
116
+ const actual = tableNames(db);
117
+ return Object.keys(requiredColumns).filter((table) =>
118
+ !actual.has(table)).length;
119
+ }
120
+
121
+ function missingColumnCount(db: Db): number {
122
+ const actualTables = tableNames(db);
123
+ return Object.entries(requiredColumns).reduce(
124
+ (total, [table, columns]) => {
125
+ if (!actualTables.has(table)) return total;
126
+ const actual = columnNames(db, table as RequiredTable);
127
+ return total + columns.filter((column) => !actual.has(column)).length;
128
+ },
129
+ 0,
130
+ );
131
+ }
132
+
133
+ const bindingSiteColumns = [
134
+ 'repo_id', 'source_file', 'variable_name',
135
+ 'binding_site_start_offset', 'binding_site_end_offset',
136
+ ];
137
+ const exactSitePredicate =
138
+ 'where binding_site_start_offset is not null and '
139
+ + 'binding_site_end_offset is not null';
140
+
141
+ function indexColumns(db: Db, name: string): string[] {
142
+ return db.prepare(`PRAGMA index_info(${name})`).all().flatMap((item) =>
143
+ typeof item.name === 'string' ? [item.name] : []);
144
+ }
145
+
146
+ function indexMetadata(
147
+ db: Db,
148
+ name: string,
149
+ ): Record<string, unknown> | undefined {
150
+ return db.prepare("PRAGMA index_list('service_bindings')")
151
+ .all().find((item) => item.name === name);
152
+ }
153
+
154
+ function normalizedSql(value: string): string {
155
+ return value.toLowerCase().replace(/\s+/g, ' ').trim();
156
+ }
157
+
158
+ function bindingIndexInvalid(
159
+ db: Db,
160
+ name: string,
161
+ unique: number,
162
+ partial: number,
163
+ ): number {
164
+ const metadata = indexMetadata(db, name);
165
+ const columns = indexColumns(db, name);
166
+ return columns.join('\0') === bindingSiteColumns.join('\0')
167
+ && metadata?.unique === unique && metadata.partial === partial ? 0 : 1;
168
+ }
169
+
170
+ function exactBindingIndexInvalid(db: Db): number {
171
+ const row = db.prepare(`SELECT sql FROM sqlite_master
172
+ WHERE type='index' AND name='uq_service_binding_exact_site'`).get();
173
+ if (typeof row?.sql !== 'string'
174
+ || !normalizedSql(row.sql).endsWith(exactSitePredicate)) return 1;
175
+ return bindingIndexInvalid(
176
+ db, 'uq_service_binding_exact_site', 1, 1,
177
+ );
178
+ }
179
+
180
+ function category(
181
+ name: string,
182
+ count: number,
183
+ ): SchemaStructureCategoryCount[] {
184
+ return count > 0 ? [{ category: name, count }] : [];
185
+ }
186
+
187
+ export function invalidSchemaStructureCategories(
188
+ db: Db,
189
+ ): SchemaStructureCategoryCount[] {
190
+ const missingTables = missingTableCount(db);
191
+ if (missingTables > 0)
192
+ return category('schema_required_table_missing', missingTables);
193
+ return [
194
+ ...category('schema_required_column_missing', missingColumnCount(db)),
195
+ ...category(
196
+ 'schema_binding_exact_site_index_invalid',
197
+ exactBindingIndexInvalid(db)
198
+ + bindingIndexInvalid(db, 'idx_service_binding_site', 0, 0),
199
+ ),
200
+ ];
201
+ }