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.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BehaviorImplementationType: () => BehaviorImplementationType,
33
34
  ExternalReferencesError: () => ExternalReferencesError,
34
35
  activateLogging: () => activateLogging,
35
36
  buildSQLQuery: () => buildSQLQuery,
@@ -537,6 +538,12 @@ var SsoAuth = class {
537
538
  }
538
539
  };
539
540
 
541
+ // src/core/auth/saml/browser.ts
542
+ var import_node_child_process = require("child_process");
543
+ var import_node_fs = require("fs");
544
+ var import_node_os = require("os");
545
+ var import_node_path2 = require("path");
546
+
540
547
  // src/core/auth/saml/types.ts
541
548
  var DEFAULT_FORM_SELECTORS = {
542
549
  username: "#j_username",
@@ -551,9 +558,16 @@ var DEFAULT_PROVIDER_CONFIG = {
551
558
  // src/core/auth/saml/browser.ts
552
559
  var TIMEOUTS = {
553
560
  PAGE_LOAD: 6e4,
554
- FORM_SELECTOR: 1e4
561
+ FORM_SELECTOR: 15e3,
562
+ NETWORK_IDLE: 3e4,
563
+ /** Quiet period with no in-flight requests that counts as "idle". */
564
+ IDLE_QUIET: 600
555
565
  };
566
+ var isBun = typeof globalThis.Bun !== "undefined";
556
567
  async function performBrowserLogin(options) {
568
+ return isBun ? performBrowserLoginViaCdp(options) : performBrowserLoginViaPlaywright(options);
569
+ }
570
+ async function performBrowserLoginViaPlaywright(options) {
557
571
  const { baseUrl, credentials, headless = true } = options;
558
572
  const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
559
573
  let playwright;
@@ -611,6 +625,222 @@ async function performBrowserLogin(options) {
611
625
  await browser.close();
612
626
  }
613
627
  }
628
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
629
+ async function resolveChromiumPath() {
630
+ const override = process.env["CATALYST_CHROMIUM_PATH"];
631
+ if (override) return [override, null];
632
+ try {
633
+ const playwright = await import("playwright");
634
+ const path = playwright.chromium.executablePath();
635
+ if (!path) {
636
+ return [null, new Error("Could not resolve the Chromium executable path from Playwright.")];
637
+ }
638
+ return [path, null];
639
+ } catch {
640
+ return [
641
+ null,
642
+ new Error(
643
+ "Playwright is required for SAML authentication but is not installed. Install it with: npm install playwright"
644
+ )
645
+ ];
646
+ }
647
+ }
648
+ async function connectCdp(wsUrl) {
649
+ const WebSocketCtor = globalThis.WebSocket;
650
+ if (!WebSocketCtor) {
651
+ throw new Error("Native WebSocket is not available in this runtime.");
652
+ }
653
+ const ws = new WebSocketCtor(wsUrl);
654
+ let nextId = 0;
655
+ const pending = /* @__PURE__ */ new Map();
656
+ const eventHandlers = [];
657
+ ws.onmessage = (event) => {
658
+ const msg = JSON.parse(String(event.data));
659
+ if (typeof msg.id === "number" && pending.has(msg.id)) {
660
+ const { resolve, reject } = pending.get(msg.id);
661
+ pending.delete(msg.id);
662
+ if (msg.error) reject(new Error(msg.error.message));
663
+ else resolve(msg.result ?? {});
664
+ } else if (msg.method) {
665
+ for (const handler of eventHandlers) {
666
+ handler({
667
+ method: msg.method,
668
+ params: msg.params ?? {},
669
+ ...msg.sessionId !== void 0 ? { sessionId: msg.sessionId } : {}
670
+ });
671
+ }
672
+ }
673
+ };
674
+ await new Promise((resolve, reject) => {
675
+ ws.onopen = () => resolve();
676
+ ws.onerror = () => reject(new Error("Failed to connect to the browser CDP endpoint."));
677
+ });
678
+ return {
679
+ send(method, params = {}, sessionId) {
680
+ return new Promise((resolve, reject) => {
681
+ const id = ++nextId;
682
+ pending.set(id, { resolve, reject });
683
+ ws.send(JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} }));
684
+ });
685
+ },
686
+ on(handler) {
687
+ eventHandlers.push(handler);
688
+ },
689
+ close() {
690
+ ws.close();
691
+ }
692
+ };
693
+ }
694
+ async function performBrowserLoginViaCdp(options) {
695
+ const { baseUrl, credentials, headless = true } = options;
696
+ const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
697
+ const [exePath, pathError] = await resolveChromiumPath();
698
+ if (pathError) return err(pathError);
699
+ const userDataDir = (0, import_node_fs.mkdtempSync)((0, import_node_path2.join)((0, import_node_os.tmpdir)(), "catalyst-saml-"));
700
+ const args = [
701
+ ...headless ? ["--headless=new"] : [],
702
+ "--remote-debugging-port=0",
703
+ "--no-first-run",
704
+ "--no-default-browser-check",
705
+ `--user-data-dir=${userDataDir}`,
706
+ ...config.ignoreHttpsErrors ? ["--ignore-certificate-errors"] : []
707
+ ];
708
+ const proc = (0, import_node_child_process.spawn)(exePath, args, { stdio: ["ignore", "pipe", "pipe"] });
709
+ let client = null;
710
+ const cleanup = () => {
711
+ try {
712
+ client?.close();
713
+ } catch {
714
+ }
715
+ try {
716
+ proc.kill();
717
+ } catch {
718
+ }
719
+ try {
720
+ (0, import_node_fs.rmSync)(userDataDir, { recursive: true, force: true });
721
+ } catch {
722
+ }
723
+ };
724
+ try {
725
+ const browserWsUrl = await new Promise((resolve, reject) => {
726
+ let buffer = "";
727
+ const timer = setTimeout(
728
+ () => reject(new Error("Failed to launch browser: timed out waiting for the CDP endpoint.")),
729
+ TIMEOUTS.PAGE_LOAD
730
+ );
731
+ proc.stderr?.on("data", (chunk) => {
732
+ buffer += String(chunk);
733
+ const match = buffer.match(/DevTools listening on (ws:\/\/\S+)/);
734
+ if (match) {
735
+ clearTimeout(timer);
736
+ resolve(match[1]);
737
+ }
738
+ });
739
+ proc.on("error", (e) => {
740
+ clearTimeout(timer);
741
+ reject(new Error(`Failed to launch browser: ${e instanceof Error ? e.message : String(e)}`));
742
+ });
743
+ proc.on("exit", (code) => {
744
+ clearTimeout(timer);
745
+ reject(new Error(`Failed to launch browser: process exited early (code ${code}).`));
746
+ });
747
+ });
748
+ client = await connectCdp(browserWsUrl);
749
+ const { targetId } = await client.send("Target.createTarget", { url: "about:blank" });
750
+ const { sessionId } = await client.send("Target.attachToTarget", {
751
+ targetId,
752
+ flatten: true
753
+ });
754
+ let inflight = 0;
755
+ let lastNetworkChange = Date.now();
756
+ client.on((msg) => {
757
+ if (msg.sessionId !== sessionId) return;
758
+ if (msg.method === "Network.requestWillBeSent") {
759
+ inflight += 1;
760
+ lastNetworkChange = Date.now();
761
+ } else if (msg.method === "Network.loadingFinished" || msg.method === "Network.loadingFailed") {
762
+ inflight = Math.max(0, inflight - 1);
763
+ lastNetworkChange = Date.now();
764
+ }
765
+ });
766
+ await client.send("Page.enable", {}, sessionId);
767
+ await client.send("Network.enable", {}, sessionId);
768
+ await client.send("Runtime.enable", {}, sessionId);
769
+ const loginUrl = `${baseUrl}/sap/bc/adt/compatibility/graph`;
770
+ const navResult = await client.send("Page.navigate", { url: loginUrl }, sessionId);
771
+ if (navResult.errorText) {
772
+ return err(new Error("Failed to load login page. Please check if the server is online."));
773
+ }
774
+ const usernameSel = config.formSelectors.username;
775
+ const formFound = await waitForSelector(client, sessionId, usernameSel, TIMEOUTS.FORM_SELECTOR);
776
+ if (!formFound) {
777
+ return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
778
+ }
779
+ const fillExpr = `(() => {
780
+ const u = document.querySelector(${JSON.stringify(config.formSelectors.username)});
781
+ const p = document.querySelector(${JSON.stringify(config.formSelectors.password)});
782
+ const s = document.querySelector(${JSON.stringify(config.formSelectors.submit)});
783
+ if (!u || !p || !s) return false;
784
+ const set = (el, val) => {
785
+ el.value = val;
786
+ el.dispatchEvent(new Event('input', { bubbles: true }));
787
+ el.dispatchEvent(new Event('change', { bubbles: true }));
788
+ };
789
+ set(u, ${JSON.stringify(credentials.username)});
790
+ set(p, ${JSON.stringify(credentials.password)});
791
+ s.click();
792
+ return true;
793
+ })()`;
794
+ const fillResult = await client.send(
795
+ "Runtime.evaluate",
796
+ { expression: fillExpr, returnByValue: true },
797
+ sessionId
798
+ );
799
+ if (fillResult.result?.value !== true) {
800
+ return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
801
+ }
802
+ const idleStart = Date.now();
803
+ while (Date.now() - idleStart < TIMEOUTS.NETWORK_IDLE) {
804
+ if (inflight === 0 && Date.now() - lastNetworkChange > TIMEOUTS.IDLE_QUIET) break;
805
+ await sleep(100);
806
+ }
807
+ const { cookies } = await client.send("Network.getAllCookies", {}, sessionId);
808
+ return ok(cookies.map(toPlaywrightCookie));
809
+ } catch (e) {
810
+ return err(
811
+ new Error(`SAML browser login failed: ${e instanceof Error ? e.message : String(e)}`)
812
+ );
813
+ } finally {
814
+ cleanup();
815
+ }
816
+ }
817
+ async function waitForSelector(client, sessionId, selector, timeoutMs) {
818
+ const start = Date.now();
819
+ const expression = `!!document.querySelector(${JSON.stringify(selector)})`;
820
+ while (Date.now() - start < timeoutMs) {
821
+ const res = await client.send(
822
+ "Runtime.evaluate",
823
+ { expression, returnByValue: true },
824
+ sessionId
825
+ );
826
+ if (res.result?.value === true) return true;
827
+ await sleep(200);
828
+ }
829
+ return false;
830
+ }
831
+ function toPlaywrightCookie(c) {
832
+ const sameSite = c.sameSite === "Strict" || c.sameSite === "Lax" || c.sameSite === "None" ? c.sameSite : "Lax";
833
+ return {
834
+ name: c.name,
835
+ value: c.value,
836
+ domain: c.domain,
837
+ path: c.path,
838
+ expires: typeof c.expires === "number" ? c.expires : -1,
839
+ httpOnly: Boolean(c.httpOnly),
840
+ secure: Boolean(c.secure),
841
+ sameSite
842
+ };
843
+ }
614
844
 
615
845
  // src/core/auth/saml/cookies.ts
616
846
  function toAuthCookies(playwrightCookies) {
@@ -815,6 +1045,16 @@ function extractError(xml) {
815
1045
  const message = messageElement?.textContent;
816
1046
  return message || "No message found";
817
1047
  }
1048
+ function extractTagText(xml, tagName) {
1049
+ if (!xml) return null;
1050
+ const [doc, parseErr] = safeParseXml(xml);
1051
+ if (parseErr) return null;
1052
+ const elements = doc.getElementsByTagName(tagName);
1053
+ if (elements.length === 0) return null;
1054
+ const text = elements[0]?.textContent;
1055
+ if (!text || text.trim().length === 0) return null;
1056
+ return text.trim();
1057
+ }
818
1058
  function escapeXml(str) {
819
1059
  if (!str) {
820
1060
  return "";
@@ -1235,6 +1475,24 @@ var OBJECT_CONFIG_MAP = {
1235
1475
  type: "PROG/I",
1236
1476
  label: "ABAP Include" /* INCLUDE */,
1237
1477
  extension: "asinc"
1478
+ },
1479
+ "srvd": {
1480
+ endpoint: "ddic/srvd/sources",
1481
+ nameSpace: 'xmlns:srvd="http://www.sap.com/adt/ddic/srvdsources"',
1482
+ rootName: "srvd:srvdSource",
1483
+ type: "SRVD/SRV",
1484
+ label: "Service Definition" /* SERVICE_DEFINITION */,
1485
+ extension: "srvd",
1486
+ rootAttributes: { "srvd:srvdSourceType": "S" }
1487
+ },
1488
+ "asbdef": {
1489
+ endpoint: "bo/behaviordefinitions",
1490
+ nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
1491
+ rootName: "blue:blueSource",
1492
+ type: "BDEF/BDO",
1493
+ label: "Behavior Definition" /* BEHAVIOR_DEFINITION */,
1494
+ extension: "asbdef",
1495
+ requiresImplementationType: true
1238
1496
  }
1239
1497
  };
1240
1498
  function getConfigByExtension(extension) {
@@ -1547,11 +1805,44 @@ async function multiDeleteObjects(client, objects, transport) {
1547
1805
  return ok(results);
1548
1806
  }
1549
1807
 
1808
+ // src/types/requests.ts
1809
+ var import_zod2 = require("zod");
1810
+ var BehaviorImplementationType = /* @__PURE__ */ ((BehaviorImplementationType2) => {
1811
+ BehaviorImplementationType2["Managed"] = "Managed";
1812
+ return BehaviorImplementationType2;
1813
+ })(BehaviorImplementationType || {});
1814
+ var objectRefSchema = import_zod2.z.object({
1815
+ name: import_zod2.z.string().min(1),
1816
+ extension: import_zod2.z.string().min(1)
1817
+ });
1818
+ var objectContentSchema = objectRefSchema.extend({
1819
+ content: import_zod2.z.string(),
1820
+ description: import_zod2.z.string().optional(),
1821
+ implementationType: import_zod2.z.nativeEnum(BehaviorImplementationType).optional()
1822
+ });
1823
+ var treeQuerySchema = import_zod2.z.object({
1824
+ package: import_zod2.z.string().min(1).optional(),
1825
+ path: import_zod2.z.string().optional(),
1826
+ owner: import_zod2.z.string().min(1).optional()
1827
+ });
1828
+ var previewQuerySchema = import_zod2.z.object({
1829
+ objectName: import_zod2.z.string().min(1),
1830
+ objectType: import_zod2.z.enum(["table", "view"]),
1831
+ sqlQuery: import_zod2.z.string().min(1),
1832
+ limit: import_zod2.z.number().positive().max(5e4).optional()
1833
+ });
1834
+
1550
1835
  // src/core/adt/craud/create.ts
1551
1836
  async function createObject(client, object, packageName, transport, username) {
1552
1837
  const [config, configErr] = requireConfig(object.extension);
1553
1838
  if (configErr) return err(configErr);
1554
1839
  const description = object.description ?? "";
1840
+ const extraAttrs = config.rootAttributes ? Object.entries(config.rootAttributes).map(([key, value]) => `
1841
+ ${key}="${escapeXml(value)}"`).join("") : "";
1842
+ const implementationType = object.implementationType ?? "Managed" /* Managed */;
1843
+ const adtTemplate = config.requiresImplementationType ? ` <adtcore:adtTemplate>
1844
+ <adtcore:adtProperty adtcore:key="implementation_type">${implementationType}</adtcore:adtProperty>
1845
+ </adtcore:adtTemplate>` : "";
1555
1846
  const body = `<?xml version="1.0" encoding="UTF-8"?>
1556
1847
  <${config.rootName} ${config.nameSpace}
1557
1848
  xmlns:adtcore="http://www.sap.com/adt/core"
@@ -1559,8 +1850,8 @@ async function createObject(client, object, packageName, transport, username) {
1559
1850
  adtcore:language="EN"
1560
1851
  adtcore:name="${object.name.toUpperCase()}"
1561
1852
  adtcore:type="${config.type}"
1562
- adtcore:responsible="${username.toUpperCase()}">
1563
-
1853
+ adtcore:responsible="${username.toUpperCase()}"${extraAttrs}>
1854
+ ${adtTemplate}
1564
1855
  <adtcore:packageRef adtcore:name="${packageName}"/>
1565
1856
 
1566
1857
  </${config.rootName}>`;
@@ -1621,14 +1912,26 @@ async function activateObjects(client, objects) {
1621
1912
  if (objects.length === 0) {
1622
1913
  return ok([]);
1623
1914
  }
1915
+ const references = [];
1624
1916
  for (const obj of objects) {
1625
1917
  const config = getConfigByExtension(obj.extension);
1626
1918
  if (!config) return err(new Error(`Unsupported extension: ${obj.extension}`));
1919
+ references.push({
1920
+ uri: `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`,
1921
+ type: config.type,
1922
+ name: obj.name,
1923
+ extension: obj.extension
1924
+ });
1627
1925
  }
1628
- const objectRefs = objects.map((obj) => {
1629
- const config = getConfigByExtension(obj.extension);
1630
- return `<adtcore:objectReference adtcore:uri="/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}" adtcore:type="${config.type}" adtcore:name="${obj.name}"/>`;
1631
- }).join("\n ");
1926
+ return activateByReferences(client, references);
1927
+ }
1928
+ async function activateByReferences(client, references) {
1929
+ if (references.length === 0) {
1930
+ return ok([]);
1931
+ }
1932
+ const objectRefs = references.map(
1933
+ (ref) => `<adtcore:objectReference adtcore:uri="${ref.uri}" adtcore:type="${ref.type}" adtcore:name="${ref.name}"/>`
1934
+ ).join("\n ");
1632
1935
  const body = `<?xml version="1.0" encoding="UTF-8"?>
1633
1936
  <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
1634
1937
  ${objectRefs}
@@ -1692,7 +1995,7 @@ async function activateObjects(client, objects) {
1692
1995
  if (!resultsRes.ok) {
1693
1996
  return err(new Error(`Failed to fetch activation results: ${extractError(resultsText)}`));
1694
1997
  }
1695
- const [results, parseErr] = extractActivationErrors(objects, resultsText);
1998
+ const [results, parseErr] = extractActivationErrors(references, resultsText);
1696
1999
  if (parseErr) return err(parseErr);
1697
2000
  return ok(results);
1698
2001
  }
@@ -2381,16 +2684,64 @@ function parseDataPreview(xml, maxRows, isTable) {
2381
2684
  const namespace = "http://www.sap.com/adt/dataPreview";
2382
2685
  const metadataElements = doc.getElementsByTagNameNS(namespace, "metadata");
2383
2686
  const columns = [];
2687
+ const SAP_TYPE_MAP = {
2688
+ "8": "integer",
2689
+ // Int8
2690
+ "I": "integer",
2691
+ // Integer
2692
+ "P": "decimal",
2693
+ // Packed decimal
2694
+ "F": "float",
2695
+ // Floating point
2696
+ "D": "date",
2697
+ // Date (YYYYMMDD)
2698
+ "T": "time",
2699
+ // Time (HHMMSS)
2700
+ "S": "timestamp",
2701
+ // Timestamp
2702
+ "C": "string",
2703
+ // Character
2704
+ "N": "string",
2705
+ // Numeric character string
2706
+ "V": "string",
2707
+ // Variable-length character
2708
+ "X": "binary"
2709
+ // Raw binary/hex
2710
+ };
2384
2711
  for (let i = 0; i < metadataElements.length; i++) {
2385
2712
  const meta = metadataElements[i];
2386
2713
  if (!meta) continue;
2387
2714
  const nameAttr = isTable ? "name" : "camelCaseName";
2388
2715
  const name = meta.getAttributeNS(namespace, nameAttr) || meta.getAttribute("name");
2389
- const dataType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
2716
+ const colType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
2717
+ const rawType = meta.getAttributeNS(namespace, "type") || meta.getAttribute("type");
2718
+ const isKeyFigure = meta.getAttributeNS(namespace, "isKeyFigure") === "true";
2719
+ let dataType;
2720
+ if (colType && colType.trim() !== "") {
2721
+ dataType = colType;
2722
+ } else if (isKeyFigure) {
2723
+ dataType = "decimal";
2724
+ } else if (rawType && SAP_TYPE_MAP[rawType]) {
2725
+ dataType = SAP_TYPE_MAP[rawType];
2726
+ } else {
2727
+ dataType = "string";
2728
+ }
2729
+ const allAttrs = {};
2730
+ for (let j = 0; j < meta.attributes.length; j++) {
2731
+ const attr = meta.attributes[j];
2732
+ if (!attr) {
2733
+ continue;
2734
+ }
2735
+ allAttrs[attr.name] = attr.value;
2736
+ }
2390
2737
  if (!name || !dataType) continue;
2391
2738
  columns.push({ name, dataType });
2392
2739
  }
2393
2740
  const dataSetElements = doc.getElementsByTagNameNS(namespace, "dataSet");
2741
+ for (let i = 0; i < dataSetElements.length; i++) {
2742
+ const dataSet = dataSetElements[i];
2743
+ if (!dataSet) continue;
2744
+ }
2394
2745
  if (columns.length === 0 && dataSetElements.length > 0) {
2395
2746
  for (let i = 0; i < dataSetElements.length; i++) {
2396
2747
  const dataSet = dataSetElements[i];
@@ -2473,7 +2824,7 @@ async function previewData(client, query) {
2473
2824
 
2474
2825
  // src/core/adt/data_extraction/freestyle.ts
2475
2826
  var DEFAULT_ROW_LIMIT = 100;
2476
- async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT) {
2827
+ async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT, timeout) {
2477
2828
  debug(`Freestyle query: ${sqlQuery}`);
2478
2829
  const [response, requestErr] = await client.request({
2479
2830
  method: "POST",
@@ -2483,16 +2834,21 @@ async function freestyleQuery(client, sqlQuery, limit = DEFAULT_ROW_LIMIT) {
2483
2834
  },
2484
2835
  headers: {
2485
2836
  "Accept": "application/xml, application/vnd.sap.adt.datapreview.table.v1+xml",
2486
- "Content-Type": "text/plain"
2837
+ "Content-Type": "text/plain",
2838
+ // Override stateful base header: each preview request is independent; stateless
2839
+ // lets SAP route to any work process and recycle it after the request, preventing
2840
+ // GENERATE_SUBPOOL_DIR_FULL (36-pool limit per work process).
2841
+ "X-sap-adt-sessiontype": "stateless"
2487
2842
  },
2488
- body: sqlQuery
2843
+ body: sqlQuery,
2844
+ ...timeout !== void 0 && { timeout }
2489
2845
  });
2490
2846
  if (requestErr) return err(requestErr);
2491
2847
  if (!response.ok) {
2492
2848
  const text2 = await response.text();
2493
2849
  debug(`Freestyle query error response: ${text2.substring(0, 500)}`);
2494
2850
  const errorMsg = extractError(text2);
2495
- return err(new Error(`Freestyle query failed: ${errorMsg}`));
2851
+ return err(new Error(`Freestyle query failed: ${errorMsg}`, { cause: text2 }));
2496
2852
  }
2497
2853
  const text = await response.text();
2498
2854
  const [dataFrame, parseErr] = parseDataPreview(text, limit, true);
@@ -2753,7 +3109,16 @@ function parseTransportTasks(doc) {
2753
3109
  position: el.getAttribute("tm:position") || ""
2754
3110
  });
2755
3111
  }
2756
- tasks.push({ taskId, objects });
3112
+ const owner = taskEl.getAttribute("tm:owner");
3113
+ const description = taskEl.getAttribute("tm:desc");
3114
+ const status = taskEl.getAttribute("tm:status");
3115
+ tasks.push({
3116
+ taskId,
3117
+ ...owner ? { owner } : {},
3118
+ ...description ? { description } : {},
3119
+ ...status ? { status } : {},
3120
+ objects
3121
+ });
2757
3122
  }
2758
3123
  return tasks;
2759
3124
  }
@@ -2991,6 +3356,203 @@ async function gitDiff(client, object) {
2991
3356
  });
2992
3357
  }
2993
3358
 
3359
+ // src/core/adt/businessservices/validate.ts
3360
+ var SEVERITY_OK = "OK";
3361
+ async function validateServiceBinding(client, options) {
3362
+ const bindingType = options.bindingType ?? "ODATA";
3363
+ const bindingVersion = options.bindingVersion ?? "V4";
3364
+ const [response, requestErr] = await client.request({
3365
+ method: "POST",
3366
+ path: "/sap/bc/adt/businessservices/bindings/validation",
3367
+ params: {
3368
+ objname: options.bindingName.toUpperCase(),
3369
+ description: options.description ?? "",
3370
+ serviceBindingVersion: `${bindingType}\\${bindingVersion}`,
3371
+ serviceDefinition: options.serviceDefinition.toUpperCase(),
3372
+ package: options.packageName.toUpperCase()
3373
+ }
3374
+ });
3375
+ const [text, checkErr] = await checkResponse(
3376
+ response,
3377
+ requestErr,
3378
+ `Failed to validate service binding ${options.bindingName}`
3379
+ );
3380
+ if (checkErr) return err(checkErr);
3381
+ const severity = extractTagText(text, "SEVERITY");
3382
+ if (severity && severity !== SEVERITY_OK) {
3383
+ const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
3384
+ return err(new Error(`Service binding validation failed (${severity}): ${shortText}`));
3385
+ }
3386
+ return ok(void 0);
3387
+ }
3388
+
3389
+ // src/core/adt/businessservices/create.ts
3390
+ var SERVICE_BINDING_TYPE_CODE = "SRVB/SVB";
3391
+ var BINDING_CATEGORY = "1";
3392
+ var CONTENT_VERSION = "0001";
3393
+ async function createServiceBindingObject(client, options, username) {
3394
+ const bindingType = options.bindingType ?? "ODATA";
3395
+ const bindingVersion = options.bindingVersion ?? "V4";
3396
+ const description = options.description ?? "";
3397
+ const bindingName = options.bindingName.toUpperCase();
3398
+ const serviceDefinition = options.serviceDefinition.toUpperCase();
3399
+ const packageName = options.packageName.toUpperCase();
3400
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
3401
+ <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()}">
3402
+ <adtcore:packageRef adtcore:name="${packageName}"/>
3403
+ <srvb:services srvb:name="${serviceDefinition}">
3404
+ <srvb:content srvb:version="${CONTENT_VERSION}">
3405
+ <srvb:serviceDefinition adtcore:name="${serviceDefinition}"/>
3406
+ </srvb:content>
3407
+ </srvb:services>
3408
+ <srvb:binding srvb:category="${BINDING_CATEGORY}" srvb:type="${bindingType}" srvb:version="${bindingVersion}">
3409
+ <srvb:implementation adtcore:name=""/>
3410
+ </srvb:binding>
3411
+ </srvb:serviceBinding>`;
3412
+ const params = {};
3413
+ if (options.transport) {
3414
+ params["corrNr"] = options.transport;
3415
+ }
3416
+ const [response, requestErr] = await client.request({
3417
+ method: "POST",
3418
+ path: "/sap/bc/adt/businessservices/bindings",
3419
+ params,
3420
+ headers: { "Content-Type": "application/*" },
3421
+ body
3422
+ });
3423
+ const [, checkErr] = await checkResponse(
3424
+ response,
3425
+ requestErr,
3426
+ `Failed to create service binding ${options.bindingName}`
3427
+ );
3428
+ if (checkErr) return err(checkErr);
3429
+ return ok(void 0);
3430
+ }
3431
+
3432
+ // src/core/adt/businessservices/activate.ts
3433
+ var SERVICE_BINDING_TYPE_CODE2 = "SRVB/SVB";
3434
+ async function activateServiceBinding(client, bindingName) {
3435
+ return activateByReferences(client, [{
3436
+ uri: `/sap/bc/adt/businessservices/bindings/${bindingName.toLowerCase()}`,
3437
+ type: SERVICE_BINDING_TYPE_CODE2,
3438
+ name: bindingName.toUpperCase(),
3439
+ extension: ""
3440
+ }]);
3441
+ }
3442
+
3443
+ // src/core/adt/businessservices/helpers.ts
3444
+ var BINDINGS_BASE_PATH = "/sap/bc/adt/businessservices/bindings";
3445
+ var LOCK_ACCEPT_HEADER = "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result";
3446
+ var PUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/publishjobs";
3447
+ var UNPUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/unpublishjobs";
3448
+ var SEVERITY_OK2 = "OK";
3449
+ function bindingPath(bindingName) {
3450
+ return `${BINDINGS_BASE_PATH}/${bindingName.toLowerCase()}`;
3451
+ }
3452
+ async function lockServiceBinding(client, bindingName) {
3453
+ const [response, requestErr] = await client.request({
3454
+ method: "POST",
3455
+ path: bindingPath(bindingName),
3456
+ params: {
3457
+ "_action": "LOCK",
3458
+ "accessMode": "MODIFY"
3459
+ },
3460
+ headers: { "Accept": LOCK_ACCEPT_HEADER }
3461
+ });
3462
+ const [text, checkErr] = await checkResponse(
3463
+ response,
3464
+ requestErr,
3465
+ `Failed to lock service binding ${bindingName}`
3466
+ );
3467
+ if (checkErr) return err(checkErr);
3468
+ const [lockHandle, extractErr] = extractLockHandle(text);
3469
+ if (extractErr) return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
3470
+ return ok(lockHandle);
3471
+ }
3472
+ async function unlockServiceBinding(client, bindingName, lockHandle) {
3473
+ const [response, requestErr] = await client.request({
3474
+ method: "POST",
3475
+ path: bindingPath(bindingName),
3476
+ params: {
3477
+ "_action": "UNLOCK",
3478
+ "lockHandle": lockHandle
3479
+ }
3480
+ });
3481
+ const [, checkErr] = await checkResponse(
3482
+ response,
3483
+ requestErr,
3484
+ `Failed to unlock service binding ${bindingName}`
3485
+ );
3486
+ if (checkErr) return err(checkErr);
3487
+ return ok(void 0);
3488
+ }
3489
+ async function submitServiceBindingJob(client, bindingName, action) {
3490
+ const path = action === "publish" ? PUBLISH_JOBS_PATH : UNPUBLISH_JOBS_PATH;
3491
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
3492
+ <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
3493
+ <adtcore:objectReference adtcore:type="SCGR" adtcore:name="${bindingName.toUpperCase()}"/>
3494
+ </adtcore:objectReferences>`;
3495
+ const [response, requestErr] = await client.request({
3496
+ method: "POST",
3497
+ path,
3498
+ headers: { "Content-Type": "application/xml" },
3499
+ body
3500
+ });
3501
+ const [text, checkErr] = await checkResponse(
3502
+ response,
3503
+ requestErr,
3504
+ `Failed to ${action} service binding ${bindingName}`
3505
+ );
3506
+ if (checkErr) return err(checkErr);
3507
+ const severity = extractTagText(text, "SEVERITY");
3508
+ if (severity && severity !== SEVERITY_OK2) {
3509
+ const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
3510
+ return err(new Error(`Service binding ${action} failed (${severity}): ${shortText}`));
3511
+ }
3512
+ return ok(extractTagText(text, "SHORT_TEXT") ?? "");
3513
+ }
3514
+
3515
+ // src/core/adt/businessservices/publish.ts
3516
+ async function publishServiceBinding(client, bindingName) {
3517
+ return runBindingJob(client, bindingName, "publish");
3518
+ }
3519
+ async function runBindingJob(client, bindingName, action) {
3520
+ const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
3521
+ if (lockErr) return err(lockErr);
3522
+ debug(`Service binding lock acquired for ${action}: handle=${lockHandle}`);
3523
+ const [message, jobErr] = await submitServiceBindingJob(client, bindingName, action);
3524
+ const [, unlockErr] = await unlockServiceBinding(client, bindingName, lockHandle);
3525
+ if (jobErr) return err(jobErr);
3526
+ if (unlockErr) return err(unlockErr);
3527
+ return ok(message);
3528
+ }
3529
+
3530
+ // src/core/adt/businessservices/delete.ts
3531
+ async function deleteServiceBinding(client, bindingName, transport) {
3532
+ const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
3533
+ if (lockErr) return err(lockErr);
3534
+ const [, unpublishErr] = await submitServiceBindingJob(client, bindingName, "unpublish");
3535
+ if (unpublishErr) debug(`Unpublish before delete skipped: ${unpublishErr.message}`);
3536
+ const params = { lockHandle };
3537
+ if (transport) params["corrNr"] = transport;
3538
+ const [response, requestErr] = await client.request({
3539
+ method: "DELETE",
3540
+ path: bindingPath(bindingName),
3541
+ params,
3542
+ headers: { "Accept": "text/plain" }
3543
+ });
3544
+ const [, checkErr] = await checkResponse(
3545
+ response,
3546
+ requestErr,
3547
+ `Failed to delete service binding ${bindingName}`
3548
+ );
3549
+ if (checkErr) {
3550
+ await unlockServiceBinding(client, bindingName, lockHandle);
3551
+ return err(checkErr);
3552
+ }
3553
+ return ok(void 0);
3554
+ }
3555
+
2994
3556
  // src/client/methods/craud/read.ts
2995
3557
  async function read(state, requestor, objects) {
2996
3558
  if (!state.session) return err(new Error("Not logged in"));
@@ -3147,6 +3709,12 @@ async function countRows2(state, requestor, objectName, objectType, parameters =
3147
3709
  return countRows(requestor, objectName, objectType, parameters);
3148
3710
  }
3149
3711
 
3712
+ // src/client/methods/preview/freestyleQuery.ts
3713
+ async function freestyleQuery2(state, requestor, sqlQuery, limit, timeout) {
3714
+ if (!state.session) return err(new Error("Not logged in"));
3715
+ return freestyleQuery(requestor, sqlQuery, limit, timeout);
3716
+ }
3717
+
3150
3718
  // src/client/methods/search/search.ts
3151
3719
  async function search(state, requestor, query, options) {
3152
3720
  if (!state.session) return err(new Error("Not logged in"));
@@ -3201,6 +3769,41 @@ function getObjectConfig() {
3201
3769
  return Object.values(OBJECT_CONFIG_MAP);
3202
3770
  }
3203
3771
 
3772
+ // src/client/methods/businessservices/createServiceBinding.ts
3773
+ async function createServiceBinding(state, requestor, options) {
3774
+ if (!state.session) return err(new Error("Not logged in"));
3775
+ const [, validateErr] = await validateServiceBinding(requestor, options);
3776
+ if (validateErr) return err(validateErr);
3777
+ const [, createErr] = await createServiceBindingObject(requestor, options, state.session.username);
3778
+ if (createErr) return err(createErr);
3779
+ const [activation, activateErr] = await activateServiceBinding(requestor, options.bindingName);
3780
+ if (activateErr) return err(activateErr);
3781
+ if (activation.some((result2) => result2.status === "error")) {
3782
+ return err(new Error(`Service binding ${options.bindingName} failed activation`));
3783
+ }
3784
+ const result = {
3785
+ name: options.bindingName.toUpperCase(),
3786
+ serviceDefinition: options.serviceDefinition.toUpperCase(),
3787
+ created: true,
3788
+ activation,
3789
+ published: false
3790
+ };
3791
+ if (options.publish === false) {
3792
+ return ok(result);
3793
+ }
3794
+ const [publishMessage, publishErr] = await publishServiceBinding(requestor, options.bindingName);
3795
+ if (publishErr) return err(publishErr);
3796
+ result.published = true;
3797
+ if (publishMessage) result.publishMessage = publishMessage;
3798
+ return ok(result);
3799
+ }
3800
+
3801
+ // src/client/methods/businessservices/deleteServiceBinding.ts
3802
+ async function deleteServiceBinding2(state, requestor, bindingName, transport) {
3803
+ if (!state.session) return err(new Error("Not logged in"));
3804
+ return deleteServiceBinding(requestor, bindingName, transport);
3805
+ }
3806
+
3204
3807
  // src/client/methods/internal/cookies.ts
3205
3808
  function storeCookies(cookies, response) {
3206
3809
  const setCookieHeader = response.headers.get("set-cookie");
@@ -3330,7 +3933,7 @@ function buildUrl2(baseUrl, path, params) {
3330
3933
  // src/client/methods/internal/request.ts
3331
3934
  async function executeRequest(deps, options, selfRequest) {
3332
3935
  const { state, ssoCerts, getCookieHeader, storeCookies: storeCookies2 } = deps;
3333
- const { method, path, params, headers: customHeaders, body } = options;
3936
+ const { method, path, params, headers: customHeaders, body, timeout: requestTimeout } = options;
3334
3937
  const { config } = state;
3335
3938
  debug(`Request ${method} ${path} - CSRF token in state: ${state.csrfToken?.substring(0, 20) || "null"}...`);
3336
3939
  const headers = buildRequestHeaders(
@@ -3357,7 +3960,7 @@ async function executeRequest(deps, options, selfRequest) {
3357
3960
  cert: ssoCerts?.cert,
3358
3961
  key: ssoCerts?.key,
3359
3962
  rejectUnauthorized: !config.insecure,
3360
- timeout: config.timeout ?? DEFAULT_TIMEOUT
3963
+ timeout: requestTimeout ?? config.timeout ?? DEFAULT_TIMEOUT
3361
3964
  });
3362
3965
  storeCookies2(response);
3363
3966
  if (response.status === 403) {
@@ -3380,7 +3983,7 @@ async function executeRequest(deps, options, selfRequest) {
3380
3983
  cert: ssoCerts?.cert,
3381
3984
  key: ssoCerts?.key,
3382
3985
  rejectUnauthorized: !config.insecure,
3383
- timeout: config.timeout ?? DEFAULT_TIMEOUT
3986
+ timeout: requestTimeout ?? config.timeout ?? DEFAULT_TIMEOUT
3384
3987
  });
3385
3988
  storeCookies2(retryResponse);
3386
3989
  return ok(retryResponse);
@@ -3551,6 +4154,9 @@ var ADTClientImpl = class {
3551
4154
  async countRows(objectName, objectType, parameters = []) {
3552
4155
  return countRows2(this.state, this.requestor, objectName, objectType, parameters);
3553
4156
  }
4157
+ async freestyleQuery(sqlQuery, limit, timeout) {
4158
+ return freestyleQuery2(this.state, this.requestor, sqlQuery, limit, timeout);
4159
+ }
3554
4160
  // --- Search ---
3555
4161
  async search(query, options) {
3556
4162
  return search(this.state, this.requestor, query, options);
@@ -3575,6 +4181,13 @@ var ADTClientImpl = class {
3575
4181
  async gitDiff(objects) {
3576
4182
  return gitDiff2(this.state, this.requestor, objects);
3577
4183
  }
4184
+ // --- Business Services ---
4185
+ async createServiceBinding(options) {
4186
+ return createServiceBinding(this.state, this.requestor, options);
4187
+ }
4188
+ async deleteServiceBinding(bindingName, transport) {
4189
+ return deleteServiceBinding2(this.state, this.requestor, bindingName, transport);
4190
+ }
3578
4191
  // --- Configuration ---
3579
4192
  getObjectConfig() {
3580
4193
  return getObjectConfig();
@@ -3592,6 +4205,7 @@ function createClient(config) {
3592
4205
  }
3593
4206
  // Annotate the CommonJS export names for ESM import in node:
3594
4207
  0 && (module.exports = {
4208
+ BehaviorImplementationType,
3595
4209
  ExternalReferencesError,
3596
4210
  activateLogging,
3597
4211
  buildSQLQuery,