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 +15 -4
- package/dist/index.d.mts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +52 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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
|
});
|