catalyst-relay 0.5.12 → 0.5.14

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
@@ -188,7 +188,9 @@ curl -X POST http://localhost:3000/login \
188
188
  | `asdcls` | Access Control | DCLS/DL |
189
189
  | `aclass` | ABAP Class | CLAS/OC |
190
190
  | `asprog` | ABAP Program | PROG/P |
191
+ | `asinc` | ABAP Include | PROG/I |
191
192
  | `astabldt` | Table | TABL/DT |
193
+ | `astablds` | Structure | STRU/D |
192
194
 
193
195
  ## Library Mode API Reference
194
196
 
@@ -199,21 +201,28 @@ curl -X POST http://localhost:3000/login \
199
201
  | `login()` | Authenticate and create session |
200
202
  | `logout()` | End session |
201
203
  | `refreshSession()` | Manually refresh session (keepalive) |
204
+ | `exportSessionState()` | Serialize session for transfer to another process |
205
+ | `importSessionState(state)` | Restore a previously exported session |
202
206
  | `read(objects)` | Batch read with content |
203
- | `create(object, package, transport?)` | Create new object |
204
- | `update(object, transport?)` | Update existing object |
205
- | `upsert(objects, package, transport?)` | Create or update |
206
- | `activate(objects)` | Compile and validate |
207
- | `delete(objects, transport?)` | Remove objects |
208
- | `getPackages()` | List packages |
209
- | `getPackageStats(name)` | Get package metadata and object count |
207
+ | `create(object, package, transport?)` | Create a new object |
208
+ | `update(object, transport?)` | Update an existing object |
209
+ | `upsert(objects, package, transport?)` | Create or update a batch |
210
+ | `activate(objects)` | Run-based activation; mixed extensions allowed |
211
+ | `checkSyntax(objects)` | Syntax check (single extension per batch) |
212
+ | `delete(objects, transport?)` | Multi-delete with dependency ordering; returns `DeleteResult[]` |
213
+ | `getPackages(options?)` | List packages (filter, includeDescriptions) |
214
+ | `getPackageStats(nameOrNames)` | Package description and recursive object count |
210
215
  | `getTree(query)` | Browse package tree (supports owner filter) |
211
216
  | `getTransports(package)` | List transports |
212
217
  | `createTransport(config)` | Create transport |
218
+ | `deleteTransport(id, removeObjects?)` | Delete transport (optionally clear contents first) |
219
+ | `removeFromTransport(id, objectName)` | Remove a single object from a transport |
220
+ | `viewTransportObjects(id)` | List tasks and objects on a transport |
221
+ | `getInactiveObjects()` | List objects/transports awaiting activation |
213
222
  | `previewData(query)` | Query table/view |
214
- | `getDistinctValues(object, column)` | Distinct values |
215
- | `countRows(object, type)` | Row count |
216
- | `search(query, types?)` | Search objects |
223
+ | `getDistinctValues(name, parameters, column, type?)` | Distinct values with counts |
224
+ | `countRows(name, type, parameters?)` | Row count |
225
+ | `search(query, options?)` | Search objects (`{ types?, includePackages? }`) |
217
226
  | `whereUsed(object)` | Find dependencies |
218
227
  | `gitDiff(objects)` | Compare with server |
219
228
  | `getObjectConfig()` | Supported object types |
@@ -261,11 +270,11 @@ if (error) {
261
270
  #### Searching Objects
262
271
 
263
272
  ```typescript
264
- const [results, error] = await client.search('ZSNAP*', ['DDLS/DF', 'CLAS/OC']);
273
+ const [results, error] = await client.search('ZSNAP*', { types: ['DDLS', 'CLAS'] });
265
274
 
266
275
  if (!error) {
267
276
  for (const result of results) {
268
- console.log(`${result.name} (${result.type})`);
277
+ console.log(`${result.name} (${result.objectType})`);
269
278
  }
270
279
  }
271
280
  ```
@@ -294,12 +303,17 @@ const [data, error] = await client.previewData({
294
303
  | GET | `/packages` | List available packages |
295
304
  | GET | `/packages/:name/stats` | Get package metadata and count |
296
305
  | POST | `/tree` | Browse package tree (supports owner filter) |
297
- | GET | `/transports/:package` | List transports |
306
+ | GET | `/transports/:package` | List transports for a package |
298
307
  | POST | `/transports` | Create transport |
308
+ | DELETE | `/transports/:transportId` | Delete a transport (`?removeObjects=true` to clear first) |
309
+ | GET | `/transports/:transportId/objects` | List tasks/objects on a transport |
310
+ | PUT | `/transports/:transportId/objects` | Remove a single object from a transport |
311
+ | GET | `/inactive-objects` | List objects/transports awaiting activation |
299
312
  | POST | `/objects/read` | Batch read objects |
300
313
  | POST | `/objects/upsert/:package/:transport?` | Create/update objects |
301
314
  | POST | `/objects/activate` | Activate objects |
302
- | DELETE | `/objects/:transport?` | Delete objects |
315
+ | POST | `/objects/check` | Syntax check objects |
316
+ | DELETE | `/objects/:transport?` | Multi-delete with dependency ordering |
303
317
  | POST | `/preview/data` | Query table/view data |
304
318
  | POST | `/preview/distinct` | Get distinct values |
305
319
  | POST | `/preview/count` | Count rows |
@@ -346,7 +360,7 @@ curl -X POST http://localhost:3000/preview/data \
346
360
  curl -X POST "http://localhost:3000/search/ZSNAP*" \
347
361
  -H "Content-Type: application/json" \
348
362
  -H "x-session-id: abc123" \
349
- -d '{ "types": ["DDLS/DF", "CLAS/OC"] }'
363
+ -d '["DDLS", "CLAS"]'
350
364
  ```
351
365
 
352
366
  ## Error Handling
@@ -483,4 +497,4 @@ Egan Bosch
483
497
 
484
498
  ---
485
499
 
486
- *Last updated: v0.4.5*
500
+ *Last updated: v0.5.13*
package/dist/index.d.mts CHANGED
@@ -597,6 +597,9 @@ interface TransportObject {
597
597
 
598
598
  interface TaskContents {
599
599
  taskId: string;
600
+ owner?: string;
601
+ description?: string;
602
+ status?: string;
600
603
  objects: TransportObject[];
601
604
  }
602
605
 
@@ -674,6 +677,7 @@ interface ADTClient {
674
677
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
675
678
  getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
676
679
  countRows(objectName: string, objectType: 'table' | 'view', parameters?: Parameter[]): AsyncResult<number>;
680
+ freestyleQuery(sqlQuery: string, limit?: number, timeout?: number): AsyncResult<DataFrame>;
677
681
  search(query: string, options?: SearchOptions): AsyncResult<SearchResult[]>;
678
682
  whereUsed(object: ObjectRef): AsyncResult<Dependency[]>;
679
683
  createTransport(config: TransportConfig): AsyncResult<string>;
package/dist/index.d.ts CHANGED
@@ -597,6 +597,9 @@ interface TransportObject {
597
597
 
598
598
  interface TaskContents {
599
599
  taskId: string;
600
+ owner?: string;
601
+ description?: string;
602
+ status?: string;
600
603
  objects: TransportObject[];
601
604
  }
602
605
 
@@ -674,6 +677,7 @@ interface ADTClient {
674
677
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
675
678
  getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
676
679
  countRows(objectName: string, objectType: 'table' | 'view', parameters?: Parameter[]): AsyncResult<number>;
680
+ freestyleQuery(sqlQuery: string, limit?: number, timeout?: number): AsyncResult<DataFrame>;
677
681
  search(query: string, options?: SearchOptions): AsyncResult<SearchResult[]>;
678
682
  whereUsed(object: ObjectRef): AsyncResult<Dependency[]>;
679
683
  createTransport(config: TransportConfig): AsyncResult<string>;
package/dist/index.js CHANGED
@@ -1614,7 +1614,9 @@ async function updateObject(client, object, lockHandle, transport) {
1614
1614
 
1615
1615
  // src/core/adt/craud/activation.ts
1616
1616
  var MAX_POLL_ATTEMPTS = 30;
1617
+ var POLL_RETRY_DELAY_MS = 1e3;
1617
1618
  var RUN_ID_REGEX = /\/activation\/runs\/([^?/]+)/;
1619
+ var BACKGROUND_RUN_MEDIA_TYPE = "application/vnd.sap.adt.backgroundrun.v1+xml";
1618
1620
  async function activateObjects(client, objects) {
1619
1621
  if (objects.length === 0) {
1620
1622
  return ok([]);
@@ -1640,7 +1642,7 @@ async function activateObjects(client, objects) {
1640
1642
  },
1641
1643
  headers: {
1642
1644
  "Content-Type": "application/xml",
1643
- "Accept": "application/xml"
1645
+ "Accept": BACKGROUND_RUN_MEDIA_TYPE
1644
1646
  },
1645
1647
  body
1646
1648
  });
@@ -1658,22 +1660,25 @@ async function activateObjects(client, objects) {
1658
1660
  }
1659
1661
  const runId = runIdMatch[1];
1660
1662
  debug(`Activation run ID: ${runId}`);
1661
- let pollAttempt = 0;
1662
- while (pollAttempt < MAX_POLL_ATTEMPTS) {
1663
+ for (let pollAttempt = 1; pollAttempt <= MAX_POLL_ATTEMPTS; pollAttempt++) {
1663
1664
  const [pollRes, pollErr] = await client.request({
1664
1665
  method: "GET",
1665
1666
  path: `/sap/bc/adt/activation/runs/${runId}`,
1666
1667
  params: { "withLongPolling": "true" },
1667
- headers: { "Accept": "application/xml" }
1668
+ headers: { "Accept": BACKGROUND_RUN_MEDIA_TYPE }
1668
1669
  });
1669
1670
  if (pollErr) return err(pollErr);
1670
- debug(`Activation poll attempt ${pollAttempt + 1} status: ${pollRes.status}`);
1671
+ debug(`Activation poll attempt ${pollAttempt} status: ${pollRes.status}`);
1671
1672
  if (pollRes.ok) break;
1672
- pollAttempt++;
1673
+ if (pollRes.status >= 400 && pollRes.status < 500) {
1674
+ const errText = await pollRes.text();
1675
+ return err(new Error(`Activation run ${runId} polling rejected (${pollRes.status}): ${extractError(errText)}`));
1676
+ }
1673
1677
  if (pollAttempt >= MAX_POLL_ATTEMPTS) {
1674
1678
  const errText = await pollRes.text();
1675
- return err(new Error(`Activation run ${runId} did not complete: ${extractError(errText)}`));
1679
+ return err(new Error(`Activation run ${runId} did not complete after ${MAX_POLL_ATTEMPTS} attempts: ${extractError(errText)}`));
1676
1680
  }
1681
+ await new Promise((resolve) => setTimeout(resolve, POLL_RETRY_DELAY_MS));
1677
1682
  }
1678
1683
  const [resultsRes, resultsErr] = await client.request({
1679
1684
  method: "GET",
@@ -2376,16 +2381,64 @@ function parseDataPreview(xml, maxRows, isTable) {
2376
2381
  const namespace = "http://www.sap.com/adt/dataPreview";
2377
2382
  const metadataElements = doc.getElementsByTagNameNS(namespace, "metadata");
2378
2383
  const columns = [];
2384
+ const SAP_TYPE_MAP = {
2385
+ "8": "integer",
2386
+ // Int8
2387
+ "I": "integer",
2388
+ // Integer
2389
+ "P": "decimal",
2390
+ // Packed decimal
2391
+ "F": "float",
2392
+ // Floating point
2393
+ "D": "date",
2394
+ // Date (YYYYMMDD)
2395
+ "T": "time",
2396
+ // Time (HHMMSS)
2397
+ "S": "timestamp",
2398
+ // Timestamp
2399
+ "C": "string",
2400
+ // Character
2401
+ "N": "string",
2402
+ // Numeric character string
2403
+ "V": "string",
2404
+ // Variable-length character
2405
+ "X": "binary"
2406
+ // Raw binary/hex
2407
+ };
2379
2408
  for (let i = 0; i < metadataElements.length; i++) {
2380
2409
  const meta = metadataElements[i];
2381
2410
  if (!meta) continue;
2382
2411
  const nameAttr = isTable ? "name" : "camelCaseName";
2383
2412
  const name = meta.getAttributeNS(namespace, nameAttr) || meta.getAttribute("name");
2384
- const dataType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
2413
+ const colType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
2414
+ const rawType = meta.getAttributeNS(namespace, "type") || meta.getAttribute("type");
2415
+ const isKeyFigure = meta.getAttributeNS(namespace, "isKeyFigure") === "true";
2416
+ let dataType;
2417
+ if (colType && colType.trim() !== "") {
2418
+ dataType = colType;
2419
+ } else if (isKeyFigure) {
2420
+ dataType = "decimal";
2421
+ } else if (rawType && SAP_TYPE_MAP[rawType]) {
2422
+ dataType = SAP_TYPE_MAP[rawType];
2423
+ } else {
2424
+ dataType = "string";
2425
+ }
2426
+ const allAttrs = {};
2427
+ for (let j = 0; j < meta.attributes.length; j++) {
2428
+ const attr = meta.attributes[j];
2429
+ if (!attr) {
2430
+ continue;
2431
+ }
2432
+ allAttrs[attr.name] = attr.value;
2433
+ }
2385
2434
  if (!name || !dataType) continue;
2386
2435
  columns.push({ name, dataType });
2387
2436
  }
2388
2437
  const dataSetElements = doc.getElementsByTagNameNS(namespace, "dataSet");
2438
+ for (let i = 0; i < dataSetElements.length; i++) {
2439
+ const dataSet = dataSetElements[i];
2440
+ if (!dataSet) continue;
2441
+ }
2389
2442
  if (columns.length === 0 && dataSetElements.length > 0) {
2390
2443
  for (let i = 0; i < dataSetElements.length; i++) {
2391
2444
  const dataSet = dataSetElements[i];
@@ -2468,7 +2521,7 @@ async function previewData(client, query) {
2468
2521
 
2469
2522
  // src/core/adt/data_extraction/freestyle.ts
2470
2523
  var DEFAULT_ROW_LIMIT = 100;
2471
- async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT) {
2524
+ async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT, timeout) {
2472
2525
  debug(`Freestyle query: ${sqlQuery}`);
2473
2526
  const [response, requestErr] = await client.request({
2474
2527
  method: "POST",
@@ -2478,16 +2531,21 @@ async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT) {
2478
2531
  },
2479
2532
  headers: {
2480
2533
  "Accept": "application/xml, application/vnd.sap.adt.datapreview.table.v1+xml",
2481
- "Content-Type": "text/plain"
2534
+ "Content-Type": "text/plain",
2535
+ // Override stateful base header: each preview request is independent; stateless
2536
+ // lets SAP route to any work process and recycle it after the request, preventing
2537
+ // GENERATE_SUBPOOL_DIR_FULL (36-pool limit per work process).
2538
+ "X-sap-adt-sessiontype": "stateless"
2482
2539
  },
2483
- body: sqlQuery
2540
+ body: sqlQuery,
2541
+ ...timeout !== void 0 && { timeout }
2484
2542
  });
2485
2543
  if (requestErr) return err(requestErr);
2486
2544
  if (!response.ok) {
2487
2545
  const text2 = await response.text();
2488
2546
  debug(`Freestyle query error response: ${text2.substring(0, 500)}`);
2489
2547
  const errorMsg = extractError(text2);
2490
- return err(new Error(`Freestyle query failed: ${errorMsg}`));
2548
+ return err(new Error(`Freestyle query failed: ${errorMsg}`, { cause: text2 }));
2491
2549
  }
2492
2550
  const text = await response.text();
2493
2551
  const [dataFrame, parseErr] = parseDataPreview(text, limit, true);
@@ -2748,7 +2806,16 @@ function parseTransportTasks(doc) {
2748
2806
  position: el.getAttribute("tm:position") || ""
2749
2807
  });
2750
2808
  }
2751
- tasks.push({ taskId, objects });
2809
+ const owner = taskEl.getAttribute("tm:owner");
2810
+ const description = taskEl.getAttribute("tm:desc");
2811
+ const status = taskEl.getAttribute("tm:status");
2812
+ tasks.push({
2813
+ taskId,
2814
+ ...owner ? { owner } : {},
2815
+ ...description ? { description } : {},
2816
+ ...status ? { status } : {},
2817
+ objects
2818
+ });
2752
2819
  }
2753
2820
  return tasks;
2754
2821
  }
@@ -3142,6 +3209,12 @@ async function countRows2(state, requestor, objectName, objectType, parameters =
3142
3209
  return countRows(requestor, objectName, objectType, parameters);
3143
3210
  }
3144
3211
 
3212
+ // src/client/methods/preview/freestyleQuery.ts
3213
+ async function freestyleQuery2(state, requestor, sqlQuery, limit, timeout) {
3214
+ if (!state.session) return err(new Error("Not logged in"));
3215
+ return freestyleQuery(requestor, sqlQuery, limit, timeout);
3216
+ }
3217
+
3145
3218
  // src/client/methods/search/search.ts
3146
3219
  async function search(state, requestor, query, options) {
3147
3220
  if (!state.session) return err(new Error("Not logged in"));
@@ -3325,7 +3398,7 @@ function buildUrl2(baseUrl, path, params) {
3325
3398
  // src/client/methods/internal/request.ts
3326
3399
  async function executeRequest(deps, options, selfRequest) {
3327
3400
  const { state, ssoCerts, getCookieHeader, storeCookies: storeCookies2 } = deps;
3328
- const { method, path, params, headers: customHeaders, body } = options;
3401
+ const { method, path, params, headers: customHeaders, body, timeout: requestTimeout } = options;
3329
3402
  const { config } = state;
3330
3403
  debug(`Request ${method} ${path} - CSRF token in state: ${state.csrfToken?.substring(0, 20) || "null"}...`);
3331
3404
  const headers = buildRequestHeaders(
@@ -3352,7 +3425,7 @@ async function executeRequest(deps, options, selfRequest) {
3352
3425
  cert: ssoCerts?.cert,
3353
3426
  key: ssoCerts?.key,
3354
3427
  rejectUnauthorized: !config.insecure,
3355
- timeout: config.timeout ?? DEFAULT_TIMEOUT
3428
+ timeout: requestTimeout ?? config.timeout ?? DEFAULT_TIMEOUT
3356
3429
  });
3357
3430
  storeCookies2(response);
3358
3431
  if (response.status === 403) {
@@ -3375,7 +3448,7 @@ async function executeRequest(deps, options, selfRequest) {
3375
3448
  cert: ssoCerts?.cert,
3376
3449
  key: ssoCerts?.key,
3377
3450
  rejectUnauthorized: !config.insecure,
3378
- timeout: config.timeout ?? DEFAULT_TIMEOUT
3451
+ timeout: requestTimeout ?? config.timeout ?? DEFAULT_TIMEOUT
3379
3452
  });
3380
3453
  storeCookies2(retryResponse);
3381
3454
  return ok(retryResponse);
@@ -3546,6 +3619,9 @@ var ADTClientImpl = class {
3546
3619
  async countRows(objectName, objectType, parameters = []) {
3547
3620
  return countRows2(this.state, this.requestor, objectName, objectType, parameters);
3548
3621
  }
3622
+ async freestyleQuery(sqlQuery, limit, timeout) {
3623
+ return freestyleQuery2(this.state, this.requestor, sqlQuery, limit, timeout);
3624
+ }
3549
3625
  // --- Search ---
3550
3626
  async search(query, options) {
3551
3627
  return search(this.state, this.requestor, query, options);