@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.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, ErrClosed, ErrQueryInProgress, ErrTxInProgress, ErrNoTx, ErrTxDone, ErrRowsClosed, ErrBadConn;
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
- ErrClosed = new Error("geode: connection closed");
149
- ErrQueryInProgress = new Error("geode: query already in progress");
150
- ErrTxInProgress = new Error("geode: transaction already in progress");
151
- ErrNoTx = new Error("geode: no transaction in progress");
152
- ErrTxDone = new Error("geode: transaction already committed or rolled back");
153
- ErrRowsClosed = new Error("geode: rows closed");
154
- ErrBadConn = new Error("geode: bad connection");
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
- function checkAgainstBlocklist(normalizedPath, fieldName) {
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
- async function initProto() {
1108
- if (protoRoot) return;
1109
- protoRoot = await protobuf.load(PROTO_PATH);
1110
- QuicClientMessageType = protoRoot.lookupType("geode.QuicClientMessage");
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 initProtoSync() {
1114
- if (protoRoot) return;
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
- async function ensureProtoInitialized() {
1120
- if (!protoRoot) {
1121
- await initProto();
1122
- }
1153
+ function createDecimal(value) {
1154
+ if (isDecimal(value)) return value;
1155
+ return new DecimalClass(value);
1123
1156
  }
1124
- function jsToProtoValue(value) {
1125
- if (value === null || value === void 0) {
1126
- return { nullVal: {} };
1127
- }
1128
- if (typeof value === "boolean") {
1129
- return { boolVal: value };
1130
- }
1131
- if (typeof value === "number") {
1132
- if (Number.isInteger(value)) {
1133
- return { intVal: { value, kind: 1 } };
1134
- }
1135
- return { doubleVal: { value, kind: 1 } };
1136
- }
1137
- if (typeof value === "bigint") {
1138
- return { intVal: { value, kind: 3 } };
1139
- }
1140
- if (typeof value === "string") {
1141
- return { stringVal: { value, kind: 1 } };
1142
- }
1143
- if (Array.isArray(value)) {
1144
- return {
1145
- listVal: {
1146
- values: value.map(jsToProtoValue)
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
- if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
1151
- return { bytesVal: { value, kind: 1 } };
1152
- }
1153
- if (typeof value === "object") {
1154
- const entries = [];
1155
- for (const [k, v] of Object.entries(value)) {
1156
- entries.push({ key: k, value: jsToProtoValue(v) });
1157
- }
1158
- return { mapVal: { entries } };
1159
- }
1160
- return { stringVal: { value: String(value), kind: 1 } };
1161
- }
1162
- function protoValueToJS(val) {
1163
- if (val.nullVal !== void 0) return null;
1164
- if (val.boolVal !== void 0) return val.boolVal;
1165
- if (val.intVal !== void 0) {
1166
- const n = val.intVal.value;
1167
- if (typeof n === "bigint") {
1168
- if (n >= BigInt(Number.MIN_SAFE_INTEGER) && n <= BigInt(Number.MAX_SAFE_INTEGER)) {
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
- let rootCerts = null;
1383
- let privateKey = null;
1384
- let certChain = null;
1385
- if (cfg.tlsCACert) {
1386
- rootCerts = Buffer.from(cfg.tlsCACert);
1387
- } else if (cfg.tlsCA) {
1388
- validateTLSCertPath(cfg.tlsCA, "tlsCA");
1389
- rootCerts = await fs.promises.readFile(cfg.tlsCA);
1390
- }
1391
- if (cfg.tlsCertPEM) {
1392
- certChain = Buffer.from(cfg.tlsCertPEM);
1393
- } else if (cfg.tlsCert) {
1394
- validateTLSCertPath(cfg.tlsCert, "tlsCert");
1395
- certChain = await fs.promises.readFile(cfg.tlsCert);
1396
- }
1397
- if (cfg.tlsKeyPEM) {
1398
- privateKey = Buffer.from(cfg.tlsKeyPEM);
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
- init_validate();
1411
- init_proto();
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((fs4) => fs4.promises.readFile(cfg.tlsCA));
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": 100 * 1024 * 1024,
1461
- // 100MB
1462
- "grpc.max_send_message_length": 100 * 1024 * 1024,
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._pendingResponses.push({ hello: response });
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._pendingResponses.push({
1499
- execute: {
1500
- status: {
1501
- statusClass: "00000",
1502
- statusSubclass: "",
1503
- additionalStatuses: [],
1504
- flaggerFindings: []
1505
- },
1506
- payload: "page",
1507
- page: { rows: [], final: true, ordered: false, orderKeys: [] }
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._pendingResponses.push({ ping: response });
2214
+ this.enqueueResponse({ ping: response });
1514
2215
  } else if (msg.begin) {
1515
2216
  const response = await this.callUnary("begin", msg.begin, signal);
1516
- this._pendingResponses.push({ begin: response });
2217
+ this.enqueueResponse({ begin: response });
1517
2218
  } else if (msg.commit) {
1518
2219
  const response = await this.callUnary("commit", msg.commit, signal);
1519
- this._pendingResponses.push({ commit: response });
2220
+ this.enqueueResponse({ commit: response });
1520
2221
  } else if (msg.rollback) {
1521
2222
  const response = await this.callUnary("rollback", msg.rollback, signal);
1522
- this._pendingResponses.push({ rollback: response });
2223
+ this.enqueueResponse({ rollback: response });
1523
2224
  } else if (msg.savepoint) {
1524
- throw new TransportError({
1525
- operation: "sendProto",
1526
- cause: new Error("Savepoint is not supported over gRPC transport")
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 TransportError({
1530
- operation: "sendProto",
1531
- cause: new Error("RollbackTo is not supported over gRPC transport")
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
- * Collects all streamed ExecutionResponse messages into _pendingResponses.
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
- const stream = this._client.execute(request);
1665
- this._activeStream = stream;
1666
- stream.on("data", (response) => {
1667
- this._pendingResponses.push({ execute: response });
1668
- });
1669
- stream.on("end", () => {
1670
- this._activeStream = null;
1671
- resolve2();
1672
- });
1673
- stream.on("error", (error) => {
1674
- this._activeStream = null;
1675
- reject(
1676
- new TransportError({
1677
- operation: "execute",
1678
- address: this._address,
1679
- cause: new Error(`gRPC error ${error.code}: ${error.message}`)
1680
- })
1681
- );
1682
- });
1683
- if (signal) {
1684
- const onAbort = () => {
1685
- stream.cancel();
1686
- reject(new TransportError({ operation: "execute", cause: new Error("Aborted") }));
1687
- };
1688
- signal.addEventListener("abort", onAbort, { once: true });
1689
- }
1690
- });
1691
- }
1692
- /**
1693
- * Check if closed and throw if so.
1694
- */
1695
- checkClosed() {
1696
- if (this._closed) {
1697
- throw ErrClosed;
1698
- }
1699
- }
1700
- };
1701
- }
1702
- });
1703
-
1704
- // src/types.ts
1705
- function isDecimal(value) {
1706
- return value !== null && typeof value === "object" && "toFixed" in value && "toNumber" in value;
1707
- }
1708
- function createDecimal(value) {
1709
- if (isDecimal(value)) return value;
1710
- return new DecimalClass(value);
1711
- }
1712
- function parseGQLType(typeStr) {
1713
- const upper = typeStr.toUpperCase();
1714
- switch (upper) {
1715
- case "INT":
1716
- case "INTEGER":
1717
- case "BIGINT":
1718
- case "INT8":
1719
- case "INT16":
1720
- case "INT32":
1721
- case "INT64":
1722
- case "UINT8":
1723
- case "UINT16":
1724
- case "UINT32":
1725
- case "UINT64":
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
- if (typeof value === "string") {
1927
- try {
1928
- const parsed = JSON.parse(value);
1929
- if (Array.isArray(parsed)) {
1930
- return GQLValue.array(parsed.map((v) => fromJSON(v)));
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
- } catch {
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
- return GQLValue.array([]);
1936
- case "OBJECT":
1937
- if (typeof value === "string") {
1938
- try {
1939
- const parsed = JSON.parse(value);
1940
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
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
- return fromJSON(value);
1947
- case "NODE":
1948
- return GQLValue.node(value);
1949
- case "EDGE": {
1950
- const rawEdge = value;
1951
- const normalizedEdge = {
1952
- id: rawEdge.id,
1953
- type: rawEdge.type,
1954
- startNode: rawEdge.startNode ?? rawEdge.from ?? "",
1955
- endNode: rawEdge.endNode ?? rawEdge.to ?? "",
1956
- properties: rawEdge.properties ?? {}
1957
- };
1958
- return GQLValue.edge(normalizedEdge);
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
- function parseRow(raw, columns) {
1967
- const row = /* @__PURE__ */ new Map();
1968
- for (const col of columns) {
1969
- const value = raw[col.name];
1970
- row.set(col.name, fromJSON(value, col.kind));
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
- return row;
1973
- }
1974
- function isSafeObjectKey(key) {
1975
- return !DANGEROUS_KEYS.has(key);
1976
- }
1977
- function rowToObject(row) {
1978
- const obj = /* @__PURE__ */ Object.create(null);
1979
- for (const [key, value] of row) {
1980
- if (!isSafeObjectKey(key)) {
1981
- throw new Error(
1982
- `Row contains dangerous key that could cause prototype pollution: column names like '__proto__', 'constructor', or 'prototype' are not allowed`
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 obj;
2547
+ return options;
1988
2548
  }
1989
- var DecimalClass, GQLValue, DANGEROUS_KEYS;
1990
- var init_types = __esm({
1991
- async "src/types.ts"() {
1992
- DecimalClass = // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Module type is incompatible
1993
- (await import('decimal.js-light')).default;
1994
- GQLValue = class _GQLValue {
1995
- kind;
1996
- _intValue = 0n;
1997
- _floatValue = 0;
1998
- _boolValue = false;
1999
- _stringValue = "";
2000
- _decimalValue;
2001
- _arrayValue = [];
2002
- _objectValue = /* @__PURE__ */ new Map();
2003
- _bytesValue = new Uint8Array();
2004
- _dateValue;
2005
- _rangeValue;
2006
- _nodeValue;
2007
- _edgeValue;
2008
- _pathValue;
2009
- _rawValue;
2010
- constructor(kind) {
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
- // Type-safe accessors
2108
- get isNull() {
2109
- return this.kind === "NULL";
2572
+ isClosed() {
2573
+ return this._closed;
2110
2574
  }
2111
- get asBool() {
2112
- if (this.kind !== "BOOL") {
2113
- throw new TypeError(`Cannot convert ${this.kind} to boolean`);
2114
- }
2115
- return this._boolValue;
2575
+ getAddress() {
2576
+ return this._address;
2116
2577
  }
2117
- get asInt() {
2118
- if (this.kind !== "INT") {
2119
- throw new TypeError(`Cannot convert ${this.kind} to int`);
2578
+ checkClosed() {
2579
+ if (this._closed) {
2580
+ throw ErrClosed();
2120
2581
  }
2121
- return this._intValue;
2122
2582
  }
2123
- get asNumber() {
2124
- if (this.kind === "INT") {
2125
- return Number(this._intValue);
2126
- }
2127
- if (this.kind === "FLOAT") {
2128
- return this._floatValue;
2129
- }
2130
- if (this.kind === "DECIMAL") {
2131
- return this._decimalValue?.toNumber() ?? 0;
2132
- }
2133
- throw new TypeError(`Cannot convert ${this.kind} to number`);
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
- get asFloat() {
2136
- if (this.kind !== "FLOAT") {
2137
- throw new TypeError(`Cannot convert ${this.kind} to float`);
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
- get asString() {
2142
- if (this.kind === "STRING" || this.kind === "UUID") {
2143
- return this._stringValue;
2144
- }
2145
- return this.toString();
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
- get asDecimal() {
2148
- if (this.kind !== "DECIMAL") {
2149
- throw new TypeError(`Cannot convert ${this.kind} to decimal`);
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
- get asArray() {
2154
- if (this.kind !== "ARRAY") {
2155
- throw new TypeError(`Cannot convert ${this.kind} to array`);
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
- return this._arrayValue;
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
- return this._objectValue;
2734
+ const consolidated = Buffer.concat(this._chunks, this._totalLength);
2735
+ this._chunks = [consolidated];
2736
+ return consolidated;
2164
2737
  }
2165
- get asBytes() {
2166
- if (this.kind !== "BYTEA") {
2167
- throw new TypeError(`Cannot convert ${this.kind} to bytes`);
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
- return this._bytesValue;
2170
- }
2171
- get asDate() {
2172
- if (this.kind !== "DATE" && this.kind !== "TIME" && this.kind !== "TIMETZ" && this.kind !== "TIMESTAMP" && this.kind !== "TIMESTAMPTZ") {
2173
- throw new TypeError(`Cannot convert ${this.kind} to date`);
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
- get asNode() {
2178
- if (this.kind !== "NODE") {
2179
- throw new TypeError(`Cannot convert ${this.kind} to node`);
2180
- }
2181
- return this._nodeValue;
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
- get asEdge() {
2184
- if (this.kind !== "EDGE") {
2185
- throw new TypeError(`Cannot convert ${this.kind} to edge`);
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
- return this._edgeValue;
2188
- }
2189
- get asPath() {
2190
- if (this.kind !== "PATH") {
2191
- throw new TypeError(`Cannot convert ${this.kind} to path`);
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
- get asRange() {
2196
- if (this.kind !== "RANGE") {
2197
- throw new TypeError(`Cannot convert ${this.kind} to range`);
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 this._rangeValue;
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
- get asJSON() {
2202
- if (this.kind !== "JSON" && this.kind !== "JSONB") {
2203
- throw new TypeError(`Cannot convert ${this.kind} to JSON`);
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
- return this._rawValue;
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
- get raw() {
2208
- return this._rawValue;
2882
+ };
2883
+ MockTransport = class extends BaseTransport {
2884
+ _protoSendQueue = [];
2885
+ _protoReceiveQueue = [];
2886
+ _pendingProtoReads = [];
2887
+ constructor(address = "mock:3141") {
2888
+ super(address);
2209
2889
  }
2210
- toString() {
2211
- switch (this.kind) {
2212
- case "NULL":
2213
- return "null";
2214
- case "BOOL":
2215
- return this._boolValue.toString();
2216
- case "INT":
2217
- return this._intValue.toString();
2218
- case "FLOAT":
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
- * Convert to a plain JavaScript value.
2902
+ * Get all sent protobuf messages.
2252
2903
  */
2253
- toJS() {
2254
- switch (this.kind) {
2255
- case "NULL":
2256
- return null;
2257
- case "BOOL":
2258
- return this._boolValue;
2259
- case "INT":
2260
- if (this._intValue >= Number.MIN_SAFE_INTEGER && this._intValue <= Number.MAX_SAFE_INTEGER) {
2261
- return Number(this._intValue);
2262
- }
2263
- return this._intValue;
2264
- case "FLOAT":
2265
- return this._floatValue;
2266
- case "STRING":
2267
- case "UUID":
2268
- return this._stringValue;
2269
- case "DECIMAL":
2270
- return this._decimalValue?.toString();
2271
- case "ARRAY":
2272
- return this._arrayValue.map((v) => v.toJS());
2273
- case "OBJECT":
2274
- return Object.fromEntries([...this._objectValue.entries()].map(([k, v]) => [k, v.toJS()]));
2275
- case "BYTEA":
2276
- return this._bytesValue;
2277
- case "DATE":
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
- * Convert to JSON-serializable format.
2300
- */
2301
- toJSON() {
2302
- return this.toJS();
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 concurrency = options?.concurrency ?? 5;
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
- this.validateUsername(username);
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
- this.validateUsername(username);
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
- this.validateUsername(username);
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
- this.validateUsername(username);
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
- this.validateUsername(username);
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
- this.validateUsername(username);
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
- this.validateRoleName(name);
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
- this.validateRoleName(name);
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
- this.validateRoleName(name);
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
- this.validateUsername(username);
3228
- this.validateRoleName(roleName);
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
- this.validateUsername(username);
3241
- this.validateRoleName(roleName);
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
- this.validateRoleName(roleName);
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
- this.validateRoleName(roleName);
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
- const tlsConfig = await buildTLSConfig(cfg);
3653
- const config = {
3654
- applicationProtos: GEODE_ALPN,
3655
- maxIdleTimeout: cfg.maxIdleTime ?? 3e4,
3656
- initialMaxData: 1e7,
3657
- initialMaxStreamDataBidiLocal: 1e6,
3658
- initialMaxStreamDataBidiRemote: 1e6,
3659
- initialMaxStreamsBidi: 100
3660
- };
3661
- if (cfg.insecureSkipVerify) {
3662
- const isProduction = process.env["NODE_ENV"] === "production" || process.env["GEODE_ENV"] === "production";
3663
- if (isProduction) {
3664
- console.warn(
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
- config.verifyPeer = false;
3677
- }
3678
- if (tlsConfig.ca) {
3679
- config.caCerts = [tlsConfig.ca];
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
- } catch (err) {
3735
- if (!this._closed) {
3736
- this.rejectPendingReads(
3737
- new TransportError({
3738
- operation: "read",
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
- } finally {
3744
- reader.releaseLock();
3745
- }
3746
- };
3747
- void read();
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
- } catch (err) {
3779
- const pending = this._pendingProtoReads.shift();
3780
- if (pending) {
3781
- pending.reject(
3782
- new TransportError({
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
- * Send a protobuf message with length prefix.
3793
- */
3794
- async sendProto(msg, signal) {
3795
- this.checkClosed();
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
- async close() {
3854
- if (this._closed) return;
3855
- this._closed = true;
3856
- for (const pending of this._pendingProtoReads) {
3857
- pending.reject(ErrClosed);
3858
- }
3859
- this._pendingProtoReads = [];
3860
- if (this._client) {
3861
- try {
3862
- const client = this._client;
3863
- await client.destroy();
3864
- } catch {
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
- this._client = null;
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
- * Clear sent messages.
3897
- */
3898
- clearSentMessages() {
3899
- this._protoSendQueue = [];
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
- // eslint-disable-next-line @typescript-eslint/require-await
3902
- async sendProto(msg, signal) {
3903
- this.checkClosed();
3904
- if (signal?.aborted) {
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
- async receiveProto(signal) {
3910
- this.checkClosed();
3911
- if (signal?.aborted) {
3912
- throw new TransportError({ operation: "receiveProto", cause: new Error("Aborted") });
3913
- }
3914
- const queued = this._protoReceiveQueue.shift();
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
- this._pendingProtoReads = [];
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
- init_proto();
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._transport.sendProto(msg);
4424
- const response = await this._transport.receiveProto();
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._transport.sendProto(msg, signal);
4462
- const response = await this._transport.receiveProto(signal);
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._transport.sendProto(pullMsg, signal);
4508
- const pullResponse = await this._transport.receiveProto(signal);
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
- options?.pageSize ?? this._config.pageSize,
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
- const rows = [];
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._transport.sendProto(msg, signal);
4572
- const response = await this._transport.receiveProto(signal);
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 && isErrorResponse(inlineResponse.execute)) {
4587
- throw protoErrorToDriverError(
4588
- inlineResponse.execute,
4589
- inlineResponse.execute.status?.statusClass
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._transport.sendProto(pullMsg, signal);
4604
- const response = await this._transport.receiveProto(signal);
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 controller = new AbortController();
4629
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
4858
+ const inlineTimeoutMs = this._config.inlineTimeout ?? 5e3;
4859
+ const { signal: timeoutSignal, cleanup } = withAbortTimeout(signal, inlineTimeoutMs);
4630
4860
  try {
4631
- const response = await this._transport.receiveProto(controller.signal);
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
- clearTimeout(timeoutId);
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._transport.sendProto(msg, signal);
4661
- const response = await this._transport.receiveProto(signal);
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._transport.sendProto(msg, signal);
4682
- const response = await this._transport.receiveProto(signal);
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._transport.sendProto(msg, signal);
4706
- const response = await this._transport.receiveProto(signal);
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._transport.sendProto(msg, signal);
4723
- const response = await this._transport.receiveProto(signal);
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._transport.sendProto(msg, signal);
4746
- const response = await this._transport.receiveProto(signal);
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._transport.sendProto(msg, signal);
4771
- const response = await this._transport.receiveProto(signal);
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 Promise.resolve().then(() => (init_explain(), explain_exports));
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 Promise.resolve().then(() => (init_explain(), explain_exports));
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
- const available = this._connections.filter((c) => !c.inUse).length;
4964
- const inUse = this._connections.filter((c) => c.inUse).length;
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: this._connections.length,
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
- signal.addEventListener(
5024
- "abort",
5025
- () => {
5026
- cleanup();
5027
- const idx = this._waitQueue.findIndex(
5028
- (w) => w.resolve === resolve2 && w.reject === reject
5029
- );
5030
- if (idx !== -1) {
5031
- this._waitQueue.splice(idx, 1);
5032
- }
5033
- reject(new TransportError({ operation: "acquire", cause: new Error("Aborted") }));
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 toRemove = Math.max(0, idleConnections.length - this._minConnections);
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 config = parseDSN(dsn);
5225
- if (options) {
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
- for (const q of queries) {
5343
- await this.exec(q.query, { params: q.params });
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
- return conn.prepare(query2);
5570
- } finally {
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
- async function createClient(dsn, options) {
5659
- return GeodeClient.connect(dsn, options);
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
- async function createClientWithConfig(config, options) {
5662
- return GeodeClient.connectWithConfig(config, options);
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
- init_validate();
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
- Object.assign(this._properties, properties);
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
- Object.assign(this._properties, properties);
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
- Object.assign(this._params, params);
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
- Object.assign(this._params, built.params);
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
- * **⚠️ SECURITY WARNING: This method bypasses all input validation.**
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
- Object.assign(this._params, params);
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] = value;
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
- Object.assign(this._params, params);
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