@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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  <!-- cspell:words VARCHAR -->
4
4
 
5
+ ## 0.3.5 - 2026-07-06
6
+
7
+ - Normalize HANA `BOOLEAN` result columns that arrive from the driver as `1`/`0`
8
+ into JavaScript `true`/`false` values before CLI/API formatting and saved refs.
9
+
5
10
  ## 0.3.4 - 2026-07-02
6
11
 
7
12
  - Keep explicit selector region lookup map-backed so unknown technical keys fail before isolated authentication unless they are added to the maintained SAP CF region catalog.
package/dist/cli.js CHANGED
@@ -698,10 +698,10 @@ async function listColumns(connection, schema, table) {
698
698
 
699
699
  // src/config.ts
700
700
  var CLI_NAME = "cf-hana";
701
- var CLI_VERSION = "0.3.4";
701
+ var CLI_VERSION = "0.3.5";
702
702
  var ENV_PREFIX = "CF_HANA";
703
- var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
704
- var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
703
+ var DEFAULT_QUERY_TIMEOUT_MS = 6e4;
704
+ var DEFAULT_CONNECT_TIMEOUT_MS = 6e4;
705
705
  var DEFAULT_POOL_MAX = 4;
706
706
  var DEFAULT_POOL_IDLE_MS = 6e4;
707
707
  var DEFAULT_AUTO_LIMIT = 100;
@@ -738,9 +738,9 @@ import { join as join2 } from "path";
738
738
  import { promisify } from "util";
739
739
  var execFileAsync = promisify(execFile);
740
740
  var MAX_BUFFER = 16 * 1024 * 1024;
741
- var DEFAULT_TIMEOUT_MS = 3e4;
741
+ var DEFAULT_TIMEOUT_MS = 6e4;
742
742
  var CF_RETRY_ATTEMPTS = 3;
743
- var CF_RETRY_BASE_DELAY_MS = 120;
743
+ var CF_RETRY_BASE_DELAY_MS = 500;
744
744
  var REGION_API_MAP = {
745
745
  ae01: "https://api.cf.ae01.hana.ondemand.com",
746
746
  ap01: "https://api.cf.ap01.hana.ondemand.com",
@@ -862,29 +862,45 @@ function buildEnv(ctx, overrides = {}) {
862
862
  env["CF_HOME"] = ctx.cfHome;
863
863
  return env;
864
864
  }
865
- async function runCf(args, ctx, overrides = {}) {
866
- const { bin, argsPrefix } = resolveCfBin();
867
- const env = buildEnv(ctx, overrides);
865
+ async function execWithRetries(bin, args, env, timeoutMs = DEFAULT_TIMEOUT_MS) {
868
866
  let lastErr;
869
867
  for (let attempt = 0; attempt < CF_RETRY_ATTEMPTS; attempt++) {
870
868
  try {
871
- const { stdout } = await execFileAsync(bin, [...argsPrefix, ...args], {
869
+ const { stdout } = await execFileAsync(bin, args, {
872
870
  env,
873
871
  maxBuffer: MAX_BUFFER,
874
- timeout: DEFAULT_TIMEOUT_MS
872
+ timeout: timeoutMs,
873
+ killSignal: "SIGKILL"
875
874
  });
876
875
  return stdout;
877
876
  } catch (err) {
878
877
  lastErr = err;
878
+ const e = err;
879
+ const output = `${e.message ?? ""} ${e.stderr ? String(e.stderr) : ""}`.toLowerCase();
880
+ const isTimeout = e.killed === true;
881
+ 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");
882
+ const isEnoent = e.code === "ENOENT";
883
+ if (isEnoent || !isTimeout && !isNetworkFlake) {
884
+ throw err;
885
+ }
879
886
  if (attempt < CF_RETRY_ATTEMPTS - 1) {
880
887
  await new Promise((r) => setTimeout(r, CF_RETRY_BASE_DELAY_MS * (attempt + 1)));
881
888
  }
882
889
  }
883
890
  }
884
- const e = lastErr;
885
- const detail = e?.stderr ? String(e.stderr) : e?.message ?? "";
886
- const cmd = args[0] === "auth" ? "cf auth" : `cf ${args.join(" ")}`;
887
- throw new Error(`${cmd} failed: ${detail}`.trim(), { cause: lastErr });
891
+ throw lastErr;
892
+ }
893
+ async function runCf(args, ctx, overrides = {}) {
894
+ const { bin, argsPrefix } = resolveCfBin();
895
+ const env = buildEnv(ctx, overrides);
896
+ try {
897
+ return await execWithRetries(bin, [...argsPrefix, ...args], env);
898
+ } catch (lastErr) {
899
+ const e = lastErr;
900
+ const detail = e?.stderr ? String(e.stderr) : e?.message ?? "";
901
+ const cmd = args[0] === "auth" ? "cf auth" : `cf ${args.join(" ")}`;
902
+ throw new Error(`${cmd} failed: ${detail}`.trim(), { cause: lastErr });
903
+ }
888
904
  }
889
905
  async function cfApi(apiEndpoint, ctx) {
890
906
  await runCf(["api", apiEndpoint], ctx);
@@ -906,12 +922,7 @@ async function cfEnvDirect(appName) {
906
922
  const env = { ...process.env };
907
923
  delete env["SAP_EMAIL"];
908
924
  delete env["SAP_PASSWORD"];
909
- const { stdout } = await execFileAsync(bin, [...argsPrefix, "env", appName], {
910
- env,
911
- maxBuffer: MAX_BUFFER,
912
- timeout: DEFAULT_TIMEOUT_MS
913
- });
914
- return stdout;
925
+ return await execWithRetries(bin, [...argsPrefix, "env", appName], env);
915
926
  }
916
927
  async function readCurrentCfTarget() {
917
928
  const { bin, argsPrefix } = resolveCfBin();
@@ -919,11 +930,7 @@ async function readCurrentCfTarget() {
919
930
  delete env["SAP_EMAIL"];
920
931
  delete env["SAP_PASSWORD"];
921
932
  try {
922
- const { stdout } = await execFileAsync(bin, [...argsPrefix, "target"], {
923
- env,
924
- maxBuffer: MAX_BUFFER,
925
- timeout: 1e4
926
- });
933
+ const stdout = await execWithRetries(bin, [...argsPrefix, "target"], env);
927
934
  return parseCfTargetOutput(stdout);
928
935
  } catch {
929
936
  return void 0;
@@ -1862,6 +1869,57 @@ function boundedRows(rows, requestedLimit) {
1862
1869
  truncated: rows.length > requestedLimit
1863
1870
  };
1864
1871
  }
1872
+ function isBooleanColumn(column) {
1873
+ return column.typeName.trim().toUpperCase() === "BOOLEAN";
1874
+ }
1875
+ function normalizeBooleanCell(value) {
1876
+ if (value === 0) {
1877
+ return false;
1878
+ }
1879
+ if (value === 1) {
1880
+ return true;
1881
+ }
1882
+ if (typeof value !== "string") {
1883
+ return value;
1884
+ }
1885
+ const normalized = value.trim().toLowerCase();
1886
+ if (normalized === "0") {
1887
+ return false;
1888
+ }
1889
+ if (normalized === "1") {
1890
+ return true;
1891
+ }
1892
+ if (normalized === "false") {
1893
+ return false;
1894
+ }
1895
+ if (normalized === "true") {
1896
+ return true;
1897
+ }
1898
+ return value;
1899
+ }
1900
+ function normalizeBooleanRow(row, columns) {
1901
+ let normalizedRow;
1902
+ for (const column of columns) {
1903
+ const value = row[column.name];
1904
+ if (value === void 0) {
1905
+ continue;
1906
+ }
1907
+ const normalizedValue = normalizeBooleanCell(value);
1908
+ if (normalizedValue === value) {
1909
+ continue;
1910
+ }
1911
+ normalizedRow ??= { ...row };
1912
+ normalizedRow[column.name] = normalizedValue;
1913
+ }
1914
+ return normalizedRow ?? row;
1915
+ }
1916
+ function normalizeBooleanRows(rows, columns) {
1917
+ const booleanColumns = columns.filter(isBooleanColumn);
1918
+ if (booleanColumns.length === 0 || rows.length === 0) {
1919
+ return rows;
1920
+ }
1921
+ return rows.map((row) => normalizeBooleanRow(row, booleanColumns));
1922
+ }
1865
1923
  async function withTimeout(work, timeoutMs, onTimeout) {
1866
1924
  let timer;
1867
1925
  const timeout = new Promise((_resolve, reject) => {
@@ -1947,7 +2005,8 @@ var Connection = class _Connection {
1947
2005
  }
1948
2006
  );
1949
2007
  const elapsedMs = Date.now() - started;
1950
- const selected = boundedRows(execResult.rows, limited.requestedLimit);
2008
+ const normalizedRows = normalizeBooleanRows(execResult.rows, execResult.columns);
2009
+ const selected = boundedRows(normalizedRows, limited.requestedLimit);
1951
2010
  return {
1952
2011
  rows: selected.rows,
1953
2012
  columns: execResult.columns,