@saptools/cf-hana 0.3.2 → 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 +18 -0
- package/README.md +41 -31
- package/dist/cli.js +176 -77
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +167 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ interface ConnectOptions {
|
|
|
43
43
|
readonly autoLimit?: number | false;
|
|
44
44
|
readonly queryTimeoutMs?: number;
|
|
45
45
|
readonly connectTimeoutMs?: number;
|
|
46
|
-
/**
|
|
46
|
+
/** Deprecated compatibility flag. Binding discovery is already live. */
|
|
47
47
|
readonly refresh?: boolean;
|
|
48
48
|
/** SAP BTP email for the live credential fetch (else `SAP_EMAIL`). */
|
|
49
49
|
readonly email?: string;
|
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.
|
|
628
|
+
var CLI_VERSION = "0.3.5";
|
|
629
629
|
var ENV_PREFIX = "CF_HANA";
|
|
630
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
631
|
-
var DEFAULT_CONNECT_TIMEOUT_MS =
|
|
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 =
|
|
664
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
665
665
|
var CF_RETRY_ATTEMPTS = 3;
|
|
666
|
-
var CF_RETRY_BASE_DELAY_MS =
|
|
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",
|
|
@@ -673,40 +673,94 @@ var REGION_API_MAP = {
|
|
|
673
673
|
ap20: "https://api.cf.ap20.hana.ondemand.com",
|
|
674
674
|
ap21: "https://api.cf.ap21.hana.ondemand.com",
|
|
675
675
|
ap30: "https://api.cf.ap30.hana.ondemand.com",
|
|
676
|
+
ap31: "https://api.cf.ap31.hana.ondemand.com",
|
|
676
677
|
br10: "https://api.cf.br10.hana.ondemand.com",
|
|
677
678
|
br20: "https://api.cf.br20.hana.ondemand.com",
|
|
678
679
|
br30: "https://api.cf.br30.hana.ondemand.com",
|
|
679
680
|
ca10: "https://api.cf.ca10.hana.ondemand.com",
|
|
680
681
|
ca20: "https://api.cf.ca20.hana.ondemand.com",
|
|
681
682
|
ch20: "https://api.cf.ch20.hana.ondemand.com",
|
|
683
|
+
cn20: "https://api.cf.cn20.platform.sapcloud.cn",
|
|
684
|
+
cn40: "https://api.cf.cn40.platform.sapcloud.cn",
|
|
685
|
+
eu01: "https://api.cf.eu01.hana.ondemand.com",
|
|
686
|
+
eu02: "https://api.cf.eu02.hana.ondemand.com",
|
|
682
687
|
eu10: "https://api.cf.eu10.hana.ondemand.com",
|
|
688
|
+
"eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
|
|
689
|
+
"eu10-003": "https://api.cf.eu10-003.hana.ondemand.com",
|
|
690
|
+
"eu10-004": "https://api.cf.eu10-004.hana.ondemand.com",
|
|
691
|
+
"eu10-005": "https://api.cf.eu10-005.hana.ondemand.com",
|
|
692
|
+
"eu10-006": "https://api.cf.eu10-006.hana.ondemand.com",
|
|
683
693
|
eu11: "https://api.cf.eu11.hana.ondemand.com",
|
|
684
694
|
eu12: "https://api.cf.eu12.hana.ondemand.com",
|
|
695
|
+
eu13: "https://api.cf.eu13.hana.ondemand.com",
|
|
685
696
|
eu20: "https://api.cf.eu20.hana.ondemand.com",
|
|
697
|
+
"eu20-001": "https://api.cf.eu20-001.hana.ondemand.com",
|
|
698
|
+
"eu20-002": "https://api.cf.eu20-002.hana.ondemand.com",
|
|
686
699
|
eu21: "https://api.cf.eu21.hana.ondemand.com",
|
|
700
|
+
eu22: "https://api.cf.eu22.hana.ondemand.com",
|
|
687
701
|
eu30: "https://api.cf.eu30.hana.ondemand.com",
|
|
688
|
-
|
|
702
|
+
eu31: "https://api.cf.eu31.hana.ondemand.com",
|
|
703
|
+
il30: "https://api.cf.il30.hana.ondemand.com",
|
|
704
|
+
in30: "https://api.cf.in30.hana.ondemand.com",
|
|
705
|
+
jp01: "https://api.cf.jp01.hana.ondemand.com",
|
|
689
706
|
jp10: "https://api.cf.jp10.hana.ondemand.com",
|
|
690
707
|
jp20: "https://api.cf.jp20.hana.ondemand.com",
|
|
691
708
|
jp30: "https://api.cf.jp30.hana.ondemand.com",
|
|
709
|
+
jp31: "https://api.cf.jp31.hana.ondemand.com",
|
|
710
|
+
sa30: "https://api.cf.sa30.hana.ondemand.com",
|
|
711
|
+
sa31: "https://api.cf.sa31.hana.ondemand.com",
|
|
712
|
+
uk20: "https://api.cf.uk20.hana.ondemand.com",
|
|
713
|
+
us01: "https://api.cf.us01.hana.ondemand.com",
|
|
714
|
+
us02: "https://api.cf.us02.hana.ondemand.com",
|
|
692
715
|
us10: "https://api.cf.us10.hana.ondemand.com",
|
|
716
|
+
"us10-001": "https://api.cf.us10-001.hana.ondemand.com",
|
|
717
|
+
"us10-002": "https://api.cf.us10-002.hana.ondemand.com",
|
|
718
|
+
"us10-003": "https://api.cf.us10-003.hana.ondemand.com",
|
|
693
719
|
us11: "https://api.cf.us11.hana.ondemand.com",
|
|
694
720
|
us20: "https://api.cf.us20.hana.ondemand.com",
|
|
695
721
|
us21: "https://api.cf.us21.hana.ondemand.com",
|
|
722
|
+
"us21-001": "https://api.cf.us21-001.hana.ondemand.com",
|
|
723
|
+
us22: "https://api.cf.us22.hana.ondemand.com",
|
|
696
724
|
us30: "https://api.cf.us30.hana.ondemand.com",
|
|
697
|
-
|
|
725
|
+
us32: "https://api.cf.us32.hana.ondemand.com"
|
|
698
726
|
};
|
|
727
|
+
function normalizeSapCfApiEndpoint(apiEndpoint) {
|
|
728
|
+
const trimmed = apiEndpoint.trim();
|
|
729
|
+
let parsed;
|
|
730
|
+
try {
|
|
731
|
+
parsed = new URL(trimmed);
|
|
732
|
+
} catch {
|
|
733
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
734
|
+
}
|
|
735
|
+
if (/^https:\/\/[^/]*:\d+(?:[/?#]|$)/i.test(trimmed)) {
|
|
736
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
737
|
+
}
|
|
738
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.port !== "" || parsed.pathname.replace(/\/+$/, "") !== "" || parsed.search !== "" || parsed.hash !== "") {
|
|
739
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
740
|
+
}
|
|
741
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
742
|
+
const match = /^api\.cf\.([a-z]{2}\d{2}(?:-\d{3})?)\.(hana\.ondemand\.com|platform\.sapcloud\.cn)$/.exec(hostname);
|
|
743
|
+
if (!match) {
|
|
744
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
745
|
+
}
|
|
746
|
+
return `https://${hostname}`;
|
|
747
|
+
}
|
|
699
748
|
function getApiEndpointForRegion(regionKey) {
|
|
700
|
-
return REGION_API_MAP[regionKey];
|
|
749
|
+
return REGION_API_MAP[regionKey.trim().toLowerCase()];
|
|
701
750
|
}
|
|
702
751
|
function getRegionKeyForApi(apiEndpoint) {
|
|
703
|
-
|
|
752
|
+
let normalized;
|
|
753
|
+
try {
|
|
754
|
+
normalized = normalizeSapCfApiEndpoint(apiEndpoint);
|
|
755
|
+
} catch {
|
|
756
|
+
return void 0;
|
|
757
|
+
}
|
|
704
758
|
for (const [key, endpoint] of Object.entries(REGION_API_MAP)) {
|
|
705
|
-
if (endpoint.toLowerCase() ===
|
|
759
|
+
if (endpoint.toLowerCase() === normalized) {
|
|
706
760
|
return key;
|
|
707
761
|
}
|
|
708
762
|
}
|
|
709
|
-
return
|
|
763
|
+
return /^https:\/\/api\.cf\.([a-z]{2}\d{2}(?:-\d{3})?)\.hana\.ondemand\.com$/.exec(normalized)?.[1];
|
|
710
764
|
}
|
|
711
765
|
async function withCfSession(work) {
|
|
712
766
|
const cfHome = await mkdtemp(join2(tmpdir(), "saptools-cf-hana-"));
|
|
@@ -731,29 +785,45 @@ function buildEnv(ctx, overrides = {}) {
|
|
|
731
785
|
env["CF_HOME"] = ctx.cfHome;
|
|
732
786
|
return env;
|
|
733
787
|
}
|
|
734
|
-
async function
|
|
735
|
-
const { bin, argsPrefix } = resolveCfBin();
|
|
736
|
-
const env = buildEnv(ctx, overrides);
|
|
788
|
+
async function execWithRetries(bin, args, env, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
737
789
|
let lastErr;
|
|
738
790
|
for (let attempt = 0; attempt < CF_RETRY_ATTEMPTS; attempt++) {
|
|
739
791
|
try {
|
|
740
|
-
const { stdout } = await execFileAsync(bin,
|
|
792
|
+
const { stdout } = await execFileAsync(bin, args, {
|
|
741
793
|
env,
|
|
742
794
|
maxBuffer: MAX_BUFFER,
|
|
743
|
-
timeout:
|
|
795
|
+
timeout: timeoutMs,
|
|
796
|
+
killSignal: "SIGKILL"
|
|
744
797
|
});
|
|
745
798
|
return stdout;
|
|
746
799
|
} catch (err) {
|
|
747
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
|
+
}
|
|
748
809
|
if (attempt < CF_RETRY_ATTEMPTS - 1) {
|
|
749
810
|
await new Promise((r) => setTimeout(r, CF_RETRY_BASE_DELAY_MS * (attempt + 1)));
|
|
750
811
|
}
|
|
751
812
|
}
|
|
752
813
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
}
|
|
757
827
|
}
|
|
758
828
|
async function cfApi(apiEndpoint, ctx) {
|
|
759
829
|
await runCf(["api", apiEndpoint], ctx);
|
|
@@ -775,12 +845,7 @@ async function cfEnvDirect(appName) {
|
|
|
775
845
|
const env = { ...process.env };
|
|
776
846
|
delete env["SAP_EMAIL"];
|
|
777
847
|
delete env["SAP_PASSWORD"];
|
|
778
|
-
|
|
779
|
-
env,
|
|
780
|
-
maxBuffer: MAX_BUFFER,
|
|
781
|
-
timeout: DEFAULT_TIMEOUT_MS
|
|
782
|
-
});
|
|
783
|
-
return stdout;
|
|
848
|
+
return await execWithRetries(bin, [...argsPrefix, "env", appName], env);
|
|
784
849
|
}
|
|
785
850
|
async function readCurrentCfTarget() {
|
|
786
851
|
const { bin, argsPrefix } = resolveCfBin();
|
|
@@ -788,11 +853,7 @@ async function readCurrentCfTarget() {
|
|
|
788
853
|
delete env["SAP_EMAIL"];
|
|
789
854
|
delete env["SAP_PASSWORD"];
|
|
790
855
|
try {
|
|
791
|
-
const
|
|
792
|
-
env,
|
|
793
|
-
maxBuffer: MAX_BUFFER,
|
|
794
|
-
timeout: 1e4
|
|
795
|
-
});
|
|
856
|
+
const stdout = await execWithRetries(bin, [...argsPrefix, "target"], env);
|
|
796
857
|
return parseCfTargetOutput(stdout);
|
|
797
858
|
} catch {
|
|
798
859
|
return void 0;
|
|
@@ -806,9 +867,15 @@ function parseCfTargetOutput(stdout) {
|
|
|
806
867
|
if (!api || !org || !space) {
|
|
807
868
|
return void 0;
|
|
808
869
|
}
|
|
809
|
-
|
|
870
|
+
let normalizedApi;
|
|
871
|
+
try {
|
|
872
|
+
normalizedApi = normalizeSapCfApiEndpoint(api);
|
|
873
|
+
} catch {
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
876
|
+
const regionKey = getRegionKeyForApi(normalizedApi);
|
|
810
877
|
return {
|
|
811
|
-
apiEndpoint:
|
|
878
|
+
apiEndpoint: normalizedApi,
|
|
812
879
|
orgName: org,
|
|
813
880
|
spaceName: space,
|
|
814
881
|
...regionKey ? { regionKey } : {}
|
|
@@ -834,12 +901,8 @@ function formatCurrentCfAppSelector(target, appName) {
|
|
|
834
901
|
if (!name) {
|
|
835
902
|
throw new Error("App name is required.");
|
|
836
903
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
`Current CF API endpoint "${target.apiEndpoint}" does not match a known SAP region. Pass a full region/org/space/app selector.`
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
return `${target.regionKey}/${target.orgName}/${target.spaceName}/${name}`;
|
|
904
|
+
const region = target.regionKey ?? "current";
|
|
905
|
+
return `${region}/${target.orgName}/${target.spaceName}/${name}`;
|
|
843
906
|
}
|
|
844
907
|
function extractVcapSection(stdout) {
|
|
845
908
|
const start = stdout.indexOf("VCAP_SERVICES:");
|
|
@@ -919,6 +982,9 @@ function classifyCfError(stderr = "", stdout = "") {
|
|
|
919
982
|
}
|
|
920
983
|
|
|
921
984
|
// src/credentials.ts
|
|
985
|
+
function errorMessageFromUnknown(error) {
|
|
986
|
+
return error instanceof Error ? error.message : "Invalid or untrusted CF API endpoint.";
|
|
987
|
+
}
|
|
922
988
|
async function resolveAppBindings(rawSelector, options) {
|
|
923
989
|
const selector = rawSelector.trim();
|
|
924
990
|
if (!selector) {
|
|
@@ -937,7 +1003,7 @@ async function resolveAppBindings(rawSelector, options) {
|
|
|
937
1003
|
const displaySelector = current.regionKey ? formatCurrentCfAppSelector(current, selector) : `current/${current.orgName}/${current.spaceName}/${selector}`;
|
|
938
1004
|
target = {
|
|
939
1005
|
selector: displaySelector,
|
|
940
|
-
apiEndpoint: current.
|
|
1006
|
+
apiEndpoint: current.apiEndpoint,
|
|
941
1007
|
orgName: current.orgName,
|
|
942
1008
|
spaceName: current.spaceName,
|
|
943
1009
|
appName: selector
|
|
@@ -955,10 +1021,16 @@ async function resolveAppBindings(rawSelector, options) {
|
|
|
955
1021
|
if (!apiEndpoint) {
|
|
956
1022
|
throw new CfHanaError(
|
|
957
1023
|
"CONFIG",
|
|
958
|
-
`Unknown region
|
|
1024
|
+
`Unknown SAP CF region "${regionKey}". Verify the current SAP region list or use the current CF target.`
|
|
959
1025
|
);
|
|
960
1026
|
}
|
|
961
|
-
|
|
1027
|
+
let normalizedApiEndpoint;
|
|
1028
|
+
try {
|
|
1029
|
+
normalizedApiEndpoint = normalizeSapCfApiEndpoint(apiEndpoint);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
throw new CfHanaError("CONFIG", errorMessageFromUnknown(error), { cause: error });
|
|
1032
|
+
}
|
|
1033
|
+
target = { selector, apiEndpoint: normalizedApiEndpoint, orgName, spaceName, appName };
|
|
962
1034
|
}
|
|
963
1035
|
let bindings;
|
|
964
1036
|
const source = "live";
|
|
@@ -982,9 +1054,6 @@ Run "cf login" + "cf target", or provide SAP_EMAIL + SAP_PASSWORD.`,
|
|
|
982
1054
|
{ cause: directError }
|
|
983
1055
|
);
|
|
984
1056
|
}
|
|
985
|
-
if (!target.apiEndpoint) {
|
|
986
|
-
throw new CfHanaError("CONFIG", "Cannot determine API endpoint for fallback auth.");
|
|
987
|
-
}
|
|
988
1057
|
const api = target.apiEndpoint;
|
|
989
1058
|
bindings = await withCfSession(async (ctx) => {
|
|
990
1059
|
await cfApi(api, ctx);
|
|
@@ -1008,9 +1077,6 @@ Run "cf login" + "cf target", or provide SAP_EMAIL + SAP_PASSWORD.`,
|
|
|
1008
1077
|
`SAP_EMAIL and SAP_PASSWORD are required to fetch HANA bindings for explicit selector "${selector}".`
|
|
1009
1078
|
);
|
|
1010
1079
|
}
|
|
1011
|
-
if (!target.apiEndpoint) {
|
|
1012
|
-
throw new CfHanaError("CONFIG", "Invalid explicit selector.");
|
|
1013
|
-
}
|
|
1014
1080
|
const api = target.apiEndpoint;
|
|
1015
1081
|
bindings = await withCfSession(async (ctx) => {
|
|
1016
1082
|
await cfApi(api, ctx);
|
|
@@ -1726,6 +1792,57 @@ function boundedRows(rows, requestedLimit) {
|
|
|
1726
1792
|
truncated: rows.length > requestedLimit
|
|
1727
1793
|
};
|
|
1728
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
|
+
}
|
|
1729
1846
|
async function withTimeout(work, timeoutMs, onTimeout) {
|
|
1730
1847
|
let timer;
|
|
1731
1848
|
const timeout = new Promise((_resolve, reject) => {
|
|
@@ -1811,7 +1928,8 @@ var Connection = class _Connection {
|
|
|
1811
1928
|
}
|
|
1812
1929
|
);
|
|
1813
1930
|
const elapsedMs = Date.now() - started;
|
|
1814
|
-
const
|
|
1931
|
+
const normalizedRows = normalizeBooleanRows(execResult.rows, execResult.columns);
|
|
1932
|
+
const selected = boundedRows(normalizedRows, limited.requestedLimit);
|
|
1815
1933
|
return {
|
|
1816
1934
|
rows: selected.rows,
|
|
1817
1935
|
columns: execResult.columns,
|