catalyst-relay 0.5.13 → 0.6.0

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.mjs CHANGED
@@ -502,6 +502,12 @@ var SsoAuth = class {
502
502
  }
503
503
  };
504
504
 
505
+ // src/core/auth/saml/browser.ts
506
+ import { spawn } from "child_process";
507
+ import { mkdtempSync, rmSync } from "fs";
508
+ import { tmpdir } from "os";
509
+ import { join as join2 } from "path";
510
+
505
511
  // src/core/auth/saml/types.ts
506
512
  var DEFAULT_FORM_SELECTORS = {
507
513
  username: "#j_username",
@@ -516,9 +522,16 @@ var DEFAULT_PROVIDER_CONFIG = {
516
522
  // src/core/auth/saml/browser.ts
517
523
  var TIMEOUTS = {
518
524
  PAGE_LOAD: 6e4,
519
- FORM_SELECTOR: 1e4
525
+ FORM_SELECTOR: 15e3,
526
+ NETWORK_IDLE: 3e4,
527
+ /** Quiet period with no in-flight requests that counts as "idle". */
528
+ IDLE_QUIET: 600
520
529
  };
530
+ var isBun = typeof globalThis.Bun !== "undefined";
521
531
  async function performBrowserLogin(options) {
532
+ return isBun ? performBrowserLoginViaCdp(options) : performBrowserLoginViaPlaywright(options);
533
+ }
534
+ async function performBrowserLoginViaPlaywright(options) {
522
535
  const { baseUrl, credentials, headless = true } = options;
523
536
  const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
524
537
  let playwright;
@@ -576,6 +589,222 @@ async function performBrowserLogin(options) {
576
589
  await browser.close();
577
590
  }
578
591
  }
592
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
593
+ async function resolveChromiumPath() {
594
+ const override = process.env["CATALYST_CHROMIUM_PATH"];
595
+ if (override) return [override, null];
596
+ try {
597
+ const playwright = await import("playwright");
598
+ const path = playwright.chromium.executablePath();
599
+ if (!path) {
600
+ return [null, new Error("Could not resolve the Chromium executable path from Playwright.")];
601
+ }
602
+ return [path, null];
603
+ } catch {
604
+ return [
605
+ null,
606
+ new Error(
607
+ "Playwright is required for SAML authentication but is not installed. Install it with: npm install playwright"
608
+ )
609
+ ];
610
+ }
611
+ }
612
+ async function connectCdp(wsUrl) {
613
+ const WebSocketCtor = globalThis.WebSocket;
614
+ if (!WebSocketCtor) {
615
+ throw new Error("Native WebSocket is not available in this runtime.");
616
+ }
617
+ const ws = new WebSocketCtor(wsUrl);
618
+ let nextId = 0;
619
+ const pending = /* @__PURE__ */ new Map();
620
+ const eventHandlers = [];
621
+ ws.onmessage = (event) => {
622
+ const msg = JSON.parse(String(event.data));
623
+ if (typeof msg.id === "number" && pending.has(msg.id)) {
624
+ const { resolve, reject } = pending.get(msg.id);
625
+ pending.delete(msg.id);
626
+ if (msg.error) reject(new Error(msg.error.message));
627
+ else resolve(msg.result ?? {});
628
+ } else if (msg.method) {
629
+ for (const handler of eventHandlers) {
630
+ handler({
631
+ method: msg.method,
632
+ params: msg.params ?? {},
633
+ ...msg.sessionId !== void 0 ? { sessionId: msg.sessionId } : {}
634
+ });
635
+ }
636
+ }
637
+ };
638
+ await new Promise((resolve, reject) => {
639
+ ws.onopen = () => resolve();
640
+ ws.onerror = () => reject(new Error("Failed to connect to the browser CDP endpoint."));
641
+ });
642
+ return {
643
+ send(method, params = {}, sessionId) {
644
+ return new Promise((resolve, reject) => {
645
+ const id = ++nextId;
646
+ pending.set(id, { resolve, reject });
647
+ ws.send(JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} }));
648
+ });
649
+ },
650
+ on(handler) {
651
+ eventHandlers.push(handler);
652
+ },
653
+ close() {
654
+ ws.close();
655
+ }
656
+ };
657
+ }
658
+ async function performBrowserLoginViaCdp(options) {
659
+ const { baseUrl, credentials, headless = true } = options;
660
+ const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
661
+ const [exePath, pathError] = await resolveChromiumPath();
662
+ if (pathError) return err(pathError);
663
+ const userDataDir = mkdtempSync(join2(tmpdir(), "catalyst-saml-"));
664
+ const args = [
665
+ ...headless ? ["--headless=new"] : [],
666
+ "--remote-debugging-port=0",
667
+ "--no-first-run",
668
+ "--no-default-browser-check",
669
+ `--user-data-dir=${userDataDir}`,
670
+ ...config.ignoreHttpsErrors ? ["--ignore-certificate-errors"] : []
671
+ ];
672
+ const proc = spawn(exePath, args, { stdio: ["ignore", "pipe", "pipe"] });
673
+ let client = null;
674
+ const cleanup = () => {
675
+ try {
676
+ client?.close();
677
+ } catch {
678
+ }
679
+ try {
680
+ proc.kill();
681
+ } catch {
682
+ }
683
+ try {
684
+ rmSync(userDataDir, { recursive: true, force: true });
685
+ } catch {
686
+ }
687
+ };
688
+ try {
689
+ const browserWsUrl = await new Promise((resolve, reject) => {
690
+ let buffer = "";
691
+ const timer = setTimeout(
692
+ () => reject(new Error("Failed to launch browser: timed out waiting for the CDP endpoint.")),
693
+ TIMEOUTS.PAGE_LOAD
694
+ );
695
+ proc.stderr?.on("data", (chunk) => {
696
+ buffer += String(chunk);
697
+ const match = buffer.match(/DevTools listening on (ws:\/\/\S+)/);
698
+ if (match) {
699
+ clearTimeout(timer);
700
+ resolve(match[1]);
701
+ }
702
+ });
703
+ proc.on("error", (e) => {
704
+ clearTimeout(timer);
705
+ reject(new Error(`Failed to launch browser: ${e instanceof Error ? e.message : String(e)}`));
706
+ });
707
+ proc.on("exit", (code) => {
708
+ clearTimeout(timer);
709
+ reject(new Error(`Failed to launch browser: process exited early (code ${code}).`));
710
+ });
711
+ });
712
+ client = await connectCdp(browserWsUrl);
713
+ const { targetId } = await client.send("Target.createTarget", { url: "about:blank" });
714
+ const { sessionId } = await client.send("Target.attachToTarget", {
715
+ targetId,
716
+ flatten: true
717
+ });
718
+ let inflight = 0;
719
+ let lastNetworkChange = Date.now();
720
+ client.on((msg) => {
721
+ if (msg.sessionId !== sessionId) return;
722
+ if (msg.method === "Network.requestWillBeSent") {
723
+ inflight += 1;
724
+ lastNetworkChange = Date.now();
725
+ } else if (msg.method === "Network.loadingFinished" || msg.method === "Network.loadingFailed") {
726
+ inflight = Math.max(0, inflight - 1);
727
+ lastNetworkChange = Date.now();
728
+ }
729
+ });
730
+ await client.send("Page.enable", {}, sessionId);
731
+ await client.send("Network.enable", {}, sessionId);
732
+ await client.send("Runtime.enable", {}, sessionId);
733
+ const loginUrl = `${baseUrl}/sap/bc/adt/compatibility/graph`;
734
+ const navResult = await client.send("Page.navigate", { url: loginUrl }, sessionId);
735
+ if (navResult.errorText) {
736
+ return err(new Error("Failed to load login page. Please check if the server is online."));
737
+ }
738
+ const usernameSel = config.formSelectors.username;
739
+ const formFound = await waitForSelector(client, sessionId, usernameSel, TIMEOUTS.FORM_SELECTOR);
740
+ if (!formFound) {
741
+ return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
742
+ }
743
+ const fillExpr = `(() => {
744
+ const u = document.querySelector(${JSON.stringify(config.formSelectors.username)});
745
+ const p = document.querySelector(${JSON.stringify(config.formSelectors.password)});
746
+ const s = document.querySelector(${JSON.stringify(config.formSelectors.submit)});
747
+ if (!u || !p || !s) return false;
748
+ const set = (el, val) => {
749
+ el.value = val;
750
+ el.dispatchEvent(new Event('input', { bubbles: true }));
751
+ el.dispatchEvent(new Event('change', { bubbles: true }));
752
+ };
753
+ set(u, ${JSON.stringify(credentials.username)});
754
+ set(p, ${JSON.stringify(credentials.password)});
755
+ s.click();
756
+ return true;
757
+ })()`;
758
+ const fillResult = await client.send(
759
+ "Runtime.evaluate",
760
+ { expression: fillExpr, returnByValue: true },
761
+ sessionId
762
+ );
763
+ if (fillResult.result?.value !== true) {
764
+ return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
765
+ }
766
+ const idleStart = Date.now();
767
+ while (Date.now() - idleStart < TIMEOUTS.NETWORK_IDLE) {
768
+ if (inflight === 0 && Date.now() - lastNetworkChange > TIMEOUTS.IDLE_QUIET) break;
769
+ await sleep(100);
770
+ }
771
+ const { cookies } = await client.send("Network.getAllCookies", {}, sessionId);
772
+ return ok(cookies.map(toPlaywrightCookie));
773
+ } catch (e) {
774
+ return err(
775
+ new Error(`SAML browser login failed: ${e instanceof Error ? e.message : String(e)}`)
776
+ );
777
+ } finally {
778
+ cleanup();
779
+ }
780
+ }
781
+ async function waitForSelector(client, sessionId, selector, timeoutMs) {
782
+ const start = Date.now();
783
+ const expression = `!!document.querySelector(${JSON.stringify(selector)})`;
784
+ while (Date.now() - start < timeoutMs) {
785
+ const res = await client.send(
786
+ "Runtime.evaluate",
787
+ { expression, returnByValue: true },
788
+ sessionId
789
+ );
790
+ if (res.result?.value === true) return true;
791
+ await sleep(200);
792
+ }
793
+ return false;
794
+ }
795
+ function toPlaywrightCookie(c) {
796
+ const sameSite = c.sameSite === "Strict" || c.sameSite === "Lax" || c.sameSite === "None" ? c.sameSite : "Lax";
797
+ return {
798
+ name: c.name,
799
+ value: c.value,
800
+ domain: c.domain,
801
+ path: c.path,
802
+ expires: typeof c.expires === "number" ? c.expires : -1,
803
+ httpOnly: Boolean(c.httpOnly),
804
+ secure: Boolean(c.secure),
805
+ sameSite
806
+ };
807
+ }
579
808
 
580
809
  // src/core/auth/saml/cookies.ts
581
810
  function toAuthCookies(playwrightCookies) {
@@ -780,6 +1009,16 @@ function extractError(xml) {
780
1009
  const message = messageElement?.textContent;
781
1010
  return message || "No message found";
782
1011
  }
1012
+ function extractTagText(xml, tagName) {
1013
+ if (!xml) return null;
1014
+ const [doc, parseErr] = safeParseXml(xml);
1015
+ if (parseErr) return null;
1016
+ const elements = doc.getElementsByTagName(tagName);
1017
+ if (elements.length === 0) return null;
1018
+ const text = elements[0]?.textContent;
1019
+ if (!text || text.trim().length === 0) return null;
1020
+ return text.trim();
1021
+ }
783
1022
  function escapeXml(str) {
784
1023
  if (!str) {
785
1024
  return "";
@@ -1200,6 +1439,24 @@ var OBJECT_CONFIG_MAP = {
1200
1439
  type: "PROG/I",
1201
1440
  label: "ABAP Include" /* INCLUDE */,
1202
1441
  extension: "asinc"
1442
+ },
1443
+ "srvd": {
1444
+ endpoint: "ddic/srvd/sources",
1445
+ nameSpace: 'xmlns:srvd="http://www.sap.com/adt/ddic/srvdsources"',
1446
+ rootName: "srvd:srvdSource",
1447
+ type: "SRVD/SRV",
1448
+ label: "Service Definition" /* SERVICE_DEFINITION */,
1449
+ extension: "srvd",
1450
+ rootAttributes: { "srvd:srvdSourceType": "S" }
1451
+ },
1452
+ "asbdef": {
1453
+ endpoint: "bo/behaviordefinitions",
1454
+ nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
1455
+ rootName: "blue:blueSource",
1456
+ type: "BDEF/BDO",
1457
+ label: "Behavior Definition" /* BEHAVIOR_DEFINITION */,
1458
+ extension: "asbdef",
1459
+ requiresImplementationType: true
1203
1460
  }
1204
1461
  };
1205
1462
  function getConfigByExtension(extension) {
@@ -1512,11 +1769,44 @@ async function multiDeleteObjects(client, objects, transport) {
1512
1769
  return ok(results);
1513
1770
  }
1514
1771
 
1772
+ // src/types/requests.ts
1773
+ import { z as z2 } from "zod";
1774
+ var BehaviorImplementationType = /* @__PURE__ */ ((BehaviorImplementationType2) => {
1775
+ BehaviorImplementationType2["Managed"] = "Managed";
1776
+ return BehaviorImplementationType2;
1777
+ })(BehaviorImplementationType || {});
1778
+ var objectRefSchema = z2.object({
1779
+ name: z2.string().min(1),
1780
+ extension: z2.string().min(1)
1781
+ });
1782
+ var objectContentSchema = objectRefSchema.extend({
1783
+ content: z2.string(),
1784
+ description: z2.string().optional(),
1785
+ implementationType: z2.nativeEnum(BehaviorImplementationType).optional()
1786
+ });
1787
+ var treeQuerySchema = z2.object({
1788
+ package: z2.string().min(1).optional(),
1789
+ path: z2.string().optional(),
1790
+ owner: z2.string().min(1).optional()
1791
+ });
1792
+ var previewQuerySchema = z2.object({
1793
+ objectName: z2.string().min(1),
1794
+ objectType: z2.enum(["table", "view"]),
1795
+ sqlQuery: z2.string().min(1),
1796
+ limit: z2.number().positive().max(5e4).optional()
1797
+ });
1798
+
1515
1799
  // src/core/adt/craud/create.ts
1516
1800
  async function createObject(client, object, packageName, transport, username) {
1517
1801
  const [config, configErr] = requireConfig(object.extension);
1518
1802
  if (configErr) return err(configErr);
1519
1803
  const description = object.description ?? "";
1804
+ const extraAttrs = config.rootAttributes ? Object.entries(config.rootAttributes).map(([key, value]) => `
1805
+ ${key}="${escapeXml(value)}"`).join("") : "";
1806
+ const implementationType = object.implementationType ?? "Managed" /* Managed */;
1807
+ const adtTemplate = config.requiresImplementationType ? ` <adtcore:adtTemplate>
1808
+ <adtcore:adtProperty adtcore:key="implementation_type">${implementationType}</adtcore:adtProperty>
1809
+ </adtcore:adtTemplate>` : "";
1520
1810
  const body = `<?xml version="1.0" encoding="UTF-8"?>
1521
1811
  <${config.rootName} ${config.nameSpace}
1522
1812
  xmlns:adtcore="http://www.sap.com/adt/core"
@@ -1524,8 +1814,8 @@ async function createObject(client, object, packageName, transport, username) {
1524
1814
  adtcore:language="EN"
1525
1815
  adtcore:name="${object.name.toUpperCase()}"
1526
1816
  adtcore:type="${config.type}"
1527
- adtcore:responsible="${username.toUpperCase()}">
1528
-
1817
+ adtcore:responsible="${username.toUpperCase()}"${extraAttrs}>
1818
+ ${adtTemplate}
1529
1819
  <adtcore:packageRef adtcore:name="${packageName}"/>
1530
1820
 
1531
1821
  </${config.rootName}>`;
@@ -1586,14 +1876,26 @@ async function activateObjects(client, objects) {
1586
1876
  if (objects.length === 0) {
1587
1877
  return ok([]);
1588
1878
  }
1879
+ const references = [];
1589
1880
  for (const obj of objects) {
1590
1881
  const config = getConfigByExtension(obj.extension);
1591
1882
  if (!config) return err(new Error(`Unsupported extension: ${obj.extension}`));
1883
+ references.push({
1884
+ uri: `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`,
1885
+ type: config.type,
1886
+ name: obj.name,
1887
+ extension: obj.extension
1888
+ });
1592
1889
  }
1593
- const objectRefs = objects.map((obj) => {
1594
- const config = getConfigByExtension(obj.extension);
1595
- return `<adtcore:objectReference adtcore:uri="/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}" adtcore:type="${config.type}" adtcore:name="${obj.name}"/>`;
1596
- }).join("\n ");
1890
+ return activateByReferences(client, references);
1891
+ }
1892
+ async function activateByReferences(client, references) {
1893
+ if (references.length === 0) {
1894
+ return ok([]);
1895
+ }
1896
+ const objectRefs = references.map(
1897
+ (ref) => `<adtcore:objectReference adtcore:uri="${ref.uri}" adtcore:type="${ref.type}" adtcore:name="${ref.name}"/>`
1898
+ ).join("\n ");
1597
1899
  const body = `<?xml version="1.0" encoding="UTF-8"?>
1598
1900
  <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
1599
1901
  ${objectRefs}
@@ -1657,7 +1959,7 @@ async function activateObjects(client, objects) {
1657
1959
  if (!resultsRes.ok) {
1658
1960
  return err(new Error(`Failed to fetch activation results: ${extractError(resultsText)}`));
1659
1961
  }
1660
- const [results, parseErr] = extractActivationErrors(objects, resultsText);
1962
+ const [results, parseErr] = extractActivationErrors(references, resultsText);
1661
1963
  if (parseErr) return err(parseErr);
1662
1964
  return ok(results);
1663
1965
  }
@@ -2346,16 +2648,64 @@ function parseDataPreview(xml, maxRows, isTable) {
2346
2648
  const namespace = "http://www.sap.com/adt/dataPreview";
2347
2649
  const metadataElements = doc.getElementsByTagNameNS(namespace, "metadata");
2348
2650
  const columns = [];
2651
+ const SAP_TYPE_MAP = {
2652
+ "8": "integer",
2653
+ // Int8
2654
+ "I": "integer",
2655
+ // Integer
2656
+ "P": "decimal",
2657
+ // Packed decimal
2658
+ "F": "float",
2659
+ // Floating point
2660
+ "D": "date",
2661
+ // Date (YYYYMMDD)
2662
+ "T": "time",
2663
+ // Time (HHMMSS)
2664
+ "S": "timestamp",
2665
+ // Timestamp
2666
+ "C": "string",
2667
+ // Character
2668
+ "N": "string",
2669
+ // Numeric character string
2670
+ "V": "string",
2671
+ // Variable-length character
2672
+ "X": "binary"
2673
+ // Raw binary/hex
2674
+ };
2349
2675
  for (let i = 0; i < metadataElements.length; i++) {
2350
2676
  const meta = metadataElements[i];
2351
2677
  if (!meta) continue;
2352
2678
  const nameAttr = isTable ? "name" : "camelCaseName";
2353
2679
  const name = meta.getAttributeNS(namespace, nameAttr) || meta.getAttribute("name");
2354
- const dataType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
2680
+ const colType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
2681
+ const rawType = meta.getAttributeNS(namespace, "type") || meta.getAttribute("type");
2682
+ const isKeyFigure = meta.getAttributeNS(namespace, "isKeyFigure") === "true";
2683
+ let dataType;
2684
+ if (colType && colType.trim() !== "") {
2685
+ dataType = colType;
2686
+ } else if (isKeyFigure) {
2687
+ dataType = "decimal";
2688
+ } else if (rawType && SAP_TYPE_MAP[rawType]) {
2689
+ dataType = SAP_TYPE_MAP[rawType];
2690
+ } else {
2691
+ dataType = "string";
2692
+ }
2693
+ const allAttrs = {};
2694
+ for (let j = 0; j < meta.attributes.length; j++) {
2695
+ const attr = meta.attributes[j];
2696
+ if (!attr) {
2697
+ continue;
2698
+ }
2699
+ allAttrs[attr.name] = attr.value;
2700
+ }
2355
2701
  if (!name || !dataType) continue;
2356
2702
  columns.push({ name, dataType });
2357
2703
  }
2358
2704
  const dataSetElements = doc.getElementsByTagNameNS(namespace, "dataSet");
2705
+ for (let i = 0; i < dataSetElements.length; i++) {
2706
+ const dataSet = dataSetElements[i];
2707
+ if (!dataSet) continue;
2708
+ }
2359
2709
  if (columns.length === 0 && dataSetElements.length > 0) {
2360
2710
  for (let i = 0; i < dataSetElements.length; i++) {
2361
2711
  const dataSet = dataSetElements[i];
@@ -2438,7 +2788,7 @@ async function previewData(client, query) {
2438
2788
 
2439
2789
  // src/core/adt/data_extraction/freestyle.ts
2440
2790
  var DEFAULT_ROW_LIMIT = 100;
2441
- async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT) {
2791
+ async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT, timeout) {
2442
2792
  debug(`Freestyle query: ${sqlQuery}`);
2443
2793
  const [response, requestErr] = await client.request({
2444
2794
  method: "POST",
@@ -2448,16 +2798,21 @@ async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT) {
2448
2798
  },
2449
2799
  headers: {
2450
2800
  "Accept": "application/xml, application/vnd.sap.adt.datapreview.table.v1+xml",
2451
- "Content-Type": "text/plain"
2801
+ "Content-Type": "text/plain",
2802
+ // Override stateful base header: each preview request is independent; stateless
2803
+ // lets SAP route to any work process and recycle it after the request, preventing
2804
+ // GENERATE_SUBPOOL_DIR_FULL (36-pool limit per work process).
2805
+ "X-sap-adt-sessiontype": "stateless"
2452
2806
  },
2453
- body: sqlQuery
2807
+ body: sqlQuery,
2808
+ ...timeout !== void 0 && { timeout }
2454
2809
  });
2455
2810
  if (requestErr) return err(requestErr);
2456
2811
  if (!response.ok) {
2457
2812
  const text2 = await response.text();
2458
2813
  debug(`Freestyle query error response: ${text2.substring(0, 500)}`);
2459
2814
  const errorMsg = extractError(text2);
2460
- return err(new Error(`Freestyle query failed: ${errorMsg}`));
2815
+ return err(new Error(`Freestyle query failed: ${errorMsg}`, { cause: text2 }));
2461
2816
  }
2462
2817
  const text = await response.text();
2463
2818
  const [dataFrame, parseErr] = parseDataPreview(text, limit, true);
@@ -2718,7 +3073,16 @@ function parseTransportTasks(doc) {
2718
3073
  position: el.getAttribute("tm:position") || ""
2719
3074
  });
2720
3075
  }
2721
- tasks.push({ taskId, objects });
3076
+ const owner = taskEl.getAttribute("tm:owner");
3077
+ const description = taskEl.getAttribute("tm:desc");
3078
+ const status = taskEl.getAttribute("tm:status");
3079
+ tasks.push({
3080
+ taskId,
3081
+ ...owner ? { owner } : {},
3082
+ ...description ? { description } : {},
3083
+ ...status ? { status } : {},
3084
+ objects
3085
+ });
2722
3086
  }
2723
3087
  return tasks;
2724
3088
  }
@@ -2956,6 +3320,203 @@ async function gitDiff(client, object) {
2956
3320
  });
2957
3321
  }
2958
3322
 
3323
+ // src/core/adt/businessservices/validate.ts
3324
+ var SEVERITY_OK = "OK";
3325
+ async function validateServiceBinding(client, options) {
3326
+ const bindingType = options.bindingType ?? "ODATA";
3327
+ const bindingVersion = options.bindingVersion ?? "V4";
3328
+ const [response, requestErr] = await client.request({
3329
+ method: "POST",
3330
+ path: "/sap/bc/adt/businessservices/bindings/validation",
3331
+ params: {
3332
+ objname: options.bindingName.toUpperCase(),
3333
+ description: options.description ?? "",
3334
+ serviceBindingVersion: `${bindingType}\\${bindingVersion}`,
3335
+ serviceDefinition: options.serviceDefinition.toUpperCase(),
3336
+ package: options.packageName.toUpperCase()
3337
+ }
3338
+ });
3339
+ const [text, checkErr] = await checkResponse(
3340
+ response,
3341
+ requestErr,
3342
+ `Failed to validate service binding ${options.bindingName}`
3343
+ );
3344
+ if (checkErr) return err(checkErr);
3345
+ const severity = extractTagText(text, "SEVERITY");
3346
+ if (severity && severity !== SEVERITY_OK) {
3347
+ const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
3348
+ return err(new Error(`Service binding validation failed (${severity}): ${shortText}`));
3349
+ }
3350
+ return ok(void 0);
3351
+ }
3352
+
3353
+ // src/core/adt/businessservices/create.ts
3354
+ var SERVICE_BINDING_TYPE_CODE = "SRVB/SVB";
3355
+ var BINDING_CATEGORY = "1";
3356
+ var CONTENT_VERSION = "0001";
3357
+ async function createServiceBindingObject(client, options, username) {
3358
+ const bindingType = options.bindingType ?? "ODATA";
3359
+ const bindingVersion = options.bindingVersion ?? "V4";
3360
+ const description = options.description ?? "";
3361
+ const bindingName = options.bindingName.toUpperCase();
3362
+ const serviceDefinition = options.serviceDefinition.toUpperCase();
3363
+ const packageName = options.packageName.toUpperCase();
3364
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
3365
+ <srvb:serviceBinding xmlns:srvb="http://www.sap.com/adt/ddic/ServiceBindings" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${escapeXml(description)}" adtcore:language="EN" adtcore:name="${bindingName}" adtcore:type="${SERVICE_BINDING_TYPE_CODE}" adtcore:masterLanguage="EN" adtcore:responsible="${username.toUpperCase()}">
3366
+ <adtcore:packageRef adtcore:name="${packageName}"/>
3367
+ <srvb:services srvb:name="${serviceDefinition}">
3368
+ <srvb:content srvb:version="${CONTENT_VERSION}">
3369
+ <srvb:serviceDefinition adtcore:name="${serviceDefinition}"/>
3370
+ </srvb:content>
3371
+ </srvb:services>
3372
+ <srvb:binding srvb:category="${BINDING_CATEGORY}" srvb:type="${bindingType}" srvb:version="${bindingVersion}">
3373
+ <srvb:implementation adtcore:name=""/>
3374
+ </srvb:binding>
3375
+ </srvb:serviceBinding>`;
3376
+ const params = {};
3377
+ if (options.transport) {
3378
+ params["corrNr"] = options.transport;
3379
+ }
3380
+ const [response, requestErr] = await client.request({
3381
+ method: "POST",
3382
+ path: "/sap/bc/adt/businessservices/bindings",
3383
+ params,
3384
+ headers: { "Content-Type": "application/*" },
3385
+ body
3386
+ });
3387
+ const [, checkErr] = await checkResponse(
3388
+ response,
3389
+ requestErr,
3390
+ `Failed to create service binding ${options.bindingName}`
3391
+ );
3392
+ if (checkErr) return err(checkErr);
3393
+ return ok(void 0);
3394
+ }
3395
+
3396
+ // src/core/adt/businessservices/activate.ts
3397
+ var SERVICE_BINDING_TYPE_CODE2 = "SRVB/SVB";
3398
+ async function activateServiceBinding(client, bindingName) {
3399
+ return activateByReferences(client, [{
3400
+ uri: `/sap/bc/adt/businessservices/bindings/${bindingName.toLowerCase()}`,
3401
+ type: SERVICE_BINDING_TYPE_CODE2,
3402
+ name: bindingName.toUpperCase(),
3403
+ extension: ""
3404
+ }]);
3405
+ }
3406
+
3407
+ // src/core/adt/businessservices/helpers.ts
3408
+ var BINDINGS_BASE_PATH = "/sap/bc/adt/businessservices/bindings";
3409
+ var LOCK_ACCEPT_HEADER = "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result";
3410
+ var PUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/publishjobs";
3411
+ var UNPUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/unpublishjobs";
3412
+ var SEVERITY_OK2 = "OK";
3413
+ function bindingPath(bindingName) {
3414
+ return `${BINDINGS_BASE_PATH}/${bindingName.toLowerCase()}`;
3415
+ }
3416
+ async function lockServiceBinding(client, bindingName) {
3417
+ const [response, requestErr] = await client.request({
3418
+ method: "POST",
3419
+ path: bindingPath(bindingName),
3420
+ params: {
3421
+ "_action": "LOCK",
3422
+ "accessMode": "MODIFY"
3423
+ },
3424
+ headers: { "Accept": LOCK_ACCEPT_HEADER }
3425
+ });
3426
+ const [text, checkErr] = await checkResponse(
3427
+ response,
3428
+ requestErr,
3429
+ `Failed to lock service binding ${bindingName}`
3430
+ );
3431
+ if (checkErr) return err(checkErr);
3432
+ const [lockHandle, extractErr] = extractLockHandle(text);
3433
+ if (extractErr) return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
3434
+ return ok(lockHandle);
3435
+ }
3436
+ async function unlockServiceBinding(client, bindingName, lockHandle) {
3437
+ const [response, requestErr] = await client.request({
3438
+ method: "POST",
3439
+ path: bindingPath(bindingName),
3440
+ params: {
3441
+ "_action": "UNLOCK",
3442
+ "lockHandle": lockHandle
3443
+ }
3444
+ });
3445
+ const [, checkErr] = await checkResponse(
3446
+ response,
3447
+ requestErr,
3448
+ `Failed to unlock service binding ${bindingName}`
3449
+ );
3450
+ if (checkErr) return err(checkErr);
3451
+ return ok(void 0);
3452
+ }
3453
+ async function submitServiceBindingJob(client, bindingName, action) {
3454
+ const path = action === "publish" ? PUBLISH_JOBS_PATH : UNPUBLISH_JOBS_PATH;
3455
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
3456
+ <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
3457
+ <adtcore:objectReference adtcore:type="SCGR" adtcore:name="${bindingName.toUpperCase()}"/>
3458
+ </adtcore:objectReferences>`;
3459
+ const [response, requestErr] = await client.request({
3460
+ method: "POST",
3461
+ path,
3462
+ headers: { "Content-Type": "application/xml" },
3463
+ body
3464
+ });
3465
+ const [text, checkErr] = await checkResponse(
3466
+ response,
3467
+ requestErr,
3468
+ `Failed to ${action} service binding ${bindingName}`
3469
+ );
3470
+ if (checkErr) return err(checkErr);
3471
+ const severity = extractTagText(text, "SEVERITY");
3472
+ if (severity && severity !== SEVERITY_OK2) {
3473
+ const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
3474
+ return err(new Error(`Service binding ${action} failed (${severity}): ${shortText}`));
3475
+ }
3476
+ return ok(extractTagText(text, "SHORT_TEXT") ?? "");
3477
+ }
3478
+
3479
+ // src/core/adt/businessservices/publish.ts
3480
+ async function publishServiceBinding(client, bindingName) {
3481
+ return runBindingJob(client, bindingName, "publish");
3482
+ }
3483
+ async function runBindingJob(client, bindingName, action) {
3484
+ const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
3485
+ if (lockErr) return err(lockErr);
3486
+ debug(`Service binding lock acquired for ${action}: handle=${lockHandle}`);
3487
+ const [message, jobErr] = await submitServiceBindingJob(client, bindingName, action);
3488
+ const [, unlockErr] = await unlockServiceBinding(client, bindingName, lockHandle);
3489
+ if (jobErr) return err(jobErr);
3490
+ if (unlockErr) return err(unlockErr);
3491
+ return ok(message);
3492
+ }
3493
+
3494
+ // src/core/adt/businessservices/delete.ts
3495
+ async function deleteServiceBinding(client, bindingName, transport) {
3496
+ const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
3497
+ if (lockErr) return err(lockErr);
3498
+ const [, unpublishErr] = await submitServiceBindingJob(client, bindingName, "unpublish");
3499
+ if (unpublishErr) debug(`Unpublish before delete skipped: ${unpublishErr.message}`);
3500
+ const params = { lockHandle };
3501
+ if (transport) params["corrNr"] = transport;
3502
+ const [response, requestErr] = await client.request({
3503
+ method: "DELETE",
3504
+ path: bindingPath(bindingName),
3505
+ params,
3506
+ headers: { "Accept": "text/plain" }
3507
+ });
3508
+ const [, checkErr] = await checkResponse(
3509
+ response,
3510
+ requestErr,
3511
+ `Failed to delete service binding ${bindingName}`
3512
+ );
3513
+ if (checkErr) {
3514
+ await unlockServiceBinding(client, bindingName, lockHandle);
3515
+ return err(checkErr);
3516
+ }
3517
+ return ok(void 0);
3518
+ }
3519
+
2959
3520
  // src/client/methods/craud/read.ts
2960
3521
  async function read(state, requestor, objects) {
2961
3522
  if (!state.session) return err(new Error("Not logged in"));
@@ -3112,6 +3673,12 @@ async function countRows2(state, requestor, objectName, objectType, parameters =
3112
3673
  return countRows(requestor, objectName, objectType, parameters);
3113
3674
  }
3114
3675
 
3676
+ // src/client/methods/preview/freestyleQuery.ts
3677
+ async function freestyleQuery2(state, requestor, sqlQuery, limit, timeout) {
3678
+ if (!state.session) return err(new Error("Not logged in"));
3679
+ return freestyleQuery(requestor, sqlQuery, limit, timeout);
3680
+ }
3681
+
3115
3682
  // src/client/methods/search/search.ts
3116
3683
  async function search(state, requestor, query, options) {
3117
3684
  if (!state.session) return err(new Error("Not logged in"));
@@ -3166,6 +3733,41 @@ function getObjectConfig() {
3166
3733
  return Object.values(OBJECT_CONFIG_MAP);
3167
3734
  }
3168
3735
 
3736
+ // src/client/methods/businessservices/createServiceBinding.ts
3737
+ async function createServiceBinding(state, requestor, options) {
3738
+ if (!state.session) return err(new Error("Not logged in"));
3739
+ const [, validateErr] = await validateServiceBinding(requestor, options);
3740
+ if (validateErr) return err(validateErr);
3741
+ const [, createErr] = await createServiceBindingObject(requestor, options, state.session.username);
3742
+ if (createErr) return err(createErr);
3743
+ const [activation, activateErr] = await activateServiceBinding(requestor, options.bindingName);
3744
+ if (activateErr) return err(activateErr);
3745
+ if (activation.some((result2) => result2.status === "error")) {
3746
+ return err(new Error(`Service binding ${options.bindingName} failed activation`));
3747
+ }
3748
+ const result = {
3749
+ name: options.bindingName.toUpperCase(),
3750
+ serviceDefinition: options.serviceDefinition.toUpperCase(),
3751
+ created: true,
3752
+ activation,
3753
+ published: false
3754
+ };
3755
+ if (options.publish === false) {
3756
+ return ok(result);
3757
+ }
3758
+ const [publishMessage, publishErr] = await publishServiceBinding(requestor, options.bindingName);
3759
+ if (publishErr) return err(publishErr);
3760
+ result.published = true;
3761
+ if (publishMessage) result.publishMessage = publishMessage;
3762
+ return ok(result);
3763
+ }
3764
+
3765
+ // src/client/methods/businessservices/deleteServiceBinding.ts
3766
+ async function deleteServiceBinding2(state, requestor, bindingName, transport) {
3767
+ if (!state.session) return err(new Error("Not logged in"));
3768
+ return deleteServiceBinding(requestor, bindingName, transport);
3769
+ }
3770
+
3169
3771
  // src/client/methods/internal/cookies.ts
3170
3772
  function storeCookies(cookies, response) {
3171
3773
  const setCookieHeader = response.headers.get("set-cookie");
@@ -3295,7 +3897,7 @@ function buildUrl2(baseUrl, path, params) {
3295
3897
  // src/client/methods/internal/request.ts
3296
3898
  async function executeRequest(deps, options, selfRequest) {
3297
3899
  const { state, ssoCerts, getCookieHeader, storeCookies: storeCookies2 } = deps;
3298
- const { method, path, params, headers: customHeaders, body } = options;
3900
+ const { method, path, params, headers: customHeaders, body, timeout: requestTimeout } = options;
3299
3901
  const { config } = state;
3300
3902
  debug(`Request ${method} ${path} - CSRF token in state: ${state.csrfToken?.substring(0, 20) || "null"}...`);
3301
3903
  const headers = buildRequestHeaders(
@@ -3322,7 +3924,7 @@ async function executeRequest(deps, options, selfRequest) {
3322
3924
  cert: ssoCerts?.cert,
3323
3925
  key: ssoCerts?.key,
3324
3926
  rejectUnauthorized: !config.insecure,
3325
- timeout: config.timeout ?? DEFAULT_TIMEOUT
3927
+ timeout: requestTimeout ?? config.timeout ?? DEFAULT_TIMEOUT
3326
3928
  });
3327
3929
  storeCookies2(response);
3328
3930
  if (response.status === 403) {
@@ -3345,7 +3947,7 @@ async function executeRequest(deps, options, selfRequest) {
3345
3947
  cert: ssoCerts?.cert,
3346
3948
  key: ssoCerts?.key,
3347
3949
  rejectUnauthorized: !config.insecure,
3348
- timeout: config.timeout ?? DEFAULT_TIMEOUT
3950
+ timeout: requestTimeout ?? config.timeout ?? DEFAULT_TIMEOUT
3349
3951
  });
3350
3952
  storeCookies2(retryResponse);
3351
3953
  return ok(retryResponse);
@@ -3516,6 +4118,9 @@ var ADTClientImpl = class {
3516
4118
  async countRows(objectName, objectType, parameters = []) {
3517
4119
  return countRows2(this.state, this.requestor, objectName, objectType, parameters);
3518
4120
  }
4121
+ async freestyleQuery(sqlQuery, limit, timeout) {
4122
+ return freestyleQuery2(this.state, this.requestor, sqlQuery, limit, timeout);
4123
+ }
3519
4124
  // --- Search ---
3520
4125
  async search(query, options) {
3521
4126
  return search(this.state, this.requestor, query, options);
@@ -3540,6 +4145,13 @@ var ADTClientImpl = class {
3540
4145
  async gitDiff(objects) {
3541
4146
  return gitDiff2(this.state, this.requestor, objects);
3542
4147
  }
4148
+ // --- Business Services ---
4149
+ async createServiceBinding(options) {
4150
+ return createServiceBinding(this.state, this.requestor, options);
4151
+ }
4152
+ async deleteServiceBinding(bindingName, transport) {
4153
+ return deleteServiceBinding2(this.state, this.requestor, bindingName, transport);
4154
+ }
3543
4155
  // --- Configuration ---
3544
4156
  getObjectConfig() {
3545
4157
  return getObjectConfig();
@@ -3556,6 +4168,7 @@ function createClient(config) {
3556
4168
  return ok(new ADTClientImpl(config));
3557
4169
  }
3558
4170
  export {
4171
+ BehaviorImplementationType,
3559
4172
  ExternalReferencesError,
3560
4173
  activateLogging,
3561
4174
  buildSQLQuery,