@saptools/cf-hana 0.3.4 → 0.3.5

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
@@ -625,10 +625,10 @@ async function listColumns(connection, schema, table) {
625
625
  }
626
626
 
627
627
  // src/config.ts
628
- var CLI_VERSION = "0.3.4";
628
+ var CLI_VERSION = "0.3.5";
629
629
  var ENV_PREFIX = "CF_HANA";
630
- var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
631
- var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
630
+ var DEFAULT_QUERY_TIMEOUT_MS = 6e4;
631
+ var DEFAULT_CONNECT_TIMEOUT_MS = 6e4;
632
632
  var DEFAULT_POOL_MAX = 4;
633
633
  var DEFAULT_POOL_IDLE_MS = 6e4;
634
634
  var DEFAULT_AUTO_LIMIT = 100;
@@ -661,9 +661,9 @@ import { join as join2 } from "path";
661
661
  import { promisify } from "util";
662
662
  var execFileAsync = promisify(execFile);
663
663
  var MAX_BUFFER = 16 * 1024 * 1024;
664
- var DEFAULT_TIMEOUT_MS = 3e4;
664
+ var DEFAULT_TIMEOUT_MS = 6e4;
665
665
  var CF_RETRY_ATTEMPTS = 3;
666
- var CF_RETRY_BASE_DELAY_MS = 120;
666
+ var CF_RETRY_BASE_DELAY_MS = 500;
667
667
  var REGION_API_MAP = {
668
668
  ae01: "https://api.cf.ae01.hana.ondemand.com",
669
669
  ap01: "https://api.cf.ap01.hana.ondemand.com",
@@ -785,29 +785,45 @@ function buildEnv(ctx, overrides = {}) {
785
785
  env["CF_HOME"] = ctx.cfHome;
786
786
  return env;
787
787
  }
788
- async function runCf(args, ctx, overrides = {}) {
789
- const { bin, argsPrefix } = resolveCfBin();
790
- const env = buildEnv(ctx, overrides);
788
+ async function execWithRetries(bin, args, env, timeoutMs = DEFAULT_TIMEOUT_MS) {
791
789
  let lastErr;
792
790
  for (let attempt = 0; attempt < CF_RETRY_ATTEMPTS; attempt++) {
793
791
  try {
794
- const { stdout } = await execFileAsync(bin, [...argsPrefix, ...args], {
792
+ const { stdout } = await execFileAsync(bin, args, {
795
793
  env,
796
794
  maxBuffer: MAX_BUFFER,
797
- timeout: DEFAULT_TIMEOUT_MS
795
+ timeout: timeoutMs,
796
+ killSignal: "SIGKILL"
798
797
  });
799
798
  return stdout;
800
799
  } catch (err) {
801
800
  lastErr = err;
801
+ const e = err;
802
+ const output = `${e.message ?? ""} ${e.stderr ? String(e.stderr) : ""}`.toLowerCase();
803
+ const isTimeout = e.killed === true;
804
+ const isNetworkFlake = output.includes("error performing request") || output.includes("timeout") || output.includes("connection reset") || output.includes("connection refused") || output.includes("eof") || output.includes("502 bad gateway") || output.includes("503 service unavailable") || output.includes("504 gateway timeout") || output.includes("dial tcp") || output.includes("no such host");
805
+ const isEnoent = e.code === "ENOENT";
806
+ if (isEnoent || !isTimeout && !isNetworkFlake) {
807
+ throw err;
808
+ }
802
809
  if (attempt < CF_RETRY_ATTEMPTS - 1) {
803
810
  await new Promise((r) => setTimeout(r, CF_RETRY_BASE_DELAY_MS * (attempt + 1)));
804
811
  }
805
812
  }
806
813
  }
807
- const e = lastErr;
808
- const detail = e?.stderr ? String(e.stderr) : e?.message ?? "";
809
- const cmd = args[0] === "auth" ? "cf auth" : `cf ${args.join(" ")}`;
810
- throw new Error(`${cmd} failed: ${detail}`.trim(), { cause: lastErr });
814
+ throw lastErr;
815
+ }
816
+ async function runCf(args, ctx, overrides = {}) {
817
+ const { bin, argsPrefix } = resolveCfBin();
818
+ const env = buildEnv(ctx, overrides);
819
+ try {
820
+ return await execWithRetries(bin, [...argsPrefix, ...args], env);
821
+ } catch (lastErr) {
822
+ const e = lastErr;
823
+ const detail = e?.stderr ? String(e.stderr) : e?.message ?? "";
824
+ const cmd = args[0] === "auth" ? "cf auth" : `cf ${args.join(" ")}`;
825
+ throw new Error(`${cmd} failed: ${detail}`.trim(), { cause: lastErr });
826
+ }
811
827
  }
812
828
  async function cfApi(apiEndpoint, ctx) {
813
829
  await runCf(["api", apiEndpoint], ctx);
@@ -829,12 +845,7 @@ async function cfEnvDirect(appName) {
829
845
  const env = { ...process.env };
830
846
  delete env["SAP_EMAIL"];
831
847
  delete env["SAP_PASSWORD"];
832
- const { stdout } = await execFileAsync(bin, [...argsPrefix, "env", appName], {
833
- env,
834
- maxBuffer: MAX_BUFFER,
835
- timeout: DEFAULT_TIMEOUT_MS
836
- });
837
- return stdout;
848
+ return await execWithRetries(bin, [...argsPrefix, "env", appName], env);
838
849
  }
839
850
  async function readCurrentCfTarget() {
840
851
  const { bin, argsPrefix } = resolveCfBin();
@@ -842,11 +853,7 @@ async function readCurrentCfTarget() {
842
853
  delete env["SAP_EMAIL"];
843
854
  delete env["SAP_PASSWORD"];
844
855
  try {
845
- const { stdout } = await execFileAsync(bin, [...argsPrefix, "target"], {
846
- env,
847
- maxBuffer: MAX_BUFFER,
848
- timeout: 1e4
849
- });
856
+ const stdout = await execWithRetries(bin, [...argsPrefix, "target"], env);
850
857
  return parseCfTargetOutput(stdout);
851
858
  } catch {
852
859
  return void 0;
@@ -1785,6 +1792,57 @@ function boundedRows(rows, requestedLimit) {
1785
1792
  truncated: rows.length > requestedLimit
1786
1793
  };
1787
1794
  }
1795
+ function isBooleanColumn(column) {
1796
+ return column.typeName.trim().toUpperCase() === "BOOLEAN";
1797
+ }
1798
+ function normalizeBooleanCell(value) {
1799
+ if (value === 0) {
1800
+ return false;
1801
+ }
1802
+ if (value === 1) {
1803
+ return true;
1804
+ }
1805
+ if (typeof value !== "string") {
1806
+ return value;
1807
+ }
1808
+ const normalized = value.trim().toLowerCase();
1809
+ if (normalized === "0") {
1810
+ return false;
1811
+ }
1812
+ if (normalized === "1") {
1813
+ return true;
1814
+ }
1815
+ if (normalized === "false") {
1816
+ return false;
1817
+ }
1818
+ if (normalized === "true") {
1819
+ return true;
1820
+ }
1821
+ return value;
1822
+ }
1823
+ function normalizeBooleanRow(row, columns) {
1824
+ let normalizedRow;
1825
+ for (const column of columns) {
1826
+ const value = row[column.name];
1827
+ if (value === void 0) {
1828
+ continue;
1829
+ }
1830
+ const normalizedValue = normalizeBooleanCell(value);
1831
+ if (normalizedValue === value) {
1832
+ continue;
1833
+ }
1834
+ normalizedRow ??= { ...row };
1835
+ normalizedRow[column.name] = normalizedValue;
1836
+ }
1837
+ return normalizedRow ?? row;
1838
+ }
1839
+ function normalizeBooleanRows(rows, columns) {
1840
+ const booleanColumns = columns.filter(isBooleanColumn);
1841
+ if (booleanColumns.length === 0 || rows.length === 0) {
1842
+ return rows;
1843
+ }
1844
+ return rows.map((row) => normalizeBooleanRow(row, booleanColumns));
1845
+ }
1788
1846
  async function withTimeout(work, timeoutMs, onTimeout) {
1789
1847
  let timer;
1790
1848
  const timeout = new Promise((_resolve, reject) => {
@@ -1870,7 +1928,8 @@ var Connection = class _Connection {
1870
1928
  }
1871
1929
  );
1872
1930
  const elapsedMs = Date.now() - started;
1873
- const selected = boundedRows(execResult.rows, limited.requestedLimit);
1931
+ const normalizedRows = normalizeBooleanRows(execResult.rows, execResult.columns);
1932
+ const selected = boundedRows(normalizedRows, limited.requestedLimit);
1874
1933
  return {
1875
1934
  rows: selected.rows,
1876
1935
  columns: execResult.columns,