@geodedb/client 1.0.0-alpha.21 → 1.0.0-alpha.25
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.d.ts +362 -398
- package/dist/index.js +2147 -1877
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -17,6 +17,30 @@ var __export = (target, all) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// src/errors.ts
|
|
20
|
+
function ErrClosed() {
|
|
21
|
+
return new Error(ERR_CLOSED_MESSAGE);
|
|
22
|
+
}
|
|
23
|
+
function ErrQueryInProgress() {
|
|
24
|
+
return new Error(ERR_QUERY_IN_PROGRESS_MESSAGE);
|
|
25
|
+
}
|
|
26
|
+
function ErrTxInProgress() {
|
|
27
|
+
return new Error(ERR_TX_IN_PROGRESS_MESSAGE);
|
|
28
|
+
}
|
|
29
|
+
function ErrNoTx() {
|
|
30
|
+
return new Error(ERR_NO_TX_MESSAGE);
|
|
31
|
+
}
|
|
32
|
+
function ErrTxDone() {
|
|
33
|
+
return new Error(ERR_TX_DONE_MESSAGE);
|
|
34
|
+
}
|
|
35
|
+
function ErrRowsClosed() {
|
|
36
|
+
return new Error(ERR_ROWS_CLOSED_MESSAGE);
|
|
37
|
+
}
|
|
38
|
+
function ErrBadConn() {
|
|
39
|
+
return new Error(ERR_BAD_CONN_MESSAGE);
|
|
40
|
+
}
|
|
41
|
+
function isSentinelError(err, message) {
|
|
42
|
+
return err instanceof Error && err.message === message;
|
|
43
|
+
}
|
|
20
44
|
function isDriverError(err) {
|
|
21
45
|
return err instanceof DriverError;
|
|
22
46
|
}
|
|
@@ -29,7 +53,7 @@ function isRetryableError(err) {
|
|
|
29
53
|
}
|
|
30
54
|
return false;
|
|
31
55
|
}
|
|
32
|
-
var StatusClass, DriverError, TransportError, SENSITIVE_CONFIG_FIELDS, ConfigError, SecurityError, StateError,
|
|
56
|
+
var StatusClass, DriverError, TransportError, SENSITIVE_CONFIG_FIELDS, ConfigError, SecurityError, StateError, ERR_CLOSED_MESSAGE, ERR_QUERY_IN_PROGRESS_MESSAGE, ERR_TX_IN_PROGRESS_MESSAGE, ERR_NO_TX_MESSAGE, ERR_TX_DONE_MESSAGE, ERR_ROWS_CLOSED_MESSAGE, ERR_BAD_CONN_MESSAGE;
|
|
33
57
|
var init_errors = __esm({
|
|
34
58
|
"src/errors.ts"() {
|
|
35
59
|
StatusClass = {
|
|
@@ -145,13 +169,13 @@ var init_errors = __esm({
|
|
|
145
169
|
return false;
|
|
146
170
|
}
|
|
147
171
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
ERR_CLOSED_MESSAGE = "geode: connection closed";
|
|
173
|
+
ERR_QUERY_IN_PROGRESS_MESSAGE = "geode: query already in progress";
|
|
174
|
+
ERR_TX_IN_PROGRESS_MESSAGE = "geode: transaction already in progress";
|
|
175
|
+
ERR_NO_TX_MESSAGE = "geode: no transaction in progress";
|
|
176
|
+
ERR_TX_DONE_MESSAGE = "geode: transaction already committed or rolled back";
|
|
177
|
+
ERR_ROWS_CLOSED_MESSAGE = "geode: rows closed";
|
|
178
|
+
ERR_BAD_CONN_MESSAGE = "geode: bad connection";
|
|
155
179
|
}
|
|
156
180
|
});
|
|
157
181
|
|
|
@@ -537,6 +561,108 @@ var init_config = __esm({
|
|
|
537
561
|
SUPPORTED_SCHEMES = ["quic", "grpc"];
|
|
538
562
|
}
|
|
539
563
|
});
|
|
564
|
+
function checkAgainstBlocklist(normalizedPath, fieldName) {
|
|
565
|
+
const lowerPath = normalizedPath.toLowerCase();
|
|
566
|
+
for (const sensitive of SENSITIVE_PATHS) {
|
|
567
|
+
if (lowerPath.includes(sensitive)) {
|
|
568
|
+
throw new SecurityError({
|
|
569
|
+
type: "input",
|
|
570
|
+
message: `${fieldName} path refers to a restricted system file`
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function validateTLSCertPathSync(path2, fieldName) {
|
|
576
|
+
if (!path2) {
|
|
577
|
+
throw new SecurityError({
|
|
578
|
+
type: "input",
|
|
579
|
+
message: `${fieldName} path cannot be empty`
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
if (path2.includes("\0")) {
|
|
583
|
+
throw new SecurityError({
|
|
584
|
+
type: "input",
|
|
585
|
+
message: `${fieldName} path contains null bytes`
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
const normalizedPath = path2.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
589
|
+
if (normalizedPath.includes("/../") || normalizedPath.endsWith("/..")) {
|
|
590
|
+
throw new SecurityError({
|
|
591
|
+
type: "input",
|
|
592
|
+
message: `${fieldName} path contains directory traversal sequence`
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
if (normalizedPath.startsWith("../") || normalizedPath === "..") {
|
|
596
|
+
throw new SecurityError({
|
|
597
|
+
type: "input",
|
|
598
|
+
message: `${fieldName} path contains directory traversal sequence`
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (/%2e%2e/i.test(path2) || /%252e/i.test(path2)) {
|
|
602
|
+
throw new SecurityError({
|
|
603
|
+
type: "input",
|
|
604
|
+
message: `${fieldName} path contains encoded traversal sequence`
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
checkAgainstBlocklist(normalizedPath, fieldName);
|
|
608
|
+
}
|
|
609
|
+
async function validateTLSCertPath(path2, fieldName) {
|
|
610
|
+
validateTLSCertPathSync(path2, fieldName);
|
|
611
|
+
try {
|
|
612
|
+
await fs.promises.access(path2);
|
|
613
|
+
const stats = await fs.promises.lstat(path2);
|
|
614
|
+
if (stats.isSymbolicLink()) {
|
|
615
|
+
const resolvedPath = await fs.promises.realpath(path2);
|
|
616
|
+
const normalizedResolved = resolvedPath.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
617
|
+
checkAgainstBlocklist(normalizedResolved, fieldName);
|
|
618
|
+
if (normalizedResolved.includes("/../") || normalizedResolved.endsWith("/..")) {
|
|
619
|
+
throw new SecurityError({
|
|
620
|
+
type: "input",
|
|
621
|
+
message: `${fieldName} symlink resolves to a path with directory traversal`
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} catch (e) {
|
|
626
|
+
if (e instanceof SecurityError) {
|
|
627
|
+
throw e;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
var SENSITIVE_PATHS;
|
|
632
|
+
var init_validate_tls = __esm({
|
|
633
|
+
"src/validate-tls.ts"() {
|
|
634
|
+
init_errors();
|
|
635
|
+
SENSITIVE_PATHS = [
|
|
636
|
+
// System files
|
|
637
|
+
"/etc/passwd",
|
|
638
|
+
"/etc/shadow",
|
|
639
|
+
"/etc/hosts",
|
|
640
|
+
"/proc/",
|
|
641
|
+
"/sys/",
|
|
642
|
+
"/dev/",
|
|
643
|
+
// Root home directory
|
|
644
|
+
"/root/",
|
|
645
|
+
// SSH keys
|
|
646
|
+
"/.ssh/",
|
|
647
|
+
// Kubernetes secrets
|
|
648
|
+
"/var/run/secrets/",
|
|
649
|
+
"/run/secrets/",
|
|
650
|
+
// AWS credentials
|
|
651
|
+
"/.aws/",
|
|
652
|
+
"/credentials",
|
|
653
|
+
// GCP credentials
|
|
654
|
+
"/.config/gcloud/",
|
|
655
|
+
"/gcloud/",
|
|
656
|
+
// Azure credentials
|
|
657
|
+
"/.azure/",
|
|
658
|
+
// Environment files
|
|
659
|
+
"/.env",
|
|
660
|
+
"/env"
|
|
661
|
+
];
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// src/validate.ts
|
|
540
666
|
function validateQuery(query2) {
|
|
541
667
|
if (!query2) {
|
|
542
668
|
throw new SecurityError({
|
|
@@ -814,6 +940,9 @@ function validateRLSAction(action) {
|
|
|
814
940
|
function validateRoleName(name) {
|
|
815
941
|
validateIdentifier(name, "variable", false);
|
|
816
942
|
}
|
|
943
|
+
function validateUsername(username) {
|
|
944
|
+
validateIdentifier(username, "variable", false);
|
|
945
|
+
}
|
|
817
946
|
function escapeGQLIdentifier(identifier, type) {
|
|
818
947
|
validateIdentifier(identifier, type, false);
|
|
819
948
|
if (GQL_IDENTIFIER_PATTERN.test(identifier)) {
|
|
@@ -902,76 +1031,12 @@ function validateRLSPredicate(predicate2) {
|
|
|
902
1031
|
});
|
|
903
1032
|
}
|
|
904
1033
|
}
|
|
905
|
-
|
|
906
|
-
const lowerPath = normalizedPath.toLowerCase();
|
|
907
|
-
for (const sensitive of SENSITIVE_PATHS) {
|
|
908
|
-
if (lowerPath.includes(sensitive)) {
|
|
909
|
-
throw new SecurityError({
|
|
910
|
-
type: "input",
|
|
911
|
-
message: `${fieldName} path refers to a restricted system file`
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
function validateTLSCertPath(path2, fieldName) {
|
|
917
|
-
if (!path2) {
|
|
918
|
-
throw new SecurityError({
|
|
919
|
-
type: "input",
|
|
920
|
-
message: `${fieldName} path cannot be empty`
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
if (path2.includes("\0")) {
|
|
924
|
-
throw new SecurityError({
|
|
925
|
-
type: "input",
|
|
926
|
-
message: `${fieldName} path contains null bytes`
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
const normalizedPath = path2.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
930
|
-
if (normalizedPath.includes("/../") || normalizedPath.endsWith("/..")) {
|
|
931
|
-
throw new SecurityError({
|
|
932
|
-
type: "input",
|
|
933
|
-
message: `${fieldName} path contains directory traversal sequence`
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
if (normalizedPath.startsWith("../") || normalizedPath === "..") {
|
|
937
|
-
throw new SecurityError({
|
|
938
|
-
type: "input",
|
|
939
|
-
message: `${fieldName} path contains directory traversal sequence`
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
if (/%2e%2e/i.test(path2) || /%252e/i.test(path2)) {
|
|
943
|
-
throw new SecurityError({
|
|
944
|
-
type: "input",
|
|
945
|
-
message: `${fieldName} path contains encoded traversal sequence`
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
checkAgainstBlocklist(normalizedPath, fieldName);
|
|
949
|
-
try {
|
|
950
|
-
if (fs.existsSync(path2)) {
|
|
951
|
-
const stats = fs.lstatSync(path2);
|
|
952
|
-
if (stats.isSymbolicLink()) {
|
|
953
|
-
const resolvedPath = fs.realpathSync(path2);
|
|
954
|
-
const normalizedResolved = resolvedPath.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
955
|
-
checkAgainstBlocklist(normalizedResolved, fieldName);
|
|
956
|
-
if (normalizedResolved.includes("/../") || normalizedResolved.endsWith("/..")) {
|
|
957
|
-
throw new SecurityError({
|
|
958
|
-
type: "input",
|
|
959
|
-
message: `${fieldName} symlink resolves to a path with directory traversal`
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
} catch (e) {
|
|
965
|
-
if (e instanceof SecurityError) {
|
|
966
|
-
throw e;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
var PARAM_NAME_PATTERN, GQL_IDENTIFIER_PATTERN, MAX_IDENTIFIER_LENGTH, ALLOWED_PERMISSION_ACTIONS, ALLOWED_PERMISSION_RESOURCES, ALLOWED_RLS_ACTIONS, RESERVED_KEYWORDS, RLS_PREDICATE_DANGEROUS_PATTERNS, SENSITIVE_PATHS;
|
|
1034
|
+
var PARAM_NAME_PATTERN, GQL_IDENTIFIER_PATTERN, MAX_IDENTIFIER_LENGTH, ALLOWED_PERMISSION_ACTIONS, ALLOWED_PERMISSION_RESOURCES, ALLOWED_RLS_ACTIONS, RESERVED_KEYWORDS, RLS_PREDICATE_DANGEROUS_PATTERNS;
|
|
971
1035
|
var init_validate = __esm({
|
|
972
1036
|
"src/validate.ts"() {
|
|
973
1037
|
init_errors();
|
|
974
1038
|
init_config();
|
|
1039
|
+
init_validate_tls();
|
|
975
1040
|
PARAM_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
|
|
976
1041
|
GQL_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
|
|
977
1042
|
MAX_IDENTIFIER_LENGTH = 128;
|
|
@@ -1075,97 +1140,687 @@ var init_validate = __esm({
|
|
|
1075
1140
|
// Multiple statements
|
|
1076
1141
|
/\}\s*\{/
|
|
1077
1142
|
];
|
|
1078
|
-
SENSITIVE_PATHS = [
|
|
1079
|
-
// System files
|
|
1080
|
-
"/etc/passwd",
|
|
1081
|
-
"/etc/shadow",
|
|
1082
|
-
"/etc/hosts",
|
|
1083
|
-
"/proc/",
|
|
1084
|
-
"/sys/",
|
|
1085
|
-
"/dev/",
|
|
1086
|
-
// Root home directory
|
|
1087
|
-
"/root/",
|
|
1088
|
-
// SSH keys
|
|
1089
|
-
"/.ssh/",
|
|
1090
|
-
// Kubernetes secrets
|
|
1091
|
-
"/var/run/secrets/",
|
|
1092
|
-
"/run/secrets/",
|
|
1093
|
-
// AWS credentials
|
|
1094
|
-
"/.aws/",
|
|
1095
|
-
"/credentials",
|
|
1096
|
-
// GCP credentials
|
|
1097
|
-
"/.config/gcloud/",
|
|
1098
|
-
"/gcloud/",
|
|
1099
|
-
// Azure credentials
|
|
1100
|
-
"/.azure/",
|
|
1101
|
-
// Environment files
|
|
1102
|
-
"/.env",
|
|
1103
|
-
"/env"
|
|
1104
|
-
];
|
|
1105
1143
|
}
|
|
1106
1144
|
});
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
QuicServerMessageType = protoRoot.lookupType("geode.QuicServerMessage");
|
|
1145
|
+
|
|
1146
|
+
// src/gql-value.ts
|
|
1147
|
+
function isSafeObjectKey(key) {
|
|
1148
|
+
return !DANGEROUS_KEYS.has(key);
|
|
1112
1149
|
}
|
|
1113
|
-
function
|
|
1114
|
-
|
|
1115
|
-
protoRoot = protobuf.loadSync(PROTO_PATH);
|
|
1116
|
-
QuicClientMessageType = protoRoot.lookupType("geode.QuicClientMessage");
|
|
1117
|
-
QuicServerMessageType = protoRoot.lookupType("geode.QuicServerMessage");
|
|
1150
|
+
function isDecimal(value) {
|
|
1151
|
+
return value !== null && typeof value === "object" && "toFixed" in value && "toNumber" in value;
|
|
1118
1152
|
}
|
|
1119
|
-
|
|
1120
|
-
if (
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1153
|
+
function createDecimal(value) {
|
|
1154
|
+
if (isDecimal(value)) return value;
|
|
1155
|
+
return new DecimalClass(value);
|
|
1123
1156
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1157
|
+
var DANGEROUS_KEYS, DecimalClass, GQLValue;
|
|
1158
|
+
var init_gql_value = __esm({
|
|
1159
|
+
async "src/gql-value.ts"() {
|
|
1160
|
+
DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1161
|
+
DecimalClass = // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Module type is incompatible
|
|
1162
|
+
(await import('decimal.js-light')).default;
|
|
1163
|
+
GQLValue = class _GQLValue {
|
|
1164
|
+
kind;
|
|
1165
|
+
_intValue;
|
|
1166
|
+
_floatValue;
|
|
1167
|
+
_boolValue;
|
|
1168
|
+
_stringValue;
|
|
1169
|
+
_decimalValue;
|
|
1170
|
+
_arrayValue;
|
|
1171
|
+
_objectValue;
|
|
1172
|
+
_bytesValue;
|
|
1173
|
+
_dateValue;
|
|
1174
|
+
_rangeValue;
|
|
1175
|
+
_nodeValue;
|
|
1176
|
+
_edgeValue;
|
|
1177
|
+
_pathValue;
|
|
1178
|
+
_rawValue;
|
|
1179
|
+
constructor(kind) {
|
|
1180
|
+
this.kind = kind;
|
|
1147
1181
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1182
|
+
// Factory methods
|
|
1183
|
+
static null() {
|
|
1184
|
+
return new _GQLValue("NULL");
|
|
1185
|
+
}
|
|
1186
|
+
static bool(value) {
|
|
1187
|
+
const v = new _GQLValue("BOOL");
|
|
1188
|
+
v._boolValue = value;
|
|
1189
|
+
return v;
|
|
1190
|
+
}
|
|
1191
|
+
static int(value) {
|
|
1192
|
+
const v = new _GQLValue("INT");
|
|
1193
|
+
v._intValue = typeof value === "bigint" ? value : BigInt(Math.trunc(value));
|
|
1194
|
+
return v;
|
|
1195
|
+
}
|
|
1196
|
+
static float(value) {
|
|
1197
|
+
const v = new _GQLValue("FLOAT");
|
|
1198
|
+
v._floatValue = value;
|
|
1199
|
+
return v;
|
|
1200
|
+
}
|
|
1201
|
+
static string(value) {
|
|
1202
|
+
const v = new _GQLValue("STRING");
|
|
1203
|
+
v._stringValue = value;
|
|
1204
|
+
return v;
|
|
1205
|
+
}
|
|
1206
|
+
static decimal(value) {
|
|
1207
|
+
const v = new _GQLValue("DECIMAL");
|
|
1208
|
+
v._decimalValue = createDecimal(value);
|
|
1209
|
+
return v;
|
|
1210
|
+
}
|
|
1211
|
+
static array(values) {
|
|
1212
|
+
const v = new _GQLValue("ARRAY");
|
|
1213
|
+
v._arrayValue = values;
|
|
1214
|
+
return v;
|
|
1215
|
+
}
|
|
1216
|
+
static object(values) {
|
|
1217
|
+
const v = new _GQLValue("OBJECT");
|
|
1218
|
+
v._objectValue = values instanceof Map ? values : new Map(Object.entries(values));
|
|
1219
|
+
return v;
|
|
1220
|
+
}
|
|
1221
|
+
static bytes(value) {
|
|
1222
|
+
const v = new _GQLValue("BYTEA");
|
|
1223
|
+
v._bytesValue = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
1224
|
+
return v;
|
|
1225
|
+
}
|
|
1226
|
+
static date(value) {
|
|
1227
|
+
const v = new _GQLValue("DATE");
|
|
1228
|
+
v._dateValue = value;
|
|
1229
|
+
return v;
|
|
1230
|
+
}
|
|
1231
|
+
static time(value) {
|
|
1232
|
+
const v = new _GQLValue("TIME");
|
|
1233
|
+
v._dateValue = value;
|
|
1234
|
+
return v;
|
|
1235
|
+
}
|
|
1236
|
+
static timestamp(value) {
|
|
1237
|
+
const v = new _GQLValue("TIMESTAMP");
|
|
1238
|
+
v._dateValue = value;
|
|
1239
|
+
return v;
|
|
1240
|
+
}
|
|
1241
|
+
static uuid(value) {
|
|
1242
|
+
const v = new _GQLValue("UUID");
|
|
1243
|
+
v._stringValue = value;
|
|
1244
|
+
return v;
|
|
1245
|
+
}
|
|
1246
|
+
static json(value) {
|
|
1247
|
+
const v = new _GQLValue("JSON");
|
|
1248
|
+
v._rawValue = value;
|
|
1249
|
+
return v;
|
|
1250
|
+
}
|
|
1251
|
+
static node(value) {
|
|
1252
|
+
const v = new _GQLValue("NODE");
|
|
1253
|
+
v._nodeValue = value;
|
|
1254
|
+
return v;
|
|
1255
|
+
}
|
|
1256
|
+
static edge(value) {
|
|
1257
|
+
const v = new _GQLValue("EDGE");
|
|
1258
|
+
v._edgeValue = value;
|
|
1259
|
+
return v;
|
|
1260
|
+
}
|
|
1261
|
+
static path(value) {
|
|
1262
|
+
const v = new _GQLValue("PATH");
|
|
1263
|
+
v._pathValue = value;
|
|
1264
|
+
return v;
|
|
1265
|
+
}
|
|
1266
|
+
static range(value) {
|
|
1267
|
+
const v = new _GQLValue("RANGE");
|
|
1268
|
+
v._rangeValue = value;
|
|
1269
|
+
return v;
|
|
1270
|
+
}
|
|
1271
|
+
static unknown(value) {
|
|
1272
|
+
const v = new _GQLValue("UNKNOWN");
|
|
1273
|
+
v._rawValue = value;
|
|
1274
|
+
return v;
|
|
1275
|
+
}
|
|
1276
|
+
// Type-safe accessors
|
|
1277
|
+
get isNull() {
|
|
1278
|
+
return this.kind === "NULL";
|
|
1279
|
+
}
|
|
1280
|
+
get asBool() {
|
|
1281
|
+
if (this.kind !== "BOOL") {
|
|
1282
|
+
throw new TypeError(`Cannot convert ${this.kind} to boolean`);
|
|
1283
|
+
}
|
|
1284
|
+
return this._boolValue;
|
|
1285
|
+
}
|
|
1286
|
+
get asInt() {
|
|
1287
|
+
if (this.kind !== "INT") {
|
|
1288
|
+
throw new TypeError(`Cannot convert ${this.kind} to int`);
|
|
1289
|
+
}
|
|
1290
|
+
return this._intValue;
|
|
1291
|
+
}
|
|
1292
|
+
get asNumber() {
|
|
1293
|
+
if (this.kind === "INT") {
|
|
1294
|
+
return Number(this._intValue);
|
|
1295
|
+
}
|
|
1296
|
+
if (this.kind === "FLOAT") {
|
|
1297
|
+
return this._floatValue;
|
|
1298
|
+
}
|
|
1299
|
+
if (this.kind === "DECIMAL") {
|
|
1300
|
+
return this._decimalValue?.toNumber() ?? 0;
|
|
1301
|
+
}
|
|
1302
|
+
throw new TypeError(`Cannot convert ${this.kind} to number`);
|
|
1303
|
+
}
|
|
1304
|
+
get asFloat() {
|
|
1305
|
+
if (this.kind !== "FLOAT") {
|
|
1306
|
+
throw new TypeError(`Cannot convert ${this.kind} to float`);
|
|
1307
|
+
}
|
|
1308
|
+
return this._floatValue;
|
|
1309
|
+
}
|
|
1310
|
+
get asString() {
|
|
1311
|
+
if (this.kind === "STRING" || this.kind === "UUID") {
|
|
1312
|
+
return this._stringValue;
|
|
1313
|
+
}
|
|
1314
|
+
return this.toString();
|
|
1315
|
+
}
|
|
1316
|
+
get asDecimal() {
|
|
1317
|
+
if (this.kind !== "DECIMAL") {
|
|
1318
|
+
throw new TypeError(`Cannot convert ${this.kind} to decimal`);
|
|
1319
|
+
}
|
|
1320
|
+
return this._decimalValue;
|
|
1321
|
+
}
|
|
1322
|
+
get asArray() {
|
|
1323
|
+
if (this.kind !== "ARRAY") {
|
|
1324
|
+
throw new TypeError(`Cannot convert ${this.kind} to array`);
|
|
1325
|
+
}
|
|
1326
|
+
return this._arrayValue;
|
|
1327
|
+
}
|
|
1328
|
+
get asObject() {
|
|
1329
|
+
if (this.kind !== "OBJECT" && this.kind !== "NODE" && this.kind !== "EDGE") {
|
|
1330
|
+
throw new TypeError(`Cannot convert ${this.kind} to object`);
|
|
1331
|
+
}
|
|
1332
|
+
return this._objectValue ?? /* @__PURE__ */ new Map();
|
|
1333
|
+
}
|
|
1334
|
+
get asBytes() {
|
|
1335
|
+
if (this.kind !== "BYTEA") {
|
|
1336
|
+
throw new TypeError(`Cannot convert ${this.kind} to bytes`);
|
|
1337
|
+
}
|
|
1338
|
+
return this._bytesValue;
|
|
1339
|
+
}
|
|
1340
|
+
get asDate() {
|
|
1341
|
+
if (this.kind !== "DATE" && this.kind !== "TIME" && this.kind !== "TIMETZ" && this.kind !== "TIMESTAMP" && this.kind !== "TIMESTAMPTZ") {
|
|
1342
|
+
throw new TypeError(`Cannot convert ${this.kind} to date`);
|
|
1343
|
+
}
|
|
1344
|
+
return this._dateValue;
|
|
1345
|
+
}
|
|
1346
|
+
get asNode() {
|
|
1347
|
+
if (this.kind !== "NODE") {
|
|
1348
|
+
throw new TypeError(`Cannot convert ${this.kind} to node`);
|
|
1349
|
+
}
|
|
1350
|
+
return this._nodeValue;
|
|
1351
|
+
}
|
|
1352
|
+
get asEdge() {
|
|
1353
|
+
if (this.kind !== "EDGE") {
|
|
1354
|
+
throw new TypeError(`Cannot convert ${this.kind} to edge`);
|
|
1355
|
+
}
|
|
1356
|
+
return this._edgeValue;
|
|
1357
|
+
}
|
|
1358
|
+
get asPath() {
|
|
1359
|
+
if (this.kind !== "PATH") {
|
|
1360
|
+
throw new TypeError(`Cannot convert ${this.kind} to path`);
|
|
1361
|
+
}
|
|
1362
|
+
return this._pathValue;
|
|
1363
|
+
}
|
|
1364
|
+
get asRange() {
|
|
1365
|
+
if (this.kind !== "RANGE") {
|
|
1366
|
+
throw new TypeError(`Cannot convert ${this.kind} to range`);
|
|
1367
|
+
}
|
|
1368
|
+
return this._rangeValue;
|
|
1369
|
+
}
|
|
1370
|
+
get asJSON() {
|
|
1371
|
+
if (this.kind !== "JSON" && this.kind !== "JSONB") {
|
|
1372
|
+
throw new TypeError(`Cannot convert ${this.kind} to JSON`);
|
|
1373
|
+
}
|
|
1374
|
+
return this._rawValue;
|
|
1375
|
+
}
|
|
1376
|
+
get raw() {
|
|
1377
|
+
return this._rawValue;
|
|
1378
|
+
}
|
|
1379
|
+
toString() {
|
|
1380
|
+
switch (this.kind) {
|
|
1381
|
+
case "NULL":
|
|
1382
|
+
return "null";
|
|
1383
|
+
case "BOOL":
|
|
1384
|
+
return (this._boolValue ?? false).toString();
|
|
1385
|
+
case "INT":
|
|
1386
|
+
return (this._intValue ?? 0n).toString();
|
|
1387
|
+
case "FLOAT":
|
|
1388
|
+
return (this._floatValue ?? 0).toString();
|
|
1389
|
+
case "STRING":
|
|
1390
|
+
case "UUID":
|
|
1391
|
+
return this._stringValue ?? "";
|
|
1392
|
+
case "DECIMAL":
|
|
1393
|
+
return this._decimalValue?.toString() ?? "0";
|
|
1394
|
+
case "ARRAY":
|
|
1395
|
+
return `[${(this._arrayValue ?? []).map((v) => v.toString()).join(", ")}]`;
|
|
1396
|
+
case "OBJECT":
|
|
1397
|
+
return `{${[...(this._objectValue ?? /* @__PURE__ */ new Map()).entries()].map(([k, v]) => `${k}: ${v.toString()}`).join(", ")}}`;
|
|
1398
|
+
case "BYTEA":
|
|
1399
|
+
return `<bytes:${(this._bytesValue ?? new Uint8Array()).length}>`;
|
|
1400
|
+
case "DATE":
|
|
1401
|
+
case "TIME":
|
|
1402
|
+
case "TIMETZ":
|
|
1403
|
+
case "TIMESTAMP":
|
|
1404
|
+
case "TIMESTAMPTZ":
|
|
1405
|
+
return this._dateValue?.toISOString() ?? "";
|
|
1406
|
+
case "NODE":
|
|
1407
|
+
return `(${this._nodeValue?.labels.join(":")} {${JSON.stringify(this._nodeValue?.properties)}})`;
|
|
1408
|
+
case "EDGE":
|
|
1409
|
+
return `[${this._edgeValue?.type} {${JSON.stringify(this._edgeValue?.properties)}}]`;
|
|
1410
|
+
case "PATH":
|
|
1411
|
+
return `<path:${this._pathValue?.nodes.length} nodes>`;
|
|
1412
|
+
case "JSON":
|
|
1413
|
+
case "JSONB":
|
|
1414
|
+
return JSON.stringify(this._rawValue);
|
|
1415
|
+
default:
|
|
1416
|
+
return String(this._rawValue ?? "");
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Convert to a plain JavaScript value.
|
|
1421
|
+
*/
|
|
1422
|
+
toJS() {
|
|
1423
|
+
switch (this.kind) {
|
|
1424
|
+
case "NULL":
|
|
1425
|
+
return null;
|
|
1426
|
+
case "BOOL":
|
|
1427
|
+
return this._boolValue ?? false;
|
|
1428
|
+
case "INT": {
|
|
1429
|
+
const intVal = this._intValue ?? 0n;
|
|
1430
|
+
if (intVal >= Number.MIN_SAFE_INTEGER && intVal <= Number.MAX_SAFE_INTEGER) {
|
|
1431
|
+
return Number(intVal);
|
|
1432
|
+
}
|
|
1433
|
+
return intVal;
|
|
1434
|
+
}
|
|
1435
|
+
case "FLOAT":
|
|
1436
|
+
return this._floatValue ?? 0;
|
|
1437
|
+
case "STRING":
|
|
1438
|
+
case "UUID":
|
|
1439
|
+
return this._stringValue ?? "";
|
|
1440
|
+
case "DECIMAL":
|
|
1441
|
+
return this._decimalValue?.toString();
|
|
1442
|
+
case "ARRAY":
|
|
1443
|
+
return (this._arrayValue ?? []).map((v) => v.toJS());
|
|
1444
|
+
case "OBJECT": {
|
|
1445
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
1446
|
+
for (const [k, v] of (this._objectValue ?? /* @__PURE__ */ new Map()).entries()) {
|
|
1447
|
+
if (!isSafeObjectKey(k)) {
|
|
1448
|
+
throw new Error(
|
|
1449
|
+
`Object contains dangerous key that could cause prototype pollution: keys like '__proto__', 'constructor', or 'prototype' are not allowed`
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
obj[k] = v.toJS();
|
|
1453
|
+
}
|
|
1454
|
+
return obj;
|
|
1455
|
+
}
|
|
1456
|
+
case "BYTEA":
|
|
1457
|
+
return this._bytesValue ?? new Uint8Array();
|
|
1458
|
+
case "DATE":
|
|
1459
|
+
case "TIME":
|
|
1460
|
+
case "TIMETZ":
|
|
1461
|
+
case "TIMESTAMP":
|
|
1462
|
+
case "TIMESTAMPTZ":
|
|
1463
|
+
return this._dateValue;
|
|
1464
|
+
case "NODE":
|
|
1465
|
+
return this._nodeValue;
|
|
1466
|
+
case "EDGE":
|
|
1467
|
+
return this._edgeValue;
|
|
1468
|
+
case "PATH":
|
|
1469
|
+
return this._pathValue;
|
|
1470
|
+
case "RANGE":
|
|
1471
|
+
return this._rangeValue;
|
|
1472
|
+
case "JSON":
|
|
1473
|
+
case "JSONB":
|
|
1474
|
+
return this._rawValue;
|
|
1475
|
+
default:
|
|
1476
|
+
return this._rawValue;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Convert to JSON-serializable format.
|
|
1481
|
+
*/
|
|
1482
|
+
toJSON() {
|
|
1483
|
+
return this.toJS();
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
// src/types.ts
|
|
1490
|
+
function parseGQLType(typeStr) {
|
|
1491
|
+
const upper = typeStr.toUpperCase();
|
|
1492
|
+
switch (upper) {
|
|
1493
|
+
case "INT":
|
|
1494
|
+
case "INTEGER":
|
|
1495
|
+
case "BIGINT":
|
|
1496
|
+
case "INT8":
|
|
1497
|
+
case "INT16":
|
|
1498
|
+
case "INT32":
|
|
1499
|
+
case "INT64":
|
|
1500
|
+
case "UINT8":
|
|
1501
|
+
case "UINT16":
|
|
1502
|
+
case "UINT32":
|
|
1503
|
+
case "UINT64":
|
|
1504
|
+
case "SMALLINT":
|
|
1505
|
+
case "TINYINT":
|
|
1506
|
+
return "INT";
|
|
1507
|
+
case "FLOAT":
|
|
1508
|
+
case "DOUBLE":
|
|
1509
|
+
case "REAL":
|
|
1510
|
+
case "FLOAT32":
|
|
1511
|
+
case "FLOAT64":
|
|
1512
|
+
return "FLOAT";
|
|
1513
|
+
case "DECIMAL":
|
|
1514
|
+
case "NUMERIC":
|
|
1515
|
+
return "DECIMAL";
|
|
1516
|
+
case "STRING":
|
|
1517
|
+
case "VARCHAR":
|
|
1518
|
+
case "TEXT":
|
|
1519
|
+
return "STRING";
|
|
1520
|
+
case "BOOL":
|
|
1521
|
+
case "BOOLEAN":
|
|
1522
|
+
return "BOOL";
|
|
1523
|
+
case "NULL":
|
|
1524
|
+
return "NULL";
|
|
1525
|
+
case "LIST":
|
|
1526
|
+
case "ARRAY":
|
|
1527
|
+
return "ARRAY";
|
|
1528
|
+
case "MAP":
|
|
1529
|
+
case "RECORD":
|
|
1530
|
+
case "OBJECT":
|
|
1531
|
+
return "OBJECT";
|
|
1532
|
+
case "NODE":
|
|
1533
|
+
return "NODE";
|
|
1534
|
+
case "EDGE":
|
|
1535
|
+
case "RELATIONSHIP":
|
|
1536
|
+
return "EDGE";
|
|
1537
|
+
case "PATH":
|
|
1538
|
+
return "PATH";
|
|
1539
|
+
case "BYTEA":
|
|
1540
|
+
case "BINARY":
|
|
1541
|
+
return "BYTEA";
|
|
1542
|
+
case "DATE":
|
|
1543
|
+
return "DATE";
|
|
1544
|
+
case "TIME":
|
|
1545
|
+
return "TIME";
|
|
1546
|
+
case "TIMETZ":
|
|
1547
|
+
case "TIME WITH TIME ZONE":
|
|
1548
|
+
return "TIMETZ";
|
|
1549
|
+
case "TIMESTAMP":
|
|
1550
|
+
return "TIMESTAMP";
|
|
1551
|
+
case "TIMESTAMPTZ":
|
|
1552
|
+
case "TIMESTAMP WITH TIME ZONE":
|
|
1553
|
+
return "TIMESTAMPTZ";
|
|
1554
|
+
case "INTERVAL":
|
|
1555
|
+
return "INTERVAL";
|
|
1556
|
+
case "JSON":
|
|
1557
|
+
return "JSON";
|
|
1558
|
+
case "JSONB":
|
|
1559
|
+
return "JSONB";
|
|
1560
|
+
case "XML":
|
|
1561
|
+
return "XML";
|
|
1562
|
+
case "UUID":
|
|
1563
|
+
return "UUID";
|
|
1564
|
+
case "URL":
|
|
1565
|
+
return "URL";
|
|
1566
|
+
case "DOMAIN":
|
|
1567
|
+
return "DOMAIN";
|
|
1568
|
+
case "ENUM":
|
|
1569
|
+
return "ENUM";
|
|
1570
|
+
case "BIT":
|
|
1571
|
+
case "VARBIT":
|
|
1572
|
+
return "BIT_STRING";
|
|
1573
|
+
default:
|
|
1574
|
+
if (upper.includes("RANGE")) {
|
|
1575
|
+
return "RANGE";
|
|
1576
|
+
}
|
|
1577
|
+
return "UNKNOWN";
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function fromJSON(value, typeHint) {
|
|
1581
|
+
if (value === null || value === void 0) {
|
|
1582
|
+
return GQLValue.null();
|
|
1583
|
+
}
|
|
1584
|
+
if (typeHint) {
|
|
1585
|
+
return convertWithType(value, typeHint);
|
|
1586
|
+
}
|
|
1587
|
+
if (typeof value === "boolean") {
|
|
1588
|
+
return GQLValue.bool(value);
|
|
1589
|
+
}
|
|
1590
|
+
if (typeof value === "number") {
|
|
1591
|
+
if (Number.isInteger(value)) {
|
|
1592
|
+
return GQLValue.int(value);
|
|
1593
|
+
}
|
|
1594
|
+
return GQLValue.float(value);
|
|
1595
|
+
}
|
|
1596
|
+
if (typeof value === "bigint") {
|
|
1597
|
+
return GQLValue.int(value);
|
|
1598
|
+
}
|
|
1599
|
+
if (typeof value === "string") {
|
|
1600
|
+
return GQLValue.string(value);
|
|
1601
|
+
}
|
|
1602
|
+
if (Array.isArray(value)) {
|
|
1603
|
+
return GQLValue.array(value.map((v) => fromJSON(v)));
|
|
1604
|
+
}
|
|
1605
|
+
if (typeof value === "object") {
|
|
1606
|
+
const obj = value;
|
|
1607
|
+
if ("id" in obj && "labels" in obj && "properties" in obj) {
|
|
1608
|
+
return GQLValue.node(obj);
|
|
1609
|
+
}
|
|
1610
|
+
if ("id" in obj && "type" in obj) {
|
|
1611
|
+
const hasStartEnd = "startNode" in obj && "endNode" in obj;
|
|
1612
|
+
const hasFromTo = "from" in obj && "to" in obj;
|
|
1613
|
+
if (hasStartEnd || hasFromTo) {
|
|
1614
|
+
const rawEdge = obj;
|
|
1615
|
+
const normalizedEdge = {
|
|
1616
|
+
id: rawEdge.id,
|
|
1617
|
+
type: rawEdge.type,
|
|
1618
|
+
startNode: rawEdge.startNode ?? rawEdge.from ?? "",
|
|
1619
|
+
endNode: rawEdge.endNode ?? rawEdge.to ?? "",
|
|
1620
|
+
properties: rawEdge.properties ?? {}
|
|
1621
|
+
};
|
|
1622
|
+
return GQLValue.edge(normalizedEdge);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if ("nodes" in obj && "edges" in obj && Array.isArray(obj.nodes)) {
|
|
1626
|
+
return GQLValue.path(obj);
|
|
1627
|
+
}
|
|
1628
|
+
const map = /* @__PURE__ */ new Map();
|
|
1629
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1630
|
+
map.set(k, fromJSON(v));
|
|
1631
|
+
}
|
|
1632
|
+
return GQLValue.object(map);
|
|
1633
|
+
}
|
|
1634
|
+
return GQLValue.unknown(value);
|
|
1635
|
+
}
|
|
1636
|
+
function convertWithType(value, type) {
|
|
1637
|
+
switch (type) {
|
|
1638
|
+
case "NULL":
|
|
1639
|
+
return GQLValue.null();
|
|
1640
|
+
case "BOOL":
|
|
1641
|
+
if (typeof value === "string") {
|
|
1642
|
+
const lower = value.toLowerCase();
|
|
1643
|
+
return GQLValue.bool(lower === "true" || lower === "1" || lower === "t");
|
|
1644
|
+
}
|
|
1645
|
+
return GQLValue.bool(Boolean(value));
|
|
1646
|
+
case "INT":
|
|
1647
|
+
if (typeof value === "number") {
|
|
1648
|
+
return GQLValue.int(value);
|
|
1649
|
+
}
|
|
1650
|
+
if (typeof value === "bigint") {
|
|
1651
|
+
return GQLValue.int(value);
|
|
1652
|
+
}
|
|
1653
|
+
if (typeof value === "string") {
|
|
1654
|
+
const trimmed = value.trim();
|
|
1655
|
+
if (trimmed === "" || isNaN(Number(trimmed))) {
|
|
1656
|
+
return GQLValue.int(0);
|
|
1657
|
+
}
|
|
1658
|
+
return GQLValue.int(BigInt(trimmed));
|
|
1659
|
+
}
|
|
1660
|
+
return GQLValue.int(0);
|
|
1661
|
+
case "FLOAT":
|
|
1662
|
+
return GQLValue.float(Number(value));
|
|
1663
|
+
case "DECIMAL":
|
|
1664
|
+
return GQLValue.decimal(String(value));
|
|
1665
|
+
case "STRING":
|
|
1666
|
+
if (typeof value !== "string") {
|
|
1667
|
+
return fromJSON(value);
|
|
1668
|
+
}
|
|
1669
|
+
return GQLValue.string(value);
|
|
1670
|
+
case "UUID":
|
|
1671
|
+
return GQLValue.uuid(String(value));
|
|
1672
|
+
case "BYTEA":
|
|
1673
|
+
if (value instanceof Uint8Array) {
|
|
1674
|
+
return GQLValue.bytes(value);
|
|
1675
|
+
}
|
|
1676
|
+
if (typeof value === "string") {
|
|
1677
|
+
return GQLValue.bytes(Buffer.from(value, "base64"));
|
|
1678
|
+
}
|
|
1679
|
+
return GQLValue.bytes(new Uint8Array());
|
|
1680
|
+
case "DATE":
|
|
1681
|
+
return GQLValue.date(value instanceof Date ? value : new Date(String(value)));
|
|
1682
|
+
case "TIME":
|
|
1683
|
+
case "TIMETZ":
|
|
1684
|
+
return GQLValue.time(value instanceof Date ? value : new Date(String(value)));
|
|
1685
|
+
case "TIMESTAMP":
|
|
1686
|
+
case "TIMESTAMPTZ":
|
|
1687
|
+
return GQLValue.timestamp(value instanceof Date ? value : new Date(String(value)));
|
|
1688
|
+
case "JSON":
|
|
1689
|
+
case "JSONB":
|
|
1690
|
+
return GQLValue.json(value);
|
|
1691
|
+
case "ARRAY":
|
|
1692
|
+
if (Array.isArray(value)) {
|
|
1693
|
+
return GQLValue.array(value.map((v) => fromJSON(v)));
|
|
1694
|
+
}
|
|
1695
|
+
if (typeof value === "string") {
|
|
1696
|
+
try {
|
|
1697
|
+
const parsed = JSON.parse(value);
|
|
1698
|
+
if (Array.isArray(parsed)) {
|
|
1699
|
+
return GQLValue.array(parsed.map((v) => fromJSON(v)));
|
|
1700
|
+
}
|
|
1701
|
+
} catch {
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return GQLValue.array([]);
|
|
1705
|
+
case "OBJECT":
|
|
1706
|
+
if (typeof value === "string") {
|
|
1707
|
+
try {
|
|
1708
|
+
const parsed = JSON.parse(value);
|
|
1709
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1710
|
+
return fromJSON(parsed);
|
|
1711
|
+
}
|
|
1712
|
+
} catch {
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return fromJSON(value);
|
|
1716
|
+
case "NODE":
|
|
1717
|
+
return GQLValue.node(value);
|
|
1718
|
+
case "EDGE": {
|
|
1719
|
+
const rawEdge = value;
|
|
1720
|
+
const normalizedEdge = {
|
|
1721
|
+
id: rawEdge.id,
|
|
1722
|
+
type: rawEdge.type,
|
|
1723
|
+
startNode: rawEdge.startNode ?? rawEdge.from ?? "",
|
|
1724
|
+
endNode: rawEdge.endNode ?? rawEdge.to ?? "",
|
|
1725
|
+
properties: rawEdge.properties ?? {}
|
|
1726
|
+
};
|
|
1727
|
+
return GQLValue.edge(normalizedEdge);
|
|
1728
|
+
}
|
|
1729
|
+
case "PATH":
|
|
1730
|
+
return GQLValue.path(value);
|
|
1731
|
+
default:
|
|
1732
|
+
return fromJSON(value);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
function parseRow(raw, columns) {
|
|
1736
|
+
const row = /* @__PURE__ */ new Map();
|
|
1737
|
+
for (const col of columns) {
|
|
1738
|
+
const value = raw[col.name];
|
|
1739
|
+
row.set(col.name, fromJSON(value, col.kind));
|
|
1740
|
+
}
|
|
1741
|
+
return row;
|
|
1742
|
+
}
|
|
1743
|
+
function rowToObject(row) {
|
|
1744
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
1745
|
+
for (const [key, value] of row) {
|
|
1746
|
+
if (!isSafeObjectKey(key)) {
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`Row contains dangerous key that could cause prototype pollution: column names like '__proto__', 'constructor', or 'prototype' are not allowed`
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
obj[key] = value.toJS();
|
|
1752
|
+
}
|
|
1753
|
+
return obj;
|
|
1754
|
+
}
|
|
1755
|
+
var init_types = __esm({
|
|
1756
|
+
async "src/types.ts"() {
|
|
1757
|
+
await init_gql_value();
|
|
1758
|
+
await init_gql_value();
|
|
1759
|
+
await init_gql_value();
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
async function initProto() {
|
|
1763
|
+
if (protoRoot) return;
|
|
1764
|
+
protoRoot = await protobuf.load(PROTO_PATH);
|
|
1765
|
+
QuicClientMessageType = protoRoot.lookupType("geode.QuicClientMessage");
|
|
1766
|
+
QuicServerMessageType = protoRoot.lookupType("geode.QuicServerMessage");
|
|
1767
|
+
}
|
|
1768
|
+
function initProtoSync() {
|
|
1769
|
+
if (protoRoot) return;
|
|
1770
|
+
protoRoot = protobuf.loadSync(PROTO_PATH);
|
|
1771
|
+
QuicClientMessageType = protoRoot.lookupType("geode.QuicClientMessage");
|
|
1772
|
+
QuicServerMessageType = protoRoot.lookupType("geode.QuicServerMessage");
|
|
1773
|
+
}
|
|
1774
|
+
async function ensureProtoInitialized() {
|
|
1775
|
+
if (!protoRoot) {
|
|
1776
|
+
await initProto();
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
function jsToProtoValue(value) {
|
|
1780
|
+
if (value === null || value === void 0) {
|
|
1781
|
+
return { nullVal: {} };
|
|
1782
|
+
}
|
|
1783
|
+
if (typeof value === "boolean") {
|
|
1784
|
+
return { boolVal: value };
|
|
1785
|
+
}
|
|
1786
|
+
if (typeof value === "number") {
|
|
1787
|
+
if (Number.isInteger(value)) {
|
|
1788
|
+
return { intVal: { value, kind: 1 } };
|
|
1789
|
+
}
|
|
1790
|
+
return { doubleVal: { value, kind: 1 } };
|
|
1791
|
+
}
|
|
1792
|
+
if (typeof value === "bigint") {
|
|
1793
|
+
return { intVal: { value, kind: 3 } };
|
|
1794
|
+
}
|
|
1795
|
+
if (typeof value === "string") {
|
|
1796
|
+
return { stringVal: { value, kind: 1 } };
|
|
1797
|
+
}
|
|
1798
|
+
if (Array.isArray(value)) {
|
|
1799
|
+
return {
|
|
1800
|
+
listVal: {
|
|
1801
|
+
values: value.map(jsToProtoValue)
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
|
|
1806
|
+
return { bytesVal: { value, kind: 1 } };
|
|
1807
|
+
}
|
|
1808
|
+
if (typeof value === "object") {
|
|
1809
|
+
const entries = [];
|
|
1810
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1811
|
+
entries.push({ key: k, value: jsToProtoValue(v) });
|
|
1812
|
+
}
|
|
1813
|
+
return { mapVal: { entries } };
|
|
1814
|
+
}
|
|
1815
|
+
return { stringVal: { value: String(value), kind: 1 } };
|
|
1816
|
+
}
|
|
1817
|
+
function protoValueToJS(val) {
|
|
1818
|
+
if (val.nullVal !== void 0) return null;
|
|
1819
|
+
if (val.boolVal !== void 0) return val.boolVal;
|
|
1820
|
+
if (val.intVal !== void 0) {
|
|
1821
|
+
const n = val.intVal.value;
|
|
1822
|
+
if (typeof n === "bigint") {
|
|
1823
|
+
if (n >= BigInt(Number.MIN_SAFE_INTEGER) && n <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
1169
1824
|
return Number(n);
|
|
1170
1825
|
}
|
|
1171
1826
|
return n;
|
|
@@ -1182,16 +1837,26 @@ function protoValueToJS(val) {
|
|
|
1182
1837
|
return val.listVal.values.map(protoValueToJS);
|
|
1183
1838
|
}
|
|
1184
1839
|
if (val.mapVal !== void 0) {
|
|
1185
|
-
const obj =
|
|
1840
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
1186
1841
|
for (const entry of val.mapVal.entries) {
|
|
1842
|
+
if (!isSafeObjectKey(entry.key)) {
|
|
1843
|
+
throw new Error(
|
|
1844
|
+
`Map contains dangerous key that could cause prototype pollution: keys like '__proto__', 'constructor', or 'prototype' are not allowed`
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1187
1847
|
obj[entry.key] = protoValueToJS(entry.value);
|
|
1188
1848
|
}
|
|
1189
1849
|
return obj;
|
|
1190
1850
|
}
|
|
1191
1851
|
if (val.nodeVal !== void 0) {
|
|
1192
1852
|
const node2 = val.nodeVal;
|
|
1193
|
-
const props =
|
|
1853
|
+
const props = /* @__PURE__ */ Object.create(null);
|
|
1194
1854
|
for (const entry of node2.properties) {
|
|
1855
|
+
if (!isSafeObjectKey(entry.key)) {
|
|
1856
|
+
throw new Error(
|
|
1857
|
+
`Node property contains dangerous key that could cause prototype pollution: keys like '__proto__', 'constructor', or 'prototype' are not allowed`
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1195
1860
|
props[entry.key] = protoValueToJS(entry.value);
|
|
1196
1861
|
}
|
|
1197
1862
|
return {
|
|
@@ -1202,8 +1867,13 @@ function protoValueToJS(val) {
|
|
|
1202
1867
|
}
|
|
1203
1868
|
if (val.edgeVal !== void 0) {
|
|
1204
1869
|
const edge2 = val.edgeVal;
|
|
1205
|
-
const props =
|
|
1870
|
+
const props = /* @__PURE__ */ Object.create(null);
|
|
1206
1871
|
for (const entry of edge2.properties) {
|
|
1872
|
+
if (!isSafeObjectKey(entry.key)) {
|
|
1873
|
+
throw new Error(
|
|
1874
|
+
`Edge property contains dangerous key that could cause prototype pollution: keys like '__proto__', 'constructor', or 'prototype' are not allowed`
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1207
1877
|
props[entry.key] = protoValueToJS(entry.value);
|
|
1208
1878
|
}
|
|
1209
1879
|
return {
|
|
@@ -1349,10 +2019,15 @@ function buildRollbackToRequest(name, sessionId) {
|
|
|
1349
2019
|
};
|
|
1350
2020
|
}
|
|
1351
2021
|
function rowToRecord(row, columns) {
|
|
1352
|
-
const record =
|
|
2022
|
+
const record = /* @__PURE__ */ Object.create(null);
|
|
1353
2023
|
for (let i = 0; i < columns.length && i < row.values.length; i++) {
|
|
1354
2024
|
const col = columns[i];
|
|
1355
2025
|
const val = row.values[i];
|
|
2026
|
+
if (!isSafeObjectKey(col.name)) {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
`Row contains dangerous column name that could cause prototype pollution: column names like '__proto__', 'constructor', or 'prototype' are not allowed`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
1356
2031
|
record[col.name] = protoValueToJS(val);
|
|
1357
2032
|
}
|
|
1358
2033
|
return record;
|
|
@@ -1362,14 +2037,14 @@ function getProtoPath() {
|
|
|
1362
2037
|
}
|
|
1363
2038
|
var __filename$1, __dirname$1, PROTO_PATH, protoRoot, QuicClientMessageType, QuicServerMessageType;
|
|
1364
2039
|
var init_proto = __esm({
|
|
1365
|
-
"src/proto.ts"() {
|
|
2040
|
+
async "src/proto.ts"() {
|
|
2041
|
+
await init_types();
|
|
1366
2042
|
__filename$1 = fileURLToPath(import.meta.url);
|
|
1367
2043
|
__dirname$1 = path.dirname(__filename$1);
|
|
1368
2044
|
PROTO_PATH = path.resolve(__dirname$1, "..", "proto", "geode.proto");
|
|
1369
2045
|
protoRoot = null;
|
|
1370
2046
|
QuicClientMessageType = null;
|
|
1371
2047
|
QuicServerMessageType = null;
|
|
1372
|
-
initProtoSync();
|
|
1373
2048
|
}
|
|
1374
2049
|
});
|
|
1375
2050
|
|
|
@@ -1379,36 +2054,31 @@ __export(grpc_transport_exports, {
|
|
|
1379
2054
|
GrpcTransport: () => GrpcTransport
|
|
1380
2055
|
});
|
|
1381
2056
|
async function buildGrpcSslOptions(cfg) {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
} else if (cfg.tlsKey) {
|
|
1400
|
-
validateTLSCertPath(cfg.tlsKey, "tlsKey");
|
|
1401
|
-
privateKey = await fs.promises.readFile(cfg.tlsKey);
|
|
1402
|
-
}
|
|
1403
|
-
return { rootCerts, privateKey, certChain };
|
|
2057
|
+
const tlsConfig = await buildTLSConfig(cfg);
|
|
2058
|
+
const toBuffer = (value) => {
|
|
2059
|
+
if (value === void 0 || value === null) return null;
|
|
2060
|
+
if (Buffer.isBuffer(value)) return value;
|
|
2061
|
+
if (typeof value === "string") return Buffer.from(value);
|
|
2062
|
+
if (Array.isArray(value)) {
|
|
2063
|
+
return Buffer.concat(
|
|
2064
|
+
value.map((v) => Buffer.isBuffer(v) ? v : Buffer.from(String(v)))
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
return null;
|
|
2068
|
+
};
|
|
2069
|
+
return {
|
|
2070
|
+
rootCerts: toBuffer(tlsConfig.ca),
|
|
2071
|
+
privateKey: toBuffer(tlsConfig.key),
|
|
2072
|
+
certChain: toBuffer(tlsConfig.cert)
|
|
2073
|
+
};
|
|
1404
2074
|
}
|
|
1405
|
-
var PROTO_PATH2, PROTO_LOADER_OPTIONS, GrpcTransport;
|
|
2075
|
+
var PROTO_PATH2, PROTO_LOADER_OPTIONS, DEFAULT_BACKPRESSURE_THRESHOLD, DEFAULT_STREAM_TIMEOUT_MS, GrpcTransport;
|
|
1406
2076
|
var init_grpc_transport = __esm({
|
|
1407
|
-
"src/grpc-transport.ts"() {
|
|
2077
|
+
async "src/grpc-transport.ts"() {
|
|
1408
2078
|
init_config();
|
|
1409
2079
|
init_errors();
|
|
1410
|
-
|
|
1411
|
-
|
|
2080
|
+
await init_proto();
|
|
2081
|
+
await init_transport();
|
|
1412
2082
|
PROTO_PATH2 = getProtoPath();
|
|
1413
2083
|
PROTO_LOADER_OPTIONS = {
|
|
1414
2084
|
keepCase: false,
|
|
@@ -1417,12 +2087,20 @@ var init_grpc_transport = __esm({
|
|
|
1417
2087
|
defaults: true,
|
|
1418
2088
|
oneofs: true
|
|
1419
2089
|
};
|
|
2090
|
+
DEFAULT_BACKPRESSURE_THRESHOLD = 2;
|
|
2091
|
+
DEFAULT_STREAM_TIMEOUT_MS = 12e4;
|
|
1420
2092
|
GrpcTransport = class _GrpcTransport {
|
|
1421
2093
|
_client = null;
|
|
1422
2094
|
_closed = false;
|
|
1423
2095
|
_address;
|
|
1424
2096
|
_pendingResponses = [];
|
|
2097
|
+
_pendingProtoReads = [];
|
|
1425
2098
|
_activeStream = null;
|
|
2099
|
+
_streamPaused = false;
|
|
2100
|
+
_backpressureThreshold = DEFAULT_BACKPRESSURE_THRESHOLD;
|
|
2101
|
+
_streamTimeoutMs = DEFAULT_STREAM_TIMEOUT_MS;
|
|
2102
|
+
_streamTimeoutTimer = null;
|
|
2103
|
+
_streamComplete = false;
|
|
1426
2104
|
constructor(address) {
|
|
1427
2105
|
this._address = address;
|
|
1428
2106
|
}
|
|
@@ -1432,6 +2110,9 @@ var init_grpc_transport = __esm({
|
|
|
1432
2110
|
static async connect(cfg) {
|
|
1433
2111
|
const address = getAddress(cfg);
|
|
1434
2112
|
const transport = new _GrpcTransport(address);
|
|
2113
|
+
if (cfg.requestTimeout !== void 0) {
|
|
2114
|
+
transport._streamTimeoutMs = cfg.requestTimeout;
|
|
2115
|
+
}
|
|
1435
2116
|
try {
|
|
1436
2117
|
const packageDefinition = await protoLoader.load(PROTO_PATH2, PROTO_LOADER_OPTIONS);
|
|
1437
2118
|
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
|
@@ -1439,9 +2120,23 @@ var init_grpc_transport = __esm({
|
|
|
1439
2120
|
if (cfg.tls === false) {
|
|
1440
2121
|
credentials2 = grpc.credentials.createInsecure();
|
|
1441
2122
|
} else if (cfg.insecureSkipVerify) {
|
|
2123
|
+
const isProduction = process.env["NODE_ENV"] === "production" || process.env["GEODE_ENV"] === "production";
|
|
2124
|
+
if (isProduction) {
|
|
2125
|
+
console.warn(
|
|
2126
|
+
"SECURITY WARNING: insecureSkipVerify is enabled in production environment. This disables TLS certificate verification and exposes the connection to MITM attacks. Set GEODE_ALLOW_INSECURE_PRODUCTION=true to override this warning."
|
|
2127
|
+
);
|
|
2128
|
+
if (process.env["GEODE_ALLOW_INSECURE_PRODUCTION"] !== "true") {
|
|
2129
|
+
throw new TransportError({
|
|
2130
|
+
operation: "connect",
|
|
2131
|
+
cause: new Error(
|
|
2132
|
+
"insecureSkipVerify is not allowed in production. Configure proper TLS certificates or set GEODE_ALLOW_INSECURE_PRODUCTION=true to override."
|
|
2133
|
+
)
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
1442
2137
|
let rootCerts = null;
|
|
1443
2138
|
if (cfg.tlsCA) {
|
|
1444
|
-
rootCerts = await import('fs').then((
|
|
2139
|
+
rootCerts = await import('fs').then((fs3) => fs3.promises.readFile(cfg.tlsCA));
|
|
1445
2140
|
} else if (cfg.tlsCACert) {
|
|
1446
2141
|
rootCerts = Buffer.from(cfg.tlsCACert);
|
|
1447
2142
|
}
|
|
@@ -1457,9 +2152,9 @@ var init_grpc_transport = __esm({
|
|
|
1457
2152
|
);
|
|
1458
2153
|
}
|
|
1459
2154
|
const options = {
|
|
1460
|
-
"grpc.max_receive_message_length":
|
|
1461
|
-
//
|
|
1462
|
-
"grpc.max_send_message_length":
|
|
2155
|
+
"grpc.max_receive_message_length": 16 * 1024 * 1024,
|
|
2156
|
+
// 16 MiB (matches QUIC transport)
|
|
2157
|
+
"grpc.max_send_message_length": 16 * 1024 * 1024,
|
|
1463
2158
|
"grpc.keepalive_time_ms": cfg.keepAliveInterval ?? 1e4,
|
|
1464
2159
|
"grpc.keepalive_timeout_ms": 5e3,
|
|
1465
2160
|
"grpc.keepalive_permit_without_calls": 1
|
|
@@ -1490,45 +2185,55 @@ var init_grpc_transport = __esm({
|
|
|
1490
2185
|
try {
|
|
1491
2186
|
if (msg.hello) {
|
|
1492
2187
|
const response = await this.callUnary("handshake", msg.hello, signal);
|
|
1493
|
-
this.
|
|
2188
|
+
this.enqueueResponse({ hello: response });
|
|
1494
2189
|
} else if (msg.execute) {
|
|
1495
2190
|
await this.callServerStream(msg.execute, signal);
|
|
1496
2191
|
} else if (msg.pull) {
|
|
1497
|
-
if (this._pendingResponses.length === 0) {
|
|
1498
|
-
this.
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
2192
|
+
if (this._pendingResponses.length === 0 && this._pendingProtoReads.length === 0) {
|
|
2193
|
+
if (this._activeStream && this._streamPaused) {
|
|
2194
|
+
this._streamPaused = false;
|
|
2195
|
+
this._activeStream.resume();
|
|
2196
|
+
}
|
|
2197
|
+
if (this._streamComplete || !this._activeStream) {
|
|
2198
|
+
this.enqueueResponse({
|
|
2199
|
+
execute: {
|
|
2200
|
+
status: {
|
|
2201
|
+
statusClass: "00000",
|
|
2202
|
+
statusSubclass: "",
|
|
2203
|
+
additionalStatuses: [],
|
|
2204
|
+
flaggerFindings: []
|
|
2205
|
+
},
|
|
2206
|
+
payload: "page",
|
|
2207
|
+
page: { rows: [], final: true, ordered: false, orderKeys: [] }
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
1510
2211
|
}
|
|
1511
2212
|
} else if (msg.ping) {
|
|
1512
2213
|
const response = await this.callUnary("ping", msg.ping, signal);
|
|
1513
|
-
this.
|
|
2214
|
+
this.enqueueResponse({ ping: response });
|
|
1514
2215
|
} else if (msg.begin) {
|
|
1515
2216
|
const response = await this.callUnary("begin", msg.begin, signal);
|
|
1516
|
-
this.
|
|
2217
|
+
this.enqueueResponse({ begin: response });
|
|
1517
2218
|
} else if (msg.commit) {
|
|
1518
2219
|
const response = await this.callUnary("commit", msg.commit, signal);
|
|
1519
|
-
this.
|
|
2220
|
+
this.enqueueResponse({ commit: response });
|
|
1520
2221
|
} else if (msg.rollback) {
|
|
1521
2222
|
const response = await this.callUnary("rollback", msg.rollback, signal);
|
|
1522
|
-
this.
|
|
2223
|
+
this.enqueueResponse({ rollback: response });
|
|
1523
2224
|
} else if (msg.savepoint) {
|
|
1524
|
-
throw new
|
|
1525
|
-
|
|
1526
|
-
|
|
2225
|
+
throw new DriverError({
|
|
2226
|
+
statusClass: StatusClass.SYSTEM,
|
|
2227
|
+
subclass: "",
|
|
2228
|
+
code: "58000",
|
|
2229
|
+
message: "Savepoint operations are not supported over gRPC transport. Use QUIC transport (quic://) for savepoint support."
|
|
1527
2230
|
});
|
|
1528
2231
|
} else if (msg.rollbackTo) {
|
|
1529
|
-
throw new
|
|
1530
|
-
|
|
1531
|
-
|
|
2232
|
+
throw new DriverError({
|
|
2233
|
+
statusClass: StatusClass.SYSTEM,
|
|
2234
|
+
subclass: "",
|
|
2235
|
+
code: "58000",
|
|
2236
|
+
message: "Savepoint operations are not supported over gRPC transport. Use QUIC transport (quic://) for savepoint support."
|
|
1532
2237
|
});
|
|
1533
2238
|
} else {
|
|
1534
2239
|
throw new TransportError({
|
|
@@ -1537,7 +2242,7 @@ var init_grpc_transport = __esm({
|
|
|
1537
2242
|
});
|
|
1538
2243
|
}
|
|
1539
2244
|
} catch (e) {
|
|
1540
|
-
if (e instanceof TransportError) throw e;
|
|
2245
|
+
if (e instanceof TransportError || e instanceof DriverError) throw e;
|
|
1541
2246
|
throw new TransportError({
|
|
1542
2247
|
operation: "sendProto",
|
|
1543
2248
|
address: this._address,
|
|
@@ -1547,8 +2252,8 @@ var init_grpc_transport = __esm({
|
|
|
1547
2252
|
}
|
|
1548
2253
|
/**
|
|
1549
2254
|
* Receive a protobuf message from the queue.
|
|
2255
|
+
* If no response is buffered, waits for the next streamed message.
|
|
1550
2256
|
*/
|
|
1551
|
-
// eslint-disable-next-line @typescript-eslint/require-await -- Method must be async for interface compatibility
|
|
1552
2257
|
async receiveProto(signal) {
|
|
1553
2258
|
this.checkClosed();
|
|
1554
2259
|
if (signal?.aborted) {
|
|
@@ -1556,33 +2261,37 @@ var init_grpc_transport = __esm({
|
|
|
1556
2261
|
}
|
|
1557
2262
|
const response = this._pendingResponses.shift();
|
|
1558
2263
|
if (response) {
|
|
2264
|
+
if (this._activeStream && this._streamPaused && this._pendingResponses.length < this._backpressureThreshold) {
|
|
2265
|
+
this._streamPaused = false;
|
|
2266
|
+
this._activeStream.resume();
|
|
2267
|
+
}
|
|
1559
2268
|
return response;
|
|
1560
2269
|
}
|
|
2270
|
+
if (this._activeStream && !this._streamComplete) {
|
|
2271
|
+
return new Promise((resolve2, reject) => {
|
|
2272
|
+
const pendingRead = { resolve: resolve2, reject };
|
|
2273
|
+
this._pendingProtoReads.push(pendingRead);
|
|
2274
|
+
if (this._activeStream && this._streamPaused) {
|
|
2275
|
+
this._streamPaused = false;
|
|
2276
|
+
this._activeStream.resume();
|
|
2277
|
+
}
|
|
2278
|
+
if (signal) {
|
|
2279
|
+
const onAbort = () => {
|
|
2280
|
+
const idx = this._pendingProtoReads.indexOf(pendingRead);
|
|
2281
|
+
if (idx !== -1) {
|
|
2282
|
+
this._pendingProtoReads.splice(idx, 1);
|
|
2283
|
+
}
|
|
2284
|
+
reject(new TransportError({ operation: "receiveProto", cause: new Error("Aborted") }));
|
|
2285
|
+
};
|
|
2286
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
1561
2290
|
throw new TransportError({
|
|
1562
2291
|
operation: "receiveProto",
|
|
1563
2292
|
cause: new Error("No response available")
|
|
1564
2293
|
});
|
|
1565
2294
|
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Legacy JSON send (not supported for gRPC).
|
|
1568
|
-
*/
|
|
1569
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1570
|
-
async send(_msg, _signal) {
|
|
1571
|
-
throw new TransportError({
|
|
1572
|
-
operation: "send",
|
|
1573
|
-
cause: new Error("JSON mode not supported for gRPC transport")
|
|
1574
|
-
});
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Legacy JSON receive (not supported for gRPC).
|
|
1578
|
-
*/
|
|
1579
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1580
|
-
async receive(_signal) {
|
|
1581
|
-
throw new TransportError({
|
|
1582
|
-
operation: "receive",
|
|
1583
|
-
cause: new Error("JSON mode not supported for gRPC transport")
|
|
1584
|
-
});
|
|
1585
|
-
}
|
|
1586
2295
|
/**
|
|
1587
2296
|
* Close the transport.
|
|
1588
2297
|
*/
|
|
@@ -1590,10 +2299,16 @@ var init_grpc_transport = __esm({
|
|
|
1590
2299
|
async close() {
|
|
1591
2300
|
if (this._closed) return;
|
|
1592
2301
|
this._closed = true;
|
|
2302
|
+
this.clearStreamTimeout();
|
|
1593
2303
|
if (this._activeStream) {
|
|
1594
2304
|
this._activeStream.cancel();
|
|
1595
2305
|
this._activeStream = null;
|
|
1596
2306
|
}
|
|
2307
|
+
const pendingReads = this._pendingProtoReads;
|
|
2308
|
+
this._pendingProtoReads = [];
|
|
2309
|
+
for (const p of pendingReads) {
|
|
2310
|
+
p.reject(ErrClosed());
|
|
2311
|
+
}
|
|
1597
2312
|
if (this._client) {
|
|
1598
2313
|
this._client.close();
|
|
1599
2314
|
this._client = null;
|
|
@@ -1611,6 +2326,17 @@ var init_grpc_transport = __esm({
|
|
|
1611
2326
|
getAddress() {
|
|
1612
2327
|
return this._address;
|
|
1613
2328
|
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Enqueue a response, resolving a pending read if one exists.
|
|
2331
|
+
*/
|
|
2332
|
+
enqueueResponse(response) {
|
|
2333
|
+
const pending = this._pendingProtoReads.shift();
|
|
2334
|
+
if (pending) {
|
|
2335
|
+
pending.resolve(response);
|
|
2336
|
+
} else {
|
|
2337
|
+
this._pendingResponses.push(response);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
1614
2340
|
/**
|
|
1615
2341
|
* Make a unary RPC call.
|
|
1616
2342
|
*/
|
|
@@ -1649,660 +2375,597 @@ var init_grpc_transport = __esm({
|
|
|
1649
2375
|
}
|
|
1650
2376
|
/**
|
|
1651
2377
|
* Call the server-streaming Execute RPC.
|
|
1652
|
-
*
|
|
2378
|
+
* Uses backpressure to prevent unbounded buffering: pauses the stream when
|
|
2379
|
+
* the pending response buffer exceeds the threshold, resumes when drained.
|
|
2380
|
+
* Enforces a configurable timeout that fires when no data arrives within the period.
|
|
1653
2381
|
*/
|
|
1654
2382
|
callServerStream(request, signal) {
|
|
1655
2383
|
return new Promise((resolve2, reject) => {
|
|
1656
2384
|
if (!this._client) {
|
|
1657
2385
|
reject(new Error("No client"));
|
|
1658
2386
|
return;
|
|
1659
|
-
}
|
|
1660
|
-
if (this._activeStream) {
|
|
1661
|
-
this._activeStream.cancel();
|
|
1662
|
-
this._activeStream = null;
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
this.
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
)
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
})
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
case "SMALLINT":
|
|
1727
|
-
case "TINYINT":
|
|
1728
|
-
return "INT";
|
|
1729
|
-
case "FLOAT":
|
|
1730
|
-
case "DOUBLE":
|
|
1731
|
-
case "REAL":
|
|
1732
|
-
case "FLOAT32":
|
|
1733
|
-
case "FLOAT64":
|
|
1734
|
-
return "FLOAT";
|
|
1735
|
-
case "DECIMAL":
|
|
1736
|
-
case "NUMERIC":
|
|
1737
|
-
return "DECIMAL";
|
|
1738
|
-
case "STRING":
|
|
1739
|
-
case "VARCHAR":
|
|
1740
|
-
case "TEXT":
|
|
1741
|
-
return "STRING";
|
|
1742
|
-
case "BOOL":
|
|
1743
|
-
case "BOOLEAN":
|
|
1744
|
-
return "BOOL";
|
|
1745
|
-
case "NULL":
|
|
1746
|
-
return "NULL";
|
|
1747
|
-
case "LIST":
|
|
1748
|
-
case "ARRAY":
|
|
1749
|
-
return "ARRAY";
|
|
1750
|
-
case "MAP":
|
|
1751
|
-
case "RECORD":
|
|
1752
|
-
case "OBJECT":
|
|
1753
|
-
return "OBJECT";
|
|
1754
|
-
case "NODE":
|
|
1755
|
-
return "NODE";
|
|
1756
|
-
case "EDGE":
|
|
1757
|
-
case "RELATIONSHIP":
|
|
1758
|
-
return "EDGE";
|
|
1759
|
-
case "PATH":
|
|
1760
|
-
return "PATH";
|
|
1761
|
-
case "BYTEA":
|
|
1762
|
-
case "BINARY":
|
|
1763
|
-
return "BYTEA";
|
|
1764
|
-
case "DATE":
|
|
1765
|
-
return "DATE";
|
|
1766
|
-
case "TIME":
|
|
1767
|
-
return "TIME";
|
|
1768
|
-
case "TIMETZ":
|
|
1769
|
-
case "TIME WITH TIME ZONE":
|
|
1770
|
-
return "TIMETZ";
|
|
1771
|
-
case "TIMESTAMP":
|
|
1772
|
-
return "TIMESTAMP";
|
|
1773
|
-
case "TIMESTAMPTZ":
|
|
1774
|
-
case "TIMESTAMP WITH TIME ZONE":
|
|
1775
|
-
return "TIMESTAMPTZ";
|
|
1776
|
-
case "INTERVAL":
|
|
1777
|
-
return "INTERVAL";
|
|
1778
|
-
case "JSON":
|
|
1779
|
-
return "JSON";
|
|
1780
|
-
case "JSONB":
|
|
1781
|
-
return "JSONB";
|
|
1782
|
-
case "XML":
|
|
1783
|
-
return "XML";
|
|
1784
|
-
case "UUID":
|
|
1785
|
-
return "UUID";
|
|
1786
|
-
case "URL":
|
|
1787
|
-
return "URL";
|
|
1788
|
-
case "DOMAIN":
|
|
1789
|
-
return "DOMAIN";
|
|
1790
|
-
case "ENUM":
|
|
1791
|
-
return "ENUM";
|
|
1792
|
-
case "BIT":
|
|
1793
|
-
case "VARBIT":
|
|
1794
|
-
return "BIT_STRING";
|
|
1795
|
-
default:
|
|
1796
|
-
if (upper.includes("RANGE")) {
|
|
1797
|
-
return "RANGE";
|
|
1798
|
-
}
|
|
1799
|
-
return "UNKNOWN";
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
function fromJSON(value, typeHint) {
|
|
1803
|
-
if (value === null || value === void 0) {
|
|
1804
|
-
return GQLValue.null();
|
|
1805
|
-
}
|
|
1806
|
-
if (typeHint) {
|
|
1807
|
-
return convertWithType(value, typeHint);
|
|
1808
|
-
}
|
|
1809
|
-
if (typeof value === "boolean") {
|
|
1810
|
-
return GQLValue.bool(value);
|
|
1811
|
-
}
|
|
1812
|
-
if (typeof value === "number") {
|
|
1813
|
-
if (Number.isInteger(value)) {
|
|
1814
|
-
return GQLValue.int(value);
|
|
1815
|
-
}
|
|
1816
|
-
return GQLValue.float(value);
|
|
1817
|
-
}
|
|
1818
|
-
if (typeof value === "bigint") {
|
|
1819
|
-
return GQLValue.int(value);
|
|
1820
|
-
}
|
|
1821
|
-
if (typeof value === "string") {
|
|
1822
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
1823
|
-
return GQLValue.uuid(value);
|
|
1824
|
-
}
|
|
1825
|
-
if (/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2})?/.test(value)) {
|
|
1826
|
-
const date = new Date(value);
|
|
1827
|
-
if (!isNaN(date.getTime())) {
|
|
1828
|
-
return GQLValue.timestamp(date);
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
return GQLValue.string(value);
|
|
1832
|
-
}
|
|
1833
|
-
if (Array.isArray(value)) {
|
|
1834
|
-
return GQLValue.array(value.map((v) => fromJSON(v)));
|
|
1835
|
-
}
|
|
1836
|
-
if (typeof value === "object") {
|
|
1837
|
-
const obj = value;
|
|
1838
|
-
if ("id" in obj && "labels" in obj && "properties" in obj) {
|
|
1839
|
-
return GQLValue.node(obj);
|
|
1840
|
-
}
|
|
1841
|
-
if ("id" in obj && "type" in obj) {
|
|
1842
|
-
const hasStartEnd = "startNode" in obj && "endNode" in obj;
|
|
1843
|
-
const hasFromTo = "from" in obj && "to" in obj;
|
|
1844
|
-
if (hasStartEnd || hasFromTo) {
|
|
1845
|
-
const rawEdge = obj;
|
|
1846
|
-
const normalizedEdge = {
|
|
1847
|
-
id: rawEdge.id,
|
|
1848
|
-
type: rawEdge.type,
|
|
1849
|
-
startNode: rawEdge.startNode ?? rawEdge.from ?? "",
|
|
1850
|
-
endNode: rawEdge.endNode ?? rawEdge.to ?? "",
|
|
1851
|
-
properties: rawEdge.properties ?? {}
|
|
1852
|
-
};
|
|
1853
|
-
return GQLValue.edge(normalizedEdge);
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
if ("nodes" in obj && "edges" in obj && Array.isArray(obj.nodes)) {
|
|
1857
|
-
return GQLValue.path(obj);
|
|
1858
|
-
}
|
|
1859
|
-
const map = /* @__PURE__ */ new Map();
|
|
1860
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
1861
|
-
map.set(k, fromJSON(v));
|
|
1862
|
-
}
|
|
1863
|
-
return GQLValue.object(map);
|
|
1864
|
-
}
|
|
1865
|
-
return GQLValue.unknown(value);
|
|
1866
|
-
}
|
|
1867
|
-
function convertWithType(value, type) {
|
|
1868
|
-
switch (type) {
|
|
1869
|
-
case "NULL":
|
|
1870
|
-
return GQLValue.null();
|
|
1871
|
-
case "BOOL":
|
|
1872
|
-
if (typeof value === "string") {
|
|
1873
|
-
const lower = value.toLowerCase();
|
|
1874
|
-
return GQLValue.bool(lower === "true" || lower === "1" || lower === "t");
|
|
1875
|
-
}
|
|
1876
|
-
return GQLValue.bool(Boolean(value));
|
|
1877
|
-
case "INT":
|
|
1878
|
-
if (typeof value === "number") {
|
|
1879
|
-
return GQLValue.int(value);
|
|
1880
|
-
}
|
|
1881
|
-
if (typeof value === "bigint") {
|
|
1882
|
-
return GQLValue.int(value);
|
|
1883
|
-
}
|
|
1884
|
-
if (typeof value === "string") {
|
|
1885
|
-
const trimmed = value.trim();
|
|
1886
|
-
if (trimmed === "" || isNaN(Number(trimmed))) {
|
|
1887
|
-
return GQLValue.int(0);
|
|
1888
|
-
}
|
|
1889
|
-
return GQLValue.int(BigInt(trimmed));
|
|
1890
|
-
}
|
|
1891
|
-
return GQLValue.int(0);
|
|
1892
|
-
case "FLOAT":
|
|
1893
|
-
return GQLValue.float(Number(value));
|
|
1894
|
-
case "DECIMAL":
|
|
1895
|
-
return GQLValue.decimal(String(value));
|
|
1896
|
-
case "STRING":
|
|
1897
|
-
if (typeof value !== "string") {
|
|
1898
|
-
return fromJSON(value);
|
|
1899
|
-
}
|
|
1900
|
-
return GQLValue.string(value);
|
|
1901
|
-
case "UUID":
|
|
1902
|
-
return GQLValue.uuid(String(value));
|
|
1903
|
-
case "BYTEA":
|
|
1904
|
-
if (value instanceof Uint8Array) {
|
|
1905
|
-
return GQLValue.bytes(value);
|
|
1906
|
-
}
|
|
1907
|
-
if (typeof value === "string") {
|
|
1908
|
-
return GQLValue.bytes(Buffer.from(value, "base64"));
|
|
1909
|
-
}
|
|
1910
|
-
return GQLValue.bytes(new Uint8Array());
|
|
1911
|
-
case "DATE":
|
|
1912
|
-
return GQLValue.date(value instanceof Date ? value : new Date(String(value)));
|
|
1913
|
-
case "TIME":
|
|
1914
|
-
case "TIMETZ":
|
|
1915
|
-
return GQLValue.time(value instanceof Date ? value : new Date(String(value)));
|
|
1916
|
-
case "TIMESTAMP":
|
|
1917
|
-
case "TIMESTAMPTZ":
|
|
1918
|
-
return GQLValue.timestamp(value instanceof Date ? value : new Date(String(value)));
|
|
1919
|
-
case "JSON":
|
|
1920
|
-
case "JSONB":
|
|
1921
|
-
return GQLValue.json(value);
|
|
1922
|
-
case "ARRAY":
|
|
1923
|
-
if (Array.isArray(value)) {
|
|
1924
|
-
return GQLValue.array(value.map((v) => fromJSON(v)));
|
|
2387
|
+
}
|
|
2388
|
+
if (this._activeStream) {
|
|
2389
|
+
this._activeStream.cancel();
|
|
2390
|
+
this._activeStream = null;
|
|
2391
|
+
}
|
|
2392
|
+
this._streamComplete = false;
|
|
2393
|
+
this._streamPaused = false;
|
|
2394
|
+
let resolved = false;
|
|
2395
|
+
const stream = this._client.execute(request);
|
|
2396
|
+
this._activeStream = stream;
|
|
2397
|
+
this.resetStreamTimeout(stream, reject, () => {
|
|
2398
|
+
resolved = true;
|
|
2399
|
+
});
|
|
2400
|
+
stream.on("data", (response) => {
|
|
2401
|
+
this.resetStreamTimeout(stream, reject, () => {
|
|
2402
|
+
resolved = true;
|
|
2403
|
+
});
|
|
2404
|
+
this.enqueueResponse({ execute: response });
|
|
2405
|
+
if (this._pendingResponses.length >= this._backpressureThreshold) {
|
|
2406
|
+
this._streamPaused = true;
|
|
2407
|
+
stream.pause();
|
|
2408
|
+
}
|
|
2409
|
+
if (!resolved) {
|
|
2410
|
+
resolved = true;
|
|
2411
|
+
resolve2();
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
stream.on("end", () => {
|
|
2415
|
+
this.clearStreamTimeout();
|
|
2416
|
+
this._activeStream = null;
|
|
2417
|
+
this._streamComplete = true;
|
|
2418
|
+
if (!resolved) {
|
|
2419
|
+
resolved = true;
|
|
2420
|
+
resolve2();
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
stream.on("error", (error) => {
|
|
2424
|
+
this.clearStreamTimeout();
|
|
2425
|
+
this._activeStream = null;
|
|
2426
|
+
this._streamComplete = true;
|
|
2427
|
+
const transportError = new TransportError({
|
|
2428
|
+
operation: "execute",
|
|
2429
|
+
address: this._address,
|
|
2430
|
+
cause: new Error(`gRPC error ${error.code}: ${error.message}`)
|
|
2431
|
+
});
|
|
2432
|
+
const pendingReads = this._pendingProtoReads;
|
|
2433
|
+
this._pendingProtoReads = [];
|
|
2434
|
+
for (const p of pendingReads) {
|
|
2435
|
+
p.reject(transportError);
|
|
2436
|
+
}
|
|
2437
|
+
if (!resolved) {
|
|
2438
|
+
resolved = true;
|
|
2439
|
+
reject(transportError);
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
if (signal) {
|
|
2443
|
+
const onAbort = () => {
|
|
2444
|
+
this.clearStreamTimeout();
|
|
2445
|
+
stream.cancel();
|
|
2446
|
+
if (!resolved) {
|
|
2447
|
+
resolved = true;
|
|
2448
|
+
reject(new TransportError({ operation: "execute", cause: new Error("Aborted") }));
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
1925
2454
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2455
|
+
/**
|
|
2456
|
+
* Reset the stream inactivity timeout. If no data arrives within
|
|
2457
|
+
* _streamTimeoutMs, the stream is cancelled and the promise rejected.
|
|
2458
|
+
*/
|
|
2459
|
+
resetStreamTimeout(stream, reject, markResolved) {
|
|
2460
|
+
this.clearStreamTimeout();
|
|
2461
|
+
if (this._streamTimeoutMs <= 0) return;
|
|
2462
|
+
this._streamTimeoutTimer = setTimeout(() => {
|
|
2463
|
+
this._streamTimeoutTimer = null;
|
|
2464
|
+
this._activeStream = null;
|
|
2465
|
+
this._streamComplete = true;
|
|
2466
|
+
stream.cancel();
|
|
2467
|
+
const timeoutError = new TransportError({
|
|
2468
|
+
operation: "execute",
|
|
2469
|
+
address: this._address,
|
|
2470
|
+
cause: new Error(
|
|
2471
|
+
`gRPC stream timed out: no data received within ${this._streamTimeoutMs}ms`
|
|
2472
|
+
)
|
|
2473
|
+
});
|
|
2474
|
+
const pendingReads = this._pendingProtoReads;
|
|
2475
|
+
this._pendingProtoReads = [];
|
|
2476
|
+
for (const p of pendingReads) {
|
|
2477
|
+
p.reject(timeoutError);
|
|
1931
2478
|
}
|
|
1932
|
-
|
|
2479
|
+
markResolved();
|
|
2480
|
+
reject(timeoutError);
|
|
2481
|
+
}, this._streamTimeoutMs);
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Clear any active stream inactivity timeout.
|
|
2485
|
+
*/
|
|
2486
|
+
clearStreamTimeout() {
|
|
2487
|
+
if (this._streamTimeoutTimer !== null) {
|
|
2488
|
+
clearTimeout(this._streamTimeoutTimer);
|
|
2489
|
+
this._streamTimeoutTimer = null;
|
|
1933
2490
|
}
|
|
1934
2491
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
return fromJSON(parsed);
|
|
1942
|
-
}
|
|
1943
|
-
} catch {
|
|
2492
|
+
/**
|
|
2493
|
+
* Check if closed and throw if so.
|
|
2494
|
+
*/
|
|
2495
|
+
checkClosed() {
|
|
2496
|
+
if (this._closed) {
|
|
2497
|
+
throw ErrClosed();
|
|
1944
2498
|
}
|
|
1945
2499
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
async function buildTLSConfig(cfg) {
|
|
2504
|
+
const options = {
|
|
2505
|
+
minVersion: "TLSv1.3",
|
|
2506
|
+
maxVersion: "TLSv1.3"
|
|
2507
|
+
};
|
|
2508
|
+
if (cfg.tlsCACert) {
|
|
2509
|
+
options.ca = cfg.tlsCACert;
|
|
2510
|
+
} else if (cfg.tlsCA) {
|
|
2511
|
+
await validateTLSCertPath(cfg.tlsCA, "tlsCA");
|
|
2512
|
+
try {
|
|
2513
|
+
options.ca = await fs.promises.readFile(cfg.tlsCA, "utf-8");
|
|
2514
|
+
} catch (e) {
|
|
2515
|
+
throw new TransportError({
|
|
2516
|
+
operation: "load_ca_cert",
|
|
2517
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2518
|
+
});
|
|
1959
2519
|
}
|
|
1960
|
-
case "PATH":
|
|
1961
|
-
return GQLValue.path(value);
|
|
1962
|
-
default:
|
|
1963
|
-
return fromJSON(value);
|
|
1964
2520
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2521
|
+
if (cfg.tlsCertPEM) {
|
|
2522
|
+
options.cert = cfg.tlsCertPEM;
|
|
2523
|
+
} else if (cfg.tlsCert) {
|
|
2524
|
+
await validateTLSCertPath(cfg.tlsCert, "tlsCert");
|
|
2525
|
+
try {
|
|
2526
|
+
options.cert = await fs.promises.readFile(cfg.tlsCert, "utf-8");
|
|
2527
|
+
} catch (e) {
|
|
2528
|
+
throw new TransportError({
|
|
2529
|
+
operation: "load_client_cert",
|
|
2530
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
1971
2533
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
);
|
|
2534
|
+
if (cfg.tlsKeyPEM) {
|
|
2535
|
+
options.key = cfg.tlsKeyPEM;
|
|
2536
|
+
} else if (cfg.tlsKey) {
|
|
2537
|
+
await validateTLSCertPath(cfg.tlsKey, "tlsKey");
|
|
2538
|
+
try {
|
|
2539
|
+
options.key = await fs.promises.readFile(cfg.tlsKey, "utf-8");
|
|
2540
|
+
} catch (e) {
|
|
2541
|
+
throw new TransportError({
|
|
2542
|
+
operation: "load_client_key",
|
|
2543
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2544
|
+
});
|
|
1984
2545
|
}
|
|
1985
|
-
obj[key] = value.toJS();
|
|
1986
2546
|
}
|
|
1987
|
-
return
|
|
2547
|
+
return options;
|
|
1988
2548
|
}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
this.kind = kind;
|
|
2012
|
-
}
|
|
2013
|
-
// Factory methods
|
|
2014
|
-
static null() {
|
|
2015
|
-
return new _GQLValue("NULL");
|
|
2016
|
-
}
|
|
2017
|
-
static bool(value) {
|
|
2018
|
-
const v = new _GQLValue("BOOL");
|
|
2019
|
-
v._boolValue = value;
|
|
2020
|
-
return v;
|
|
2021
|
-
}
|
|
2022
|
-
static int(value) {
|
|
2023
|
-
const v = new _GQLValue("INT");
|
|
2024
|
-
v._intValue = typeof value === "bigint" ? value : BigInt(Math.trunc(value));
|
|
2025
|
-
return v;
|
|
2026
|
-
}
|
|
2027
|
-
static float(value) {
|
|
2028
|
-
const v = new _GQLValue("FLOAT");
|
|
2029
|
-
v._floatValue = value;
|
|
2030
|
-
return v;
|
|
2031
|
-
}
|
|
2032
|
-
static string(value) {
|
|
2033
|
-
const v = new _GQLValue("STRING");
|
|
2034
|
-
v._stringValue = value;
|
|
2035
|
-
return v;
|
|
2036
|
-
}
|
|
2037
|
-
static decimal(value) {
|
|
2038
|
-
const v = new _GQLValue("DECIMAL");
|
|
2039
|
-
v._decimalValue = createDecimal(value);
|
|
2040
|
-
return v;
|
|
2041
|
-
}
|
|
2042
|
-
static array(values) {
|
|
2043
|
-
const v = new _GQLValue("ARRAY");
|
|
2044
|
-
v._arrayValue = values;
|
|
2045
|
-
return v;
|
|
2046
|
-
}
|
|
2047
|
-
static object(values) {
|
|
2048
|
-
const v = new _GQLValue("OBJECT");
|
|
2049
|
-
v._objectValue = values instanceof Map ? values : new Map(Object.entries(values));
|
|
2050
|
-
return v;
|
|
2051
|
-
}
|
|
2052
|
-
static bytes(value) {
|
|
2053
|
-
const v = new _GQLValue("BYTEA");
|
|
2054
|
-
v._bytesValue = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
2055
|
-
return v;
|
|
2056
|
-
}
|
|
2057
|
-
static date(value) {
|
|
2058
|
-
const v = new _GQLValue("DATE");
|
|
2059
|
-
v._dateValue = value;
|
|
2060
|
-
return v;
|
|
2061
|
-
}
|
|
2062
|
-
static time(value) {
|
|
2063
|
-
const v = new _GQLValue("TIME");
|
|
2064
|
-
v._dateValue = value;
|
|
2065
|
-
return v;
|
|
2066
|
-
}
|
|
2067
|
-
static timestamp(value) {
|
|
2068
|
-
const v = new _GQLValue("TIMESTAMP");
|
|
2069
|
-
v._dateValue = value;
|
|
2070
|
-
return v;
|
|
2071
|
-
}
|
|
2072
|
-
static uuid(value) {
|
|
2073
|
-
const v = new _GQLValue("UUID");
|
|
2074
|
-
v._stringValue = value;
|
|
2075
|
-
return v;
|
|
2076
|
-
}
|
|
2077
|
-
static json(value) {
|
|
2078
|
-
const v = new _GQLValue("JSON");
|
|
2079
|
-
v._rawValue = value;
|
|
2080
|
-
return v;
|
|
2081
|
-
}
|
|
2082
|
-
static node(value) {
|
|
2083
|
-
const v = new _GQLValue("NODE");
|
|
2084
|
-
v._nodeValue = value;
|
|
2085
|
-
return v;
|
|
2086
|
-
}
|
|
2087
|
-
static edge(value) {
|
|
2088
|
-
const v = new _GQLValue("EDGE");
|
|
2089
|
-
v._edgeValue = value;
|
|
2090
|
-
return v;
|
|
2091
|
-
}
|
|
2092
|
-
static path(value) {
|
|
2093
|
-
const v = new _GQLValue("PATH");
|
|
2094
|
-
v._pathValue = value;
|
|
2095
|
-
return v;
|
|
2096
|
-
}
|
|
2097
|
-
static range(value) {
|
|
2098
|
-
const v = new _GQLValue("RANGE");
|
|
2099
|
-
v._rangeValue = value;
|
|
2100
|
-
return v;
|
|
2101
|
-
}
|
|
2102
|
-
static unknown(value) {
|
|
2103
|
-
const v = new _GQLValue("UNKNOWN");
|
|
2104
|
-
v._rawValue = value;
|
|
2105
|
-
return v;
|
|
2549
|
+
async function createTransport(cfg) {
|
|
2550
|
+
if (cfg.transport === "grpc") {
|
|
2551
|
+
const { GrpcTransport: GrpcTransport2 } = await init_grpc_transport().then(() => grpc_transport_exports);
|
|
2552
|
+
return GrpcTransport2.connect(cfg);
|
|
2553
|
+
}
|
|
2554
|
+
return QuicTransport.connect(cfg);
|
|
2555
|
+
}
|
|
2556
|
+
var GEODE_ALPN, DEFAULT_MAX_MESSAGE_SIZE, DEFAULT_MAX_BUFFER_BYTES, BaseTransport, QuicTransport, MockTransport;
|
|
2557
|
+
var init_transport = __esm({
|
|
2558
|
+
async "src/transport.ts"() {
|
|
2559
|
+
init_config();
|
|
2560
|
+
init_errors();
|
|
2561
|
+
init_validate();
|
|
2562
|
+
await init_proto();
|
|
2563
|
+
GEODE_ALPN = ["geode/1"];
|
|
2564
|
+
DEFAULT_MAX_MESSAGE_SIZE = 16 * 1024 * 1024;
|
|
2565
|
+
DEFAULT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
2566
|
+
BaseTransport = class {
|
|
2567
|
+
_closed = false;
|
|
2568
|
+
_address;
|
|
2569
|
+
constructor(address) {
|
|
2570
|
+
this._address = address;
|
|
2106
2571
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
return this.kind === "NULL";
|
|
2572
|
+
isClosed() {
|
|
2573
|
+
return this._closed;
|
|
2110
2574
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
throw new TypeError(`Cannot convert ${this.kind} to boolean`);
|
|
2114
|
-
}
|
|
2115
|
-
return this._boolValue;
|
|
2575
|
+
getAddress() {
|
|
2576
|
+
return this._address;
|
|
2116
2577
|
}
|
|
2117
|
-
|
|
2118
|
-
if (this.
|
|
2119
|
-
throw
|
|
2578
|
+
checkClosed() {
|
|
2579
|
+
if (this._closed) {
|
|
2580
|
+
throw ErrClosed();
|
|
2120
2581
|
}
|
|
2121
|
-
return this._intValue;
|
|
2122
2582
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2583
|
+
};
|
|
2584
|
+
QuicTransport = class _QuicTransport extends BaseTransport {
|
|
2585
|
+
_client = null;
|
|
2586
|
+
_stream = null;
|
|
2587
|
+
_chunks = [];
|
|
2588
|
+
_totalLength = 0;
|
|
2589
|
+
_maxMessageSize;
|
|
2590
|
+
_maxBufferBytes;
|
|
2591
|
+
_pendingProtoReads = [];
|
|
2592
|
+
constructor(address, maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE, maxBufferBytes = DEFAULT_MAX_BUFFER_BYTES) {
|
|
2593
|
+
super(address);
|
|
2594
|
+
this._maxMessageSize = maxMessageSize;
|
|
2595
|
+
this._maxBufferBytes = maxBufferBytes;
|
|
2134
2596
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2597
|
+
/**
|
|
2598
|
+
* Connect to the Geode server using QUIC.
|
|
2599
|
+
*/
|
|
2600
|
+
static async connect(cfg) {
|
|
2601
|
+
const address = getAddress(cfg);
|
|
2602
|
+
const transport = new _QuicTransport(address);
|
|
2603
|
+
try {
|
|
2604
|
+
const { QUICClient } = await import('@matrixai/quic');
|
|
2605
|
+
const [host = "localhost", portStr] = address.includes("[") ? [address.slice(1, address.lastIndexOf("]")), address.slice(address.lastIndexOf(":") + 1)] : address.split(":");
|
|
2606
|
+
const port = parseInt(portStr ?? "3141", 10);
|
|
2607
|
+
const cryptoOps = {
|
|
2608
|
+
ops: {
|
|
2609
|
+
randomBytes: (data) => {
|
|
2610
|
+
const buf = Buffer.from(data);
|
|
2611
|
+
crypto.randomFillSync(buf);
|
|
2612
|
+
return Promise.resolve();
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2616
|
+
const tlsConfig = await buildTLSConfig(cfg);
|
|
2617
|
+
const config = {
|
|
2618
|
+
applicationProtos: GEODE_ALPN,
|
|
2619
|
+
maxIdleTimeout: cfg.maxIdleTime ?? 3e4,
|
|
2620
|
+
initialMaxData: 1e7,
|
|
2621
|
+
initialMaxStreamDataBidiLocal: 1e6,
|
|
2622
|
+
initialMaxStreamDataBidiRemote: 1e6,
|
|
2623
|
+
initialMaxStreamsBidi: 100
|
|
2624
|
+
};
|
|
2625
|
+
if (cfg.insecureSkipVerify) {
|
|
2626
|
+
const isProduction = process.env["NODE_ENV"] === "production" || process.env["GEODE_ENV"] === "production";
|
|
2627
|
+
if (isProduction) {
|
|
2628
|
+
console.warn(
|
|
2629
|
+
"SECURITY WARNING: insecureSkipVerify is enabled in production environment. This disables TLS certificate verification and exposes the connection to MITM attacks. Set GEODE_ALLOW_INSECURE_PRODUCTION=true to override this warning."
|
|
2630
|
+
);
|
|
2631
|
+
if (process.env["GEODE_ALLOW_INSECURE_PRODUCTION"] !== "true") {
|
|
2632
|
+
throw new TransportError({
|
|
2633
|
+
operation: "connect",
|
|
2634
|
+
cause: new Error(
|
|
2635
|
+
"insecureSkipVerify is not allowed in production. Configure proper TLS certificates or set GEODE_ALLOW_INSECURE_PRODUCTION=true to override."
|
|
2636
|
+
)
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
config.verifyPeer = false;
|
|
2641
|
+
}
|
|
2642
|
+
if (tlsConfig.ca) {
|
|
2643
|
+
config.caCerts = [tlsConfig.ca];
|
|
2644
|
+
}
|
|
2645
|
+
if (tlsConfig.cert && tlsConfig.key) {
|
|
2646
|
+
config.cert = tlsConfig.cert;
|
|
2647
|
+
config.key = tlsConfig.key;
|
|
2648
|
+
}
|
|
2649
|
+
const client = await QUICClient.createQUICClient(
|
|
2650
|
+
{
|
|
2651
|
+
host,
|
|
2652
|
+
port,
|
|
2653
|
+
serverName: cfg.serverName ?? host,
|
|
2654
|
+
crypto: cryptoOps,
|
|
2655
|
+
config
|
|
2656
|
+
},
|
|
2657
|
+
{ timer: cfg.connectTimeout ?? 3e4 }
|
|
2658
|
+
);
|
|
2659
|
+
transport._client = client;
|
|
2660
|
+
const connection = client.connection;
|
|
2661
|
+
const stream = await connection.newStream();
|
|
2662
|
+
transport._stream = stream;
|
|
2663
|
+
transport.startReading();
|
|
2664
|
+
return transport;
|
|
2665
|
+
} catch (e) {
|
|
2666
|
+
await transport.close();
|
|
2667
|
+
throw new TransportError({
|
|
2668
|
+
operation: "connect",
|
|
2669
|
+
address,
|
|
2670
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2671
|
+
});
|
|
2138
2672
|
}
|
|
2139
|
-
return this._floatValue;
|
|
2140
2673
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2674
|
+
/**
|
|
2675
|
+
* Start reading from the stream in the background.
|
|
2676
|
+
*/
|
|
2677
|
+
startReading() {
|
|
2678
|
+
if (!this._stream) return;
|
|
2679
|
+
const stream = this._stream;
|
|
2680
|
+
const reader = stream.readable.getReader();
|
|
2681
|
+
const read = async () => {
|
|
2682
|
+
try {
|
|
2683
|
+
while (!this._closed) {
|
|
2684
|
+
const { done, value } = await reader.read();
|
|
2685
|
+
if (done) {
|
|
2686
|
+
this.rejectPendingReads(
|
|
2687
|
+
new TransportError({
|
|
2688
|
+
operation: "read",
|
|
2689
|
+
cause: new Error("Stream ended unexpectedly")
|
|
2690
|
+
})
|
|
2691
|
+
);
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
if (value) {
|
|
2695
|
+
this.processData(Buffer.from(value));
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
} catch (err) {
|
|
2699
|
+
if (!this._closed) {
|
|
2700
|
+
this.rejectPendingReads(
|
|
2701
|
+
new TransportError({
|
|
2702
|
+
operation: "read",
|
|
2703
|
+
cause: err instanceof Error ? err : new Error(String(err))
|
|
2704
|
+
})
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
} finally {
|
|
2708
|
+
reader.releaseLock();
|
|
2709
|
+
}
|
|
2710
|
+
};
|
|
2711
|
+
void read();
|
|
2146
2712
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2713
|
+
/**
|
|
2714
|
+
* Reject all pending reads with an error.
|
|
2715
|
+
*/
|
|
2716
|
+
rejectPendingReads(error) {
|
|
2717
|
+
const pending = this._pendingProtoReads;
|
|
2718
|
+
this._pendingProtoReads = [];
|
|
2719
|
+
for (const p of pending) {
|
|
2720
|
+
p.reject(error);
|
|
2150
2721
|
}
|
|
2151
|
-
return this._decimalValue;
|
|
2152
2722
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2723
|
+
/**
|
|
2724
|
+
* Consolidate the chunk list into a single buffer.
|
|
2725
|
+
* Called only when we know we have enough data for at least one operation.
|
|
2726
|
+
*/
|
|
2727
|
+
consolidateChunks() {
|
|
2728
|
+
if (this._chunks.length === 0) {
|
|
2729
|
+
return Buffer.alloc(0);
|
|
2156
2730
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
get asObject() {
|
|
2160
|
-
if (this.kind !== "OBJECT" && this.kind !== "NODE" && this.kind !== "EDGE") {
|
|
2161
|
-
throw new TypeError(`Cannot convert ${this.kind} to object`);
|
|
2731
|
+
if (this._chunks.length === 1) {
|
|
2732
|
+
return this._chunks[0];
|
|
2162
2733
|
}
|
|
2163
|
-
|
|
2734
|
+
const consolidated = Buffer.concat(this._chunks, this._totalLength);
|
|
2735
|
+
this._chunks = [consolidated];
|
|
2736
|
+
return consolidated;
|
|
2164
2737
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2738
|
+
/**
|
|
2739
|
+
* Process received data (length-prefixed protobuf messages).
|
|
2740
|
+
*
|
|
2741
|
+
* Uses a chunk list pattern instead of Buffer.concat on every call
|
|
2742
|
+
* to avoid O(n^2) copying of the accumulated buffer.
|
|
2743
|
+
*/
|
|
2744
|
+
processData(data) {
|
|
2745
|
+
this._chunks.push(data);
|
|
2746
|
+
this._totalLength += data.length;
|
|
2747
|
+
if (this._totalLength > this._maxBufferBytes) {
|
|
2748
|
+
this.handleOversize("buffer");
|
|
2168
2749
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2750
|
+
while (this._totalLength >= 4) {
|
|
2751
|
+
const buf = this.consolidateChunks();
|
|
2752
|
+
const msgLen = decodeLengthPrefix(buf);
|
|
2753
|
+
if (msgLen > this._maxMessageSize) {
|
|
2754
|
+
this.handleOversize("message");
|
|
2755
|
+
}
|
|
2756
|
+
const totalLen = 4 + msgLen;
|
|
2757
|
+
if (this._totalLength < totalLen) {
|
|
2758
|
+
break;
|
|
2759
|
+
}
|
|
2760
|
+
const msgData = buf.subarray(4, totalLen);
|
|
2761
|
+
const remaining = buf.subarray(totalLen);
|
|
2762
|
+
if (remaining.length > 0) {
|
|
2763
|
+
this._chunks = [remaining];
|
|
2764
|
+
} else {
|
|
2765
|
+
this._chunks = [];
|
|
2766
|
+
}
|
|
2767
|
+
this._totalLength = remaining.length;
|
|
2768
|
+
try {
|
|
2769
|
+
const msg = decodeQuicServerMessage(msgData);
|
|
2770
|
+
const pending = this._pendingProtoReads.shift();
|
|
2771
|
+
if (pending) {
|
|
2772
|
+
pending.resolve(msg);
|
|
2773
|
+
}
|
|
2774
|
+
} catch (err) {
|
|
2775
|
+
const pending = this._pendingProtoReads.shift();
|
|
2776
|
+
if (pending) {
|
|
2777
|
+
pending.reject(
|
|
2778
|
+
new TransportError({
|
|
2779
|
+
operation: "decode",
|
|
2780
|
+
cause: err instanceof Error ? err : new Error(String(err))
|
|
2781
|
+
})
|
|
2782
|
+
);
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2174
2785
|
}
|
|
2175
|
-
return this._dateValue;
|
|
2176
2786
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2787
|
+
/**
|
|
2788
|
+
* Handle oversized data by closing the transport.
|
|
2789
|
+
*/
|
|
2790
|
+
handleOversize(kind) {
|
|
2791
|
+
const error = new TransportError({
|
|
2792
|
+
operation: "receive",
|
|
2793
|
+
cause: new Error(`Received ${kind} exceeds maximum allowed size`)
|
|
2794
|
+
});
|
|
2795
|
+
this._chunks = [];
|
|
2796
|
+
this._totalLength = 0;
|
|
2797
|
+
this.rejectPendingReads(error);
|
|
2798
|
+
void this.close();
|
|
2799
|
+
throw error;
|
|
2182
2800
|
}
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2801
|
+
/**
|
|
2802
|
+
* Send a protobuf message with length prefix.
|
|
2803
|
+
*/
|
|
2804
|
+
async sendProto(msg, signal) {
|
|
2805
|
+
this.checkClosed();
|
|
2806
|
+
if (signal?.aborted) {
|
|
2807
|
+
throw new TransportError({ operation: "sendProto", cause: new Error("Aborted") });
|
|
2186
2808
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2809
|
+
if (!this._stream) {
|
|
2810
|
+
throw new TransportError({ operation: "sendProto", cause: new Error("No stream") });
|
|
2811
|
+
}
|
|
2812
|
+
const data = encodeWithLengthPrefix(msg);
|
|
2813
|
+
try {
|
|
2814
|
+
const stream = this._stream;
|
|
2815
|
+
const writer = stream.writable.getWriter();
|
|
2816
|
+
await writer.write(new Uint8Array(data));
|
|
2817
|
+
writer.releaseLock();
|
|
2818
|
+
} catch (e) {
|
|
2819
|
+
throw new TransportError({
|
|
2820
|
+
operation: "sendProto",
|
|
2821
|
+
address: this._address,
|
|
2822
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2823
|
+
});
|
|
2192
2824
|
}
|
|
2193
|
-
return this._pathValue;
|
|
2194
2825
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2826
|
+
/**
|
|
2827
|
+
* Receive a protobuf message.
|
|
2828
|
+
*/
|
|
2829
|
+
async receiveProto(signal) {
|
|
2830
|
+
this.checkClosed();
|
|
2831
|
+
if (signal?.aborted) {
|
|
2832
|
+
throw new TransportError({ operation: "receiveProto", cause: new Error("Aborted") });
|
|
2198
2833
|
}
|
|
2199
|
-
return
|
|
2834
|
+
return new Promise((resolve2, reject) => {
|
|
2835
|
+
let onAbort = null;
|
|
2836
|
+
const pendingRead = {
|
|
2837
|
+
resolve: (value) => {
|
|
2838
|
+
if (onAbort && signal) {
|
|
2839
|
+
signal.removeEventListener("abort", onAbort);
|
|
2840
|
+
}
|
|
2841
|
+
resolve2(value);
|
|
2842
|
+
},
|
|
2843
|
+
reject: (error) => {
|
|
2844
|
+
if (onAbort && signal) {
|
|
2845
|
+
signal.removeEventListener("abort", onAbort);
|
|
2846
|
+
}
|
|
2847
|
+
reject(error);
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
this._pendingProtoReads.push(pendingRead);
|
|
2851
|
+
if (signal) {
|
|
2852
|
+
onAbort = () => {
|
|
2853
|
+
const idx = this._pendingProtoReads.indexOf(pendingRead);
|
|
2854
|
+
if (idx !== -1) {
|
|
2855
|
+
this._pendingProtoReads.splice(idx, 1);
|
|
2856
|
+
}
|
|
2857
|
+
reject(new TransportError({ operation: "receiveProto", cause: new Error("Aborted") }));
|
|
2858
|
+
};
|
|
2859
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2200
2862
|
}
|
|
2201
|
-
|
|
2202
|
-
if (this.
|
|
2203
|
-
|
|
2863
|
+
async close() {
|
|
2864
|
+
if (this._closed) return;
|
|
2865
|
+
this._closed = true;
|
|
2866
|
+
for (const pending of this._pendingProtoReads) {
|
|
2867
|
+
pending.reject(ErrClosed());
|
|
2204
2868
|
}
|
|
2205
|
-
|
|
2869
|
+
this._pendingProtoReads = [];
|
|
2870
|
+
this._chunks = [];
|
|
2871
|
+
this._totalLength = 0;
|
|
2872
|
+
if (this._client) {
|
|
2873
|
+
try {
|
|
2874
|
+
const client = this._client;
|
|
2875
|
+
await client.destroy();
|
|
2876
|
+
} catch {
|
|
2877
|
+
}
|
|
2878
|
+
this._client = null;
|
|
2879
|
+
}
|
|
2880
|
+
this._stream = null;
|
|
2206
2881
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2882
|
+
};
|
|
2883
|
+
MockTransport = class extends BaseTransport {
|
|
2884
|
+
_protoSendQueue = [];
|
|
2885
|
+
_protoReceiveQueue = [];
|
|
2886
|
+
_pendingProtoReads = [];
|
|
2887
|
+
constructor(address = "mock:3141") {
|
|
2888
|
+
super(address);
|
|
2209
2889
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
return this._floatValue.toString();
|
|
2220
|
-
case "STRING":
|
|
2221
|
-
case "UUID":
|
|
2222
|
-
return this._stringValue;
|
|
2223
|
-
case "DECIMAL":
|
|
2224
|
-
return this._decimalValue?.toString() ?? "0";
|
|
2225
|
-
case "ARRAY":
|
|
2226
|
-
return `[${this._arrayValue.map((v) => v.toString()).join(", ")}]`;
|
|
2227
|
-
case "OBJECT":
|
|
2228
|
-
return `{${[...this._objectValue.entries()].map(([k, v]) => `${k}: ${v.toString()}`).join(", ")}}`;
|
|
2229
|
-
case "BYTEA":
|
|
2230
|
-
return `<bytes:${this._bytesValue.length}>`;
|
|
2231
|
-
case "DATE":
|
|
2232
|
-
case "TIME":
|
|
2233
|
-
case "TIMETZ":
|
|
2234
|
-
case "TIMESTAMP":
|
|
2235
|
-
case "TIMESTAMPTZ":
|
|
2236
|
-
return this._dateValue?.toISOString() ?? "";
|
|
2237
|
-
case "NODE":
|
|
2238
|
-
return `(${this._nodeValue?.labels.join(":")} {${JSON.stringify(this._nodeValue?.properties)}})`;
|
|
2239
|
-
case "EDGE":
|
|
2240
|
-
return `[${this._edgeValue?.type} {${JSON.stringify(this._edgeValue?.properties)}}]`;
|
|
2241
|
-
case "PATH":
|
|
2242
|
-
return `<path:${this._pathValue?.nodes.length} nodes>`;
|
|
2243
|
-
case "JSON":
|
|
2244
|
-
case "JSONB":
|
|
2245
|
-
return JSON.stringify(this._rawValue);
|
|
2246
|
-
default:
|
|
2247
|
-
return String(this._rawValue ?? "");
|
|
2890
|
+
/**
|
|
2891
|
+
* Queue a protobuf response.
|
|
2892
|
+
*/
|
|
2893
|
+
queueProtoResponse(response) {
|
|
2894
|
+
const pending = this._pendingProtoReads.shift();
|
|
2895
|
+
if (pending) {
|
|
2896
|
+
pending.resolve(response);
|
|
2897
|
+
} else {
|
|
2898
|
+
this._protoReceiveQueue.push(response);
|
|
2248
2899
|
}
|
|
2249
2900
|
}
|
|
2250
2901
|
/**
|
|
2251
|
-
*
|
|
2902
|
+
* Get all sent protobuf messages.
|
|
2252
2903
|
*/
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
case "TIME":
|
|
2279
|
-
case "TIMETZ":
|
|
2280
|
-
case "TIMESTAMP":
|
|
2281
|
-
case "TIMESTAMPTZ":
|
|
2282
|
-
return this._dateValue;
|
|
2283
|
-
case "NODE":
|
|
2284
|
-
return this._nodeValue;
|
|
2285
|
-
case "EDGE":
|
|
2286
|
-
return this._edgeValue;
|
|
2287
|
-
case "PATH":
|
|
2288
|
-
return this._pathValue;
|
|
2289
|
-
case "RANGE":
|
|
2290
|
-
return this._rangeValue;
|
|
2291
|
-
case "JSON":
|
|
2292
|
-
case "JSONB":
|
|
2293
|
-
return this._rawValue;
|
|
2294
|
-
default:
|
|
2295
|
-
return this._rawValue;
|
|
2904
|
+
getSentProtoMessages() {
|
|
2905
|
+
return [...this._protoSendQueue];
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Clear sent messages.
|
|
2909
|
+
*/
|
|
2910
|
+
clearSentMessages() {
|
|
2911
|
+
this._protoSendQueue = [];
|
|
2912
|
+
}
|
|
2913
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2914
|
+
async sendProto(msg, signal) {
|
|
2915
|
+
this.checkClosed();
|
|
2916
|
+
if (signal?.aborted) {
|
|
2917
|
+
throw new TransportError({ operation: "sendProto", cause: new Error("Aborted") });
|
|
2918
|
+
}
|
|
2919
|
+
this._protoSendQueue.push(msg);
|
|
2920
|
+
}
|
|
2921
|
+
async receiveProto(signal) {
|
|
2922
|
+
this.checkClosed();
|
|
2923
|
+
if (signal?.aborted) {
|
|
2924
|
+
throw new TransportError({ operation: "receiveProto", cause: new Error("Aborted") });
|
|
2925
|
+
}
|
|
2926
|
+
const queued = this._protoReceiveQueue.shift();
|
|
2927
|
+
if (queued) {
|
|
2928
|
+
return queued;
|
|
2296
2929
|
}
|
|
2930
|
+
return new Promise((resolve2, reject) => {
|
|
2931
|
+
let onAbort = null;
|
|
2932
|
+
const pendingRead = {
|
|
2933
|
+
resolve: (value) => {
|
|
2934
|
+
if (onAbort && signal) {
|
|
2935
|
+
signal.removeEventListener("abort", onAbort);
|
|
2936
|
+
}
|
|
2937
|
+
resolve2(value);
|
|
2938
|
+
},
|
|
2939
|
+
reject: (error) => {
|
|
2940
|
+
if (onAbort && signal) {
|
|
2941
|
+
signal.removeEventListener("abort", onAbort);
|
|
2942
|
+
}
|
|
2943
|
+
reject(error);
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
this._pendingProtoReads.push(pendingRead);
|
|
2947
|
+
if (signal) {
|
|
2948
|
+
onAbort = () => {
|
|
2949
|
+
const idx = this._pendingProtoReads.indexOf(pendingRead);
|
|
2950
|
+
if (idx !== -1) {
|
|
2951
|
+
this._pendingProtoReads.splice(idx, 1);
|
|
2952
|
+
}
|
|
2953
|
+
reject(new TransportError({ operation: "receiveProto", cause: new Error("Aborted") }));
|
|
2954
|
+
};
|
|
2955
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2297
2958
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2959
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2960
|
+
async close() {
|
|
2961
|
+
if (this._closed) return;
|
|
2962
|
+
this._closed = true;
|
|
2963
|
+
for (const pending of this._pendingProtoReads) {
|
|
2964
|
+
pending.reject(ErrClosed());
|
|
2965
|
+
}
|
|
2966
|
+
this._pendingProtoReads = [];
|
|
2303
2967
|
}
|
|
2304
2968
|
};
|
|
2305
|
-
DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
2306
2969
|
}
|
|
2307
2970
|
});
|
|
2308
2971
|
|
|
@@ -2392,16 +3055,22 @@ var init_prepared = __esm({
|
|
|
2392
3055
|
_query;
|
|
2393
3056
|
_parameters;
|
|
2394
3057
|
_closed = false;
|
|
3058
|
+
_onClose;
|
|
2395
3059
|
/**
|
|
2396
3060
|
* Create a new prepared statement.
|
|
2397
3061
|
*
|
|
3062
|
+
* @param conn - Connection to use for execution
|
|
3063
|
+
* @param query - Query text with parameters
|
|
3064
|
+
* @param onClose - Optional callback invoked when close() is called (e.g., to release a pooled connection)
|
|
3065
|
+
*
|
|
2398
3066
|
* @internal Use Connection.prepare() instead.
|
|
2399
3067
|
*/
|
|
2400
|
-
constructor(conn, query2) {
|
|
3068
|
+
constructor(conn, query2, onClose) {
|
|
2401
3069
|
validateQuery(query2);
|
|
2402
3070
|
this._conn = conn;
|
|
2403
3071
|
this._query = query2;
|
|
2404
3072
|
this._parameters = extractParameters(query2);
|
|
3073
|
+
this._onClose = onClose;
|
|
2405
3074
|
}
|
|
2406
3075
|
/**
|
|
2407
3076
|
* Get the query text.
|
|
@@ -2488,9 +3157,18 @@ var init_prepared = __esm({
|
|
|
2488
3157
|
* Close the prepared statement.
|
|
2489
3158
|
*
|
|
2490
3159
|
* After closing, the statement cannot be executed.
|
|
3160
|
+
* If a release callback was provided (e.g., to return a pooled connection),
|
|
3161
|
+
* it is invoked exactly once on the first call to close().
|
|
2491
3162
|
*/
|
|
2492
3163
|
close() {
|
|
3164
|
+
if (this._closed) {
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
2493
3167
|
this._closed = true;
|
|
3168
|
+
if (this._onClose) {
|
|
3169
|
+
this._onClose();
|
|
3170
|
+
this._onClose = void 0;
|
|
3171
|
+
}
|
|
2494
3172
|
}
|
|
2495
3173
|
/**
|
|
2496
3174
|
* Resolve and validate parameters.
|
|
@@ -2771,10 +3449,10 @@ function formatBytes(bytes) {
|
|
|
2771
3449
|
return `${value.toFixed(2)} ${units[unitIndex]}`;
|
|
2772
3450
|
}
|
|
2773
3451
|
var init_explain = __esm({
|
|
2774
|
-
"src/explain.ts"() {
|
|
3452
|
+
async "src/explain.ts"() {
|
|
2775
3453
|
init_validate();
|
|
2776
3454
|
init_errors();
|
|
2777
|
-
init_proto();
|
|
3455
|
+
await init_proto();
|
|
2778
3456
|
}
|
|
2779
3457
|
});
|
|
2780
3458
|
|
|
@@ -2788,6 +3466,10 @@ __export(batch_exports, {
|
|
|
2788
3466
|
batchParallel: () => batchParallel
|
|
2789
3467
|
});
|
|
2790
3468
|
async function batch(conn, queries, options) {
|
|
3469
|
+
const concurrency = options?.concurrency ?? 1;
|
|
3470
|
+
if (concurrency > 1) {
|
|
3471
|
+
return batchParallel(conn, queries, { ...options, concurrency });
|
|
3472
|
+
}
|
|
2791
3473
|
const results = [];
|
|
2792
3474
|
const startTime = Date.now();
|
|
2793
3475
|
let successful = 0;
|
|
@@ -2909,7 +3591,8 @@ async function batchMap(conn, queryTemplate, items, options) {
|
|
|
2909
3591
|
return batch(conn, queries, options);
|
|
2910
3592
|
}
|
|
2911
3593
|
async function batchParallel(conn, queries, options) {
|
|
2912
|
-
const
|
|
3594
|
+
const requestedConcurrency = options?.concurrency ?? 5;
|
|
3595
|
+
const concurrency = Math.max(1, Math.min(requestedConcurrency, 1));
|
|
2913
3596
|
const results = [];
|
|
2914
3597
|
const startTime = Date.now();
|
|
2915
3598
|
let successful = 0;
|
|
@@ -3003,18 +3686,38 @@ var init_auth = __esm({
|
|
|
3003
3686
|
AuthClient = class {
|
|
3004
3687
|
_conn;
|
|
3005
3688
|
_passwordPolicy;
|
|
3689
|
+
_onClose;
|
|
3690
|
+
_closed = false;
|
|
3006
3691
|
/**
|
|
3007
3692
|
* Create a new auth client.
|
|
3008
3693
|
*
|
|
3009
3694
|
* @param conn - Connection to use
|
|
3010
3695
|
* @param passwordPolicy - Optional password policy configuration
|
|
3696
|
+
* @param onClose - Optional callback invoked when close() is called (e.g., to release a pooled connection)
|
|
3011
3697
|
*/
|
|
3012
|
-
constructor(conn, passwordPolicy) {
|
|
3698
|
+
constructor(conn, passwordPolicy, onClose) {
|
|
3013
3699
|
this._conn = conn;
|
|
3014
3700
|
this._passwordPolicy = {
|
|
3015
3701
|
...DEFAULT_PASSWORD_POLICY,
|
|
3016
3702
|
...passwordPolicy
|
|
3017
3703
|
};
|
|
3704
|
+
this._onClose = onClose;
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Close the auth client and release the underlying connection.
|
|
3708
|
+
*
|
|
3709
|
+
* If a release callback was provided (e.g., to return a pooled connection),
|
|
3710
|
+
* it is invoked exactly once on the first call to close().
|
|
3711
|
+
*/
|
|
3712
|
+
close() {
|
|
3713
|
+
if (this._closed) {
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
this._closed = true;
|
|
3717
|
+
if (this._onClose) {
|
|
3718
|
+
this._onClose();
|
|
3719
|
+
this._onClose = void 0;
|
|
3720
|
+
}
|
|
3018
3721
|
}
|
|
3019
3722
|
/**
|
|
3020
3723
|
* Get the current password policy.
|
|
@@ -3032,14 +3735,9 @@ var init_auth = __esm({
|
|
|
3032
3735
|
};
|
|
3033
3736
|
}
|
|
3034
3737
|
// User Management
|
|
3035
|
-
/**
|
|
3036
|
-
* Create a new user.
|
|
3037
|
-
*
|
|
3038
|
-
* @param username - Username for the new user
|
|
3039
|
-
* @param options - User creation options
|
|
3040
|
-
*/
|
|
3738
|
+
/** Create a new user. */
|
|
3041
3739
|
async createUser(username, options) {
|
|
3042
|
-
|
|
3740
|
+
validateUsername(username);
|
|
3043
3741
|
this.validatePassword(options.password);
|
|
3044
3742
|
if (options.roles) {
|
|
3045
3743
|
for (const role of options.roles) {
|
|
@@ -3061,25 +3759,16 @@ var init_auth = __esm({
|
|
|
3061
3759
|
}
|
|
3062
3760
|
});
|
|
3063
3761
|
}
|
|
3064
|
-
/**
|
|
3065
|
-
* Delete a user.
|
|
3066
|
-
*
|
|
3067
|
-
* @param username - Username to delete
|
|
3068
|
-
*/
|
|
3762
|
+
/** Delete a user. */
|
|
3069
3763
|
async deleteUser(username) {
|
|
3070
|
-
|
|
3764
|
+
validateUsername(username);
|
|
3071
3765
|
await this._conn.exec("DROP USER $username", {
|
|
3072
3766
|
params: { username }
|
|
3073
3767
|
});
|
|
3074
3768
|
}
|
|
3075
|
-
/**
|
|
3076
|
-
* Get user information.
|
|
3077
|
-
*
|
|
3078
|
-
* @param username - Username to look up
|
|
3079
|
-
* @returns User information or null if not found
|
|
3080
|
-
*/
|
|
3769
|
+
/** Get user information, or null if not found. */
|
|
3081
3770
|
async getUser(username) {
|
|
3082
|
-
|
|
3771
|
+
validateUsername(username);
|
|
3083
3772
|
const result = await this._conn.queryAll("SHOW USER $username", { params: { username } });
|
|
3084
3773
|
if (result.length === 0) {
|
|
3085
3774
|
return null;
|
|
@@ -3097,11 +3786,7 @@ var init_auth = __esm({
|
|
|
3097
3786
|
metadata: row["metadata"]
|
|
3098
3787
|
};
|
|
3099
3788
|
}
|
|
3100
|
-
/**
|
|
3101
|
-
* List all users.
|
|
3102
|
-
*
|
|
3103
|
-
* @returns Array of users
|
|
3104
|
-
*/
|
|
3789
|
+
/** List all users. */
|
|
3105
3790
|
async listUsers() {
|
|
3106
3791
|
const result = await this._conn.queryAll("SHOW USERS");
|
|
3107
3792
|
return result.map((row) => ({
|
|
@@ -3112,50 +3797,32 @@ var init_auth = __esm({
|
|
|
3112
3797
|
lastLoginAt: row["last_login"] ? new Date(row["last_login"]) : void 0
|
|
3113
3798
|
}));
|
|
3114
3799
|
}
|
|
3115
|
-
/**
|
|
3116
|
-
* Change a user's password.
|
|
3117
|
-
*
|
|
3118
|
-
* @param username - Username
|
|
3119
|
-
* @param newPassword - New password
|
|
3120
|
-
*/
|
|
3800
|
+
/** Change a user's password. */
|
|
3121
3801
|
async changePassword(username, newPassword) {
|
|
3122
|
-
|
|
3802
|
+
validateUsername(username);
|
|
3123
3803
|
this.validatePassword(newPassword);
|
|
3124
3804
|
await this._conn.exec("ALTER USER $username SET password = $password", {
|
|
3125
3805
|
params: { username, password: newPassword }
|
|
3126
3806
|
});
|
|
3127
3807
|
}
|
|
3128
|
-
/**
|
|
3129
|
-
* Activate a user.
|
|
3130
|
-
*
|
|
3131
|
-
* @param username - Username to activate
|
|
3132
|
-
*/
|
|
3808
|
+
/** Activate a user. */
|
|
3133
3809
|
async activateUser(username) {
|
|
3134
|
-
|
|
3810
|
+
validateUsername(username);
|
|
3135
3811
|
await this._conn.exec("ALTER USER $username SET active = true", {
|
|
3136
3812
|
params: { username }
|
|
3137
3813
|
});
|
|
3138
3814
|
}
|
|
3139
|
-
/**
|
|
3140
|
-
* Deactivate a user.
|
|
3141
|
-
*
|
|
3142
|
-
* @param username - Username to deactivate
|
|
3143
|
-
*/
|
|
3815
|
+
/** Deactivate a user. */
|
|
3144
3816
|
async deactivateUser(username) {
|
|
3145
|
-
|
|
3817
|
+
validateUsername(username);
|
|
3146
3818
|
await this._conn.exec("ALTER USER $username SET active = false", {
|
|
3147
3819
|
params: { username }
|
|
3148
3820
|
});
|
|
3149
3821
|
}
|
|
3150
3822
|
// Role Management
|
|
3151
|
-
/**
|
|
3152
|
-
* Create a new role.
|
|
3153
|
-
*
|
|
3154
|
-
* @param name - Role name
|
|
3155
|
-
* @param options - Role creation options
|
|
3156
|
-
*/
|
|
3823
|
+
/** Create a new role. */
|
|
3157
3824
|
async createRole(name, options) {
|
|
3158
|
-
|
|
3825
|
+
validateRoleName(name);
|
|
3159
3826
|
let query2 = "CREATE ROLE $name";
|
|
3160
3827
|
if (options?.description) {
|
|
3161
3828
|
query2 += " SET description = $description";
|
|
@@ -3172,25 +3839,16 @@ var init_auth = __esm({
|
|
|
3172
3839
|
}
|
|
3173
3840
|
}
|
|
3174
3841
|
}
|
|
3175
|
-
/**
|
|
3176
|
-
* Delete a role.
|
|
3177
|
-
*
|
|
3178
|
-
* @param name - Role name to delete
|
|
3179
|
-
*/
|
|
3842
|
+
/** Delete a role. */
|
|
3180
3843
|
async deleteRole(name) {
|
|
3181
|
-
|
|
3844
|
+
validateRoleName(name);
|
|
3182
3845
|
await this._conn.exec("DROP ROLE $name", {
|
|
3183
3846
|
params: { name }
|
|
3184
3847
|
});
|
|
3185
3848
|
}
|
|
3186
|
-
/**
|
|
3187
|
-
* Get role information.
|
|
3188
|
-
*
|
|
3189
|
-
* @param name - Role name
|
|
3190
|
-
* @returns Role information or null if not found
|
|
3191
|
-
*/
|
|
3849
|
+
/** Get role information, or null if not found. */
|
|
3192
3850
|
async getRole(name) {
|
|
3193
|
-
|
|
3851
|
+
validateRoleName(name);
|
|
3194
3852
|
const result = await this._conn.queryAll("SHOW ROLE $name", { params: { name } });
|
|
3195
3853
|
const row = result[0];
|
|
3196
3854
|
if (!row) {
|
|
@@ -3203,11 +3861,7 @@ var init_auth = __esm({
|
|
|
3203
3861
|
system: row["system"]
|
|
3204
3862
|
};
|
|
3205
3863
|
}
|
|
3206
|
-
/**
|
|
3207
|
-
* List all roles.
|
|
3208
|
-
*
|
|
3209
|
-
* @returns Array of roles
|
|
3210
|
-
*/
|
|
3864
|
+
/** List all roles. */
|
|
3211
3865
|
async listRoles() {
|
|
3212
3866
|
const result = await this._conn.queryAll("SHOW ROLES");
|
|
3213
3867
|
return result.map((row) => ({
|
|
@@ -3217,40 +3871,25 @@ var init_auth = __esm({
|
|
|
3217
3871
|
system: row["system"]
|
|
3218
3872
|
}));
|
|
3219
3873
|
}
|
|
3220
|
-
/**
|
|
3221
|
-
* Assign a role to a user.
|
|
3222
|
-
*
|
|
3223
|
-
* @param username - Username
|
|
3224
|
-
* @param roleName - Role to assign
|
|
3225
|
-
*/
|
|
3874
|
+
/** Assign a role to a user. */
|
|
3226
3875
|
async assignRole(username, roleName) {
|
|
3227
|
-
|
|
3228
|
-
|
|
3876
|
+
validateUsername(username);
|
|
3877
|
+
validateRoleName(roleName);
|
|
3229
3878
|
await this._conn.exec("GRANT ROLE $role TO $user", {
|
|
3230
3879
|
params: { role: roleName, user: username }
|
|
3231
3880
|
});
|
|
3232
3881
|
}
|
|
3233
|
-
/**
|
|
3234
|
-
* Revoke a role from a user.
|
|
3235
|
-
*
|
|
3236
|
-
* @param username - Username
|
|
3237
|
-
* @param roleName - Role to revoke
|
|
3238
|
-
*/
|
|
3882
|
+
/** Revoke a role from a user. */
|
|
3239
3883
|
async revokeRole(username, roleName) {
|
|
3240
|
-
|
|
3241
|
-
|
|
3884
|
+
validateUsername(username);
|
|
3885
|
+
validateRoleName(roleName);
|
|
3242
3886
|
await this._conn.exec("REVOKE ROLE $role FROM $user", {
|
|
3243
3887
|
params: { role: roleName, user: username }
|
|
3244
3888
|
});
|
|
3245
3889
|
}
|
|
3246
|
-
/**
|
|
3247
|
-
* Grant a permission to a role.
|
|
3248
|
-
*
|
|
3249
|
-
* @param roleName - Role name
|
|
3250
|
-
* @param permission - Permission to grant
|
|
3251
|
-
*/
|
|
3890
|
+
/** Grant a permission to a role. */
|
|
3252
3891
|
async grantPermission(roleName, permission) {
|
|
3253
|
-
|
|
3892
|
+
validateRoleName(roleName);
|
|
3254
3893
|
validatePermissionAction(permission.action);
|
|
3255
3894
|
validatePermissionResource(permission.resource);
|
|
3256
3895
|
if (permission.label) {
|
|
@@ -3265,14 +3904,9 @@ var init_auth = __esm({
|
|
|
3265
3904
|
params: { role: roleName }
|
|
3266
3905
|
});
|
|
3267
3906
|
}
|
|
3268
|
-
/**
|
|
3269
|
-
* Revoke a permission from a role.
|
|
3270
|
-
*
|
|
3271
|
-
* @param roleName - Role name
|
|
3272
|
-
* @param permission - Permission to revoke
|
|
3273
|
-
*/
|
|
3907
|
+
/** Revoke a permission from a role. */
|
|
3274
3908
|
async revokePermission(roleName, permission) {
|
|
3275
|
-
|
|
3909
|
+
validateRoleName(roleName);
|
|
3276
3910
|
validatePermissionAction(permission.action);
|
|
3277
3911
|
validatePermissionResource(permission.resource);
|
|
3278
3912
|
if (permission.label) {
|
|
@@ -3432,538 +4066,121 @@ var init_auth = __esm({
|
|
|
3432
4066
|
validatePermissionResource(resource);
|
|
3433
4067
|
validatePermissionAction(action);
|
|
3434
4068
|
if (label) {
|
|
3435
|
-
validateLabel(label);
|
|
3436
|
-
}
|
|
3437
|
-
const target = label ? `${resource}:${label}` : resource;
|
|
3438
|
-
const result = await this._conn.queryAll("RETURN has_permission($target, $action) AS allowed", {
|
|
3439
|
-
params: { target, action }
|
|
3440
|
-
});
|
|
3441
|
-
return result[0]?.["allowed"] ?? false;
|
|
3442
|
-
}
|
|
3443
|
-
// Validation helpers
|
|
3444
|
-
validateUsername(username) {
|
|
3445
|
-
if (!username || username.length === 0) {
|
|
3446
|
-
throw new SecurityError({
|
|
3447
|
-
type: "validation",
|
|
3448
|
-
message: "Username cannot be empty"
|
|
3449
|
-
});
|
|
3450
|
-
}
|
|
3451
|
-
if (username.length > 128) {
|
|
3452
|
-
throw new SecurityError({
|
|
3453
|
-
type: "validation",
|
|
3454
|
-
message: "Username too long (max 128 characters)"
|
|
3455
|
-
});
|
|
3456
|
-
}
|
|
3457
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(username)) {
|
|
3458
|
-
throw new SecurityError({
|
|
3459
|
-
type: "validation",
|
|
3460
|
-
message: "Invalid username format"
|
|
3461
|
-
});
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
validatePassword(password) {
|
|
3465
|
-
const policy = this._passwordPolicy;
|
|
3466
|
-
if (!password || password.length < policy.minLength) {
|
|
3467
|
-
throw new SecurityError({
|
|
3468
|
-
type: "validation",
|
|
3469
|
-
message: `Password must be at least ${policy.minLength} characters`
|
|
3470
|
-
});
|
|
3471
|
-
}
|
|
3472
|
-
if (password.length > policy.maxLength) {
|
|
3473
|
-
throw new SecurityError({
|
|
3474
|
-
type: "validation",
|
|
3475
|
-
message: `Password too long (max ${policy.maxLength} characters)`
|
|
3476
|
-
});
|
|
3477
|
-
}
|
|
3478
|
-
if (policy.requireUppercase && !/[A-Z]/.test(password)) {
|
|
3479
|
-
throw new SecurityError({
|
|
3480
|
-
type: "validation",
|
|
3481
|
-
message: "Password must contain at least one uppercase letter"
|
|
3482
|
-
});
|
|
3483
|
-
}
|
|
3484
|
-
if (policy.requireLowercase && !/[a-z]/.test(password)) {
|
|
3485
|
-
throw new SecurityError({
|
|
3486
|
-
type: "validation",
|
|
3487
|
-
message: "Password must contain at least one lowercase letter"
|
|
3488
|
-
});
|
|
3489
|
-
}
|
|
3490
|
-
if (policy.requireDigit && !/[0-9]/.test(password)) {
|
|
3491
|
-
throw new SecurityError({
|
|
3492
|
-
type: "validation",
|
|
3493
|
-
message: "Password must contain at least one digit"
|
|
3494
|
-
});
|
|
3495
|
-
}
|
|
3496
|
-
if (policy.requireSpecialChar) {
|
|
3497
|
-
const specialCharsEscaped = policy.specialChars.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
|
|
3498
|
-
const specialRegex = new RegExp(`[${specialCharsEscaped}]`);
|
|
3499
|
-
if (!specialRegex.test(password)) {
|
|
3500
|
-
throw new SecurityError({
|
|
3501
|
-
type: "validation",
|
|
3502
|
-
message: `Password must contain at least one special character (${policy.specialChars})`
|
|
3503
|
-
});
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
validateRoleName(name) {
|
|
3508
|
-
if (!name || name.length === 0) {
|
|
3509
|
-
throw new SecurityError({
|
|
3510
|
-
type: "validation",
|
|
3511
|
-
message: "Role name cannot be empty"
|
|
3512
|
-
});
|
|
3513
|
-
}
|
|
3514
|
-
if (name.length > 128) {
|
|
3515
|
-
throw new SecurityError({
|
|
3516
|
-
type: "validation",
|
|
3517
|
-
message: "Role name too long (max 128 characters)"
|
|
3518
|
-
});
|
|
3519
|
-
}
|
|
3520
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
3521
|
-
throw new SecurityError({
|
|
3522
|
-
type: "validation",
|
|
3523
|
-
message: "Invalid role name format"
|
|
3524
|
-
});
|
|
3525
|
-
}
|
|
3526
|
-
}
|
|
3527
|
-
validatePolicyName(name) {
|
|
3528
|
-
if (!name || name.length === 0) {
|
|
3529
|
-
throw new SecurityError({
|
|
3530
|
-
type: "validation",
|
|
3531
|
-
message: "Policy name cannot be empty"
|
|
3532
|
-
});
|
|
3533
|
-
}
|
|
3534
|
-
if (name.length > 128) {
|
|
3535
|
-
throw new SecurityError({
|
|
3536
|
-
type: "validation",
|
|
3537
|
-
message: "Policy name too long (max 128 characters)"
|
|
3538
|
-
});
|
|
3539
|
-
}
|
|
3540
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
3541
|
-
throw new SecurityError({
|
|
3542
|
-
type: "validation",
|
|
3543
|
-
message: "Invalid policy name format"
|
|
3544
|
-
});
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
};
|
|
3548
|
-
}
|
|
3549
|
-
});
|
|
3550
|
-
|
|
3551
|
-
// src/connection.ts
|
|
3552
|
-
init_config();
|
|
3553
|
-
init_errors();
|
|
3554
|
-
|
|
3555
|
-
// src/transport.ts
|
|
3556
|
-
init_config();
|
|
3557
|
-
init_errors();
|
|
3558
|
-
init_validate();
|
|
3559
|
-
init_proto();
|
|
3560
|
-
var GEODE_ALPN = ["geode/1"];
|
|
3561
|
-
async function buildTLSConfig(cfg) {
|
|
3562
|
-
const options = {
|
|
3563
|
-
minVersion: "TLSv1.3",
|
|
3564
|
-
maxVersion: "TLSv1.3"
|
|
3565
|
-
};
|
|
3566
|
-
if (cfg.tlsCACert) {
|
|
3567
|
-
options.ca = cfg.tlsCACert;
|
|
3568
|
-
} else if (cfg.tlsCA) {
|
|
3569
|
-
validateTLSCertPath(cfg.tlsCA, "tlsCA");
|
|
3570
|
-
try {
|
|
3571
|
-
options.ca = await fs.promises.readFile(cfg.tlsCA, "utf-8");
|
|
3572
|
-
} catch (e) {
|
|
3573
|
-
throw new TransportError({
|
|
3574
|
-
operation: "load_ca_cert",
|
|
3575
|
-
cause: e instanceof Error ? e : new Error(String(e))
|
|
3576
|
-
});
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
if (cfg.tlsCertPEM) {
|
|
3580
|
-
options.cert = cfg.tlsCertPEM;
|
|
3581
|
-
} else if (cfg.tlsCert) {
|
|
3582
|
-
validateTLSCertPath(cfg.tlsCert, "tlsCert");
|
|
3583
|
-
try {
|
|
3584
|
-
options.cert = await fs.promises.readFile(cfg.tlsCert, "utf-8");
|
|
3585
|
-
} catch (e) {
|
|
3586
|
-
throw new TransportError({
|
|
3587
|
-
operation: "load_client_cert",
|
|
3588
|
-
cause: e instanceof Error ? e : new Error(String(e))
|
|
3589
|
-
});
|
|
3590
|
-
}
|
|
3591
|
-
}
|
|
3592
|
-
if (cfg.tlsKeyPEM) {
|
|
3593
|
-
options.key = cfg.tlsKeyPEM;
|
|
3594
|
-
} else if (cfg.tlsKey) {
|
|
3595
|
-
validateTLSCertPath(cfg.tlsKey, "tlsKey");
|
|
3596
|
-
try {
|
|
3597
|
-
options.key = await fs.promises.readFile(cfg.tlsKey, "utf-8");
|
|
3598
|
-
} catch (e) {
|
|
3599
|
-
throw new TransportError({
|
|
3600
|
-
operation: "load_client_key",
|
|
3601
|
-
cause: e instanceof Error ? e : new Error(String(e))
|
|
3602
|
-
});
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
return options;
|
|
3606
|
-
}
|
|
3607
|
-
var BaseTransport = class {
|
|
3608
|
-
_closed = false;
|
|
3609
|
-
_address;
|
|
3610
|
-
constructor(address) {
|
|
3611
|
-
this._address = address;
|
|
3612
|
-
}
|
|
3613
|
-
isClosed() {
|
|
3614
|
-
return this._closed;
|
|
3615
|
-
}
|
|
3616
|
-
getAddress() {
|
|
3617
|
-
return this._address;
|
|
3618
|
-
}
|
|
3619
|
-
checkClosed() {
|
|
3620
|
-
if (this._closed) {
|
|
3621
|
-
throw ErrClosed;
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
};
|
|
3625
|
-
var QuicTransport = class _QuicTransport extends BaseTransport {
|
|
3626
|
-
_client = null;
|
|
3627
|
-
_stream = null;
|
|
3628
|
-
_protoBuffer = Buffer.alloc(0);
|
|
3629
|
-
_pendingProtoReads = [];
|
|
3630
|
-
constructor(address) {
|
|
3631
|
-
super(address);
|
|
3632
|
-
}
|
|
3633
|
-
/**
|
|
3634
|
-
* Connect to the Geode server using QUIC.
|
|
3635
|
-
*/
|
|
3636
|
-
static async connect(cfg) {
|
|
3637
|
-
const address = getAddress(cfg);
|
|
3638
|
-
const transport = new _QuicTransport(address);
|
|
3639
|
-
try {
|
|
3640
|
-
const { QUICClient } = await import('@matrixai/quic');
|
|
3641
|
-
const [host = "localhost", portStr] = address.includes("[") ? [address.slice(1, address.lastIndexOf("]")), address.slice(address.lastIndexOf(":") + 1)] : address.split(":");
|
|
3642
|
-
const port = parseInt(portStr ?? "3141", 10);
|
|
3643
|
-
const cryptoOps = {
|
|
3644
|
-
ops: {
|
|
3645
|
-
randomBytes: (data) => {
|
|
3646
|
-
const buf = Buffer.from(data);
|
|
3647
|
-
crypto.randomFillSync(buf);
|
|
3648
|
-
return Promise.resolve();
|
|
3649
|
-
}
|
|
4069
|
+
validateLabel(label);
|
|
3650
4070
|
}
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
"SECURITY WARNING: insecureSkipVerify is enabled in production environment. This disables TLS certificate verification and exposes the connection to MITM attacks. Set GEODE_ALLOW_INSECURE_PRODUCTION=true to override this warning."
|
|
3666
|
-
);
|
|
3667
|
-
if (process.env["GEODE_ALLOW_INSECURE_PRODUCTION"] !== "true") {
|
|
3668
|
-
throw new TransportError({
|
|
3669
|
-
operation: "connect",
|
|
3670
|
-
cause: new Error(
|
|
3671
|
-
"insecureSkipVerify is not allowed in production. Configure proper TLS certificates or set GEODE_ALLOW_INSECURE_PRODUCTION=true to override."
|
|
3672
|
-
)
|
|
3673
|
-
});
|
|
3674
|
-
}
|
|
4071
|
+
const target = label ? `${resource}:${label}` : resource;
|
|
4072
|
+
const result = await this._conn.queryAll("RETURN has_permission($target, $action) AS allowed", {
|
|
4073
|
+
params: { target, action }
|
|
4074
|
+
});
|
|
4075
|
+
return result[0]?.["allowed"] ?? false;
|
|
4076
|
+
}
|
|
4077
|
+
// Validation helpers
|
|
4078
|
+
validatePassword(password) {
|
|
4079
|
+
const policy = this._passwordPolicy;
|
|
4080
|
+
if (!password || password.length < policy.minLength) {
|
|
4081
|
+
throw new SecurityError({
|
|
4082
|
+
type: "validation",
|
|
4083
|
+
message: `Password must be at least ${policy.minLength} characters`
|
|
4084
|
+
});
|
|
3675
4085
|
}
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
if (tlsConfig.cert && tlsConfig.key) {
|
|
3682
|
-
config.cert = tlsConfig.cert;
|
|
3683
|
-
config.key = tlsConfig.key;
|
|
3684
|
-
}
|
|
3685
|
-
const client = await QUICClient.createQUICClient(
|
|
3686
|
-
{
|
|
3687
|
-
host,
|
|
3688
|
-
port,
|
|
3689
|
-
serverName: cfg.serverName ?? host,
|
|
3690
|
-
crypto: cryptoOps,
|
|
3691
|
-
config
|
|
3692
|
-
},
|
|
3693
|
-
{ timer: cfg.connectTimeout ?? 3e4 }
|
|
3694
|
-
);
|
|
3695
|
-
transport._client = client;
|
|
3696
|
-
const connection = client.connection;
|
|
3697
|
-
const stream = await connection.newStream();
|
|
3698
|
-
transport._stream = stream;
|
|
3699
|
-
transport.startReading();
|
|
3700
|
-
return transport;
|
|
3701
|
-
} catch (e) {
|
|
3702
|
-
await transport.close();
|
|
3703
|
-
throw new TransportError({
|
|
3704
|
-
operation: "connect",
|
|
3705
|
-
address,
|
|
3706
|
-
cause: e instanceof Error ? e : new Error(String(e))
|
|
3707
|
-
});
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
/**
|
|
3711
|
-
* Start reading from the stream in the background.
|
|
3712
|
-
*/
|
|
3713
|
-
startReading() {
|
|
3714
|
-
if (!this._stream) return;
|
|
3715
|
-
const stream = this._stream;
|
|
3716
|
-
const reader = stream.readable.getReader();
|
|
3717
|
-
const read = async () => {
|
|
3718
|
-
try {
|
|
3719
|
-
while (!this._closed) {
|
|
3720
|
-
const { done, value } = await reader.read();
|
|
3721
|
-
if (done) {
|
|
3722
|
-
this.rejectPendingReads(
|
|
3723
|
-
new TransportError({
|
|
3724
|
-
operation: "read",
|
|
3725
|
-
cause: new Error("Stream ended unexpectedly")
|
|
3726
|
-
})
|
|
3727
|
-
);
|
|
3728
|
-
break;
|
|
3729
|
-
}
|
|
3730
|
-
if (value) {
|
|
3731
|
-
this.processData(Buffer.from(value));
|
|
3732
|
-
}
|
|
4086
|
+
if (password.length > policy.maxLength) {
|
|
4087
|
+
throw new SecurityError({
|
|
4088
|
+
type: "validation",
|
|
4089
|
+
message: `Password too long (max ${policy.maxLength} characters)`
|
|
4090
|
+
});
|
|
3733
4091
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
cause: err instanceof Error ? err : new Error(String(err))
|
|
3740
|
-
})
|
|
3741
|
-
);
|
|
4092
|
+
if (policy.requireUppercase && !/[A-Z]/.test(password)) {
|
|
4093
|
+
throw new SecurityError({
|
|
4094
|
+
type: "validation",
|
|
4095
|
+
message: "Password must contain at least one uppercase letter"
|
|
4096
|
+
});
|
|
3742
4097
|
}
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
}
|
|
3749
|
-
/**
|
|
3750
|
-
* Reject all pending reads with an error.
|
|
3751
|
-
*/
|
|
3752
|
-
rejectPendingReads(error) {
|
|
3753
|
-
const pending = this._pendingProtoReads;
|
|
3754
|
-
this._pendingProtoReads = [];
|
|
3755
|
-
for (const p of pending) {
|
|
3756
|
-
p.reject(error);
|
|
3757
|
-
}
|
|
3758
|
-
}
|
|
3759
|
-
/**
|
|
3760
|
-
* Process received data (length-prefixed protobuf messages).
|
|
3761
|
-
*/
|
|
3762
|
-
processData(data) {
|
|
3763
|
-
this._protoBuffer = Buffer.concat([this._protoBuffer, data]);
|
|
3764
|
-
while (this._protoBuffer.length >= 4) {
|
|
3765
|
-
const msgLen = decodeLengthPrefix(this._protoBuffer);
|
|
3766
|
-
const totalLen = 4 + msgLen;
|
|
3767
|
-
if (this._protoBuffer.length < totalLen) {
|
|
3768
|
-
break;
|
|
3769
|
-
}
|
|
3770
|
-
const msgData = this._protoBuffer.subarray(4, totalLen);
|
|
3771
|
-
this._protoBuffer = this._protoBuffer.subarray(totalLen);
|
|
3772
|
-
try {
|
|
3773
|
-
const msg = decodeQuicServerMessage(msgData);
|
|
3774
|
-
const pending = this._pendingProtoReads.shift();
|
|
3775
|
-
if (pending) {
|
|
3776
|
-
pending.resolve(msg);
|
|
4098
|
+
if (policy.requireLowercase && !/[a-z]/.test(password)) {
|
|
4099
|
+
throw new SecurityError({
|
|
4100
|
+
type: "validation",
|
|
4101
|
+
message: "Password must contain at least one lowercase letter"
|
|
4102
|
+
});
|
|
3777
4103
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
operation: "decode",
|
|
3784
|
-
cause: err instanceof Error ? err : new Error(String(err))
|
|
3785
|
-
})
|
|
3786
|
-
);
|
|
4104
|
+
if (policy.requireDigit && !/[0-9]/.test(password)) {
|
|
4105
|
+
throw new SecurityError({
|
|
4106
|
+
type: "validation",
|
|
4107
|
+
message: "Password must contain at least one digit"
|
|
4108
|
+
});
|
|
3787
4109
|
}
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
if (signal?.aborted) {
|
|
3797
|
-
throw new TransportError({ operation: "sendProto", cause: new Error("Aborted") });
|
|
3798
|
-
}
|
|
3799
|
-
if (!this._stream) {
|
|
3800
|
-
throw new TransportError({ operation: "sendProto", cause: new Error("No stream") });
|
|
3801
|
-
}
|
|
3802
|
-
const data = encodeWithLengthPrefix(msg);
|
|
3803
|
-
try {
|
|
3804
|
-
const stream = this._stream;
|
|
3805
|
-
const writer = stream.writable.getWriter();
|
|
3806
|
-
await writer.write(new Uint8Array(data));
|
|
3807
|
-
writer.releaseLock();
|
|
3808
|
-
} catch (e) {
|
|
3809
|
-
throw new TransportError({
|
|
3810
|
-
operation: "sendProto",
|
|
3811
|
-
address: this._address,
|
|
3812
|
-
cause: e instanceof Error ? e : new Error(String(e))
|
|
3813
|
-
});
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
/**
|
|
3817
|
-
* Receive a protobuf message.
|
|
3818
|
-
*/
|
|
3819
|
-
async receiveProto(signal) {
|
|
3820
|
-
this.checkClosed();
|
|
3821
|
-
if (signal?.aborted) {
|
|
3822
|
-
throw new TransportError({ operation: "receiveProto", cause: new Error("Aborted") });
|
|
3823
|
-
}
|
|
3824
|
-
return new Promise((resolve2, reject) => {
|
|
3825
|
-
let onAbort = null;
|
|
3826
|
-
const pendingRead = {
|
|
3827
|
-
resolve: (value) => {
|
|
3828
|
-
if (onAbort && signal) {
|
|
3829
|
-
signal.removeEventListener("abort", onAbort);
|
|
3830
|
-
}
|
|
3831
|
-
resolve2(value);
|
|
3832
|
-
},
|
|
3833
|
-
reject: (error) => {
|
|
3834
|
-
if (onAbort && signal) {
|
|
3835
|
-
signal.removeEventListener("abort", onAbort);
|
|
4110
|
+
if (policy.requireSpecialChar) {
|
|
4111
|
+
const specialCharsEscaped = policy.specialChars.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
|
|
4112
|
+
const specialRegex = new RegExp(`[${specialCharsEscaped}]`);
|
|
4113
|
+
if (!specialRegex.test(password)) {
|
|
4114
|
+
throw new SecurityError({
|
|
4115
|
+
type: "validation",
|
|
4116
|
+
message: `Password must contain at least one special character (${policy.specialChars})`
|
|
4117
|
+
});
|
|
3836
4118
|
}
|
|
3837
|
-
reject(error);
|
|
3838
4119
|
}
|
|
3839
|
-
};
|
|
3840
|
-
this._pendingProtoReads.push(pendingRead);
|
|
3841
|
-
if (signal) {
|
|
3842
|
-
onAbort = () => {
|
|
3843
|
-
const idx = this._pendingProtoReads.indexOf(pendingRead);
|
|
3844
|
-
if (idx !== -1) {
|
|
3845
|
-
this._pendingProtoReads.splice(idx, 1);
|
|
3846
|
-
}
|
|
3847
|
-
reject(new TransportError({ operation: "receiveProto", cause: new Error("Aborted") }));
|
|
3848
|
-
};
|
|
3849
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
3850
4120
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
4121
|
+
validatePolicyName(name) {
|
|
4122
|
+
if (!name || name.length === 0) {
|
|
4123
|
+
throw new SecurityError({
|
|
4124
|
+
type: "validation",
|
|
4125
|
+
message: "Policy name cannot be empty"
|
|
4126
|
+
});
|
|
4127
|
+
}
|
|
4128
|
+
if (name.length > 128) {
|
|
4129
|
+
throw new SecurityError({
|
|
4130
|
+
type: "validation",
|
|
4131
|
+
message: "Policy name too long (max 128 characters)"
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
4134
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
4135
|
+
throw new SecurityError({
|
|
4136
|
+
type: "validation",
|
|
4137
|
+
message: "Invalid policy name format"
|
|
4138
|
+
});
|
|
4139
|
+
}
|
|
3865
4140
|
}
|
|
3866
|
-
|
|
3867
|
-
}
|
|
3868
|
-
this._stream = null;
|
|
3869
|
-
}
|
|
3870
|
-
};
|
|
3871
|
-
var MockTransport = class extends BaseTransport {
|
|
3872
|
-
_protoSendQueue = [];
|
|
3873
|
-
_protoReceiveQueue = [];
|
|
3874
|
-
_pendingProtoReads = [];
|
|
3875
|
-
constructor(address = "mock:3141") {
|
|
3876
|
-
super(address);
|
|
3877
|
-
}
|
|
3878
|
-
/**
|
|
3879
|
-
* Queue a protobuf response.
|
|
3880
|
-
*/
|
|
3881
|
-
queueProtoResponse(response) {
|
|
3882
|
-
const pending = this._pendingProtoReads.shift();
|
|
3883
|
-
if (pending) {
|
|
3884
|
-
pending.resolve(response);
|
|
3885
|
-
} else {
|
|
3886
|
-
this._protoReceiveQueue.push(response);
|
|
3887
|
-
}
|
|
3888
|
-
}
|
|
3889
|
-
/**
|
|
3890
|
-
* Get all sent protobuf messages.
|
|
3891
|
-
*/
|
|
3892
|
-
getSentProtoMessages() {
|
|
3893
|
-
return [...this._protoSendQueue];
|
|
4141
|
+
};
|
|
3894
4142
|
}
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
// src/abort-utils.ts
|
|
4146
|
+
function withAbortTimeout(signal, timeoutMs) {
|
|
4147
|
+
const effectiveTimeout = timeoutMs ?? 0;
|
|
4148
|
+
if (!effectiveTimeout || effectiveTimeout <= 0) {
|
|
4149
|
+
return { signal, cleanup: () => {
|
|
4150
|
+
} };
|
|
4151
|
+
}
|
|
4152
|
+
const controller = new AbortController();
|
|
4153
|
+
const timeoutId = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
4154
|
+
if (signal?.aborted) {
|
|
4155
|
+
controller.abort();
|
|
4156
|
+
return {
|
|
4157
|
+
signal: controller.signal,
|
|
4158
|
+
cleanup: () => {
|
|
4159
|
+
clearTimeout(timeoutId);
|
|
4160
|
+
}
|
|
4161
|
+
};
|
|
3900
4162
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
throw new TransportError({ operation: "sendProto", cause: new Error("Aborted") });
|
|
3906
|
-
}
|
|
3907
|
-
this._protoSendQueue.push(msg);
|
|
4163
|
+
let onAbort = null;
|
|
4164
|
+
if (signal) {
|
|
4165
|
+
onAbort = () => controller.abort();
|
|
4166
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3908
4167
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
if (queued) {
|
|
3916
|
-
return queued;
|
|
3917
|
-
}
|
|
3918
|
-
return new Promise((resolve2, reject) => {
|
|
3919
|
-
let onAbort = null;
|
|
3920
|
-
const pendingRead = {
|
|
3921
|
-
resolve: (value) => {
|
|
3922
|
-
if (onAbort && signal) {
|
|
3923
|
-
signal.removeEventListener("abort", onAbort);
|
|
3924
|
-
}
|
|
3925
|
-
resolve2(value);
|
|
3926
|
-
},
|
|
3927
|
-
reject: (error) => {
|
|
3928
|
-
if (onAbort && signal) {
|
|
3929
|
-
signal.removeEventListener("abort", onAbort);
|
|
3930
|
-
}
|
|
3931
|
-
reject(error);
|
|
3932
|
-
}
|
|
3933
|
-
};
|
|
3934
|
-
this._pendingProtoReads.push(pendingRead);
|
|
3935
|
-
if (signal) {
|
|
3936
|
-
onAbort = () => {
|
|
3937
|
-
const idx = this._pendingProtoReads.indexOf(pendingRead);
|
|
3938
|
-
if (idx !== -1) {
|
|
3939
|
-
this._pendingProtoReads.splice(idx, 1);
|
|
3940
|
-
}
|
|
3941
|
-
reject(new TransportError({ operation: "receiveProto", cause: new Error("Aborted") }));
|
|
3942
|
-
};
|
|
3943
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
4168
|
+
return {
|
|
4169
|
+
signal: controller.signal,
|
|
4170
|
+
cleanup: () => {
|
|
4171
|
+
clearTimeout(timeoutId);
|
|
4172
|
+
if (signal && onAbort) {
|
|
4173
|
+
signal.removeEventListener("abort", onAbort);
|
|
3944
4174
|
}
|
|
3945
|
-
});
|
|
3946
|
-
}
|
|
3947
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
3948
|
-
async close() {
|
|
3949
|
-
if (this._closed) return;
|
|
3950
|
-
this._closed = true;
|
|
3951
|
-
for (const pending of this._pendingProtoReads) {
|
|
3952
|
-
pending.reject(ErrClosed);
|
|
3953
4175
|
}
|
|
3954
|
-
|
|
3955
|
-
}
|
|
3956
|
-
};
|
|
3957
|
-
async function createTransport(cfg) {
|
|
3958
|
-
if (cfg.transport === "grpc") {
|
|
3959
|
-
const { GrpcTransport: GrpcTransport2 } = await Promise.resolve().then(() => (init_grpc_transport(), grpc_transport_exports));
|
|
3960
|
-
return GrpcTransport2.connect(cfg);
|
|
3961
|
-
}
|
|
3962
|
-
return QuicTransport.connect(cfg);
|
|
4176
|
+
};
|
|
3963
4177
|
}
|
|
3964
4178
|
|
|
3965
4179
|
// src/connection.ts
|
|
3966
|
-
|
|
4180
|
+
init_config();
|
|
4181
|
+
init_errors();
|
|
4182
|
+
await init_transport();
|
|
4183
|
+
await init_proto();
|
|
3967
4184
|
init_validate();
|
|
3968
4185
|
|
|
3969
4186
|
// src/transaction.ts
|
|
@@ -4080,10 +4297,10 @@ var Transaction = class {
|
|
|
4080
4297
|
*/
|
|
4081
4298
|
checkDone() {
|
|
4082
4299
|
if (this._done) {
|
|
4083
|
-
throw ErrTxDone;
|
|
4300
|
+
throw ErrTxDone();
|
|
4084
4301
|
}
|
|
4085
4302
|
if (!this._conn.inTransaction) {
|
|
4086
|
-
throw ErrNoTx;
|
|
4303
|
+
throw ErrNoTx();
|
|
4087
4304
|
}
|
|
4088
4305
|
}
|
|
4089
4306
|
};
|
|
@@ -4187,6 +4404,38 @@ var QueryResult = class {
|
|
|
4187
4404
|
this.close();
|
|
4188
4405
|
}
|
|
4189
4406
|
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Collect all remaining rows as raw records without GQLValue conversion.
|
|
4409
|
+
* This is the fast path used by queryAll() to avoid double-conversion.
|
|
4410
|
+
*/
|
|
4411
|
+
async _collectRawRecords() {
|
|
4412
|
+
try {
|
|
4413
|
+
const records = [];
|
|
4414
|
+
while (!this._closed) {
|
|
4415
|
+
if (this._signal?.aborted) {
|
|
4416
|
+
break;
|
|
4417
|
+
}
|
|
4418
|
+
while (this._bufferIndex < this._buffer.length) {
|
|
4419
|
+
const rawRow = this._buffer[this._bufferIndex];
|
|
4420
|
+
if (rawRow) {
|
|
4421
|
+
this._bufferIndex++;
|
|
4422
|
+
this._rowCount++;
|
|
4423
|
+
records.push(rawRow);
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
if (this._final) {
|
|
4427
|
+
break;
|
|
4428
|
+
}
|
|
4429
|
+
const { rows, final } = await this._conn._fetchNextPage(this._pageSize, this._signal);
|
|
4430
|
+
this._buffer = rows;
|
|
4431
|
+
this._bufferIndex = 0;
|
|
4432
|
+
this._final = final;
|
|
4433
|
+
}
|
|
4434
|
+
return records;
|
|
4435
|
+
} finally {
|
|
4436
|
+
this.close();
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4190
4439
|
/**
|
|
4191
4440
|
* Get all remaining rows as an array.
|
|
4192
4441
|
*/
|
|
@@ -4379,39 +4628,27 @@ var Connection = class _Connection {
|
|
|
4379
4628
|
await conn.hello();
|
|
4380
4629
|
return conn;
|
|
4381
4630
|
}
|
|
4382
|
-
/**
|
|
4383
|
-
* Get connection configuration.
|
|
4384
|
-
*/
|
|
4631
|
+
/** Get connection configuration. */
|
|
4385
4632
|
get config() {
|
|
4386
4633
|
return this._config;
|
|
4387
4634
|
}
|
|
4388
|
-
/**
|
|
4389
|
-
* Get current connection state.
|
|
4390
|
-
*/
|
|
4635
|
+
/** Get current connection state. */
|
|
4391
4636
|
get state() {
|
|
4392
4637
|
return this._state;
|
|
4393
4638
|
}
|
|
4394
|
-
/**
|
|
4395
|
-
* Check if connection is in a transaction.
|
|
4396
|
-
*/
|
|
4639
|
+
/** Check if connection is in a transaction. */
|
|
4397
4640
|
get inTransaction() {
|
|
4398
4641
|
return this._inTransaction;
|
|
4399
4642
|
}
|
|
4400
|
-
/**
|
|
4401
|
-
* Check if connection is closed.
|
|
4402
|
-
*/
|
|
4643
|
+
/** Check if connection is closed. */
|
|
4403
4644
|
get isClosed() {
|
|
4404
4645
|
return this._state === "closed" || this._transport.isClosed();
|
|
4405
4646
|
}
|
|
4406
|
-
/**
|
|
4407
|
-
* Get session ID.
|
|
4408
|
-
*/
|
|
4647
|
+
/** Get session ID. */
|
|
4409
4648
|
get sessionId() {
|
|
4410
4649
|
return this._sessionId;
|
|
4411
4650
|
}
|
|
4412
|
-
/**
|
|
4413
|
-
* Perform the HELLO handshake.
|
|
4414
|
-
*/
|
|
4651
|
+
/** Perform the HELLO handshake. */
|
|
4415
4652
|
async hello() {
|
|
4416
4653
|
const msg = buildHelloRequest(
|
|
4417
4654
|
this._config.username ?? "",
|
|
@@ -4420,8 +4657,8 @@ var Connection = class _Connection {
|
|
|
4420
4657
|
this._config.helloVersion,
|
|
4421
4658
|
this._config.conformance
|
|
4422
4659
|
);
|
|
4423
|
-
await this.
|
|
4424
|
-
const response = await this.
|
|
4660
|
+
await this._sendWithTimeout(msg);
|
|
4661
|
+
const response = await this._receiveWithTimeout();
|
|
4425
4662
|
if (response.hello) {
|
|
4426
4663
|
if (!response.hello.success) {
|
|
4427
4664
|
const hasCredentials = Boolean(this._config.username) || Boolean(this._config.password);
|
|
@@ -4444,9 +4681,7 @@ var Connection = class _Connection {
|
|
|
4444
4681
|
});
|
|
4445
4682
|
}
|
|
4446
4683
|
}
|
|
4447
|
-
/**
|
|
4448
|
-
* Execute a query that returns rows.
|
|
4449
|
-
*/
|
|
4684
|
+
/** Execute a query that returns rows. */
|
|
4450
4685
|
async query(query2, options) {
|
|
4451
4686
|
this.checkState("query");
|
|
4452
4687
|
validateQuery(query2);
|
|
@@ -4455,11 +4690,14 @@ var Connection = class _Connection {
|
|
|
4455
4690
|
validateParamValue(value);
|
|
4456
4691
|
}
|
|
4457
4692
|
const signal = options?.signal;
|
|
4693
|
+
if (options?.pageSize !== void 0) {
|
|
4694
|
+
validatePageSize(options.pageSize);
|
|
4695
|
+
}
|
|
4458
4696
|
try {
|
|
4459
4697
|
this._state = "fetching";
|
|
4460
4698
|
const msg = buildExecuteRequest(this._sessionId, query2, params);
|
|
4461
|
-
await this.
|
|
4462
|
-
const response = await this.
|
|
4699
|
+
await this._sendWithTimeout(msg, signal);
|
|
4700
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4463
4701
|
if (!response.execute) {
|
|
4464
4702
|
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
4465
4703
|
throw new TransportError({
|
|
@@ -4503,9 +4741,10 @@ var Connection = class _Connection {
|
|
|
4503
4741
|
if (!isFinal && initialRows.length === 0) {
|
|
4504
4742
|
this._requestId++;
|
|
4505
4743
|
const pageSize = options?.pageSize ?? this._config.pageSize;
|
|
4744
|
+
validatePageSize(pageSize);
|
|
4506
4745
|
const pullMsg = buildPullRequest(this._requestId, pageSize, this._sessionId);
|
|
4507
|
-
await this.
|
|
4508
|
-
const pullResponse = await this.
|
|
4746
|
+
await this._sendWithTimeout(pullMsg, signal);
|
|
4747
|
+
const pullResponse = await this._receiveWithTimeout(signal);
|
|
4509
4748
|
const pullExec = pullResponse.pull?.response ?? pullResponse.execute;
|
|
4510
4749
|
if (pullExec) {
|
|
4511
4750
|
if (isErrorResponse(pullExec)) {
|
|
@@ -4521,6 +4760,8 @@ var Connection = class _Connection {
|
|
|
4521
4760
|
}
|
|
4522
4761
|
}
|
|
4523
4762
|
}
|
|
4763
|
+
const resultPageSize = options?.pageSize ?? this._config.pageSize;
|
|
4764
|
+
validatePageSize(resultPageSize);
|
|
4524
4765
|
const result = new QueryResult(
|
|
4525
4766
|
this,
|
|
4526
4767
|
columns,
|
|
@@ -4528,7 +4769,7 @@ var Connection = class _Connection {
|
|
|
4528
4769
|
isFinal,
|
|
4529
4770
|
isOrdered,
|
|
4530
4771
|
orderKeys,
|
|
4531
|
-
|
|
4772
|
+
resultPageSize,
|
|
4532
4773
|
signal
|
|
4533
4774
|
);
|
|
4534
4775
|
this._activeResult = result;
|
|
@@ -4538,25 +4779,12 @@ var Connection = class _Connection {
|
|
|
4538
4779
|
throw e;
|
|
4539
4780
|
}
|
|
4540
4781
|
}
|
|
4541
|
-
/**
|
|
4542
|
-
* Execute a query and return all rows as an array.
|
|
4543
|
-
*/
|
|
4782
|
+
/** Execute a query and return all rows as an array. */
|
|
4544
4783
|
async queryAll(query2, options) {
|
|
4545
4784
|
const result = await this.query(query2, options);
|
|
4546
|
-
|
|
4547
|
-
for await (const row of result) {
|
|
4548
|
-
const obj = {};
|
|
4549
|
-
for (const col of result.columns) {
|
|
4550
|
-
const value = row.get(col.name);
|
|
4551
|
-
obj[col.name] = value?.toJS();
|
|
4552
|
-
}
|
|
4553
|
-
rows.push(obj);
|
|
4554
|
-
}
|
|
4555
|
-
return rows;
|
|
4785
|
+
return result._collectRawRecords();
|
|
4556
4786
|
}
|
|
4557
|
-
/**
|
|
4558
|
-
* Execute a query that doesn't return rows.
|
|
4559
|
-
*/
|
|
4787
|
+
/** Execute a query that doesn't return rows. */
|
|
4560
4788
|
async exec(query2, options) {
|
|
4561
4789
|
this.checkState("exec");
|
|
4562
4790
|
validateQuery(query2);
|
|
@@ -4568,8 +4796,8 @@ var Connection = class _Connection {
|
|
|
4568
4796
|
try {
|
|
4569
4797
|
this._state = "executing";
|
|
4570
4798
|
const msg = buildExecuteRequest(this._sessionId, query2, params);
|
|
4571
|
-
await this.
|
|
4572
|
-
const response = await this.
|
|
4799
|
+
await this._sendWithTimeout(msg, signal);
|
|
4800
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4573
4801
|
if (!response.execute) {
|
|
4574
4802
|
throw new TransportError({
|
|
4575
4803
|
operation: "exec",
|
|
@@ -4583,25 +4811,29 @@ var Connection = class _Connection {
|
|
|
4583
4811
|
if (isSchemaResponse(execResp)) {
|
|
4584
4812
|
this._columns = execResp.schema.columns;
|
|
4585
4813
|
const inlineResponse = await this._tryReceiveInline(signal);
|
|
4586
|
-
if (inlineResponse?.execute
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4814
|
+
if (inlineResponse?.execute) {
|
|
4815
|
+
if (isErrorResponse(inlineResponse.execute)) {
|
|
4816
|
+
throw protoErrorToDriverError(
|
|
4817
|
+
inlineResponse.execute,
|
|
4818
|
+
inlineResponse.execute.status?.statusClass
|
|
4819
|
+
);
|
|
4820
|
+
}
|
|
4821
|
+
if (isDataPageResponse(inlineResponse.execute) && !inlineResponse.execute.page.final) {
|
|
4822
|
+
await this._drainRemainingPages(signal);
|
|
4823
|
+
}
|
|
4591
4824
|
}
|
|
4592
4825
|
}
|
|
4593
4826
|
} finally {
|
|
4594
4827
|
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
4595
4828
|
}
|
|
4596
4829
|
}
|
|
4597
|
-
/**
|
|
4598
|
-
* Fetch the next page of results (internal).
|
|
4599
|
-
*/
|
|
4830
|
+
/** @internal Fetch the next page of results. Called by QueryResult. */
|
|
4600
4831
|
async _fetchNextPage(pageSize, signal) {
|
|
4832
|
+
validatePageSize(pageSize);
|
|
4601
4833
|
this._requestId++;
|
|
4602
4834
|
const pullMsg = buildPullRequest(this._requestId, pageSize, this._sessionId);
|
|
4603
|
-
await this.
|
|
4604
|
-
const response = await this.
|
|
4835
|
+
await this._sendWithTimeout(pullMsg, signal);
|
|
4836
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4605
4837
|
const execResp = response.pull?.response ?? response.execute;
|
|
4606
4838
|
if (!execResp) {
|
|
4607
4839
|
return { rows: [], final: true };
|
|
@@ -4618,47 +4850,63 @@ var Connection = class _Connection {
|
|
|
4618
4850
|
}
|
|
4619
4851
|
return { rows: [], final: true };
|
|
4620
4852
|
}
|
|
4621
|
-
/**
|
|
4622
|
-
* Try to receive an inline response with short timeout.
|
|
4623
|
-
*/
|
|
4853
|
+
/** Try to receive an inline response with short timeout. */
|
|
4624
4854
|
async _tryReceiveInline(signal) {
|
|
4625
4855
|
if (signal?.aborted) {
|
|
4626
4856
|
return null;
|
|
4627
4857
|
}
|
|
4628
|
-
const
|
|
4629
|
-
const
|
|
4858
|
+
const inlineTimeoutMs = this._config.inlineTimeout ?? 5e3;
|
|
4859
|
+
const { signal: timeoutSignal, cleanup } = withAbortTimeout(signal, inlineTimeoutMs);
|
|
4630
4860
|
try {
|
|
4631
|
-
|
|
4632
|
-
return response;
|
|
4861
|
+
return await this._transport.receiveProto(timeoutSignal);
|
|
4633
4862
|
} catch (err) {
|
|
4634
4863
|
if (err instanceof TransportError && err.cause?.message === "Aborted") {
|
|
4635
4864
|
return null;
|
|
4636
4865
|
}
|
|
4637
|
-
throw err;
|
|
4638
|
-
} finally {
|
|
4639
|
-
|
|
4866
|
+
throw err;
|
|
4867
|
+
} finally {
|
|
4868
|
+
cleanup();
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
/** Drain remaining data pages until final=true to prevent query corruption (QUAL-T7). */
|
|
4872
|
+
async _drainRemainingPages(signal) {
|
|
4873
|
+
let done = false;
|
|
4874
|
+
while (!done) {
|
|
4875
|
+
this._requestId++;
|
|
4876
|
+
const pageSize = this._config.pageSize;
|
|
4877
|
+
const pullMsg = buildPullRequest(this._requestId, pageSize, this._sessionId);
|
|
4878
|
+
await this._sendWithTimeout(pullMsg, signal);
|
|
4879
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4880
|
+
const pullExec = response.pull?.response ?? response.execute;
|
|
4881
|
+
if (!pullExec) {
|
|
4882
|
+
break;
|
|
4883
|
+
}
|
|
4884
|
+
if (isErrorResponse(pullExec)) {
|
|
4885
|
+
throw protoErrorToDriverError(pullExec, pullExec.status?.statusClass);
|
|
4886
|
+
}
|
|
4887
|
+
if (isDataPageResponse(pullExec)) {
|
|
4888
|
+
done = pullExec.page.final;
|
|
4889
|
+
} else {
|
|
4890
|
+
done = true;
|
|
4891
|
+
}
|
|
4640
4892
|
}
|
|
4641
4893
|
}
|
|
4642
|
-
/**
|
|
4643
|
-
* Release active result (internal).
|
|
4644
|
-
*/
|
|
4894
|
+
/** @internal Release the active result, returning the connection to idle. */
|
|
4645
4895
|
_releaseResult(result) {
|
|
4646
4896
|
if (this._activeResult === result) {
|
|
4647
4897
|
this._activeResult = null;
|
|
4648
4898
|
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
4649
4899
|
}
|
|
4650
4900
|
}
|
|
4651
|
-
/**
|
|
4652
|
-
* Begin a transaction.
|
|
4653
|
-
*/
|
|
4901
|
+
/** Begin a transaction. */
|
|
4654
4902
|
async begin(signal) {
|
|
4655
4903
|
this.checkState("begin");
|
|
4656
4904
|
if (this._inTransaction) {
|
|
4657
|
-
throw ErrTxInProgress;
|
|
4905
|
+
throw ErrTxInProgress();
|
|
4658
4906
|
}
|
|
4659
4907
|
const msg = buildBeginRequest(false, this._sessionId);
|
|
4660
|
-
await this.
|
|
4661
|
-
const response = await this.
|
|
4908
|
+
await this._sendWithTimeout(msg, signal);
|
|
4909
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4662
4910
|
if (response.begin) {
|
|
4663
4911
|
if (response.begin.sessionId) {
|
|
4664
4912
|
this._sessionId = response.begin.sessionId;
|
|
@@ -4673,13 +4921,11 @@ var Connection = class _Connection {
|
|
|
4673
4921
|
this._state = "in_transaction";
|
|
4674
4922
|
return new Transaction(this);
|
|
4675
4923
|
}
|
|
4676
|
-
/**
|
|
4677
|
-
* Commit the current transaction (internal).
|
|
4678
|
-
*/
|
|
4924
|
+
/** @internal Commit the current transaction. Called by Transaction. */
|
|
4679
4925
|
async _commit(signal) {
|
|
4680
4926
|
const msg = buildCommitRequest(this._sessionId);
|
|
4681
|
-
await this.
|
|
4682
|
-
const response = await this.
|
|
4927
|
+
await this._sendWithTimeout(msg, signal);
|
|
4928
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4683
4929
|
if (!response.commit) {
|
|
4684
4930
|
throw new TransportError({
|
|
4685
4931
|
operation: "commit",
|
|
@@ -4697,13 +4943,11 @@ var Connection = class _Connection {
|
|
|
4697
4943
|
this._inTransaction = false;
|
|
4698
4944
|
this._state = "idle";
|
|
4699
4945
|
}
|
|
4700
|
-
/**
|
|
4701
|
-
* Rollback the current transaction (internal).
|
|
4702
|
-
*/
|
|
4946
|
+
/** @internal Rollback the current transaction. Called by Transaction. */
|
|
4703
4947
|
async _rollback(signal) {
|
|
4704
4948
|
const msg = buildRollbackRequest(this._sessionId);
|
|
4705
|
-
await this.
|
|
4706
|
-
const response = await this.
|
|
4949
|
+
await this._sendWithTimeout(msg, signal);
|
|
4950
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4707
4951
|
if (!response.rollback) {
|
|
4708
4952
|
throw new TransportError({
|
|
4709
4953
|
operation: "rollback",
|
|
@@ -4713,14 +4957,12 @@ var Connection = class _Connection {
|
|
|
4713
4957
|
this._inTransaction = false;
|
|
4714
4958
|
this._state = "idle";
|
|
4715
4959
|
}
|
|
4716
|
-
/**
|
|
4717
|
-
* Create a savepoint (internal).
|
|
4718
|
-
*/
|
|
4960
|
+
/** @internal Create a named savepoint. Called by Transaction. */
|
|
4719
4961
|
async _savepoint(name, signal) {
|
|
4720
4962
|
validateSavepointName(name);
|
|
4721
4963
|
const msg = buildSavepointRequest(name, this._sessionId);
|
|
4722
|
-
await this.
|
|
4723
|
-
const response = await this.
|
|
4964
|
+
await this._sendWithTimeout(msg, signal);
|
|
4965
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4724
4966
|
if (!response.savepoint) {
|
|
4725
4967
|
throw new TransportError({
|
|
4726
4968
|
operation: "savepoint",
|
|
@@ -4736,14 +4978,12 @@ var Connection = class _Connection {
|
|
|
4736
4978
|
});
|
|
4737
4979
|
}
|
|
4738
4980
|
}
|
|
4739
|
-
/**
|
|
4740
|
-
* Rollback to a savepoint (internal).
|
|
4741
|
-
*/
|
|
4981
|
+
/** @internal Rollback to a previously created savepoint. Called by Transaction. */
|
|
4742
4982
|
async _rollbackTo(name, signal) {
|
|
4743
4983
|
validateSavepointName(name);
|
|
4744
4984
|
const msg = buildRollbackToRequest(name, this._sessionId);
|
|
4745
|
-
await this.
|
|
4746
|
-
const response = await this.
|
|
4985
|
+
await this._sendWithTimeout(msg, signal);
|
|
4986
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4747
4987
|
if (!response.rollbackTo) {
|
|
4748
4988
|
throw new TransportError({
|
|
4749
4989
|
operation: "rollback_to",
|
|
@@ -4759,16 +4999,14 @@ var Connection = class _Connection {
|
|
|
4759
4999
|
});
|
|
4760
5000
|
}
|
|
4761
5001
|
}
|
|
4762
|
-
/**
|
|
4763
|
-
* Ping the server to check connection health.
|
|
4764
|
-
*/
|
|
5002
|
+
/** Ping the server to check connection health. */
|
|
4765
5003
|
async ping(signal) {
|
|
4766
5004
|
if (this._state === "closed") {
|
|
4767
|
-
throw ErrClosed;
|
|
5005
|
+
throw ErrClosed();
|
|
4768
5006
|
}
|
|
4769
5007
|
const msg = buildPingRequest();
|
|
4770
|
-
await this.
|
|
4771
|
-
const response = await this.
|
|
5008
|
+
await this._sendWithTimeout(msg, signal);
|
|
5009
|
+
const response = await this._receiveWithTimeout(signal);
|
|
4772
5010
|
if (!response.ping) {
|
|
4773
5011
|
throw new TransportError({
|
|
4774
5012
|
operation: "ping",
|
|
@@ -4782,12 +5020,10 @@ var Connection = class _Connection {
|
|
|
4782
5020
|
});
|
|
4783
5021
|
}
|
|
4784
5022
|
}
|
|
4785
|
-
/**
|
|
4786
|
-
* Reset the connection session.
|
|
4787
|
-
*/
|
|
5023
|
+
/** Reset the connection session. */
|
|
4788
5024
|
async reset(signal) {
|
|
4789
5025
|
if (this._state === "closed") {
|
|
4790
|
-
throw ErrClosed;
|
|
5026
|
+
throw ErrClosed();
|
|
4791
5027
|
}
|
|
4792
5028
|
if (this._inTransaction) {
|
|
4793
5029
|
await this._rollback(signal);
|
|
@@ -4795,9 +5031,7 @@ var Connection = class _Connection {
|
|
|
4795
5031
|
this._state = "idle";
|
|
4796
5032
|
this._activeResult = null;
|
|
4797
5033
|
}
|
|
4798
|
-
/**
|
|
4799
|
-
* Close the connection.
|
|
4800
|
-
*/
|
|
5034
|
+
/** Close the connection. */
|
|
4801
5035
|
async close() {
|
|
4802
5036
|
if (this._state === "closed") {
|
|
4803
5037
|
return;
|
|
@@ -4809,48 +5043,60 @@ var Connection = class _Connection {
|
|
|
4809
5043
|
}
|
|
4810
5044
|
await this._transport.close();
|
|
4811
5045
|
}
|
|
4812
|
-
/**
|
|
4813
|
-
* Create a prepared statement.
|
|
4814
|
-
*/
|
|
5046
|
+
/** Create a prepared statement. */
|
|
4815
5047
|
async prepare(query2) {
|
|
4816
5048
|
const { PreparedStatement: PreparedStatement2 } = await Promise.resolve().then(() => (init_prepared(), prepared_exports));
|
|
4817
5049
|
return new PreparedStatement2(this, query2);
|
|
4818
5050
|
}
|
|
4819
|
-
/**
|
|
4820
|
-
* Get the query execution plan without executing.
|
|
4821
|
-
*/
|
|
5051
|
+
/** Get the query execution plan without executing. */
|
|
4822
5052
|
async explain(query2, options) {
|
|
4823
|
-
const { explain: doExplain } = await
|
|
5053
|
+
const { explain: doExplain } = await init_explain().then(() => explain_exports);
|
|
4824
5054
|
return doExplain(this, query2, options);
|
|
4825
5055
|
}
|
|
4826
|
-
/**
|
|
4827
|
-
* Execute a query with profiling.
|
|
4828
|
-
*/
|
|
5056
|
+
/** Execute a query with profiling. */
|
|
4829
5057
|
async profile(query2, options) {
|
|
4830
|
-
const { profile: doProfile } = await
|
|
5058
|
+
const { profile: doProfile } = await init_explain().then(() => explain_exports);
|
|
4831
5059
|
return doProfile(this, query2, options);
|
|
4832
5060
|
}
|
|
4833
|
-
/**
|
|
4834
|
-
* Execute multiple queries in a batch.
|
|
4835
|
-
*/
|
|
5061
|
+
/** Execute multiple queries in a batch. */
|
|
4836
5062
|
async batch(queries, options) {
|
|
4837
5063
|
const { batch: doBatch } = await init_batch().then(() => batch_exports);
|
|
4838
5064
|
return doBatch(this, queries, options);
|
|
4839
5065
|
}
|
|
4840
|
-
/**
|
|
4841
|
-
* Check connection state before operation.
|
|
4842
|
-
*/
|
|
5066
|
+
/** Check connection state before operation. */
|
|
4843
5067
|
checkState(operation) {
|
|
4844
5068
|
if (this._state === "closed") {
|
|
4845
|
-
throw ErrClosed;
|
|
5069
|
+
throw ErrClosed();
|
|
4846
5070
|
}
|
|
4847
5071
|
if (this._state === "fetching" || this._activeResult) {
|
|
4848
|
-
throw ErrQueryInProgress;
|
|
5072
|
+
throw ErrQueryInProgress();
|
|
4849
5073
|
}
|
|
4850
5074
|
if (operation === "begin" && this._inTransaction) {
|
|
4851
|
-
throw ErrTxInProgress;
|
|
5075
|
+
throw ErrTxInProgress();
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
/** Send a protobuf message with request timeout enforcement. */
|
|
5079
|
+
async _sendWithTimeout(msg, signal) {
|
|
5080
|
+
const { signal: timeoutSignal, cleanup } = this._withRequestTimeout(signal);
|
|
5081
|
+
try {
|
|
5082
|
+
await this._transport.sendProto(msg, timeoutSignal);
|
|
5083
|
+
} finally {
|
|
5084
|
+
cleanup();
|
|
4852
5085
|
}
|
|
4853
5086
|
}
|
|
5087
|
+
/** Receive a protobuf message with request timeout enforcement. */
|
|
5088
|
+
async _receiveWithTimeout(signal) {
|
|
5089
|
+
const { signal: timeoutSignal, cleanup } = this._withRequestTimeout(signal);
|
|
5090
|
+
try {
|
|
5091
|
+
return await this._transport.receiveProto(timeoutSignal);
|
|
5092
|
+
} finally {
|
|
5093
|
+
cleanup();
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
/** Create a combined abort signal from requestTimeout and optional caller signal (CWE-703). */
|
|
5097
|
+
_withRequestTimeout(signal) {
|
|
5098
|
+
return withAbortTimeout(signal, this._config.requestTimeout);
|
|
5099
|
+
}
|
|
4854
5100
|
};
|
|
4855
5101
|
|
|
4856
5102
|
// src/pool.ts
|
|
@@ -4938,6 +5184,9 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
4938
5184
|
this._maintenanceInterval = setInterval(() => {
|
|
4939
5185
|
this.maintenance();
|
|
4940
5186
|
}, 1e4);
|
|
5187
|
+
if (this._maintenanceInterval.unref) {
|
|
5188
|
+
this._maintenanceInterval.unref();
|
|
5189
|
+
}
|
|
4941
5190
|
}
|
|
4942
5191
|
/**
|
|
4943
5192
|
* Create a new connection pool.
|
|
@@ -4960,11 +5209,16 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
4960
5209
|
* Get pool statistics.
|
|
4961
5210
|
*/
|
|
4962
5211
|
get stats() {
|
|
4963
|
-
|
|
4964
|
-
const
|
|
5212
|
+
let inUse = 0;
|
|
5213
|
+
for (const c of this._connections) {
|
|
5214
|
+
if (c.inUse) {
|
|
5215
|
+
inUse++;
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
const total = this._connections.length;
|
|
4965
5219
|
return {
|
|
4966
|
-
total
|
|
4967
|
-
available,
|
|
5220
|
+
total,
|
|
5221
|
+
available: total - inUse,
|
|
4968
5222
|
inUse,
|
|
4969
5223
|
waiting: this._waitQueue.length
|
|
4970
5224
|
};
|
|
@@ -4980,7 +5234,7 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
4980
5234
|
*/
|
|
4981
5235
|
async acquire(signal) {
|
|
4982
5236
|
if (this._closed) {
|
|
4983
|
-
throw ErrClosed;
|
|
5237
|
+
throw ErrClosed();
|
|
4984
5238
|
}
|
|
4985
5239
|
if (signal?.aborted) {
|
|
4986
5240
|
throw new TransportError({ operation: "acquire", cause: new Error("Aborted") });
|
|
@@ -5016,24 +5270,25 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
5016
5270
|
})
|
|
5017
5271
|
);
|
|
5018
5272
|
}, this._acquireTimeout);
|
|
5273
|
+
let onAbort = null;
|
|
5019
5274
|
const cleanup = () => {
|
|
5020
5275
|
clearTimeout(timeout);
|
|
5276
|
+
if (signal && onAbort) {
|
|
5277
|
+
signal.removeEventListener("abort", onAbort);
|
|
5278
|
+
}
|
|
5021
5279
|
};
|
|
5022
5280
|
if (signal) {
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
);
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
},
|
|
5035
|
-
{ once: true }
|
|
5036
|
-
);
|
|
5281
|
+
onAbort = () => {
|
|
5282
|
+
cleanup();
|
|
5283
|
+
const idx = this._waitQueue.findIndex(
|
|
5284
|
+
(w) => w.resolve === resolve2 && w.reject === reject
|
|
5285
|
+
);
|
|
5286
|
+
if (idx !== -1) {
|
|
5287
|
+
this._waitQueue.splice(idx, 1);
|
|
5288
|
+
}
|
|
5289
|
+
reject(new TransportError({ operation: "acquire", cause: new Error("Aborted") }));
|
|
5290
|
+
};
|
|
5291
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5037
5292
|
}
|
|
5038
5293
|
this._waitQueue.push({
|
|
5039
5294
|
resolve: (conn) => {
|
|
@@ -5126,7 +5381,7 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
5126
5381
|
this._maintenanceInterval = void 0;
|
|
5127
5382
|
}
|
|
5128
5383
|
for (const waiter of this._waitQueue) {
|
|
5129
|
-
waiter.reject(ErrClosed);
|
|
5384
|
+
waiter.reject(ErrClosed());
|
|
5130
5385
|
}
|
|
5131
5386
|
this._waitQueue = [];
|
|
5132
5387
|
const closePromises = this._connections.map(async (c) => {
|
|
@@ -5142,6 +5397,9 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
5142
5397
|
* Add a new connection to the pool with rate limiting and exponential backoff.
|
|
5143
5398
|
*/
|
|
5144
5399
|
async addConnection() {
|
|
5400
|
+
if (this._connections.length >= this._maxConnections) {
|
|
5401
|
+
return;
|
|
5402
|
+
}
|
|
5145
5403
|
if (!this._rateLimiter.isAllowed()) {
|
|
5146
5404
|
throw new TransportError({
|
|
5147
5405
|
operation: "connect",
|
|
@@ -5186,7 +5444,8 @@ var ConnectionPool = class _ConnectionPool {
|
|
|
5186
5444
|
}
|
|
5187
5445
|
const now = Date.now();
|
|
5188
5446
|
const idleConnections = this._connections.filter((c) => !c.inUse && now - c.lastUsedAt > this._idleTimeout).sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
5189
|
-
const
|
|
5447
|
+
const activeCount = this._connections.filter((c) => !c.connection.isClosed).length;
|
|
5448
|
+
const toRemove = Math.max(0, activeCount - this._minConnections);
|
|
5190
5449
|
for (let i = 0; i < toRemove; i++) {
|
|
5191
5450
|
const conn = idleConnections[i];
|
|
5192
5451
|
if (conn) {
|
|
@@ -5221,10 +5480,8 @@ var GeodeClient = class _GeodeClient {
|
|
|
5221
5480
|
* Create a new client from a DSN string.
|
|
5222
5481
|
*/
|
|
5223
5482
|
static async connect(dsn, options) {
|
|
5224
|
-
const
|
|
5225
|
-
|
|
5226
|
-
Object.assign(config, options);
|
|
5227
|
-
}
|
|
5483
|
+
const baseConfig = parseDSN(dsn);
|
|
5484
|
+
const config = options ? { ...baseConfig, ...options } : baseConfig;
|
|
5228
5485
|
const usePool = options?.pooling ?? true;
|
|
5229
5486
|
const client = new _GeodeClient(config);
|
|
5230
5487
|
if (usePool) {
|
|
@@ -5337,10 +5594,27 @@ var GeodeClient = class _GeodeClient {
|
|
|
5337
5594
|
}
|
|
5338
5595
|
/**
|
|
5339
5596
|
* Execute multiple statements.
|
|
5597
|
+
*
|
|
5598
|
+
* When a connection pool is available, all queries are executed within a
|
|
5599
|
+
* single transaction to reduce round-trips (one BEGIN + N queries + COMMIT
|
|
5600
|
+
* instead of N independent round-trips with connection acquire/release).
|
|
5601
|
+
*
|
|
5602
|
+
* For non-pooled (single connection) clients, queries are executed
|
|
5603
|
+
* sequentially without an implicit transaction wrapper, preserving the
|
|
5604
|
+
* caller's control over transaction boundaries.
|
|
5340
5605
|
*/
|
|
5341
5606
|
async execBatch(queries) {
|
|
5342
|
-
|
|
5343
|
-
|
|
5607
|
+
if (queries.length === 0) return;
|
|
5608
|
+
if (this._pool) {
|
|
5609
|
+
await this.withTransaction(async (tx) => {
|
|
5610
|
+
for (const q of queries) {
|
|
5611
|
+
await tx.exec(q.query, { params: q.params });
|
|
5612
|
+
}
|
|
5613
|
+
});
|
|
5614
|
+
} else {
|
|
5615
|
+
for (const q of queries) {
|
|
5616
|
+
await this.exec(q.query, { params: q.params });
|
|
5617
|
+
}
|
|
5344
5618
|
}
|
|
5345
5619
|
}
|
|
5346
5620
|
/**
|
|
@@ -5564,11 +5838,16 @@ var GeodeClient = class _GeodeClient {
|
|
|
5564
5838
|
* ```
|
|
5565
5839
|
*/
|
|
5566
5840
|
async prepare(query2) {
|
|
5841
|
+
const { PreparedStatement: PreparedStatement2 } = await Promise.resolve().then(() => (init_prepared(), prepared_exports));
|
|
5567
5842
|
const conn = await this.getConnection();
|
|
5568
5843
|
try {
|
|
5569
|
-
|
|
5570
|
-
|
|
5844
|
+
const stmt = new PreparedStatement2(conn, query2, () => {
|
|
5845
|
+
void this.releaseConnection(conn);
|
|
5846
|
+
});
|
|
5847
|
+
return stmt;
|
|
5848
|
+
} catch (err) {
|
|
5571
5849
|
await this.releaseConnection(conn);
|
|
5850
|
+
throw err;
|
|
5572
5851
|
}
|
|
5573
5852
|
}
|
|
5574
5853
|
/**
|
|
@@ -5652,25 +5931,242 @@ var GeodeClient = class _GeodeClient {
|
|
|
5652
5931
|
async auth() {
|
|
5653
5932
|
const { AuthClient: AuthClient2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
5654
5933
|
const conn = await this.getConnection();
|
|
5655
|
-
return new AuthClient2(conn)
|
|
5934
|
+
return new AuthClient2(conn, void 0, () => {
|
|
5935
|
+
void this.releaseConnection(conn);
|
|
5936
|
+
});
|
|
5937
|
+
}
|
|
5938
|
+
};
|
|
5939
|
+
async function createClient(dsn, options) {
|
|
5940
|
+
return GeodeClient.connect(dsn, options);
|
|
5941
|
+
}
|
|
5942
|
+
async function createClientWithConfig(config, options) {
|
|
5943
|
+
return GeodeClient.connectWithConfig(config, options);
|
|
5944
|
+
}
|
|
5945
|
+
|
|
5946
|
+
// src/index.ts
|
|
5947
|
+
init_config();
|
|
5948
|
+
await init_types();
|
|
5949
|
+
init_errors();
|
|
5950
|
+
await init_transport();
|
|
5951
|
+
await init_grpc_transport();
|
|
5952
|
+
init_validate();
|
|
5953
|
+
|
|
5954
|
+
// src/query-builder.ts
|
|
5955
|
+
init_validate();
|
|
5956
|
+
|
|
5957
|
+
// src/predicate-builder.ts
|
|
5958
|
+
init_validate();
|
|
5959
|
+
var _predicateRawWarned = false;
|
|
5960
|
+
var PredicateBuilder = class {
|
|
5961
|
+
_predicates = [];
|
|
5962
|
+
_params = {};
|
|
5963
|
+
_paramCounter = 0;
|
|
5964
|
+
/**
|
|
5965
|
+
* Add a comparison predicate.
|
|
5966
|
+
*/
|
|
5967
|
+
compare(left, op, right) {
|
|
5968
|
+
if (op === "IS NULL") {
|
|
5969
|
+
this._predicates.push(`${left} IS NULL`);
|
|
5970
|
+
} else if (op === "IS NOT NULL") {
|
|
5971
|
+
this._predicates.push(`${left} IS NOT NULL`);
|
|
5972
|
+
} else {
|
|
5973
|
+
const paramName = this.nextParam();
|
|
5974
|
+
this._predicates.push(`${left} ${op} $${paramName}`);
|
|
5975
|
+
this._params[paramName] = right;
|
|
5976
|
+
}
|
|
5977
|
+
return this;
|
|
5978
|
+
}
|
|
5979
|
+
/**
|
|
5980
|
+
* Add an equality predicate.
|
|
5981
|
+
*/
|
|
5982
|
+
eq(left, right) {
|
|
5983
|
+
return this.compare(left, "=", right);
|
|
5984
|
+
}
|
|
5985
|
+
/**
|
|
5986
|
+
* Add a not-equal predicate.
|
|
5987
|
+
*/
|
|
5988
|
+
neq(left, right) {
|
|
5989
|
+
return this.compare(left, "<>", right);
|
|
5990
|
+
}
|
|
5991
|
+
/**
|
|
5992
|
+
* Add a less-than predicate.
|
|
5993
|
+
*/
|
|
5994
|
+
lt(left, right) {
|
|
5995
|
+
return this.compare(left, "<", right);
|
|
5996
|
+
}
|
|
5997
|
+
/**
|
|
5998
|
+
* Add a less-than-or-equal predicate.
|
|
5999
|
+
*/
|
|
6000
|
+
lte(left, right) {
|
|
6001
|
+
return this.compare(left, "<=", right);
|
|
6002
|
+
}
|
|
6003
|
+
/**
|
|
6004
|
+
* Add a greater-than predicate.
|
|
6005
|
+
*/
|
|
6006
|
+
gt(left, right) {
|
|
6007
|
+
return this.compare(left, ">", right);
|
|
6008
|
+
}
|
|
6009
|
+
/**
|
|
6010
|
+
* Add a greater-than-or-equal predicate.
|
|
6011
|
+
*/
|
|
6012
|
+
gte(left, right) {
|
|
6013
|
+
return this.compare(left, ">=", right);
|
|
6014
|
+
}
|
|
6015
|
+
/**
|
|
6016
|
+
* Add an IN predicate.
|
|
6017
|
+
*/
|
|
6018
|
+
in(left, values) {
|
|
6019
|
+
const paramName = this.nextParam();
|
|
6020
|
+
this._predicates.push(`${left} IN $${paramName}`);
|
|
6021
|
+
this._params[paramName] = values;
|
|
6022
|
+
return this;
|
|
6023
|
+
}
|
|
6024
|
+
/**
|
|
6025
|
+
* Add a NOT IN predicate.
|
|
6026
|
+
*/
|
|
6027
|
+
notIn(left, values) {
|
|
6028
|
+
const paramName = this.nextParam();
|
|
6029
|
+
this._predicates.push(`${left} NOT IN $${paramName}`);
|
|
6030
|
+
this._params[paramName] = values;
|
|
6031
|
+
return this;
|
|
6032
|
+
}
|
|
6033
|
+
/**
|
|
6034
|
+
* Add a CONTAINS predicate.
|
|
6035
|
+
*/
|
|
6036
|
+
contains(left, value) {
|
|
6037
|
+
return this.compare(left, "CONTAINS", value);
|
|
6038
|
+
}
|
|
6039
|
+
/**
|
|
6040
|
+
* Add a STARTS WITH predicate.
|
|
6041
|
+
*/
|
|
6042
|
+
startsWith(left, value) {
|
|
6043
|
+
return this.compare(left, "STARTS WITH", value);
|
|
6044
|
+
}
|
|
6045
|
+
/**
|
|
6046
|
+
* Add an ENDS WITH predicate.
|
|
6047
|
+
*/
|
|
6048
|
+
endsWith(left, value) {
|
|
6049
|
+
return this.compare(left, "ENDS WITH", value);
|
|
6050
|
+
}
|
|
6051
|
+
/**
|
|
6052
|
+
* Add an IS NULL predicate.
|
|
6053
|
+
*/
|
|
6054
|
+
isNull(expr) {
|
|
6055
|
+
return this.compare(expr, "IS NULL", null);
|
|
6056
|
+
}
|
|
6057
|
+
/**
|
|
6058
|
+
* Add an IS NOT NULL predicate.
|
|
6059
|
+
*/
|
|
6060
|
+
isNotNull(expr) {
|
|
6061
|
+
return this.compare(expr, "IS NOT NULL", null);
|
|
6062
|
+
}
|
|
6063
|
+
/**
|
|
6064
|
+
* Add a raw predicate expression.
|
|
6065
|
+
*
|
|
6066
|
+
* **SECURITY WARNING: This method bypasses all input validation.**
|
|
6067
|
+
*
|
|
6068
|
+
* Using raw() with user-supplied input can lead to GQL injection attacks.
|
|
6069
|
+
* Only use this method when:
|
|
6070
|
+
* 1. The expression is entirely constructed from trusted, hardcoded strings
|
|
6071
|
+
* 2. All dynamic values are passed via the `params` argument (using $paramName syntax)
|
|
6072
|
+
*
|
|
6073
|
+
* @example
|
|
6074
|
+
* ```typescript
|
|
6075
|
+
* // SAFE: Using parameters for dynamic values
|
|
6076
|
+
* predicate().raw('n.custom_field CONTAINS $pattern', { pattern: userInput })
|
|
6077
|
+
*
|
|
6078
|
+
* // UNSAFE: Never interpolate user input directly
|
|
6079
|
+
* predicate().raw(`n.name = '${userInput}'`) // VULNERABLE TO INJECTION!
|
|
6080
|
+
* ```
|
|
6081
|
+
*
|
|
6082
|
+
* Consider using the typed predicate methods (eq, gt, contains, etc.) instead.
|
|
6083
|
+
*
|
|
6084
|
+
* @param expr - Raw predicate expression (MUST be trusted input)
|
|
6085
|
+
* @param params - Parameters to bind (safe for user input)
|
|
6086
|
+
* @deprecated Consider using typed predicate methods instead for better security.
|
|
6087
|
+
*/
|
|
6088
|
+
raw(expr, params) {
|
|
6089
|
+
if (!_predicateRawWarned) {
|
|
6090
|
+
_predicateRawWarned = true;
|
|
6091
|
+
process.emitWarning(
|
|
6092
|
+
"PredicateBuilder.raw() is deprecated and bypasses input validation. Use typed predicate methods (eq, gt, contains, etc.) instead.",
|
|
6093
|
+
"DeprecationWarning"
|
|
6094
|
+
);
|
|
6095
|
+
}
|
|
6096
|
+
this._predicates.push(expr);
|
|
6097
|
+
if (params) {
|
|
6098
|
+
this._params = { ...this._params, ...params };
|
|
6099
|
+
}
|
|
6100
|
+
return this;
|
|
6101
|
+
}
|
|
6102
|
+
/**
|
|
6103
|
+
* Combine predicates with AND.
|
|
6104
|
+
*/
|
|
6105
|
+
and(builder) {
|
|
6106
|
+
const sub = builder.build();
|
|
6107
|
+
if (sub.predicate) {
|
|
6108
|
+
this._predicates.push(`(${sub.predicate})`);
|
|
6109
|
+
this._params = { ...this._params, ...sub.params };
|
|
6110
|
+
}
|
|
6111
|
+
return this;
|
|
6112
|
+
}
|
|
6113
|
+
/**
|
|
6114
|
+
* Combine predicates with OR.
|
|
6115
|
+
*/
|
|
6116
|
+
or(builder) {
|
|
6117
|
+
const sub = builder.build();
|
|
6118
|
+
if (sub.predicate) {
|
|
6119
|
+
if (this._predicates.length > 0) {
|
|
6120
|
+
const current = this._predicates.join(" AND ");
|
|
6121
|
+
this._predicates = [`(${current}) OR (${sub.predicate})`];
|
|
6122
|
+
} else {
|
|
6123
|
+
this._predicates.push(sub.predicate);
|
|
6124
|
+
}
|
|
6125
|
+
this._params = { ...this._params, ...sub.params };
|
|
6126
|
+
}
|
|
6127
|
+
return this;
|
|
6128
|
+
}
|
|
6129
|
+
/**
|
|
6130
|
+
* Build the predicate.
|
|
6131
|
+
*/
|
|
6132
|
+
build() {
|
|
6133
|
+
return {
|
|
6134
|
+
predicate: this._predicates.join(" AND "),
|
|
6135
|
+
params: this._params
|
|
6136
|
+
};
|
|
6137
|
+
}
|
|
6138
|
+
nextParam() {
|
|
6139
|
+
return `pred_${++this._paramCounter}`;
|
|
5656
6140
|
}
|
|
5657
6141
|
};
|
|
5658
|
-
|
|
5659
|
-
|
|
6142
|
+
function propsToGQL(props) {
|
|
6143
|
+
const entries = Object.entries(props).map(([k, v]) => {
|
|
6144
|
+
validatePropertyName(k);
|
|
6145
|
+
return `${k}: ${valueToGQL(v)}`;
|
|
6146
|
+
});
|
|
6147
|
+
return `{${entries.join(", ")}}`;
|
|
5660
6148
|
}
|
|
5661
|
-
|
|
5662
|
-
|
|
6149
|
+
function valueToGQL(value) {
|
|
6150
|
+
if (value === null || value === void 0) {
|
|
6151
|
+
return "null";
|
|
6152
|
+
}
|
|
6153
|
+
if (typeof value === "string") {
|
|
6154
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
6155
|
+
}
|
|
6156
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
6157
|
+
return String(value);
|
|
6158
|
+
}
|
|
6159
|
+
if (Array.isArray(value)) {
|
|
6160
|
+
return `[${value.map(valueToGQL).join(", ")}]`;
|
|
6161
|
+
}
|
|
6162
|
+
if (typeof value === "object") {
|
|
6163
|
+
return propsToGQL(value);
|
|
6164
|
+
}
|
|
6165
|
+
return String(value);
|
|
5663
6166
|
}
|
|
5664
6167
|
|
|
5665
|
-
// src/index.ts
|
|
5666
|
-
init_config();
|
|
5667
|
-
await init_types();
|
|
5668
|
-
init_errors();
|
|
5669
|
-
init_grpc_transport();
|
|
5670
|
-
init_validate();
|
|
5671
|
-
|
|
5672
6168
|
// src/query-builder.ts
|
|
5673
|
-
|
|
6169
|
+
var _queryRawWarned = false;
|
|
5674
6170
|
var NodePatternBuilder = class {
|
|
5675
6171
|
_variable;
|
|
5676
6172
|
_labels = [];
|
|
@@ -5716,7 +6212,7 @@ var NodePatternBuilder = class {
|
|
|
5716
6212
|
for (const key of Object.keys(properties)) {
|
|
5717
6213
|
validatePropertyName(key);
|
|
5718
6214
|
}
|
|
5719
|
-
|
|
6215
|
+
this._properties = { ...this._properties, ...properties };
|
|
5720
6216
|
return this;
|
|
5721
6217
|
}
|
|
5722
6218
|
/**
|
|
@@ -5812,7 +6308,7 @@ var EdgePatternBuilder = class {
|
|
|
5812
6308
|
for (const key of Object.keys(properties)) {
|
|
5813
6309
|
validatePropertyName(key);
|
|
5814
6310
|
}
|
|
5815
|
-
|
|
6311
|
+
this._properties = { ...this._properties, ...properties };
|
|
5816
6312
|
return this;
|
|
5817
6313
|
}
|
|
5818
6314
|
/**
|
|
@@ -5936,181 +6432,6 @@ var PatternBuilder = class {
|
|
|
5936
6432
|
return result;
|
|
5937
6433
|
}
|
|
5938
6434
|
};
|
|
5939
|
-
var PredicateBuilder = class {
|
|
5940
|
-
_predicates = [];
|
|
5941
|
-
_params = {};
|
|
5942
|
-
_paramCounter = 0;
|
|
5943
|
-
/**
|
|
5944
|
-
* Add a comparison predicate.
|
|
5945
|
-
*/
|
|
5946
|
-
compare(left, op, right) {
|
|
5947
|
-
if (op === "IS NULL") {
|
|
5948
|
-
this._predicates.push(`${left} IS NULL`);
|
|
5949
|
-
} else if (op === "IS NOT NULL") {
|
|
5950
|
-
this._predicates.push(`${left} IS NOT NULL`);
|
|
5951
|
-
} else {
|
|
5952
|
-
const paramName = this.nextParam();
|
|
5953
|
-
this._predicates.push(`${left} ${op} $${paramName}`);
|
|
5954
|
-
this._params[paramName] = right;
|
|
5955
|
-
}
|
|
5956
|
-
return this;
|
|
5957
|
-
}
|
|
5958
|
-
/**
|
|
5959
|
-
* Add an equality predicate.
|
|
5960
|
-
*/
|
|
5961
|
-
eq(left, right) {
|
|
5962
|
-
return this.compare(left, "=", right);
|
|
5963
|
-
}
|
|
5964
|
-
/**
|
|
5965
|
-
* Add a not-equal predicate.
|
|
5966
|
-
*/
|
|
5967
|
-
neq(left, right) {
|
|
5968
|
-
return this.compare(left, "<>", right);
|
|
5969
|
-
}
|
|
5970
|
-
/**
|
|
5971
|
-
* Add a less-than predicate.
|
|
5972
|
-
*/
|
|
5973
|
-
lt(left, right) {
|
|
5974
|
-
return this.compare(left, "<", right);
|
|
5975
|
-
}
|
|
5976
|
-
/**
|
|
5977
|
-
* Add a less-than-or-equal predicate.
|
|
5978
|
-
*/
|
|
5979
|
-
lte(left, right) {
|
|
5980
|
-
return this.compare(left, "<=", right);
|
|
5981
|
-
}
|
|
5982
|
-
/**
|
|
5983
|
-
* Add a greater-than predicate.
|
|
5984
|
-
*/
|
|
5985
|
-
gt(left, right) {
|
|
5986
|
-
return this.compare(left, ">", right);
|
|
5987
|
-
}
|
|
5988
|
-
/**
|
|
5989
|
-
* Add a greater-than-or-equal predicate.
|
|
5990
|
-
*/
|
|
5991
|
-
gte(left, right) {
|
|
5992
|
-
return this.compare(left, ">=", right);
|
|
5993
|
-
}
|
|
5994
|
-
/**
|
|
5995
|
-
* Add an IN predicate.
|
|
5996
|
-
*/
|
|
5997
|
-
in(left, values) {
|
|
5998
|
-
const paramName = this.nextParam();
|
|
5999
|
-
this._predicates.push(`${left} IN $${paramName}`);
|
|
6000
|
-
this._params[paramName] = values;
|
|
6001
|
-
return this;
|
|
6002
|
-
}
|
|
6003
|
-
/**
|
|
6004
|
-
* Add a NOT IN predicate.
|
|
6005
|
-
*/
|
|
6006
|
-
notIn(left, values) {
|
|
6007
|
-
const paramName = this.nextParam();
|
|
6008
|
-
this._predicates.push(`${left} NOT IN $${paramName}`);
|
|
6009
|
-
this._params[paramName] = values;
|
|
6010
|
-
return this;
|
|
6011
|
-
}
|
|
6012
|
-
/**
|
|
6013
|
-
* Add a CONTAINS predicate.
|
|
6014
|
-
*/
|
|
6015
|
-
contains(left, value) {
|
|
6016
|
-
return this.compare(left, "CONTAINS", value);
|
|
6017
|
-
}
|
|
6018
|
-
/**
|
|
6019
|
-
* Add a STARTS WITH predicate.
|
|
6020
|
-
*/
|
|
6021
|
-
startsWith(left, value) {
|
|
6022
|
-
return this.compare(left, "STARTS WITH", value);
|
|
6023
|
-
}
|
|
6024
|
-
/**
|
|
6025
|
-
* Add an ENDS WITH predicate.
|
|
6026
|
-
*/
|
|
6027
|
-
endsWith(left, value) {
|
|
6028
|
-
return this.compare(left, "ENDS WITH", value);
|
|
6029
|
-
}
|
|
6030
|
-
/**
|
|
6031
|
-
* Add an IS NULL predicate.
|
|
6032
|
-
*/
|
|
6033
|
-
isNull(expr) {
|
|
6034
|
-
return this.compare(expr, "IS NULL", null);
|
|
6035
|
-
}
|
|
6036
|
-
/**
|
|
6037
|
-
* Add an IS NOT NULL predicate.
|
|
6038
|
-
*/
|
|
6039
|
-
isNotNull(expr) {
|
|
6040
|
-
return this.compare(expr, "IS NOT NULL", null);
|
|
6041
|
-
}
|
|
6042
|
-
/**
|
|
6043
|
-
* Add a raw predicate expression.
|
|
6044
|
-
*
|
|
6045
|
-
* **⚠️ SECURITY WARNING: This method bypasses all input validation.**
|
|
6046
|
-
*
|
|
6047
|
-
* Using raw() with user-supplied input can lead to GQL injection attacks.
|
|
6048
|
-
* Only use this method when:
|
|
6049
|
-
* 1. The expression is entirely constructed from trusted, hardcoded strings
|
|
6050
|
-
* 2. All dynamic values are passed via the `params` argument (using $paramName syntax)
|
|
6051
|
-
*
|
|
6052
|
-
* @example
|
|
6053
|
-
* ```typescript
|
|
6054
|
-
* // SAFE: Using parameters for dynamic values
|
|
6055
|
-
* predicate().raw('n.custom_field CONTAINS $pattern', { pattern: userInput })
|
|
6056
|
-
*
|
|
6057
|
-
* // UNSAFE: Never interpolate user input directly
|
|
6058
|
-
* predicate().raw(`n.name = '${userInput}'`) // VULNERABLE TO INJECTION!
|
|
6059
|
-
* ```
|
|
6060
|
-
*
|
|
6061
|
-
* Consider using the typed predicate methods (eq, gt, contains, etc.) instead.
|
|
6062
|
-
*
|
|
6063
|
-
* @param expr - Raw predicate expression (MUST be trusted input)
|
|
6064
|
-
* @param params - Parameters to bind (safe for user input)
|
|
6065
|
-
* @deprecated Consider using typed predicate methods instead for better security.
|
|
6066
|
-
*/
|
|
6067
|
-
raw(expr, params) {
|
|
6068
|
-
this._predicates.push(expr);
|
|
6069
|
-
if (params) {
|
|
6070
|
-
Object.assign(this._params, params);
|
|
6071
|
-
}
|
|
6072
|
-
return this;
|
|
6073
|
-
}
|
|
6074
|
-
/**
|
|
6075
|
-
* Combine predicates with AND.
|
|
6076
|
-
*/
|
|
6077
|
-
and(builder) {
|
|
6078
|
-
const sub = builder.build();
|
|
6079
|
-
if (sub.predicate) {
|
|
6080
|
-
this._predicates.push(`(${sub.predicate})`);
|
|
6081
|
-
Object.assign(this._params, sub.params);
|
|
6082
|
-
}
|
|
6083
|
-
return this;
|
|
6084
|
-
}
|
|
6085
|
-
/**
|
|
6086
|
-
* Combine predicates with OR.
|
|
6087
|
-
*/
|
|
6088
|
-
or(builder) {
|
|
6089
|
-
const sub = builder.build();
|
|
6090
|
-
if (sub.predicate) {
|
|
6091
|
-
if (this._predicates.length > 0) {
|
|
6092
|
-
const current = this._predicates.join(" AND ");
|
|
6093
|
-
this._predicates = [`(${current}) OR (${sub.predicate})`];
|
|
6094
|
-
} else {
|
|
6095
|
-
this._predicates.push(sub.predicate);
|
|
6096
|
-
}
|
|
6097
|
-
Object.assign(this._params, sub.params);
|
|
6098
|
-
}
|
|
6099
|
-
return this;
|
|
6100
|
-
}
|
|
6101
|
-
/**
|
|
6102
|
-
* Build the predicate.
|
|
6103
|
-
*/
|
|
6104
|
-
build() {
|
|
6105
|
-
return {
|
|
6106
|
-
predicate: this._predicates.join(" AND "),
|
|
6107
|
-
params: this._params
|
|
6108
|
-
};
|
|
6109
|
-
}
|
|
6110
|
-
nextParam() {
|
|
6111
|
-
return `pred_${++this._paramCounter}`;
|
|
6112
|
-
}
|
|
6113
|
-
};
|
|
6114
6435
|
var QueryBuilder = class {
|
|
6115
6436
|
_clauses = [];
|
|
6116
6437
|
_params = {};
|
|
@@ -6132,37 +6453,17 @@ var QueryBuilder = class {
|
|
|
6132
6453
|
}
|
|
6133
6454
|
/**
|
|
6134
6455
|
* Add a WHERE clause.
|
|
6135
|
-
*
|
|
6136
|
-
* **⚠️ SECURITY WARNING when using string predicates:**
|
|
6137
|
-
*
|
|
6138
|
-
* When passing a string predicate, this method does NOT validate or sanitize input.
|
|
6139
|
-
* Using string predicates with user-supplied data can lead to GQL injection attacks.
|
|
6140
|
-
*
|
|
6141
|
-
* @example
|
|
6142
|
-
* ```typescript
|
|
6143
|
-
* // RECOMMENDED: Use PredicateBuilder for type-safe, injection-resistant queries
|
|
6144
|
-
* query().where(predicate().eq('n.name', userInput))
|
|
6145
|
-
*
|
|
6146
|
-
* // SAFE: Using parameters with string predicates
|
|
6147
|
-
* query().where('n.name = $name', { name: userInput })
|
|
6148
|
-
*
|
|
6149
|
-
* // UNSAFE: Never interpolate user input directly in string predicates
|
|
6150
|
-
* query().where(`n.name = '${userInput}'`) // VULNERABLE TO INJECTION!
|
|
6151
|
-
* ```
|
|
6152
|
-
*
|
|
6153
|
-
* @param predicate - A PredicateBuilder (recommended) or raw predicate string
|
|
6154
|
-
* @param params - Parameters to bind when using string predicates
|
|
6155
6456
|
*/
|
|
6156
6457
|
where(predicate2, params) {
|
|
6157
6458
|
if (typeof predicate2 === "string") {
|
|
6158
6459
|
this._clauses.push(`WHERE ${predicate2}`);
|
|
6159
6460
|
if (params) {
|
|
6160
|
-
|
|
6461
|
+
this._params = { ...this._params, ...params };
|
|
6161
6462
|
}
|
|
6162
6463
|
} else {
|
|
6163
6464
|
const built = predicate2.build();
|
|
6164
6465
|
this._clauses.push(`WHERE ${built.predicate}`);
|
|
6165
|
-
|
|
6466
|
+
this._params = { ...this._params, ...built.params };
|
|
6166
6467
|
}
|
|
6167
6468
|
return this;
|
|
6168
6469
|
}
|
|
@@ -6261,32 +6562,23 @@ var QueryBuilder = class {
|
|
|
6261
6562
|
/**
|
|
6262
6563
|
* Add a raw GQL clause.
|
|
6263
6564
|
*
|
|
6264
|
-
*
|
|
6265
|
-
*
|
|
6266
|
-
* Using raw() with user-supplied input can lead to GQL injection attacks.
|
|
6267
|
-
* Only use this method when:
|
|
6268
|
-
* 1. The clause is entirely constructed from trusted, hardcoded strings
|
|
6269
|
-
* 2. All dynamic values are passed via the `params` argument (using $paramName syntax)
|
|
6270
|
-
*
|
|
6271
|
-
* @example
|
|
6272
|
-
* ```typescript
|
|
6273
|
-
* // SAFE: Using parameters for dynamic values
|
|
6274
|
-
* query().raw('CALL db.index.fulltext.queryNodes("idx", $term)', { term: userInput })
|
|
6275
|
-
*
|
|
6276
|
-
* // UNSAFE: Never interpolate user input directly
|
|
6277
|
-
* query().raw(`MATCH (n:${userLabel})`) // VULNERABLE TO INJECTION!
|
|
6278
|
-
* ```
|
|
6279
|
-
*
|
|
6280
|
-
* Consider using the typed query builder methods instead.
|
|
6565
|
+
* **SECURITY WARNING: This method bypasses all input validation.**
|
|
6281
6566
|
*
|
|
6282
6567
|
* @param clause - Raw GQL clause (MUST be trusted input)
|
|
6283
6568
|
* @param params - Parameters to bind (safe for user input)
|
|
6284
6569
|
* @deprecated Consider using typed query builder methods instead for better security.
|
|
6285
6570
|
*/
|
|
6286
6571
|
raw(clause, params) {
|
|
6572
|
+
if (!_queryRawWarned) {
|
|
6573
|
+
_queryRawWarned = true;
|
|
6574
|
+
process.emitWarning(
|
|
6575
|
+
"QueryBuilder.raw() is deprecated and bypasses input validation. Use typed query builder methods instead.",
|
|
6576
|
+
"DeprecationWarning"
|
|
6577
|
+
);
|
|
6578
|
+
}
|
|
6287
6579
|
this._clauses.push(clause);
|
|
6288
6580
|
if (params) {
|
|
6289
|
-
|
|
6581
|
+
this._params = { ...this._params, ...params };
|
|
6290
6582
|
}
|
|
6291
6583
|
return this;
|
|
6292
6584
|
}
|
|
@@ -6294,14 +6586,14 @@ var QueryBuilder = class {
|
|
|
6294
6586
|
* Add a parameter.
|
|
6295
6587
|
*/
|
|
6296
6588
|
param(name, value) {
|
|
6297
|
-
this._params[name]
|
|
6589
|
+
this._params = { ...this._params, [name]: value };
|
|
6298
6590
|
return this;
|
|
6299
6591
|
}
|
|
6300
6592
|
/**
|
|
6301
6593
|
* Add multiple parameters.
|
|
6302
6594
|
*/
|
|
6303
6595
|
params(params) {
|
|
6304
|
-
|
|
6596
|
+
this._params = { ...this._params, ...params };
|
|
6305
6597
|
return this;
|
|
6306
6598
|
}
|
|
6307
6599
|
/**
|
|
@@ -6326,28 +6618,6 @@ var QueryBuilder = class {
|
|
|
6326
6618
|
return { ...this._params };
|
|
6327
6619
|
}
|
|
6328
6620
|
};
|
|
6329
|
-
function propsToGQL(props) {
|
|
6330
|
-
const entries = Object.entries(props).map(([k, v]) => `${k}: ${valueToGQL(v)}`);
|
|
6331
|
-
return `{${entries.join(", ")}}`;
|
|
6332
|
-
}
|
|
6333
|
-
function valueToGQL(value) {
|
|
6334
|
-
if (value === null || value === void 0) {
|
|
6335
|
-
return "null";
|
|
6336
|
-
}
|
|
6337
|
-
if (typeof value === "string") {
|
|
6338
|
-
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
6339
|
-
}
|
|
6340
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
6341
|
-
return String(value);
|
|
6342
|
-
}
|
|
6343
|
-
if (Array.isArray(value)) {
|
|
6344
|
-
return `[${value.map(valueToGQL).join(", ")}]`;
|
|
6345
|
-
}
|
|
6346
|
-
if (typeof value === "object") {
|
|
6347
|
-
return propsToGQL(value);
|
|
6348
|
-
}
|
|
6349
|
-
return String(value);
|
|
6350
|
-
}
|
|
6351
6621
|
function nodePatternToGQL(pattern2) {
|
|
6352
6622
|
let result = "(";
|
|
6353
6623
|
if (pattern2.variable) {
|
|
@@ -6414,11 +6684,11 @@ function edge() {
|
|
|
6414
6684
|
|
|
6415
6685
|
// src/index.ts
|
|
6416
6686
|
init_prepared();
|
|
6417
|
-
init_explain();
|
|
6687
|
+
await init_explain();
|
|
6418
6688
|
await init_batch();
|
|
6419
6689
|
init_auth();
|
|
6420
|
-
init_proto();
|
|
6690
|
+
await init_proto();
|
|
6421
6691
|
|
|
6422
|
-
export { AuthClient, BaseTransport, ConfigError, Connection, ConnectionPool, DEFAULT_CONFORMANCE, DEFAULT_GRPC_PORT, DEFAULT_HELLO_NAME, DEFAULT_HELLO_VERSION, DEFAULT_PAGE_SIZE, DEFAULT_PORT, DriverError, EdgePatternBuilder, ErrBadConn, ErrClosed, ErrNoTx, ErrQueryInProgress, ErrRowsClosed, ErrTxDone, ErrTxInProgress, GQLValue, GeodeClient, GrpcTransport, MAX_PAGE_SIZE, MAX_QUERY_LENGTH, MockTransport, NodePatternBuilder, PatternBuilder, PredicateBuilder, PreparedStatement, QueryBuilder, QueryResult, QueryResultIterator, QuicTransport, QuicTransport as QuicheTransport, SUPPORTED_SCHEMES, SecurityError, StateError, StatusClass, Transaction, TransportError, batch, batchAll, batchFirst, batchMap, batchParallel, buildBeginRequest, buildCommitRequest, buildExecuteRequest, buildHelloRequest, buildPingRequest, buildPullRequest, buildRollbackRequest, buildRollbackToRequest, buildSavepointRequest, buildTLSConfig, cloneConfig, createAuthClient, createClient, createClientWithConfig, createTransport, decodeLengthPrefix, decodeQuicServerMessage, defaultConfig, edge, encodeQuicClientMessage, encodeWithLengthPrefix, ensureProtoInitialized, explain, extractParameters, formatPlan, formatProfile, fromJSON, getAddress, getProtoPath, isDriverError, isGeodeError, isRetryableError, jsToProtoValue, node, parseDSN, parseGQLType, parseRow, pattern, predicate, prepare, profile, protoValueToJS, query, redactConfig, redactDSN, rowToObject, rowToRecord, sanitizeForLog, validateConfig, validateHostname, validatePageSize, validateParamName, validateParamValue, validatePort, validateQuery, validateSavepointName, withTransaction };
|
|
6692
|
+
export { AuthClient, BaseTransport, ConfigError, Connection, ConnectionPool, DEFAULT_CONFORMANCE, DEFAULT_GRPC_PORT, DEFAULT_HELLO_NAME, DEFAULT_HELLO_VERSION, DEFAULT_PAGE_SIZE, DEFAULT_PORT, DriverError, ERR_BAD_CONN_MESSAGE, ERR_CLOSED_MESSAGE, ERR_NO_TX_MESSAGE, ERR_QUERY_IN_PROGRESS_MESSAGE, ERR_ROWS_CLOSED_MESSAGE, ERR_TX_DONE_MESSAGE, ERR_TX_IN_PROGRESS_MESSAGE, EdgePatternBuilder, ErrBadConn, ErrClosed, ErrNoTx, ErrQueryInProgress, ErrRowsClosed, ErrTxDone, ErrTxInProgress, GQLValue, GeodeClient, GrpcTransport, MAX_PAGE_SIZE, MAX_QUERY_LENGTH, MockTransport, NodePatternBuilder, PatternBuilder, PredicateBuilder, PreparedStatement, QueryBuilder, QueryResult, QueryResultIterator, QuicTransport, QuicTransport as QuicheTransport, SUPPORTED_SCHEMES, SecurityError, StateError, StatusClass, Transaction, TransportError, batch, batchAll, batchFirst, batchMap, batchParallel, buildBeginRequest, buildCommitRequest, buildExecuteRequest, buildHelloRequest, buildPingRequest, buildPullRequest, buildRollbackRequest, buildRollbackToRequest, buildSavepointRequest, buildTLSConfig, cloneConfig, createAuthClient, createClient, createClientWithConfig, createTransport, decodeLengthPrefix, decodeQuicServerMessage, defaultConfig, edge, encodeQuicClientMessage, encodeWithLengthPrefix, ensureProtoInitialized, explain, extractParameters, formatPlan, formatProfile, fromJSON, getAddress, getProtoPath, initProtoSync, isDriverError, isGeodeError, isRetryableError, isSentinelError, jsToProtoValue, node, parseDSN, parseGQLType, parseRow, pattern, predicate, prepare, profile, protoValueToJS, query, redactConfig, redactDSN, rowToObject, rowToRecord, sanitizeForLog, validateConfig, validateHostname, validatePageSize, validateParamName, validateParamValue, validatePort, validateQuery, validateSavepointName, withTransaction };
|
|
6423
6693
|
//# sourceMappingURL=index.js.map
|
|
6424
6694
|
//# sourceMappingURL=index.js.map
|