@saptools/service-flow 0.1.40 → 0.1.41

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/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  parsePackageJson,
14
14
  parseServiceBindings,
15
15
  trace
16
- } from "./chunk-774PPGGK.js";
16
+ } from "./chunk-7L4QKKGN.js";
17
17
 
18
18
  // src/cli.ts
19
19
  import { Command } from "commander";
@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS workspaces (id INTEGER PRIMARY KEY, root_path TEXT UN
96
96
  CREATE TABLE IF NOT EXISTS repositories (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, name TEXT NOT NULL, absolute_path TEXT NOT NULL, relative_path TEXT NOT NULL, package_name TEXT, package_version TEXT, dependencies_json TEXT DEFAULT '{}', kind TEXT NOT NULL, is_git_repo INTEGER NOT NULL, last_indexed_at TEXT, index_status TEXT DEFAULT 'pending', error_count INTEGER DEFAULT 0, fingerprint TEXT, fact_generation INTEGER NOT NULL DEFAULT 0, graph_generation INTEGER NOT NULL DEFAULT 0, graph_stale_reason TEXT, graph_stale_at TEXT, fact_analyzer_version TEXT, UNIQUE(workspace_id, absolute_path), FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
97
97
  CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, relative_path TEXT NOT NULL, extension TEXT NOT NULL, sha256 TEXT NOT NULL, size_bytes INTEGER NOT NULL, last_indexed_at TEXT NOT NULL, UNIQUE(repo_id, relative_path), FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
98
98
  CREATE TABLE IF NOT EXISTS cds_requires (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, alias TEXT NOT NULL, kind TEXT, model TEXT, destination TEXT, service_path TEXT, request_timeout INTEGER, raw_json TEXT NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
99
- CREATE TABLE IF NOT EXISTS cds_services (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, namespace TEXT, service_name TEXT NOT NULL, qualified_name TEXT NOT NULL, service_path TEXT NOT NULL, is_extend INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
100
- CREATE TABLE IF NOT EXISTS cds_operations (id INTEGER PRIMARY KEY, service_id INTEGER NOT NULL, operation_type TEXT NOT NULL, operation_name TEXT NOT NULL, operation_path TEXT NOT NULL, params_json TEXT NOT NULL, return_type TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(service_id) REFERENCES cds_services(id) ON DELETE CASCADE);
99
+ CREATE TABLE IF NOT EXISTS cds_services (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, namespace TEXT, service_name TEXT NOT NULL, qualified_name TEXT NOT NULL, service_path TEXT NOT NULL, is_extend INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, extension_local_ref TEXT, extension_imported_symbol TEXT, extension_local_alias TEXT, extension_module_specifier TEXT, extension_import_kind TEXT, extension_base_service_id INTEGER, extension_base_status TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(extension_base_service_id) REFERENCES cds_services(id) ON DELETE SET NULL);
100
+ CREATE TABLE IF NOT EXISTS cds_operations (id INTEGER PRIMARY KEY, service_id INTEGER NOT NULL, operation_type TEXT NOT NULL, operation_name TEXT NOT NULL, operation_path TEXT NOT NULL, params_json TEXT NOT NULL, return_type TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, provenance TEXT NOT NULL DEFAULT 'direct', base_operation_id INTEGER, FOREIGN KEY(service_id) REFERENCES cds_services(id) ON DELETE CASCADE, FOREIGN KEY(base_operation_id) REFERENCES cds_operations(id) ON DELETE SET NULL);
101
101
  CREATE TABLE IF NOT EXISTS symbols (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, file_id INTEGER, kind TEXT NOT NULL, name TEXT NOT NULL, qualified_name TEXT NOT NULL, exported INTEGER NOT NULL, start_line INTEGER NOT NULL, end_line INTEGER NOT NULL, start_offset INTEGER, end_offset INTEGER, source_file TEXT, exported_name TEXT, evidence_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE CASCADE);
102
102
  CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, class_name TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
103
103
  CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
@@ -110,14 +110,16 @@ CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTE
110
110
  CREATE TABLE IF NOT EXISTS diagnostics (id INTEGER PRIMARY KEY, repo_id INTEGER, file_id INTEGER, severity TEXT NOT NULL, code TEXT NOT NULL, message TEXT NOT NULL, source_file TEXT, source_line INTEGER, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE SET NULL);
111
111
  CREATE INDEX IF NOT EXISTS idx_repo_name ON repositories(name);
112
112
  CREATE INDEX IF NOT EXISTS idx_service_path ON cds_services(service_path);
113
+ CREATE INDEX IF NOT EXISTS idx_extension_import ON cds_services(extension_module_specifier, extension_imported_symbol, is_extend);
113
114
  CREATE INDEX IF NOT EXISTS idx_operation_name ON cds_operations(operation_name, operation_path);
115
+ CREATE INDEX IF NOT EXISTS idx_operation_base ON cds_operations(base_operation_id);
114
116
  CREATE INDEX IF NOT EXISTS idx_calls_repo ON outbound_calls(repo_id, call_type);
115
117
  CREATE INDEX IF NOT EXISTS idx_symbol_calls_caller ON symbol_calls(repo_id, caller_symbol_id);
116
118
  CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, repo);
117
119
  `;
118
120
 
119
121
  // src/db/migrations.ts
120
- var CURRENT_SCHEMA_VERSION = 8;
122
+ var CURRENT_SCHEMA_VERSION = 9;
121
123
  var columns = {
122
124
  service_bindings: [
123
125
  { name: "helper_chain_json", ddl: "ALTER TABLE service_bindings ADD COLUMN helper_chain_json TEXT" },
@@ -146,6 +148,19 @@ var columns = {
146
148
  { name: "exported_name", ddl: "ALTER TABLE symbols ADD COLUMN exported_name TEXT" },
147
149
  { name: "evidence_json", ddl: "ALTER TABLE symbols ADD COLUMN evidence_json TEXT" }
148
150
  ],
151
+ cds_services: [
152
+ { name: "extension_local_ref", ddl: "ALTER TABLE cds_services ADD COLUMN extension_local_ref TEXT" },
153
+ { name: "extension_imported_symbol", ddl: "ALTER TABLE cds_services ADD COLUMN extension_imported_symbol TEXT" },
154
+ { name: "extension_local_alias", ddl: "ALTER TABLE cds_services ADD COLUMN extension_local_alias TEXT" },
155
+ { name: "extension_module_specifier", ddl: "ALTER TABLE cds_services ADD COLUMN extension_module_specifier TEXT" },
156
+ { name: "extension_import_kind", ddl: "ALTER TABLE cds_services ADD COLUMN extension_import_kind TEXT" },
157
+ { name: "extension_base_service_id", ddl: "ALTER TABLE cds_services ADD COLUMN extension_base_service_id INTEGER" },
158
+ { name: "extension_base_status", ddl: "ALTER TABLE cds_services ADD COLUMN extension_base_status TEXT" }
159
+ ],
160
+ cds_operations: [
161
+ { name: "provenance", ddl: "ALTER TABLE cds_operations ADD COLUMN provenance TEXT NOT NULL DEFAULT 'direct'" },
162
+ { name: "base_operation_id", ddl: "ALTER TABLE cds_operations ADD COLUMN base_operation_id INTEGER" }
163
+ ],
149
164
  outbound_calls: [
150
165
  { name: "local_service_name", ddl: "ALTER TABLE outbound_calls ADD COLUMN local_service_name TEXT" },
151
166
  { name: "local_service_lookup", ddl: "ALTER TABLE outbound_calls ADD COLUMN local_service_lookup TEXT" },
@@ -194,7 +209,7 @@ function migrate(db) {
194
209
  // package.json
195
210
  var package_default = {
196
211
  name: "@saptools/service-flow",
197
- version: "0.1.40",
212
+ version: "0.1.41",
198
213
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
199
214
  type: "module",
200
215
  publishConfig: {
@@ -439,7 +454,7 @@ function insertRequires(db, repoId, rows) {
439
454
  function insertService(db, repoId, s) {
440
455
  const id = Number(
441
456
  db.prepare(
442
- "INSERT INTO cds_services(repo_id,namespace,service_name,qualified_name,service_path,is_extend,source_file,source_line) VALUES(?,?,?,?,?,?,?,?) RETURNING id"
457
+ "INSERT INTO cds_services(repo_id,namespace,service_name,qualified_name,service_path,is_extend,source_file,source_line,extension_local_ref,extension_imported_symbol,extension_local_alias,extension_module_specifier,extension_import_kind) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"
443
458
  ).get(
444
459
  repoId,
445
460
  s.namespace,
@@ -448,11 +463,16 @@ function insertService(db, repoId, s) {
448
463
  s.servicePath,
449
464
  s.isExtend ? 1 : 0,
450
465
  s.sourceFile,
451
- s.sourceLine
466
+ s.sourceLine,
467
+ s.extension?.localReference,
468
+ s.extension?.importedSymbol,
469
+ s.extension?.localAlias,
470
+ s.extension?.moduleSpecifier,
471
+ s.extension?.importKind
452
472
  )?.id
453
473
  );
454
474
  const stmt = db.prepare(
455
- "INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
475
+ "INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,provenance,base_operation_id) VALUES(?,?,?,?,?,?,?,?,?,?)"
456
476
  );
457
477
  db.prepare(
458
478
  "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
@@ -466,7 +486,9 @@ function insertService(db, repoId, s) {
466
486
  o.paramsJson,
467
487
  o.returnType,
468
488
  o.sourceFile,
469
- o.sourceLine
489
+ o.sourceLine,
490
+ o.provenance ?? "direct",
491
+ o.baseOperationId ?? null
470
492
  );
471
493
  const search = db.prepare(
472
494
  "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
@@ -1125,6 +1147,53 @@ async function repositoryFingerprint(root, files, facts) {
1125
1147
  return sha256Text(entries.join("\n"));
1126
1148
  }
1127
1149
 
1150
+ // src/indexer/cds-extension-resolver.ts
1151
+ function materializeCdsExtensionOperations(db, workspaceId) {
1152
+ const extensions = db.prepare(`SELECT s.id,r.id repoId,s.service_name serviceName,s.qualified_name qualifiedName,s.source_file sourceFile,s.extension_module_specifier moduleSpecifier,s.extension_imported_symbol importedSymbol,s.extension_import_kind importKind
1153
+ FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=1`).all(workspaceId);
1154
+ const insert = db.prepare(`INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,provenance,base_operation_id)
1155
+ SELECT ?,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,'inherited',id FROM cds_operations WHERE service_id=? AND NOT EXISTS (SELECT 1 FROM cds_operations existing WHERE existing.service_id=? AND existing.operation_name=cds_operations.operation_name AND existing.operation_path=cds_operations.operation_path)`);
1156
+ for (const extension of extensions) {
1157
+ const bases = resolveBase(db, workspaceId, extension);
1158
+ const status = bases.length === 1 ? "resolved" : bases.length > 1 ? "ambiguous" : "unresolved";
1159
+ db.prepare("UPDATE cds_services SET extension_base_status=?, extension_base_service_id=? WHERE id=?").run(status, bases[0]?.id ?? null, extension.id);
1160
+ if (bases.length === 1) insert.run(extension.id, bases[0]?.id, extension.id);
1161
+ }
1162
+ }
1163
+ function resolveBase(db, workspaceId, extension) {
1164
+ const symbol = extension.importedSymbol ?? extension.serviceName;
1165
+ if (extension.importKind === "relative" && extension.moduleSpecifier) return relativeBase(db, extension, symbol);
1166
+ if (extension.importKind === "package" && extension.moduleSpecifier) return packageBase(db, workspaceId, extension.moduleSpecifier, symbol);
1167
+ return sameRepoBase(db, extension, symbol);
1168
+ }
1169
+ function relativeBase(db, extension, symbol) {
1170
+ const modulePath = normalizeModulePath(extension.sourceFile, extension.moduleSpecifier ?? "");
1171
+ return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s WHERE s.repo_id=? AND s.is_extend=0 AND (s.qualified_name=? OR s.service_name=?) AND (s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(extension.repoId, symbol, symbol, modulePath, `${modulePath}.cds`);
1172
+ }
1173
+ function packageBase(db, workspaceId, specifier, symbol) {
1174
+ const packageName = packageNameFromSpecifier(specifier);
1175
+ const moduleSuffix = specifier.slice(packageName.length).replace(/^\//, "");
1176
+ return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=0 AND r.package_name=? AND (s.qualified_name=? OR s.service_name=?) AND (?='' OR s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(workspaceId, packageName, symbol, symbol, moduleSuffix, moduleSuffix, `${moduleSuffix}.cds`);
1177
+ }
1178
+ function sameRepoBase(db, extension, symbol) {
1179
+ return db.prepare("SELECT id,repo_id repoId FROM cds_services WHERE repo_id=? AND is_extend=0 AND (qualified_name=? OR service_name=?) ORDER BY id").all(extension.repoId, symbol, symbol);
1180
+ }
1181
+ function normalizeModulePath(sourceFile, specifier) {
1182
+ const base = sourceFile.split("/").slice(0, -1).join("/");
1183
+ const parts = `${base}/${specifier}`.split("/");
1184
+ const out = [];
1185
+ for (const part of parts) {
1186
+ if (!part || part === ".") continue;
1187
+ if (part === "..") out.pop();
1188
+ else out.push(part);
1189
+ }
1190
+ return out.join("/").replace(/\.cds$/, "");
1191
+ }
1192
+ function packageNameFromSpecifier(specifier) {
1193
+ const parts = specifier.split("/");
1194
+ return specifier.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0] ?? specifier;
1195
+ }
1196
+
1128
1197
  // src/indexer/workspace-indexer.ts
1129
1198
  async function indexWorkspace(db, workspaceId, options) {
1130
1199
  const started = (/* @__PURE__ */ new Date()).toISOString();
@@ -1140,6 +1209,7 @@ async function indexWorkspace(db, workspaceId, options) {
1140
1209
  diagnosticCount += result.diagnosticCount;
1141
1210
  skippedCount += result.skipped ? 1 : 0;
1142
1211
  }
1212
+ materializeCdsExtensionOperations(db, workspaceId);
1143
1213
  db.prepare("UPDATE index_runs SET finished_at=?, status=?, file_count=?, diagnostic_count=? WHERE id=?").run((/* @__PURE__ */ new Date()).toISOString(), diagnosticCount ? "failed" : "success", fileCount, diagnosticCount, runId);
1144
1214
  return { repoCount: repos.length, indexedCount: repos.length - skippedCount, skippedCount, fileCount, diagnosticCount };
1145
1215
  } catch (error) {