catalyst-relay 0.5.14 → 0.6.1

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}>`;
@@ -1612,6 +1903,33 @@ async function updateObject(client, object, lockHandle, transport) {
1612
1903
  return ok(void 0);
1613
1904
  }
1614
1905
 
1906
+ // src/core/adt/craud/classInclude.ts
1907
+ async function updateClassInclude(client, className, includeType, source, lockHandle, transport) {
1908
+ const [config, configErr] = requireConfig("aclass");
1909
+ if (configErr) return err(configErr);
1910
+ const params = {
1911
+ "lockHandle": lockHandle
1912
+ };
1913
+ if (transport) {
1914
+ params["corrNr"] = transport;
1915
+ }
1916
+ debug(`Update class include ${className}/${includeType}: length=${source.length}`);
1917
+ const [response, requestErr] = await client.request({
1918
+ method: "PUT",
1919
+ path: `/sap/bc/adt/${config.endpoint}/${className.toLowerCase()}/includes/${includeType}`,
1920
+ params,
1921
+ headers: { "Content-Type": "*/*" },
1922
+ body: source
1923
+ });
1924
+ const [, checkErr] = await checkResponse(
1925
+ response,
1926
+ requestErr,
1927
+ `Failed to write ${includeType} include of class ${className}`
1928
+ );
1929
+ if (checkErr) return err(checkErr);
1930
+ return ok(void 0);
1931
+ }
1932
+
1615
1933
  // src/core/adt/craud/activation.ts
1616
1934
  var MAX_POLL_ATTEMPTS = 30;
1617
1935
  var POLL_RETRY_DELAY_MS = 1e3;
@@ -1621,14 +1939,26 @@ async function activateObjects(client, objects) {
1621
1939
  if (objects.length === 0) {
1622
1940
  return ok([]);
1623
1941
  }
1942
+ const references = [];
1624
1943
  for (const obj of objects) {
1625
1944
  const config = getConfigByExtension(obj.extension);
1626
1945
  if (!config) return err(new Error(`Unsupported extension: ${obj.extension}`));
1946
+ references.push({
1947
+ uri: `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`,
1948
+ type: config.type,
1949
+ name: obj.name,
1950
+ extension: obj.extension
1951
+ });
1627
1952
  }
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 ");
1953
+ return activateByReferences(client, references);
1954
+ }
1955
+ async function activateByReferences(client, references) {
1956
+ if (references.length === 0) {
1957
+ return ok([]);
1958
+ }
1959
+ const objectRefs = references.map(
1960
+ (ref) => `<adtcore:objectReference adtcore:uri="${ref.uri}" adtcore:type="${ref.type}" adtcore:name="${ref.name}"/>`
1961
+ ).join("\n ");
1632
1962
  const body = `<?xml version="1.0" encoding="UTF-8"?>
1633
1963
  <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
1634
1964
  ${objectRefs}
@@ -1692,7 +2022,7 @@ async function activateObjects(client, objects) {
1692
2022
  if (!resultsRes.ok) {
1693
2023
  return err(new Error(`Failed to fetch activation results: ${extractError(resultsText)}`));
1694
2024
  }
1695
- const [results, parseErr] = extractActivationErrors(objects, resultsText);
2025
+ const [results, parseErr] = extractActivationErrors(references, resultsText);
1696
2026
  if (parseErr) return err(parseErr);
1697
2027
  return ok(results);
1698
2028
  }
@@ -3053,6 +3383,203 @@ async function gitDiff(client, object) {
3053
3383
  });
3054
3384
  }
3055
3385
 
3386
+ // src/core/adt/businessservices/validate.ts
3387
+ var SEVERITY_OK = "OK";
3388
+ async function validateServiceBinding(client, options) {
3389
+ const bindingType = options.bindingType ?? "ODATA";
3390
+ const bindingVersion = options.bindingVersion ?? "V4";
3391
+ const [response, requestErr] = await client.request({
3392
+ method: "POST",
3393
+ path: "/sap/bc/adt/businessservices/bindings/validation",
3394
+ params: {
3395
+ objname: options.bindingName.toUpperCase(),
3396
+ description: options.description ?? "",
3397
+ serviceBindingVersion: `${bindingType}\\${bindingVersion}`,
3398
+ serviceDefinition: options.serviceDefinition.toUpperCase(),
3399
+ package: options.packageName.toUpperCase()
3400
+ }
3401
+ });
3402
+ const [text, checkErr] = await checkResponse(
3403
+ response,
3404
+ requestErr,
3405
+ `Failed to validate service binding ${options.bindingName}`
3406
+ );
3407
+ if (checkErr) return err(checkErr);
3408
+ const severity = extractTagText(text, "SEVERITY");
3409
+ if (severity && severity !== SEVERITY_OK) {
3410
+ const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
3411
+ return err(new Error(`Service binding validation failed (${severity}): ${shortText}`));
3412
+ }
3413
+ return ok(void 0);
3414
+ }
3415
+
3416
+ // src/core/adt/businessservices/create.ts
3417
+ var SERVICE_BINDING_TYPE_CODE = "SRVB/SVB";
3418
+ var BINDING_CATEGORY = "1";
3419
+ var CONTENT_VERSION = "0001";
3420
+ async function createServiceBindingObject(client, options, username) {
3421
+ const bindingType = options.bindingType ?? "ODATA";
3422
+ const bindingVersion = options.bindingVersion ?? "V4";
3423
+ const description = options.description ?? "";
3424
+ const bindingName = options.bindingName.toUpperCase();
3425
+ const serviceDefinition = options.serviceDefinition.toUpperCase();
3426
+ const packageName = options.packageName.toUpperCase();
3427
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
3428
+ <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()}">
3429
+ <adtcore:packageRef adtcore:name="${packageName}"/>
3430
+ <srvb:services srvb:name="${serviceDefinition}">
3431
+ <srvb:content srvb:version="${CONTENT_VERSION}">
3432
+ <srvb:serviceDefinition adtcore:name="${serviceDefinition}"/>
3433
+ </srvb:content>
3434
+ </srvb:services>
3435
+ <srvb:binding srvb:category="${BINDING_CATEGORY}" srvb:type="${bindingType}" srvb:version="${bindingVersion}">
3436
+ <srvb:implementation adtcore:name=""/>
3437
+ </srvb:binding>
3438
+ </srvb:serviceBinding>`;
3439
+ const params = {};
3440
+ if (options.transport) {
3441
+ params["corrNr"] = options.transport;
3442
+ }
3443
+ const [response, requestErr] = await client.request({
3444
+ method: "POST",
3445
+ path: "/sap/bc/adt/businessservices/bindings",
3446
+ params,
3447
+ headers: { "Content-Type": "application/*" },
3448
+ body
3449
+ });
3450
+ const [, checkErr] = await checkResponse(
3451
+ response,
3452
+ requestErr,
3453
+ `Failed to create service binding ${options.bindingName}`
3454
+ );
3455
+ if (checkErr) return err(checkErr);
3456
+ return ok(void 0);
3457
+ }
3458
+
3459
+ // src/core/adt/businessservices/activate.ts
3460
+ var SERVICE_BINDING_TYPE_CODE2 = "SRVB/SVB";
3461
+ async function activateServiceBinding(client, bindingName) {
3462
+ return activateByReferences(client, [{
3463
+ uri: `/sap/bc/adt/businessservices/bindings/${bindingName.toLowerCase()}`,
3464
+ type: SERVICE_BINDING_TYPE_CODE2,
3465
+ name: bindingName.toUpperCase(),
3466
+ extension: ""
3467
+ }]);
3468
+ }
3469
+
3470
+ // src/core/adt/businessservices/helpers.ts
3471
+ var BINDINGS_BASE_PATH = "/sap/bc/adt/businessservices/bindings";
3472
+ var LOCK_ACCEPT_HEADER = "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result";
3473
+ var PUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/publishjobs";
3474
+ var UNPUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/unpublishjobs";
3475
+ var SEVERITY_OK2 = "OK";
3476
+ function bindingPath(bindingName) {
3477
+ return `${BINDINGS_BASE_PATH}/${bindingName.toLowerCase()}`;
3478
+ }
3479
+ async function lockServiceBinding(client, bindingName) {
3480
+ const [response, requestErr] = await client.request({
3481
+ method: "POST",
3482
+ path: bindingPath(bindingName),
3483
+ params: {
3484
+ "_action": "LOCK",
3485
+ "accessMode": "MODIFY"
3486
+ },
3487
+ headers: { "Accept": LOCK_ACCEPT_HEADER }
3488
+ });
3489
+ const [text, checkErr] = await checkResponse(
3490
+ response,
3491
+ requestErr,
3492
+ `Failed to lock service binding ${bindingName}`
3493
+ );
3494
+ if (checkErr) return err(checkErr);
3495
+ const [lockHandle, extractErr] = extractLockHandle(text);
3496
+ if (extractErr) return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
3497
+ return ok(lockHandle);
3498
+ }
3499
+ async function unlockServiceBinding(client, bindingName, lockHandle) {
3500
+ const [response, requestErr] = await client.request({
3501
+ method: "POST",
3502
+ path: bindingPath(bindingName),
3503
+ params: {
3504
+ "_action": "UNLOCK",
3505
+ "lockHandle": lockHandle
3506
+ }
3507
+ });
3508
+ const [, checkErr] = await checkResponse(
3509
+ response,
3510
+ requestErr,
3511
+ `Failed to unlock service binding ${bindingName}`
3512
+ );
3513
+ if (checkErr) return err(checkErr);
3514
+ return ok(void 0);
3515
+ }
3516
+ async function submitServiceBindingJob(client, bindingName, action) {
3517
+ const path = action === "publish" ? PUBLISH_JOBS_PATH : UNPUBLISH_JOBS_PATH;
3518
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
3519
+ <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
3520
+ <adtcore:objectReference adtcore:type="SCGR" adtcore:name="${bindingName.toUpperCase()}"/>
3521
+ </adtcore:objectReferences>`;
3522
+ const [response, requestErr] = await client.request({
3523
+ method: "POST",
3524
+ path,
3525
+ headers: { "Content-Type": "application/xml" },
3526
+ body
3527
+ });
3528
+ const [text, checkErr] = await checkResponse(
3529
+ response,
3530
+ requestErr,
3531
+ `Failed to ${action} service binding ${bindingName}`
3532
+ );
3533
+ if (checkErr) return err(checkErr);
3534
+ const severity = extractTagText(text, "SEVERITY");
3535
+ if (severity && severity !== SEVERITY_OK2) {
3536
+ const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
3537
+ return err(new Error(`Service binding ${action} failed (${severity}): ${shortText}`));
3538
+ }
3539
+ return ok(extractTagText(text, "SHORT_TEXT") ?? "");
3540
+ }
3541
+
3542
+ // src/core/adt/businessservices/publish.ts
3543
+ async function publishServiceBinding(client, bindingName) {
3544
+ return runBindingJob(client, bindingName, "publish");
3545
+ }
3546
+ async function runBindingJob(client, bindingName, action) {
3547
+ const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
3548
+ if (lockErr) return err(lockErr);
3549
+ debug(`Service binding lock acquired for ${action}: handle=${lockHandle}`);
3550
+ const [message, jobErr] = await submitServiceBindingJob(client, bindingName, action);
3551
+ const [, unlockErr] = await unlockServiceBinding(client, bindingName, lockHandle);
3552
+ if (jobErr) return err(jobErr);
3553
+ if (unlockErr) return err(unlockErr);
3554
+ return ok(message);
3555
+ }
3556
+
3557
+ // src/core/adt/businessservices/delete.ts
3558
+ async function deleteServiceBinding(client, bindingName, transport) {
3559
+ const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
3560
+ if (lockErr) return err(lockErr);
3561
+ const [, unpublishErr] = await submitServiceBindingJob(client, bindingName, "unpublish");
3562
+ if (unpublishErr) debug(`Unpublish before delete skipped: ${unpublishErr.message}`);
3563
+ const params = { lockHandle };
3564
+ if (transport) params["corrNr"] = transport;
3565
+ const [response, requestErr] = await client.request({
3566
+ method: "DELETE",
3567
+ path: bindingPath(bindingName),
3568
+ params,
3569
+ headers: { "Accept": "text/plain" }
3570
+ });
3571
+ const [, checkErr] = await checkResponse(
3572
+ response,
3573
+ requestErr,
3574
+ `Failed to delete service binding ${bindingName}`
3575
+ );
3576
+ if (checkErr) {
3577
+ await unlockServiceBinding(client, bindingName, lockHandle);
3578
+ return err(checkErr);
3579
+ }
3580
+ return ok(void 0);
3581
+ }
3582
+
3056
3583
  // src/client/methods/craud/read.ts
3057
3584
  async function read(state, requestor, objects) {
3058
3585
  if (!state.session) return err(new Error("Not logged in"));
@@ -3093,6 +3620,19 @@ async function update(state, requestor, object, transport) {
3093
3620
  return ok(void 0);
3094
3621
  }
3095
3622
 
3623
+ // src/client/methods/craud/specialcases/classes/writeClassInclude.ts
3624
+ async function writeClassInclude(state, requestor, className, includeType, source, transport) {
3625
+ if (!state.session) return err(new Error("Not logged in"));
3626
+ const objRef = { name: className, extension: "aclass" };
3627
+ const [lockHandle, lockErr] = await lockObject(requestor, objRef);
3628
+ if (lockErr) return err(lockErr);
3629
+ const [, updateErr] = await updateClassInclude(requestor, className, includeType, source, lockHandle, transport);
3630
+ const [, unlockErr] = await unlockObject(requestor, objRef, lockHandle);
3631
+ if (updateErr) return err(updateErr);
3632
+ if (unlockErr) return err(unlockErr);
3633
+ return ok(void 0);
3634
+ }
3635
+
3096
3636
  // src/client/methods/craud/upsert.ts
3097
3637
  async function upsertSingle(state, requestor, object, packageName, transport) {
3098
3638
  if (!state.session) return err(new Error("Not logged in"));
@@ -3269,6 +3809,41 @@ function getObjectConfig() {
3269
3809
  return Object.values(OBJECT_CONFIG_MAP);
3270
3810
  }
3271
3811
 
3812
+ // src/client/methods/craud/specialcases/businessservices/createServiceBinding.ts
3813
+ async function createServiceBinding(state, requestor, options) {
3814
+ if (!state.session) return err(new Error("Not logged in"));
3815
+ const [, validateErr] = await validateServiceBinding(requestor, options);
3816
+ if (validateErr) return err(validateErr);
3817
+ const [, createErr] = await createServiceBindingObject(requestor, options, state.session.username);
3818
+ if (createErr) return err(createErr);
3819
+ const [activation, activateErr] = await activateServiceBinding(requestor, options.bindingName);
3820
+ if (activateErr) return err(activateErr);
3821
+ if (activation.some((result2) => result2.status === "error")) {
3822
+ return err(new Error(`Service binding ${options.bindingName} failed activation`));
3823
+ }
3824
+ const result = {
3825
+ name: options.bindingName.toUpperCase(),
3826
+ serviceDefinition: options.serviceDefinition.toUpperCase(),
3827
+ created: true,
3828
+ activation,
3829
+ published: false
3830
+ };
3831
+ if (options.publish === false) {
3832
+ return ok(result);
3833
+ }
3834
+ const [publishMessage, publishErr] = await publishServiceBinding(requestor, options.bindingName);
3835
+ if (publishErr) return err(publishErr);
3836
+ result.published = true;
3837
+ if (publishMessage) result.publishMessage = publishMessage;
3838
+ return ok(result);
3839
+ }
3840
+
3841
+ // src/client/methods/craud/specialcases/businessservices/deleteServiceBinding.ts
3842
+ async function deleteServiceBinding2(state, requestor, bindingName, transport) {
3843
+ if (!state.session) return err(new Error("Not logged in"));
3844
+ return deleteServiceBinding(requestor, bindingName, transport);
3845
+ }
3846
+
3272
3847
  // src/client/methods/internal/cookies.ts
3273
3848
  function storeCookies(cookies, response) {
3274
3849
  const setCookieHeader = response.headers.get("set-cookie");
@@ -3581,6 +4156,9 @@ var ADTClientImpl = class {
3581
4156
  async update(object, transport) {
3582
4157
  return update(this.state, this.requestor, object, transport);
3583
4158
  }
4159
+ async writeClassInclude(className, includeType, source, transport) {
4160
+ return writeClassInclude(this.state, this.requestor, className, includeType, source, transport);
4161
+ }
3584
4162
  async upsert(objects, packageName, transport) {
3585
4163
  return upsert(this.state, this.requestor, objects, packageName, transport);
3586
4164
  }
@@ -3646,6 +4224,13 @@ var ADTClientImpl = class {
3646
4224
  async gitDiff(objects) {
3647
4225
  return gitDiff2(this.state, this.requestor, objects);
3648
4226
  }
4227
+ // --- Business Services ---
4228
+ async createServiceBinding(options) {
4229
+ return createServiceBinding(this.state, this.requestor, options);
4230
+ }
4231
+ async deleteServiceBinding(bindingName, transport) {
4232
+ return deleteServiceBinding2(this.state, this.requestor, bindingName, transport);
4233
+ }
3649
4234
  // --- Configuration ---
3650
4235
  getObjectConfig() {
3651
4236
  return getObjectConfig();
@@ -3663,6 +4248,7 @@ function createClient(config) {
3663
4248
  }
3664
4249
  // Annotate the CommonJS export names for ESM import in node:
3665
4250
  0 && (module.exports = {
4251
+ BehaviorImplementationType,
3666
4252
  ExternalReferencesError,
3667
4253
  activateLogging,
3668
4254
  buildSQLQuery,