catalyst-relay 0.4.2 → 0.4.4

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/index.d.mts CHANGED
@@ -64,6 +64,15 @@ interface SsoAuthConfig {
64
64
  * Union of all auth configurations
65
65
  */
66
66
  type AuthConfig = BasicAuthConfig | SamlAuthConfig | SsoAuthConfig;
67
+ /**
68
+ * Auto-refresh configuration for session keepalive
69
+ */
70
+ interface AutoRefreshConfig {
71
+ /** Enable automatic session refresh (default: true) */
72
+ enabled: boolean;
73
+ /** Refresh interval in milliseconds (default: 7200000 = 2 hours) */
74
+ intervalMs?: number;
75
+ }
67
76
  /**
68
77
  * Client configuration for connecting to SAP ADT
69
78
  */
@@ -78,6 +87,8 @@ interface ClientConfig {
78
87
  timeout?: number;
79
88
  /** Skip SSL verification (dev only) */
80
89
  insecure?: boolean;
90
+ /** Auto-refresh configuration for session keepalive (default: enabled with 2-hour interval) */
91
+ autoRefresh?: AutoRefreshConfig;
81
92
  }
82
93
 
83
94
  /**
@@ -187,6 +198,27 @@ interface Session {
187
198
  expiresAt: number;
188
199
  }
189
200
 
201
+ /**
202
+ * Session Refresh via Reentrance Ticket
203
+ *
204
+ * Fetches a reentrance ticket from SAP ADT to keep the session alive.
205
+ * Eclipse ADT uses this mechanism to maintain sessions across extended periods.
206
+ *
207
+ * Endpoint: GET /sap/bc/adt/security/reentranceticket
208
+ * - Returns a base64-encoded SSO ticket
209
+ * - Refreshes server-side session cookies (MYSAPSSO2)
210
+ */
211
+
212
+ /**
213
+ * Result of a session refresh operation
214
+ */
215
+ interface RefreshResult {
216
+ /** Base64-encoded reentrance ticket */
217
+ ticket: string;
218
+ /** Updated session expiration timestamp (ms since epoch) */
219
+ expiresAt: number;
220
+ }
221
+
190
222
  interface ObjectConfig {
191
223
  /** ADT endpoint path (e.g., 'ddic/ddl/sources') */
192
224
  endpoint: string;
@@ -273,16 +305,11 @@ interface FolderNode {
273
305
  displayName: string;
274
306
  numContents: number;
275
307
  }
276
- interface ApiState {
277
- useInCloudDevelopment: boolean;
278
- useInCloudDvlpmntActive: boolean;
279
- useInKeyUserApps: boolean;
280
- }
281
308
  interface ObjectNode {
282
309
  name: string;
283
310
  objectType: string;
284
311
  extension: string;
285
- apiState?: ApiState;
312
+ description?: string;
286
313
  }
287
314
 
288
315
  /**
@@ -487,6 +514,7 @@ interface ADTClient {
487
514
  readonly session: Session | null;
488
515
  login(): AsyncResult<Session>;
489
516
  logout(): AsyncResult<void>;
517
+ refreshSession(): AsyncResult<RefreshResult>;
490
518
  read(objects: ObjectRef[]): AsyncResult<ObjectWithContent[]>;
491
519
  create(object: ObjectContent, packageName: string, transport?: string): AsyncResult<void>;
492
520
  update(object: ObjectContent, transport?: string): AsyncResult<void>;
@@ -496,6 +524,7 @@ interface ADTClient {
496
524
  getPackages(filter?: string): AsyncResult<Package[]>;
497
525
  getTree(query: TreeQuery): AsyncResult<TreeResponse>;
498
526
  getPackageStats(packageName: string): AsyncResult<PackageNode>;
527
+ getPackageStats(packageNames: string[]): AsyncResult<PackageNode[]>;
499
528
  getTransports(packageName: string): AsyncResult<Transport[]>;
500
529
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
501
530
  getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
@@ -508,4 +537,4 @@ interface ADTClient {
508
537
  }
509
538
  declare function createClient(config: ClientConfig): Result<ADTClient, Error>;
510
539
 
511
- export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type ApiState, 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 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 };
540
+ 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 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 };
package/dist/index.d.ts CHANGED
@@ -64,6 +64,15 @@ interface SsoAuthConfig {
64
64
  * Union of all auth configurations
65
65
  */
66
66
  type AuthConfig = BasicAuthConfig | SamlAuthConfig | SsoAuthConfig;
67
+ /**
68
+ * Auto-refresh configuration for session keepalive
69
+ */
70
+ interface AutoRefreshConfig {
71
+ /** Enable automatic session refresh (default: true) */
72
+ enabled: boolean;
73
+ /** Refresh interval in milliseconds (default: 7200000 = 2 hours) */
74
+ intervalMs?: number;
75
+ }
67
76
  /**
68
77
  * Client configuration for connecting to SAP ADT
69
78
  */
@@ -78,6 +87,8 @@ interface ClientConfig {
78
87
  timeout?: number;
79
88
  /** Skip SSL verification (dev only) */
80
89
  insecure?: boolean;
90
+ /** Auto-refresh configuration for session keepalive (default: enabled with 2-hour interval) */
91
+ autoRefresh?: AutoRefreshConfig;
81
92
  }
82
93
 
83
94
  /**
@@ -187,6 +198,27 @@ interface Session {
187
198
  expiresAt: number;
188
199
  }
189
200
 
201
+ /**
202
+ * Session Refresh via Reentrance Ticket
203
+ *
204
+ * Fetches a reentrance ticket from SAP ADT to keep the session alive.
205
+ * Eclipse ADT uses this mechanism to maintain sessions across extended periods.
206
+ *
207
+ * Endpoint: GET /sap/bc/adt/security/reentranceticket
208
+ * - Returns a base64-encoded SSO ticket
209
+ * - Refreshes server-side session cookies (MYSAPSSO2)
210
+ */
211
+
212
+ /**
213
+ * Result of a session refresh operation
214
+ */
215
+ interface RefreshResult {
216
+ /** Base64-encoded reentrance ticket */
217
+ ticket: string;
218
+ /** Updated session expiration timestamp (ms since epoch) */
219
+ expiresAt: number;
220
+ }
221
+
190
222
  interface ObjectConfig {
191
223
  /** ADT endpoint path (e.g., 'ddic/ddl/sources') */
192
224
  endpoint: string;
@@ -273,16 +305,11 @@ interface FolderNode {
273
305
  displayName: string;
274
306
  numContents: number;
275
307
  }
276
- interface ApiState {
277
- useInCloudDevelopment: boolean;
278
- useInCloudDvlpmntActive: boolean;
279
- useInKeyUserApps: boolean;
280
- }
281
308
  interface ObjectNode {
282
309
  name: string;
283
310
  objectType: string;
284
311
  extension: string;
285
- apiState?: ApiState;
312
+ description?: string;
286
313
  }
287
314
 
288
315
  /**
@@ -487,6 +514,7 @@ interface ADTClient {
487
514
  readonly session: Session | null;
488
515
  login(): AsyncResult<Session>;
489
516
  logout(): AsyncResult<void>;
517
+ refreshSession(): AsyncResult<RefreshResult>;
490
518
  read(objects: ObjectRef[]): AsyncResult<ObjectWithContent[]>;
491
519
  create(object: ObjectContent, packageName: string, transport?: string): AsyncResult<void>;
492
520
  update(object: ObjectContent, transport?: string): AsyncResult<void>;
@@ -496,6 +524,7 @@ interface ADTClient {
496
524
  getPackages(filter?: string): AsyncResult<Package[]>;
497
525
  getTree(query: TreeQuery): AsyncResult<TreeResponse>;
498
526
  getPackageStats(packageName: string): AsyncResult<PackageNode>;
527
+ getPackageStats(packageNames: string[]): AsyncResult<PackageNode[]>;
499
528
  getTransports(packageName: string): AsyncResult<Transport[]>;
500
529
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
501
530
  getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
@@ -508,4 +537,4 @@ interface ADTClient {
508
537
  }
509
538
  declare function createClient(config: ClientConfig): Result<ADTClient, Error>;
510
539
 
511
- export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type ApiState, 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 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 };
540
+ 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 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 };
package/dist/index.js CHANGED
@@ -237,7 +237,11 @@ var clientConfigSchema = import_zod.z.object({
237
237
  })
238
238
  ]),
239
239
  timeout: import_zod.z.number().positive().optional(),
240
- insecure: import_zod.z.boolean().optional()
240
+ insecure: import_zod.z.boolean().optional(),
241
+ autoRefresh: import_zod.z.object({
242
+ enabled: import_zod.z.boolean(),
243
+ intervalMs: import_zod.z.number().positive().optional()
244
+ }).optional()
241
245
  });
242
246
 
243
247
  // src/core/session/types.ts
@@ -351,6 +355,33 @@ async function sessionReset(state, request3) {
351
355
  return ok(void 0);
352
356
  }
353
357
 
358
+ // src/core/session/refresh.ts
359
+ var REENTRANCE_TICKET_PATH = "/sap/bc/adt/security/reentranceticket";
360
+ async function refreshSession(state, request3) {
361
+ if (!state.session) {
362
+ return err(new Error("Not logged in"));
363
+ }
364
+ debug("Fetching reentrance ticket to refresh session...");
365
+ const [response, reqErr] = await request3({
366
+ method: "GET",
367
+ path: REENTRANCE_TICKET_PATH,
368
+ headers: { "Accept": "text/plain" }
369
+ });
370
+ if (reqErr) {
371
+ return err(new Error(`Session refresh failed: ${reqErr.message}`));
372
+ }
373
+ if (!response.ok) {
374
+ const text = await response.text();
375
+ return err(new Error(`Session refresh failed (${response.status}): ${text}`));
376
+ }
377
+ const ticket = await response.text();
378
+ debug(`Received reentrance ticket: ${ticket.substring(0, 20)}...`);
379
+ const timeout = getSessionTimeout(state.config.auth.type);
380
+ state.session.expiresAt = Date.now() + timeout;
381
+ debug(`Session refreshed, new expiration: ${new Date(state.session.expiresAt).toISOString()}`);
382
+ return ok({ ticket, expiresAt: state.session.expiresAt });
383
+ }
384
+
354
385
  // src/core/adt/types.ts
355
386
  var OBJECT_CONFIG_MAP = {
356
387
  "asddls": {
@@ -745,14 +776,6 @@ async function getPackages(client, filter = "*") {
745
776
  return ok(packages);
746
777
  }
747
778
 
748
- // src/core/adt/discovery/tree/types.ts
749
- var API_FOLDERS = [
750
- "NOT_RELEASED",
751
- "USE_IN_CLOUD_DEVELOPMENT",
752
- "USE_IN_CLOUD_DVLPMNT_ACTIVE",
753
- "USE_IN_KEY_USER_APPS"
754
- ];
755
-
756
779
  // src/core/adt/discovery/tree/parsers.ts
757
780
  function buildQueryFromPath(packageName, path) {
758
781
  const query = {
@@ -795,13 +818,14 @@ function constructTreeBody(query, searchPattern) {
795
818
  const specifiedXml = Object.entries(specified).map(([facet, name]) => ` <vfs:preselection facet="${facet.toLowerCase()}">
796
819
  <vfs:value>${name}</vfs:value>
797
820
  </vfs:preselection>`).join("\n");
798
- const facetsXml = facets.map((facet) => ` <vfs:facet>${facet.toLowerCase()}</vfs:facet>`).join("\n");
821
+ const atObjectLevel = query.PACKAGE && query.GROUP && query.TYPE;
822
+ const facetorderXml = atObjectLevel || facets.length === 0 ? " <vfs:facetorder/>" : ` <vfs:facetorder>
823
+ ${facets.map((f) => ` <vfs:facet>${f.toLowerCase()}</vfs:facet>`).join("\n")}
824
+ </vfs:facetorder>`;
799
825
  return `<?xml version="1.0" encoding="UTF-8"?>
800
826
  <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="${searchPattern}">
801
827
  ${specifiedXml}
802
- <vfs:facetorder>
803
- ${facetsXml}
804
- </vfs:facetorder>
828
+ ${facetorderXml}
805
829
  </vfs:virtualFoldersRequest>`;
806
830
  }
807
831
  function parseTreeXml(xml) {
@@ -842,11 +866,14 @@ function parseTreeXml(xml) {
842
866
  if (!name || !type) continue;
843
867
  const config = getConfigByType(type);
844
868
  if (!config) continue;
845
- objects.push({
869
+ const text = obj.getAttribute("text");
870
+ const parsedObj = {
846
871
  name,
847
872
  objectType: config.label,
848
873
  extension: config.extension
849
- });
874
+ };
875
+ if (text) parsedObj.description = text;
876
+ objects.push(parsedObj);
850
877
  }
851
878
  return ok({ folders, objects });
852
879
  }
@@ -870,11 +897,15 @@ function transformToTreeResponse(parsed, queryPackage) {
870
897
  });
871
898
  }
872
899
  }
873
- const objects = parsed.objects.map((obj) => ({
874
- name: obj.name,
875
- objectType: obj.objectType,
876
- extension: obj.extension
877
- }));
900
+ const objects = parsed.objects.map((obj) => {
901
+ const node = {
902
+ name: obj.name,
903
+ objectType: obj.objectType,
904
+ extension: obj.extension
905
+ };
906
+ if (obj.description) node.description = obj.description;
907
+ return node;
908
+ });
878
909
  return { packages, folders, objects };
879
910
  }
880
911
 
@@ -938,58 +969,6 @@ async function fetchVirtualFolders(client, query) {
938
969
  const text = await response.text();
939
970
  return parseTreeXml(text);
940
971
  }
941
- async function fetchObjectsWithApiState(client, packageName, pathSegments, apiFolders) {
942
- const group = pathSegments[0];
943
- const type = pathSegments[1];
944
- if (!group || !type) return ok([]);
945
- const apiQueries = apiFolders.map((apiFolder) => ({
946
- apiFolder,
947
- query: {
948
- PACKAGE: { name: `..${packageName}`, hasChildrenOfSameFacet: false },
949
- GROUP: { name: group, hasChildrenOfSameFacet: false },
950
- TYPE: { name: type, hasChildrenOfSameFacet: false },
951
- API: { name: apiFolder, hasChildrenOfSameFacet: false }
952
- }
953
- }));
954
- const results = await Promise.all(
955
- apiQueries.map(async ({ apiFolder, query }) => {
956
- const [parsed, parseErr] = await fetchVirtualFolders(client, query);
957
- if (parseErr) return { apiFolder, objects: [], error: parseErr };
958
- return { apiFolder, objects: parsed.objects, error: null };
959
- })
960
- );
961
- const errors = results.filter((r) => r.error !== null);
962
- if (errors.length === results.length) {
963
- return err(errors[0].error);
964
- }
965
- const objectMap = /* @__PURE__ */ new Map();
966
- for (const { apiFolder, objects } of results) {
967
- for (const obj of objects) {
968
- let node = objectMap.get(obj.name);
969
- if (!node) {
970
- node = {
971
- name: obj.name,
972
- objectType: obj.objectType,
973
- extension: obj.extension,
974
- apiState: {
975
- useInCloudDevelopment: false,
976
- useInCloudDvlpmntActive: false,
977
- useInKeyUserApps: false
978
- }
979
- };
980
- objectMap.set(obj.name, node);
981
- }
982
- if (apiFolder === "USE_IN_CLOUD_DEVELOPMENT") {
983
- node.apiState.useInCloudDevelopment = true;
984
- } else if (apiFolder === "USE_IN_CLOUD_DVLPMNT_ACTIVE") {
985
- node.apiState.useInCloudDvlpmntActive = true;
986
- } else if (apiFolder === "USE_IN_KEY_USER_APPS") {
987
- node.apiState.useInKeyUserApps = true;
988
- }
989
- }
990
- }
991
- return ok(Array.from(objectMap.values()));
992
- }
993
972
 
994
973
  // src/core/adt/discovery/tree/index.ts
995
974
  async function getTree(client, query = {}) {
@@ -1019,70 +998,61 @@ async function getTree(client, query = {}) {
1019
998
  const internalQuery = buildQueryFromPath(query.package, query.path);
1020
999
  const [parsed, parseErr] = await fetchVirtualFolders(client, internalQuery);
1021
1000
  if (parseErr) return err(parseErr);
1022
- const pathSegments = query.path?.split("/").filter((s) => s.length > 0) ?? [];
1023
- const hasApiFolders = parsed.folders.length > 0 && parsed.folders.every((f) => f.facet === "API");
1024
- if (pathSegments.length >= 2 && hasApiFolders) {
1025
- const [objects, objErr] = await fetchObjectsWithApiState(
1026
- client,
1027
- query.package,
1028
- pathSegments,
1029
- API_FOLDERS
1030
- );
1031
- if (objErr) return err(objErr);
1032
- return ok({
1033
- packages,
1034
- folders: [],
1035
- objects
1036
- });
1037
- }
1038
1001
  const result = transformToTreeResponse(parsed, query.package);
1039
1002
  result.packages = packages;
1040
1003
  return ok(result);
1041
1004
  }
1042
1005
 
1043
1006
  // src/core/adt/discovery/tree/packageStats.ts
1044
- async function fetchPackageMetadata(client, packageName) {
1045
- const [response, requestErr] = await client.request({
1046
- method: "GET",
1047
- path: `/sap/bc/adt/packages/${packageName.toLowerCase()}`,
1048
- headers: {
1049
- "Accept": "application/vnd.sap.adt.packages.v1+xml"
1050
- }
1051
- });
1052
- if (requestErr) return err(requestErr);
1053
- if (!response.ok) {
1054
- const text = await response.text();
1055
- const errorMsg = extractError(text);
1056
- return err(new Error(`Package metadata fetch failed: ${errorMsg}`));
1057
- }
1058
- const xml = await response.text();
1059
- const [doc, parseErr] = safeParseXml(xml);
1060
- if (parseErr) return err(parseErr);
1061
- const packageElements = doc.getElementsByTagName("pak:package");
1062
- if (packageElements.length === 0) {
1063
- return err(new Error(`Package ${packageName} not found`));
1064
- }
1065
- const pkgEl = packageElements[0];
1066
- const name = pkgEl.getAttribute("adtcore:name") || pkgEl.getAttributeNS("http://www.sap.com/adt/core", "name") || packageName.toUpperCase();
1067
- const description = pkgEl.getAttribute("adtcore:description") || pkgEl.getAttributeNS("http://www.sap.com/adt/core", "description");
1068
- const result = { name };
1069
- if (description) result.description = description;
1070
- return ok(result);
1071
- }
1072
- function constructCountRequestBody(packageName) {
1007
+ function constructPackageStatsBody(packageNames) {
1008
+ const names = packageNames.length === 1 ? [...packageNames, "SRIS_TEST_DATA_VFS_EMPTY"] : packageNames;
1009
+ const values = names.map((name) => ` <vfs:value>${name}</vfs:value>`).join("\n");
1073
1010
  return `<?xml version="1.0" encoding="UTF-8"?>
1074
1011
  <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="*">
1075
1012
  <vfs:preselection facet="package">
1076
- <vfs:value>${packageName}</vfs:value>
1013
+ ${values}
1077
1014
  </vfs:preselection>
1078
- <vfs:facetorder/>
1015
+ <vfs:facetorder>
1016
+ <vfs:facet>package</vfs:facet>
1017
+ <vfs:facet>group</vfs:facet>
1018
+ <vfs:facet>type</vfs:facet>
1019
+ </vfs:facetorder>
1079
1020
  </vfs:virtualFoldersRequest>`;
1080
1021
  }
1081
- async function fetchContentCount(client, packageName) {
1082
- const body = constructCountRequestBody(packageName);
1022
+ function parsePackageStats(xml) {
1023
+ const [doc, parseErr] = safeParseXml(xml);
1024
+ if (parseErr) return [];
1025
+ const packages = [];
1026
+ const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
1027
+ for (let i = 0; i < virtualFolders.length; i++) {
1028
+ const vf = virtualFolders[i];
1029
+ if (!vf) continue;
1030
+ const facet = vf.getAttribute("facet")?.toUpperCase();
1031
+ if (facet !== "PACKAGE") continue;
1032
+ const name = vf.getAttribute("name");
1033
+ if (!name) continue;
1034
+ const countAttr = vf.getAttribute("counter");
1035
+ const count = countAttr ? parseInt(countAttr, 10) : 0;
1036
+ const description = vf.getAttribute("text");
1037
+ const pkg = {
1038
+ name,
1039
+ numContents: count
1040
+ };
1041
+ if (description) pkg.description = description;
1042
+ packages.push(pkg);
1043
+ }
1044
+ return packages;
1045
+ }
1046
+ async function getPackageStats(client, packageNames) {
1047
+ const isSingle = typeof packageNames === "string";
1048
+ const names = isSingle ? [packageNames] : packageNames;
1049
+ if (names.length === 0) {
1050
+ return ok([]);
1051
+ }
1052
+ const body = constructPackageStatsBody(names);
1083
1053
  const [response, requestErr] = await client.request({
1084
1054
  method: "POST",
1085
- path: "/sap/bc/adt/repository/informationsystem/virtualfolders",
1055
+ path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
1086
1056
  headers: {
1087
1057
  "Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
1088
1058
  "Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
@@ -1093,41 +1063,17 @@ async function fetchContentCount(client, packageName) {
1093
1063
  if (!response.ok) {
1094
1064
  const text = await response.text();
1095
1065
  const errorMsg = extractError(text);
1096
- return err(new Error(`Package count fetch failed: ${errorMsg}`));
1066
+ return err(new Error(`Package stats fetch failed: ${errorMsg}`));
1097
1067
  }
1098
1068
  const xml = await response.text();
1099
- const [doc, parseErr] = safeParseXml(xml);
1100
- if (parseErr) return err(parseErr);
1101
- const resultElements = doc.getElementsByTagName("vfs:virtualFoldersResult");
1102
- if (resultElements.length === 0) {
1103
- return err(new Error("Invalid virtualfolders response: missing result element"));
1104
- }
1105
- const resultEl = resultElements[0];
1106
- const objectCountAttr = resultEl.getAttribute("objectCount");
1107
- if (!objectCountAttr) {
1108
- return err(new Error("Invalid virtualfolders response: missing objectCount attribute"));
1109
- }
1110
- const count = parseInt(objectCountAttr, 10);
1111
- if (isNaN(count)) {
1112
- return err(new Error(`Invalid objectCount value: ${objectCountAttr}`));
1069
+ const packages = parsePackageStats(xml).filter((pkg) => pkg.name !== "SRIS_TEST_DATA_VFS_EMPTY");
1070
+ if (isSingle) {
1071
+ if (packages.length === 0) {
1072
+ return err(new Error(`Package ${packageNames} not found`));
1073
+ }
1074
+ return ok(packages[0]);
1113
1075
  }
1114
- return ok(count);
1115
- }
1116
- async function getPackageStats(client, packageName) {
1117
- const [metadataResult, countResult] = await Promise.all([
1118
- fetchPackageMetadata(client, packageName),
1119
- fetchContentCount(client, packageName)
1120
- ]);
1121
- const [metadata, metaErr] = metadataResult;
1122
- if (metaErr) return err(metaErr);
1123
- const [numContents, countErr] = countResult;
1124
- if (countErr) return err(countErr);
1125
- const result = {
1126
- name: metadata.name,
1127
- numContents
1128
- };
1129
- if (metadata.description) result.description = metadata.description;
1130
- return ok(result);
1076
+ return ok(packages);
1131
1077
  }
1132
1078
 
1133
1079
  // src/core/adt/transports/transports.ts
@@ -2338,6 +2284,7 @@ async function httpsRequest2(url, options) {
2338
2284
  req.end();
2339
2285
  });
2340
2286
  }
2287
+ var DEFAULT_REFRESH_INTERVAL = 30 * 60 * 1e3;
2341
2288
  function buildParams(baseParams, clientNum) {
2342
2289
  const params = new URLSearchParams();
2343
2290
  if (baseParams) {
@@ -2362,6 +2309,8 @@ var ADTClientImpl = class {
2362
2309
  requestor;
2363
2310
  // Store SSO certificates for mTLS authentication
2364
2311
  ssoCerts;
2312
+ // Auto-refresh timer handle
2313
+ refreshTimer = null;
2365
2314
  constructor(config) {
2366
2315
  const authOptions = {
2367
2316
  config: config.auth,
@@ -2399,6 +2348,22 @@ var ADTClientImpl = class {
2399
2348
  if (this.state.cookies.size === 0) return null;
2400
2349
  return Array.from(this.state.cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
2401
2350
  }
2351
+ startAutoRefresh(intervalMs) {
2352
+ this.stopAutoRefresh();
2353
+ this.refreshTimer = setInterval(async () => {
2354
+ if (!this.state.session) return;
2355
+ const [, refreshErr] = await this.refreshSession();
2356
+ if (refreshErr) {
2357
+ debug(`Auto-refresh failed: ${refreshErr.message}`);
2358
+ }
2359
+ }, intervalMs);
2360
+ }
2361
+ stopAutoRefresh() {
2362
+ if (this.refreshTimer) {
2363
+ clearInterval(this.refreshTimer);
2364
+ this.refreshTimer = null;
2365
+ }
2366
+ }
2402
2367
  // Core HTTP request function with CSRF token injection and automatic retry on 403 errors
2403
2368
  async request(options) {
2404
2369
  const { method, path, params, headers: customHeaders, body } = options;
@@ -2490,9 +2455,9 @@ var ADTClientImpl = class {
2490
2455
  async login() {
2491
2456
  const { authStrategy } = this.state;
2492
2457
  if (authStrategy.performLogin) {
2493
- const [, loginErr] = await authStrategy.performLogin(fetch);
2494
- if (loginErr) {
2495
- return err(loginErr);
2458
+ const [, loginErr2] = await authStrategy.performLogin(fetch);
2459
+ if (loginErr2) {
2460
+ return err(loginErr2);
2496
2461
  }
2497
2462
  }
2498
2463
  if (authStrategy.type === "saml" && authStrategy.getCookies) {
@@ -2512,11 +2477,28 @@ var ADTClientImpl = class {
2512
2477
  debug("Stored mTLS certificates for SSO authentication");
2513
2478
  }
2514
2479
  }
2515
- return login(this.state, this.request.bind(this));
2480
+ const [session, loginErr] = await login(this.state, this.request.bind(this));
2481
+ if (loginErr) {
2482
+ return err(loginErr);
2483
+ }
2484
+ const autoRefresh = this.state.config.autoRefresh ?? { enabled: true };
2485
+ if (autoRefresh.enabled) {
2486
+ const interval = autoRefresh.intervalMs ?? DEFAULT_REFRESH_INTERVAL;
2487
+ this.startAutoRefresh(interval);
2488
+ debug(`Auto-refresh started with ${interval}ms interval`);
2489
+ }
2490
+ return ok(session);
2516
2491
  }
2517
2492
  async logout() {
2493
+ this.stopAutoRefresh();
2518
2494
  return logout(this.state, this.request.bind(this));
2519
2495
  }
2496
+ async refreshSession() {
2497
+ if (!this.state.session) {
2498
+ return err(new Error("Not logged in"));
2499
+ }
2500
+ return refreshSession(this.state, this.request.bind(this));
2501
+ }
2520
2502
  // --- CRAUD Operations ---
2521
2503
  async read(objects) {
2522
2504
  if (!this.state.session) return err(new Error("Not logged in"));
@@ -2626,9 +2608,9 @@ var ADTClientImpl = class {
2626
2608
  if (!this.state.session) return err(new Error("Not logged in"));
2627
2609
  return getTree(this.requestor, query);
2628
2610
  }
2629
- async getPackageStats(packageName) {
2611
+ async getPackageStats(packageNames) {
2630
2612
  if (!this.state.session) return err(new Error("Not logged in"));
2631
- return getPackageStats(this.requestor, packageName);
2613
+ return getPackageStats(this.requestor, packageNames);
2632
2614
  }
2633
2615
  async getTransports(packageName) {
2634
2616
  if (!this.state.session) return err(new Error("Not logged in"));