catalyst-relay 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -66,7 +66,8 @@ const [samlClient] = createClient({
66
66
  auth: {
67
67
  type: 'saml',
68
68
  username: 'user@company.com',
69
- password: 'pass'
69
+ password: 'pass',
70
+ sapUser: 'SAPUSER01' // Required: SAP username for object attribution
70
71
  },
71
72
  insecure: true
72
73
  });
@@ -124,7 +125,7 @@ curl -X POST http://localhost:3000/login \
124
125
  -d '{
125
126
  "url": "https://sap-server:443",
126
127
  "client": "100",
127
- "auth": { "type": "saml", "username": "user@company.com", "password": "pass" }
128
+ "auth": { "type": "saml", "username": "user@company.com", "password": "pass", "sapUser": "SAPUSER01" }
128
129
  }'
129
130
 
130
131
  # Login with SSO (Kerberos)
@@ -148,6 +149,8 @@ curl -X POST http://localhost:3000/login \
148
149
  - Login/logout with session tokens
149
150
  - Automatic CSRF token handling and refresh
150
151
  - Session expiration with configurable cleanup
152
+ - Automatic session refresh (keepalive) during long operations
153
+ - Manual session refresh via `refreshSession()`
151
154
  - Support for multiple concurrent sessions
152
155
 
153
156
  ### CRAUD Operations
@@ -195,6 +198,7 @@ curl -X POST http://localhost:3000/login \
195
198
  |--------|-------------|
196
199
  | `login()` | Authenticate and create session |
197
200
  | `logout()` | End session |
201
+ | `refreshSession()` | Manually refresh session (keepalive) |
198
202
  | `read(objects)` | Batch read with content |
199
203
  | `create(object, package, transport?)` | Create new object |
200
204
  | `update(object, transport?)` | Update existing object |
@@ -202,7 +206,8 @@ curl -X POST http://localhost:3000/login \
202
206
  | `activate(objects)` | Compile and validate |
203
207
  | `delete(objects, transport?)` | Remove objects |
204
208
  | `getPackages()` | List packages |
205
- | `getTree(query)` | Browse package tree |
209
+ | `getPackageStats(name)` | Get package metadata and object count |
210
+ | `getTree(query)` | Browse package tree (supports owner filter) |
206
211
  | `getTransports(package)` | List transports |
207
212
  | `createTransport(config)` | Create transport |
208
213
  | `previewData(query)` | Query table/view |
@@ -284,9 +289,11 @@ const [data, error] = await client.previewData({
284
289
  |--------|----------|-------------|
285
290
  | POST | `/login` | Authenticate and get session ID |
286
291
  | DELETE | `/logout` | End session |
292
+ | POST | `/session/refresh` | Refresh session (keepalive) |
287
293
  | GET | `/object-config` | List supported object types |
288
294
  | GET | `/packages` | List available packages |
289
- | POST | `/tree` | Browse package tree |
295
+ | GET | `/packages/:name/stats` | Get package metadata and count |
296
+ | POST | `/tree` | Browse package tree (supports owner filter) |
290
297
  | GET | `/transports/:package` | List transports |
291
298
  | POST | `/transports` | Create transport |
292
299
  | POST | `/objects/read` | Batch read objects |
@@ -473,3 +480,7 @@ MIT
473
480
  ## Author
474
481
 
475
482
  Egan Bosch
483
+
484
+ ---
485
+
486
+ *Last updated: v0.4.5*
package/dist/index.d.mts CHANGED
@@ -551,7 +551,7 @@ interface ADTClient {
551
551
  getTransports(packageName: string): AsyncResult<Transport[]>;
552
552
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
553
553
  getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
554
- countRows(objectName: string, objectType: 'table' | 'view'): AsyncResult<number>;
554
+ countRows(objectName: string, objectType: 'table' | 'view', parameters?: Parameter[]): AsyncResult<number>;
555
555
  search(query: string, types?: string[]): AsyncResult<SearchResult[]>;
556
556
  whereUsed(object: ObjectRef): AsyncResult<Dependency[]>;
557
557
  createTransport(config: TransportConfig): AsyncResult<string>;
@@ -567,4 +567,18 @@ interface ADTClient {
567
567
 
568
568
  declare function createClient(config: ClientConfig): Result<ADTClient, Error>;
569
569
 
570
- export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type AsyncResult, type AuthConfig, type AuthType, type BasicAuthConfig, type BasicFilter, type BetweenFilter, type ClientConfig, type ColumnInfo, type DataFrame, type DataPreviewQuery, type Dependency, type DiffResult, type DistinctResult, type ErrorCode, type ErrorResponse, type ExportableSessionState, type FolderNode, type ListFilter, type ObjectConfig, type ObjectContent, type ObjectMetadata, type ObjectNode, type ObjectRef, type ObjectWithContent, type Package, type PackageNode, type Parameter, type PreviewSQL, type QueryFilter, type Result, type SamlAuthConfig, type SearchResult, type Session, type Sorting, type SsoAuthConfig, type SuccessResponse, type Transport, type TransportConfig, type TreeQuery, type TreeResponse, type UpsertResult, buildSQLQuery, createClient, err, ok };
570
+ /**
571
+ * Logging Utility — Debug logging with activation control
572
+ *
573
+ * Logs are silent by default. Call activateLogging() to enable console output.
574
+ */
575
+ /**
576
+ * Enable debug logging to console
577
+ */
578
+ declare function activateLogging(): void;
579
+ /**
580
+ * Disable debug logging (default state)
581
+ */
582
+ declare function deactivateLogging(): void;
583
+
584
+ export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type AsyncResult, type AuthConfig, type AuthType, type BasicAuthConfig, type BasicFilter, type BetweenFilter, type ClientConfig, type ColumnInfo, type DataFrame, type DataPreviewQuery, type Dependency, type DiffResult, type DistinctResult, type ErrorCode, type ErrorResponse, type ExportableSessionState, type FolderNode, type ListFilter, type ObjectConfig, type ObjectContent, type ObjectMetadata, type ObjectNode, type ObjectRef, type ObjectWithContent, type Package, type PackageNode, type Parameter, type PreviewSQL, type QueryFilter, type Result, type SamlAuthConfig, type SearchResult, type Session, type Sorting, type SsoAuthConfig, type SuccessResponse, type Transport, type TransportConfig, type TreeQuery, type TreeResponse, type UpsertResult, activateLogging, buildSQLQuery, createClient, deactivateLogging, err, ok };
package/dist/index.d.ts CHANGED
@@ -551,7 +551,7 @@ interface ADTClient {
551
551
  getTransports(packageName: string): AsyncResult<Transport[]>;
552
552
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
553
553
  getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
554
- countRows(objectName: string, objectType: 'table' | 'view'): AsyncResult<number>;
554
+ countRows(objectName: string, objectType: 'table' | 'view', parameters?: Parameter[]): AsyncResult<number>;
555
555
  search(query: string, types?: string[]): AsyncResult<SearchResult[]>;
556
556
  whereUsed(object: ObjectRef): AsyncResult<Dependency[]>;
557
557
  createTransport(config: TransportConfig): AsyncResult<string>;
@@ -567,4 +567,18 @@ interface ADTClient {
567
567
 
568
568
  declare function createClient(config: ClientConfig): Result<ADTClient, Error>;
569
569
 
570
- export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type AsyncResult, type AuthConfig, type AuthType, type BasicAuthConfig, type BasicFilter, type BetweenFilter, type ClientConfig, type ColumnInfo, type DataFrame, type DataPreviewQuery, type Dependency, type DiffResult, type DistinctResult, type ErrorCode, type ErrorResponse, type ExportableSessionState, type FolderNode, type ListFilter, type ObjectConfig, type ObjectContent, type ObjectMetadata, type ObjectNode, type ObjectRef, type ObjectWithContent, type Package, type PackageNode, type Parameter, type PreviewSQL, type QueryFilter, type Result, type SamlAuthConfig, type SearchResult, type Session, type Sorting, type SsoAuthConfig, type SuccessResponse, type Transport, type TransportConfig, type TreeQuery, type TreeResponse, type UpsertResult, buildSQLQuery, createClient, err, ok };
570
+ /**
571
+ * Logging Utility — Debug logging with activation control
572
+ *
573
+ * Logs are silent by default. Call activateLogging() to enable console output.
574
+ */
575
+ /**
576
+ * Enable debug logging to console
577
+ */
578
+ declare function activateLogging(): void;
579
+ /**
580
+ * Disable debug logging (default state)
581
+ */
582
+ declare function deactivateLogging(): void;
583
+
584
+ export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type AsyncResult, type AuthConfig, type AuthType, type BasicAuthConfig, type BasicFilter, type BetweenFilter, type ClientConfig, type ColumnInfo, type DataFrame, type DataPreviewQuery, type Dependency, type DiffResult, type DistinctResult, type ErrorCode, type ErrorResponse, type ExportableSessionState, type FolderNode, type ListFilter, type ObjectConfig, type ObjectContent, type ObjectMetadata, type ObjectNode, type ObjectRef, type ObjectWithContent, type Package, type PackageNode, type Parameter, type PreviewSQL, type QueryFilter, type Result, type SamlAuthConfig, type SearchResult, type Session, type Sorting, type SsoAuthConfig, type SuccessResponse, type Transport, type TransportConfig, type TreeQuery, type TreeResponse, type UpsertResult, activateLogging, buildSQLQuery, createClient, deactivateLogging, err, ok };
package/dist/index.js CHANGED
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ activateLogging: () => activateLogging,
33
34
  buildSQLQuery: () => buildSQLQuery,
34
35
  createClient: () => createClient,
36
+ deactivateLogging: () => deactivateLogging,
35
37
  err: () => err,
36
38
  ok: () => ok
37
39
  });
@@ -871,6 +873,12 @@ function extractCsrfToken(headers) {
871
873
 
872
874
  // src/core/utils/logging.ts
873
875
  var isActive = false;
876
+ function activateLogging() {
877
+ isActive = true;
878
+ }
879
+ function deactivateLogging() {
880
+ isActive = false;
881
+ }
874
882
  function debug(message) {
875
883
  if (isActive) {
876
884
  console.log(`[DEBUG] ${message}`);
@@ -915,7 +923,7 @@ async function fetchCsrfToken(state, request4) {
915
923
  if (!token) {
916
924
  debug("Response headers:");
917
925
  response.headers.forEach((value, key) => debug(` ${key}: ${value.substring(0, 50)}`));
918
- return err(new Error("No CSRF token returned in response headers"));
926
+ return err(new Error("Invalid credentials"));
919
927
  }
920
928
  state.csrfToken = token;
921
929
  debug(`Stored CSRF token in state: ${state.csrfToken?.substring(0, 20)}...`);
@@ -951,7 +959,7 @@ function extractUsername(auth) {
951
959
  async function login(state, request4) {
952
960
  const [token, tokenErr] = await fetchCsrfToken(state, request4);
953
961
  if (tokenErr) {
954
- return err(new Error(`Login failed: ${tokenErr.message}`));
962
+ return err(tokenErr);
955
963
  }
956
964
  const username = extractUsername(state.config.auth);
957
965
  const timeout = getSessionTimeout(state.config.auth.type);
@@ -1093,16 +1101,22 @@ function exportSessionState(ctx, ssoCerts) {
1093
1101
  // src/client/methods/session/importSessionState.ts
1094
1102
  var DEFAULT_REFRESH_INTERVAL2 = 30 * 60 * 1e3;
1095
1103
  async function importSessionState(ctx, state, setSsoCerts) {
1104
+ debug(`importSessionState: starting import`);
1096
1105
  if (state.session.expiresAt <= Date.now()) {
1106
+ debug(`importSessionState: session expired`);
1097
1107
  return err(new Error("Session has expired"));
1098
1108
  }
1109
+ debug(`importSessionState: session expiry OK (expires at ${state.session.expiresAt})`);
1099
1110
  ctx.state.session = state.session;
1100
1111
  ctx.state.csrfToken = state.csrfToken;
1112
+ debug(`importSessionState: restored session and CSRF token`);
1101
1113
  ctx.state.cookies.clear();
1102
1114
  for (const cookie of state.cookies) {
1103
1115
  ctx.state.cookies.set(cookie.name, cookie.value);
1104
1116
  }
1117
+ debug(`importSessionState: restored ${state.cookies.length} cookies`);
1105
1118
  if (state.authType === "sso" && state.ssoCertPaths) {
1119
+ debug(`importSessionState: loading SSO certs from ${state.ssoCertPaths.fullChainPath}`);
1106
1120
  try {
1107
1121
  const fs = await import("fs/promises");
1108
1122
  const [fullChain, key] = await Promise.all([
@@ -1110,32 +1124,52 @@ async function importSessionState(ctx, state, setSsoCerts) {
1110
1124
  fs.readFile(state.ssoCertPaths.keyPath, "utf-8")
1111
1125
  ]);
1112
1126
  setSsoCerts({ cert: fullChain, key });
1127
+ debug(`importSessionState: SSO certs loaded successfully`);
1113
1128
  } catch (certErr) {
1129
+ debug(`importSessionState: SSO cert load failed: ${certErr}`);
1114
1130
  return err(new Error(`Failed to load SSO certificates: ${certErr instanceof Error ? certErr.message : String(certErr)}`));
1115
1131
  }
1116
1132
  }
1133
+ debug(`importSessionState: fetching fresh CSRF token...`);
1117
1134
  const [response, reqErr] = await ctx.request({
1118
1135
  method: "GET",
1119
- path: "/sap/bc/adt/compatibility/graph"
1136
+ path: "/sap/bc/adt/compatibility/graph",
1137
+ headers: {
1138
+ [CSRF_TOKEN_HEADER]: FETCH_CSRF_TOKEN
1139
+ }
1120
1140
  });
1141
+ debug(`importSessionState: CSRF fetch completed`);
1121
1142
  if (reqErr) {
1143
+ debug(`importSessionState: CSRF fetch error: ${reqErr.message}`);
1122
1144
  ctx.state.session = null;
1123
1145
  ctx.state.csrfToken = null;
1124
1146
  ctx.state.cookies.clear();
1125
1147
  return err(new Error(`Session validation failed: ${reqErr.message}`));
1126
1148
  }
1127
1149
  if (!response.ok) {
1150
+ debug(`importSessionState: CSRF fetch failed with status ${response.status}`);
1128
1151
  ctx.state.session = null;
1129
1152
  ctx.state.csrfToken = null;
1130
1153
  ctx.state.cookies.clear();
1131
1154
  return err(new Error(`Session validation failed with status ${response.status}`));
1132
1155
  }
1156
+ const newToken = extractCsrfToken(response.headers);
1157
+ if (!newToken) {
1158
+ debug(`importSessionState: no CSRF token in response headers`);
1159
+ ctx.state.session = null;
1160
+ ctx.state.csrfToken = null;
1161
+ ctx.state.cookies.clear();
1162
+ return err(new Error("Session validation failed: no CSRF token returned"));
1163
+ }
1164
+ ctx.state.csrfToken = newToken;
1165
+ debug(`importSessionState: new CSRF token obtained: ${newToken.substring(0, 20)}...`);
1133
1166
  const autoRefresh = ctx.state.config.autoRefresh ?? { enabled: true };
1134
1167
  if (autoRefresh.enabled) {
1135
1168
  const interval = autoRefresh.intervalMs ?? DEFAULT_REFRESH_INTERVAL2;
1136
1169
  ctx.startAutoRefresh(interval);
1137
1170
  debug(`Auto-refresh started with ${interval}ms interval (after import)`);
1138
1171
  }
1172
+ debug(`importSessionState: import complete`);
1139
1173
  return ok(true);
1140
1174
  }
1141
1175
 
@@ -1177,6 +1211,14 @@ var OBJECT_CONFIG_MAP = {
1177
1211
  dpEndpoint: "ddic",
1178
1212
  dpParam: "ddicEntityName"
1179
1213
  },
1214
+ "astablds": {
1215
+ endpoint: "ddic/structures",
1216
+ nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
1217
+ rootName: "blue:blueSource",
1218
+ type: "STRU/D",
1219
+ label: "Structure" /* STRUCTURE */,
1220
+ extension: "astablds"
1221
+ },
1180
1222
  "asprog": {
1181
1223
  endpoint: "programs/programs",
1182
1224
  nameSpace: 'xmlns:program="http://www.sap.com/adt/programs/programs"',
@@ -2117,8 +2159,8 @@ async function getDistinctValues(client, objectName, parameters, column, _object
2117
2159
  }
2118
2160
 
2119
2161
  // src/core/adt/data_extraction/count.ts
2120
- async function countRows(client, objectName, _objectType) {
2121
- const sqlQuery = `SELECT COUNT(*) AS row_count FROM ${objectName}`;
2162
+ async function countRows(client, objectName, _objectType, parameters = []) {
2163
+ const sqlQuery = `SELECT COUNT(*) AS row_count FROM ${objectName}${parametersToSQLParams(parameters)}`;
2122
2164
  const [dataFrame, error] = await freestyleQuery(client, sqlQuery, 1);
2123
2165
  if (error) {
2124
2166
  return err(new Error(`Row count query failed: ${error.message}`));
@@ -2534,9 +2576,9 @@ async function getDistinctValues2(state, requestor, objectName, parameters, colu
2534
2576
  }
2535
2577
 
2536
2578
  // src/client/methods/preview/countRows.ts
2537
- async function countRows2(state, requestor, objectName, objectType) {
2579
+ async function countRows2(state, requestor, objectName, objectType, parameters = []) {
2538
2580
  if (!state.session) return err(new Error("Not logged in"));
2539
- return countRows(requestor, objectName, objectType);
2581
+ return countRows(requestor, objectName, objectType, parameters);
2540
2582
  }
2541
2583
 
2542
2584
  // src/client/methods/search/search.ts
@@ -2605,6 +2647,7 @@ function createAutoRefresh(getSession, refreshSession3) {
2605
2647
  debug(`Auto-refresh failed: ${refreshErr.message}`);
2606
2648
  }
2607
2649
  }, intervalMs);
2650
+ timer.unref();
2608
2651
  },
2609
2652
  stop() {
2610
2653
  if (timer) {
@@ -2915,8 +2958,8 @@ var ADTClientImpl = class {
2915
2958
  async getDistinctValues(objectName, parameters, column, objectType = "view") {
2916
2959
  return getDistinctValues2(this.state, this.requestor, objectName, parameters, column, objectType);
2917
2960
  }
2918
- async countRows(objectName, objectType) {
2919
- return countRows2(this.state, this.requestor, objectName, objectType);
2961
+ async countRows(objectName, objectType, parameters = []) {
2962
+ return countRows2(this.state, this.requestor, objectName, objectType, parameters);
2920
2963
  }
2921
2964
  // --- Search ---
2922
2965
  async search(query, types) {
@@ -2950,8 +2993,10 @@ function createClient(config) {
2950
2993
  }
2951
2994
  // Annotate the CommonJS export names for ESM import in node:
2952
2995
  0 && (module.exports = {
2996
+ activateLogging,
2953
2997
  buildSQLQuery,
2954
2998
  createClient,
2999
+ deactivateLogging,
2955
3000
  err,
2956
3001
  ok
2957
3002
  });