@geodedb/client 1.0.0-alpha.11
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/LICENSE +201 -0
- package/README.md +594 -0
- package/dist/index.d.mts +2430 -0
- package/dist/index.d.ts +2430 -0
- package/dist/index.js +5202 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5101 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +80 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,5101 @@
|
|
|
1
|
+
import Decimal from 'decimal.js-light';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/errors.ts
|
|
16
|
+
function isDriverError(err) {
|
|
17
|
+
return err instanceof DriverError;
|
|
18
|
+
}
|
|
19
|
+
function isGeodeError(err) {
|
|
20
|
+
return err instanceof DriverError || err instanceof TransportError || err instanceof ConfigError || err instanceof SecurityError || err instanceof StateError;
|
|
21
|
+
}
|
|
22
|
+
function isRetryableError(err) {
|
|
23
|
+
if (isGeodeError(err)) {
|
|
24
|
+
return err.isRetryable;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
var StatusClass, DriverError, TransportError, ConfigError, SecurityError, StateError, ErrClosed, ErrQueryInProgress, ErrTxInProgress, ErrNoTx, ErrTxDone, ErrRowsClosed, ErrBadConn;
|
|
29
|
+
var init_errors = __esm({
|
|
30
|
+
"src/errors.ts"() {
|
|
31
|
+
StatusClass = {
|
|
32
|
+
SUCCESS: "00000",
|
|
33
|
+
WARNING: "01000",
|
|
34
|
+
NO_DATA: "02000",
|
|
35
|
+
TRANSACTION_STATE: "25000",
|
|
36
|
+
AUTH: "28000",
|
|
37
|
+
SERIALIZATION: "40001",
|
|
38
|
+
DEADLOCK: "40502",
|
|
39
|
+
SYNTAX: "42000",
|
|
40
|
+
CONSTRAINT: "23000",
|
|
41
|
+
DUPLICATE: "42P07",
|
|
42
|
+
INVALID_PARAM: "22023",
|
|
43
|
+
SYSTEM: "58000"
|
|
44
|
+
};
|
|
45
|
+
DriverError = class _DriverError extends Error {
|
|
46
|
+
statusClass;
|
|
47
|
+
subclass;
|
|
48
|
+
code;
|
|
49
|
+
anchor;
|
|
50
|
+
additional;
|
|
51
|
+
findings;
|
|
52
|
+
constructor(options) {
|
|
53
|
+
const msg = options.message ? `geode: ${options.code}: ${options.message}` : `geode: ${options.code}`;
|
|
54
|
+
super(msg);
|
|
55
|
+
this.name = "DriverError";
|
|
56
|
+
this.statusClass = options.statusClass;
|
|
57
|
+
this.subclass = options.subclass;
|
|
58
|
+
this.code = options.code;
|
|
59
|
+
this.anchor = options.anchor;
|
|
60
|
+
this.additional = options.additional ?? [];
|
|
61
|
+
this.findings = options.findings ?? [];
|
|
62
|
+
Object.setPrototypeOf(this, _DriverError.prototype);
|
|
63
|
+
}
|
|
64
|
+
get isRetryable() {
|
|
65
|
+
return this.statusClass === StatusClass.SERIALIZATION || this.statusClass === StatusClass.DEADLOCK;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
TransportError = class _TransportError extends Error {
|
|
69
|
+
statusClass = StatusClass.SYSTEM;
|
|
70
|
+
operation;
|
|
71
|
+
address;
|
|
72
|
+
cause;
|
|
73
|
+
constructor(options) {
|
|
74
|
+
const addr = options.address ? ` to ${options.address}` : "";
|
|
75
|
+
const causeMsg = options.cause ? `: ${options.cause.message}` : "";
|
|
76
|
+
super(`geode: transport ${options.operation}${addr}${causeMsg}`);
|
|
77
|
+
this.name = "TransportError";
|
|
78
|
+
this.operation = options.operation;
|
|
79
|
+
this.address = options.address;
|
|
80
|
+
this.cause = options.cause;
|
|
81
|
+
Object.setPrototypeOf(this, _TransportError.prototype);
|
|
82
|
+
}
|
|
83
|
+
get isRetryable() {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
ConfigError = class _ConfigError extends Error {
|
|
88
|
+
statusClass = StatusClass.INVALID_PARAM;
|
|
89
|
+
field;
|
|
90
|
+
value;
|
|
91
|
+
constructor(options) {
|
|
92
|
+
const valStr = options.value !== void 0 ? `=${JSON.stringify(options.value)}` : "";
|
|
93
|
+
super(`geode: config ${options.field}${valStr}: ${options.message}`);
|
|
94
|
+
this.name = "ConfigError";
|
|
95
|
+
this.field = options.field;
|
|
96
|
+
this.value = options.value;
|
|
97
|
+
Object.setPrototypeOf(this, _ConfigError.prototype);
|
|
98
|
+
}
|
|
99
|
+
get isRetryable() {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
SecurityError = class _SecurityError extends Error {
|
|
104
|
+
statusClass = StatusClass.AUTH;
|
|
105
|
+
type;
|
|
106
|
+
cause;
|
|
107
|
+
constructor(options) {
|
|
108
|
+
const causeMsg = options.cause ? `: ${options.cause.message}` : "";
|
|
109
|
+
super(`geode: security ${options.type}: ${options.message}${causeMsg}`);
|
|
110
|
+
this.name = "SecurityError";
|
|
111
|
+
this.type = options.type;
|
|
112
|
+
this.cause = options.cause;
|
|
113
|
+
Object.setPrototypeOf(this, _SecurityError.prototype);
|
|
114
|
+
}
|
|
115
|
+
get isRetryable() {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
StateError = class _StateError extends Error {
|
|
120
|
+
statusClass = StatusClass.TRANSACTION_STATE;
|
|
121
|
+
currentState;
|
|
122
|
+
operation;
|
|
123
|
+
constructor(options) {
|
|
124
|
+
const msg = options.message ? `geode: state error: ${options.operation} (state=${options.currentState}): ${options.message}` : `geode: state error: ${options.operation} (state=${options.currentState})`;
|
|
125
|
+
super(msg);
|
|
126
|
+
this.name = "StateError";
|
|
127
|
+
this.currentState = options.currentState;
|
|
128
|
+
this.operation = options.operation;
|
|
129
|
+
Object.setPrototypeOf(this, _StateError.prototype);
|
|
130
|
+
}
|
|
131
|
+
get isRetryable() {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
ErrClosed = new Error("geode: connection closed");
|
|
136
|
+
ErrQueryInProgress = new Error("geode: query already in progress");
|
|
137
|
+
ErrTxInProgress = new Error("geode: transaction already in progress");
|
|
138
|
+
ErrNoTx = new Error("geode: no transaction in progress");
|
|
139
|
+
ErrTxDone = new Error("geode: transaction already committed or rolled back");
|
|
140
|
+
ErrRowsClosed = new Error("geode: rows closed");
|
|
141
|
+
ErrBadConn = new Error("geode: bad connection");
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// src/config.ts
|
|
146
|
+
function defaultConfig() {
|
|
147
|
+
return {
|
|
148
|
+
host: "localhost",
|
|
149
|
+
port: DEFAULT_PORT,
|
|
150
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
151
|
+
helloName: DEFAULT_HELLO_NAME,
|
|
152
|
+
helloVersion: DEFAULT_HELLO_VERSION,
|
|
153
|
+
conformance: DEFAULT_CONFORMANCE,
|
|
154
|
+
connectTimeout: 3e4,
|
|
155
|
+
requestTimeout: 12e4,
|
|
156
|
+
keepAliveInterval: 1e4,
|
|
157
|
+
maxIdleTime: 3e4
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function parseDSN(dsn) {
|
|
161
|
+
const cfg = defaultConfig();
|
|
162
|
+
if (process.env["GEODE_HOST"]) {
|
|
163
|
+
cfg.host = process.env["GEODE_HOST"];
|
|
164
|
+
}
|
|
165
|
+
if (process.env["GEODE_PORT"]) {
|
|
166
|
+
const port = parseInt(process.env["GEODE_PORT"], 10);
|
|
167
|
+
if (!isNaN(port)) {
|
|
168
|
+
cfg.port = port;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (process.env["GEODE_TLS_CA"]) {
|
|
172
|
+
cfg.tlsCA = process.env["GEODE_TLS_CA"];
|
|
173
|
+
}
|
|
174
|
+
if (process.env["GEODE_USERNAME"]) {
|
|
175
|
+
cfg.username = process.env["GEODE_USERNAME"];
|
|
176
|
+
}
|
|
177
|
+
if (process.env["GEODE_PASSWORD"]) {
|
|
178
|
+
cfg.password = process.env["GEODE_PASSWORD"];
|
|
179
|
+
}
|
|
180
|
+
dsn = dsn.trim();
|
|
181
|
+
if (!dsn) {
|
|
182
|
+
return cfg;
|
|
183
|
+
}
|
|
184
|
+
if (dsn.startsWith("geode://")) {
|
|
185
|
+
return parseURLDSN(dsn, cfg);
|
|
186
|
+
}
|
|
187
|
+
return parseSimpleDSN(dsn, cfg);
|
|
188
|
+
}
|
|
189
|
+
function parseURLDSN(dsn, cfg) {
|
|
190
|
+
let url;
|
|
191
|
+
try {
|
|
192
|
+
url = new URL(dsn);
|
|
193
|
+
} catch {
|
|
194
|
+
throw new ConfigError({
|
|
195
|
+
field: "dsn",
|
|
196
|
+
value: dsn,
|
|
197
|
+
message: "invalid URL format"
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (!url.hostname) {
|
|
201
|
+
throw new ConfigError({
|
|
202
|
+
field: "host",
|
|
203
|
+
message: "host is required"
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
cfg.host = url.hostname;
|
|
207
|
+
if (url.port) {
|
|
208
|
+
cfg.port = parseInt(url.port, 10);
|
|
209
|
+
}
|
|
210
|
+
if (url.username) {
|
|
211
|
+
cfg.username = decodeURIComponent(url.username);
|
|
212
|
+
}
|
|
213
|
+
if (url.password) {
|
|
214
|
+
cfg.password = decodeURIComponent(url.password);
|
|
215
|
+
}
|
|
216
|
+
applyQueryParams(url.searchParams, cfg);
|
|
217
|
+
return cfg;
|
|
218
|
+
}
|
|
219
|
+
function parseSimpleDSN(dsn, cfg) {
|
|
220
|
+
let queryStr = "";
|
|
221
|
+
const queryIdx = dsn.indexOf("?");
|
|
222
|
+
if (queryIdx >= 0) {
|
|
223
|
+
queryStr = dsn.slice(queryIdx + 1);
|
|
224
|
+
dsn = dsn.slice(0, queryIdx);
|
|
225
|
+
}
|
|
226
|
+
if (dsn) {
|
|
227
|
+
const result = parseHostPort(dsn);
|
|
228
|
+
cfg.host = result.host;
|
|
229
|
+
cfg.port = result.port;
|
|
230
|
+
}
|
|
231
|
+
if (queryStr) {
|
|
232
|
+
const params = new URLSearchParams(queryStr);
|
|
233
|
+
applyQueryParams(params, cfg);
|
|
234
|
+
}
|
|
235
|
+
return cfg;
|
|
236
|
+
}
|
|
237
|
+
function parseHostPort(addr) {
|
|
238
|
+
if (addr.startsWith("[")) {
|
|
239
|
+
const closeBracket = addr.indexOf("]");
|
|
240
|
+
if (closeBracket === -1) {
|
|
241
|
+
throw new ConfigError({
|
|
242
|
+
field: "host",
|
|
243
|
+
value: addr,
|
|
244
|
+
message: "invalid IPv6 address format"
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const host2 = addr.slice(1, closeBracket);
|
|
248
|
+
const rest = addr.slice(closeBracket + 1);
|
|
249
|
+
if (rest.startsWith(":")) {
|
|
250
|
+
const port2 = parseInt(rest.slice(1), 10);
|
|
251
|
+
if (isNaN(port2)) {
|
|
252
|
+
throw new ConfigError({
|
|
253
|
+
field: "port",
|
|
254
|
+
value: rest.slice(1),
|
|
255
|
+
message: "must be a valid port number"
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return { host: host2, port: port2 };
|
|
259
|
+
}
|
|
260
|
+
return { host: host2, port: DEFAULT_PORT };
|
|
261
|
+
}
|
|
262
|
+
const lastColon = addr.lastIndexOf(":");
|
|
263
|
+
if (lastColon === -1) {
|
|
264
|
+
return { host: addr, port: DEFAULT_PORT };
|
|
265
|
+
}
|
|
266
|
+
if (addr.indexOf(":") !== lastColon) {
|
|
267
|
+
return { host: addr, port: DEFAULT_PORT };
|
|
268
|
+
}
|
|
269
|
+
const host = addr.slice(0, lastColon);
|
|
270
|
+
const portStr = addr.slice(lastColon + 1);
|
|
271
|
+
const port = parseInt(portStr, 10);
|
|
272
|
+
if (isNaN(port)) {
|
|
273
|
+
throw new ConfigError({
|
|
274
|
+
field: "port",
|
|
275
|
+
value: portStr,
|
|
276
|
+
message: "must be a valid port number"
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (!host) {
|
|
280
|
+
throw new ConfigError({
|
|
281
|
+
field: "host",
|
|
282
|
+
message: "host is required"
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return { host, port };
|
|
286
|
+
}
|
|
287
|
+
function applyQueryParams(params, cfg) {
|
|
288
|
+
for (const [key, value] of params) {
|
|
289
|
+
switch (key) {
|
|
290
|
+
case "page_size": {
|
|
291
|
+
const n = parseInt(value, 10);
|
|
292
|
+
if (isNaN(n) || n <= 0) {
|
|
293
|
+
throw new ConfigError({
|
|
294
|
+
field: "page_size",
|
|
295
|
+
value,
|
|
296
|
+
message: "must be a positive integer"
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (n > MAX_PAGE_SIZE) {
|
|
300
|
+
throw new ConfigError({
|
|
301
|
+
field: "page_size",
|
|
302
|
+
value,
|
|
303
|
+
message: `must not exceed ${MAX_PAGE_SIZE}`
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
cfg.pageSize = n;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case "hello_name":
|
|
310
|
+
if (!value) {
|
|
311
|
+
throw new ConfigError({
|
|
312
|
+
field: "hello_name",
|
|
313
|
+
message: "cannot be empty"
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
cfg.helloName = value;
|
|
317
|
+
break;
|
|
318
|
+
case "hello_ver":
|
|
319
|
+
if (!value) {
|
|
320
|
+
throw new ConfigError({
|
|
321
|
+
field: "hello_ver",
|
|
322
|
+
message: "cannot be empty"
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
cfg.helloVersion = value;
|
|
326
|
+
break;
|
|
327
|
+
case "conformance":
|
|
328
|
+
cfg.conformance = value;
|
|
329
|
+
break;
|
|
330
|
+
case "username":
|
|
331
|
+
case "user":
|
|
332
|
+
cfg.username = value;
|
|
333
|
+
break;
|
|
334
|
+
case "password":
|
|
335
|
+
case "pass":
|
|
336
|
+
cfg.password = value;
|
|
337
|
+
break;
|
|
338
|
+
case "ca":
|
|
339
|
+
cfg.tlsCA = value;
|
|
340
|
+
break;
|
|
341
|
+
case "cert":
|
|
342
|
+
cfg.tlsCert = value;
|
|
343
|
+
break;
|
|
344
|
+
case "key":
|
|
345
|
+
cfg.tlsKey = value;
|
|
346
|
+
break;
|
|
347
|
+
case "insecure":
|
|
348
|
+
cfg.insecureSkipVerify = value === "true" || value === "1";
|
|
349
|
+
break;
|
|
350
|
+
case "server_name":
|
|
351
|
+
cfg.serverName = value;
|
|
352
|
+
break;
|
|
353
|
+
case "connect_timeout": {
|
|
354
|
+
const n = parseInt(value, 10);
|
|
355
|
+
if (!isNaN(n) && n > 0) {
|
|
356
|
+
cfg.connectTimeout = n;
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case "request_timeout": {
|
|
361
|
+
const n = parseInt(value, 10);
|
|
362
|
+
if (!isNaN(n) && n > 0) {
|
|
363
|
+
cfg.requestTimeout = n;
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (cfg.tlsCert && !cfg.tlsKey || !cfg.tlsCert && cfg.tlsKey) {
|
|
370
|
+
throw new ConfigError({
|
|
371
|
+
field: "tls",
|
|
372
|
+
message: "both cert and key must be provided for mTLS"
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function validateConfig(cfg) {
|
|
377
|
+
if (!cfg.host) {
|
|
378
|
+
throw new ConfigError({
|
|
379
|
+
field: "host",
|
|
380
|
+
message: "host is required"
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (!cfg.port || cfg.port < 1 || cfg.port > 65535) {
|
|
384
|
+
throw new ConfigError({
|
|
385
|
+
field: "port",
|
|
386
|
+
value: String(cfg.port),
|
|
387
|
+
message: "must be a valid port number (1-65535)"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (cfg.pageSize <= 0) {
|
|
391
|
+
throw new ConfigError({
|
|
392
|
+
field: "pageSize",
|
|
393
|
+
value: String(cfg.pageSize),
|
|
394
|
+
message: "must be positive"
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (cfg.pageSize > MAX_PAGE_SIZE) {
|
|
398
|
+
throw new ConfigError({
|
|
399
|
+
field: "pageSize",
|
|
400
|
+
value: String(cfg.pageSize),
|
|
401
|
+
message: `must not exceed ${MAX_PAGE_SIZE}`
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function getAddress(cfg) {
|
|
406
|
+
if (cfg.host.includes(":")) {
|
|
407
|
+
return `[${cfg.host}]:${cfg.port}`;
|
|
408
|
+
}
|
|
409
|
+
return `${cfg.host}:${cfg.port}`;
|
|
410
|
+
}
|
|
411
|
+
function cloneConfig(cfg) {
|
|
412
|
+
return { ...cfg };
|
|
413
|
+
}
|
|
414
|
+
var DEFAULT_PORT, DEFAULT_PAGE_SIZE, DEFAULT_HELLO_NAME, DEFAULT_HELLO_VERSION, DEFAULT_CONFORMANCE, MAX_QUERY_LENGTH, MAX_PAGE_SIZE;
|
|
415
|
+
var init_config = __esm({
|
|
416
|
+
"src/config.ts"() {
|
|
417
|
+
init_errors();
|
|
418
|
+
DEFAULT_PORT = 3141;
|
|
419
|
+
DEFAULT_PAGE_SIZE = 1e3;
|
|
420
|
+
DEFAULT_HELLO_NAME = "geode-nodejs";
|
|
421
|
+
DEFAULT_HELLO_VERSION = "1.0.0";
|
|
422
|
+
DEFAULT_CONFORMANCE = "min";
|
|
423
|
+
MAX_QUERY_LENGTH = 1 << 20;
|
|
424
|
+
MAX_PAGE_SIZE = 1e5;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
function parseGQLType(typeStr) {
|
|
428
|
+
const upper = typeStr.toUpperCase();
|
|
429
|
+
switch (upper) {
|
|
430
|
+
case "INT":
|
|
431
|
+
case "INTEGER":
|
|
432
|
+
case "BIGINT":
|
|
433
|
+
return "INT";
|
|
434
|
+
case "FLOAT":
|
|
435
|
+
case "DOUBLE":
|
|
436
|
+
case "REAL":
|
|
437
|
+
return "FLOAT";
|
|
438
|
+
case "DECIMAL":
|
|
439
|
+
case "NUMERIC":
|
|
440
|
+
return "DECIMAL";
|
|
441
|
+
case "STRING":
|
|
442
|
+
case "VARCHAR":
|
|
443
|
+
case "TEXT":
|
|
444
|
+
return "STRING";
|
|
445
|
+
case "BOOL":
|
|
446
|
+
case "BOOLEAN":
|
|
447
|
+
return "BOOL";
|
|
448
|
+
case "NULL":
|
|
449
|
+
return "NULL";
|
|
450
|
+
case "LIST":
|
|
451
|
+
case "ARRAY":
|
|
452
|
+
return "ARRAY";
|
|
453
|
+
case "MAP":
|
|
454
|
+
case "RECORD":
|
|
455
|
+
case "OBJECT":
|
|
456
|
+
return "OBJECT";
|
|
457
|
+
case "NODE":
|
|
458
|
+
return "NODE";
|
|
459
|
+
case "EDGE":
|
|
460
|
+
case "RELATIONSHIP":
|
|
461
|
+
return "EDGE";
|
|
462
|
+
case "PATH":
|
|
463
|
+
return "PATH";
|
|
464
|
+
case "BYTEA":
|
|
465
|
+
case "BINARY":
|
|
466
|
+
return "BYTEA";
|
|
467
|
+
case "DATE":
|
|
468
|
+
return "DATE";
|
|
469
|
+
case "TIME":
|
|
470
|
+
return "TIME";
|
|
471
|
+
case "TIMETZ":
|
|
472
|
+
case "TIME WITH TIME ZONE":
|
|
473
|
+
return "TIMETZ";
|
|
474
|
+
case "TIMESTAMP":
|
|
475
|
+
return "TIMESTAMP";
|
|
476
|
+
case "TIMESTAMPTZ":
|
|
477
|
+
case "TIMESTAMP WITH TIME ZONE":
|
|
478
|
+
return "TIMESTAMPTZ";
|
|
479
|
+
case "INTERVAL":
|
|
480
|
+
return "INTERVAL";
|
|
481
|
+
case "JSON":
|
|
482
|
+
return "JSON";
|
|
483
|
+
case "JSONB":
|
|
484
|
+
return "JSONB";
|
|
485
|
+
case "XML":
|
|
486
|
+
return "XML";
|
|
487
|
+
case "UUID":
|
|
488
|
+
return "UUID";
|
|
489
|
+
case "URL":
|
|
490
|
+
return "URL";
|
|
491
|
+
case "DOMAIN":
|
|
492
|
+
return "DOMAIN";
|
|
493
|
+
case "ENUM":
|
|
494
|
+
return "ENUM";
|
|
495
|
+
case "BIT":
|
|
496
|
+
case "VARBIT":
|
|
497
|
+
return "BIT_STRING";
|
|
498
|
+
default:
|
|
499
|
+
if (upper.includes("RANGE")) {
|
|
500
|
+
return "RANGE";
|
|
501
|
+
}
|
|
502
|
+
return "UNKNOWN";
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function fromJSON(value, typeHint) {
|
|
506
|
+
if (value === null || value === void 0) {
|
|
507
|
+
return GQLValue.null();
|
|
508
|
+
}
|
|
509
|
+
if (typeHint) {
|
|
510
|
+
return convertWithType(value, typeHint);
|
|
511
|
+
}
|
|
512
|
+
if (typeof value === "boolean") {
|
|
513
|
+
return GQLValue.bool(value);
|
|
514
|
+
}
|
|
515
|
+
if (typeof value === "number") {
|
|
516
|
+
if (Number.isInteger(value)) {
|
|
517
|
+
return GQLValue.int(value);
|
|
518
|
+
}
|
|
519
|
+
return GQLValue.float(value);
|
|
520
|
+
}
|
|
521
|
+
if (typeof value === "bigint") {
|
|
522
|
+
return GQLValue.int(value);
|
|
523
|
+
}
|
|
524
|
+
if (typeof value === "string") {
|
|
525
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
526
|
+
return GQLValue.uuid(value);
|
|
527
|
+
}
|
|
528
|
+
if (/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2})?/.test(value)) {
|
|
529
|
+
const date = new Date(value);
|
|
530
|
+
if (!isNaN(date.getTime())) {
|
|
531
|
+
return GQLValue.timestamp(date);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return GQLValue.string(value);
|
|
535
|
+
}
|
|
536
|
+
if (Array.isArray(value)) {
|
|
537
|
+
return GQLValue.array(value.map((v) => fromJSON(v)));
|
|
538
|
+
}
|
|
539
|
+
if (typeof value === "object") {
|
|
540
|
+
const obj = value;
|
|
541
|
+
if ("id" in obj && "labels" in obj && "properties" in obj) {
|
|
542
|
+
return GQLValue.node(obj);
|
|
543
|
+
}
|
|
544
|
+
if ("id" in obj && "type" in obj && "startNode" in obj && "endNode" in obj) {
|
|
545
|
+
return GQLValue.edge(obj);
|
|
546
|
+
}
|
|
547
|
+
if ("nodes" in obj && "edges" in obj && Array.isArray(obj.nodes)) {
|
|
548
|
+
return GQLValue.path(obj);
|
|
549
|
+
}
|
|
550
|
+
const map = /* @__PURE__ */ new Map();
|
|
551
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
552
|
+
map.set(k, fromJSON(v));
|
|
553
|
+
}
|
|
554
|
+
return GQLValue.object(map);
|
|
555
|
+
}
|
|
556
|
+
return GQLValue.unknown(value);
|
|
557
|
+
}
|
|
558
|
+
function convertWithType(value, type) {
|
|
559
|
+
switch (type) {
|
|
560
|
+
case "NULL":
|
|
561
|
+
return GQLValue.null();
|
|
562
|
+
case "BOOL":
|
|
563
|
+
return GQLValue.bool(Boolean(value));
|
|
564
|
+
case "INT":
|
|
565
|
+
if (typeof value === "number") {
|
|
566
|
+
return GQLValue.int(value);
|
|
567
|
+
}
|
|
568
|
+
if (typeof value === "bigint") {
|
|
569
|
+
return GQLValue.int(value);
|
|
570
|
+
}
|
|
571
|
+
if (typeof value === "string") {
|
|
572
|
+
return GQLValue.int(BigInt(value));
|
|
573
|
+
}
|
|
574
|
+
return GQLValue.int(0);
|
|
575
|
+
case "FLOAT":
|
|
576
|
+
return GQLValue.float(Number(value));
|
|
577
|
+
case "DECIMAL":
|
|
578
|
+
return GQLValue.decimal(String(value));
|
|
579
|
+
case "STRING":
|
|
580
|
+
return GQLValue.string(String(value));
|
|
581
|
+
case "UUID":
|
|
582
|
+
return GQLValue.uuid(String(value));
|
|
583
|
+
case "BYTEA":
|
|
584
|
+
if (value instanceof Uint8Array) {
|
|
585
|
+
return GQLValue.bytes(value);
|
|
586
|
+
}
|
|
587
|
+
if (typeof value === "string") {
|
|
588
|
+
return GQLValue.bytes(Buffer.from(value, "base64"));
|
|
589
|
+
}
|
|
590
|
+
return GQLValue.bytes(new Uint8Array());
|
|
591
|
+
case "DATE":
|
|
592
|
+
return GQLValue.date(value instanceof Date ? value : new Date(String(value)));
|
|
593
|
+
case "TIME":
|
|
594
|
+
case "TIMETZ":
|
|
595
|
+
return GQLValue.time(value instanceof Date ? value : new Date(String(value)));
|
|
596
|
+
case "TIMESTAMP":
|
|
597
|
+
case "TIMESTAMPTZ":
|
|
598
|
+
return GQLValue.timestamp(value instanceof Date ? value : new Date(String(value)));
|
|
599
|
+
case "JSON":
|
|
600
|
+
case "JSONB":
|
|
601
|
+
return GQLValue.json(value);
|
|
602
|
+
case "ARRAY":
|
|
603
|
+
if (Array.isArray(value)) {
|
|
604
|
+
return GQLValue.array(value.map((v) => fromJSON(v)));
|
|
605
|
+
}
|
|
606
|
+
return GQLValue.array([]);
|
|
607
|
+
case "OBJECT":
|
|
608
|
+
return fromJSON(value);
|
|
609
|
+
case "NODE":
|
|
610
|
+
return GQLValue.node(value);
|
|
611
|
+
case "EDGE":
|
|
612
|
+
return GQLValue.edge(value);
|
|
613
|
+
case "PATH":
|
|
614
|
+
return GQLValue.path(value);
|
|
615
|
+
default:
|
|
616
|
+
return fromJSON(value);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function parseRow(raw, columns) {
|
|
620
|
+
const row = /* @__PURE__ */ new Map();
|
|
621
|
+
for (const col of columns) {
|
|
622
|
+
const value = raw[col.name];
|
|
623
|
+
row.set(col.name, fromJSON(value, col.kind));
|
|
624
|
+
}
|
|
625
|
+
return row;
|
|
626
|
+
}
|
|
627
|
+
function rowToObject(row) {
|
|
628
|
+
const obj = {};
|
|
629
|
+
for (const [key, value] of row) {
|
|
630
|
+
obj[key] = value.toJS();
|
|
631
|
+
}
|
|
632
|
+
return obj;
|
|
633
|
+
}
|
|
634
|
+
var GQLValue;
|
|
635
|
+
var init_types = __esm({
|
|
636
|
+
"src/types.ts"() {
|
|
637
|
+
GQLValue = class _GQLValue {
|
|
638
|
+
kind;
|
|
639
|
+
_intValue = 0n;
|
|
640
|
+
_floatValue = 0;
|
|
641
|
+
_boolValue = false;
|
|
642
|
+
_stringValue = "";
|
|
643
|
+
_decimalValue;
|
|
644
|
+
_arrayValue = [];
|
|
645
|
+
_objectValue = /* @__PURE__ */ new Map();
|
|
646
|
+
_bytesValue = new Uint8Array();
|
|
647
|
+
_dateValue;
|
|
648
|
+
_rangeValue;
|
|
649
|
+
_nodeValue;
|
|
650
|
+
_edgeValue;
|
|
651
|
+
_pathValue;
|
|
652
|
+
_rawValue;
|
|
653
|
+
constructor(kind) {
|
|
654
|
+
this.kind = kind;
|
|
655
|
+
}
|
|
656
|
+
// Factory methods
|
|
657
|
+
static null() {
|
|
658
|
+
return new _GQLValue("NULL");
|
|
659
|
+
}
|
|
660
|
+
static bool(value) {
|
|
661
|
+
const v = new _GQLValue("BOOL");
|
|
662
|
+
v._boolValue = value;
|
|
663
|
+
return v;
|
|
664
|
+
}
|
|
665
|
+
static int(value) {
|
|
666
|
+
const v = new _GQLValue("INT");
|
|
667
|
+
v._intValue = typeof value === "bigint" ? value : BigInt(Math.trunc(value));
|
|
668
|
+
return v;
|
|
669
|
+
}
|
|
670
|
+
static float(value) {
|
|
671
|
+
const v = new _GQLValue("FLOAT");
|
|
672
|
+
v._floatValue = value;
|
|
673
|
+
return v;
|
|
674
|
+
}
|
|
675
|
+
static string(value) {
|
|
676
|
+
const v = new _GQLValue("STRING");
|
|
677
|
+
v._stringValue = value;
|
|
678
|
+
return v;
|
|
679
|
+
}
|
|
680
|
+
static decimal(value) {
|
|
681
|
+
const v = new _GQLValue("DECIMAL");
|
|
682
|
+
v._decimalValue = value instanceof Decimal ? value : new Decimal(value);
|
|
683
|
+
return v;
|
|
684
|
+
}
|
|
685
|
+
static array(values) {
|
|
686
|
+
const v = new _GQLValue("ARRAY");
|
|
687
|
+
v._arrayValue = values;
|
|
688
|
+
return v;
|
|
689
|
+
}
|
|
690
|
+
static object(values) {
|
|
691
|
+
const v = new _GQLValue("OBJECT");
|
|
692
|
+
v._objectValue = values instanceof Map ? values : new Map(Object.entries(values));
|
|
693
|
+
return v;
|
|
694
|
+
}
|
|
695
|
+
static bytes(value) {
|
|
696
|
+
const v = new _GQLValue("BYTEA");
|
|
697
|
+
v._bytesValue = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
698
|
+
return v;
|
|
699
|
+
}
|
|
700
|
+
static date(value) {
|
|
701
|
+
const v = new _GQLValue("DATE");
|
|
702
|
+
v._dateValue = value;
|
|
703
|
+
return v;
|
|
704
|
+
}
|
|
705
|
+
static time(value) {
|
|
706
|
+
const v = new _GQLValue("TIME");
|
|
707
|
+
v._dateValue = value;
|
|
708
|
+
return v;
|
|
709
|
+
}
|
|
710
|
+
static timestamp(value) {
|
|
711
|
+
const v = new _GQLValue("TIMESTAMP");
|
|
712
|
+
v._dateValue = value;
|
|
713
|
+
return v;
|
|
714
|
+
}
|
|
715
|
+
static uuid(value) {
|
|
716
|
+
const v = new _GQLValue("UUID");
|
|
717
|
+
v._stringValue = value;
|
|
718
|
+
return v;
|
|
719
|
+
}
|
|
720
|
+
static json(value) {
|
|
721
|
+
const v = new _GQLValue("JSON");
|
|
722
|
+
v._rawValue = value;
|
|
723
|
+
return v;
|
|
724
|
+
}
|
|
725
|
+
static node(value) {
|
|
726
|
+
const v = new _GQLValue("NODE");
|
|
727
|
+
v._nodeValue = value;
|
|
728
|
+
return v;
|
|
729
|
+
}
|
|
730
|
+
static edge(value) {
|
|
731
|
+
const v = new _GQLValue("EDGE");
|
|
732
|
+
v._edgeValue = value;
|
|
733
|
+
return v;
|
|
734
|
+
}
|
|
735
|
+
static path(value) {
|
|
736
|
+
const v = new _GQLValue("PATH");
|
|
737
|
+
v._pathValue = value;
|
|
738
|
+
return v;
|
|
739
|
+
}
|
|
740
|
+
static range(value) {
|
|
741
|
+
const v = new _GQLValue("RANGE");
|
|
742
|
+
v._rangeValue = value;
|
|
743
|
+
return v;
|
|
744
|
+
}
|
|
745
|
+
static unknown(value) {
|
|
746
|
+
const v = new _GQLValue("UNKNOWN");
|
|
747
|
+
v._rawValue = value;
|
|
748
|
+
return v;
|
|
749
|
+
}
|
|
750
|
+
// Type-safe accessors
|
|
751
|
+
get isNull() {
|
|
752
|
+
return this.kind === "NULL";
|
|
753
|
+
}
|
|
754
|
+
get asBool() {
|
|
755
|
+
if (this.kind !== "BOOL") {
|
|
756
|
+
throw new TypeError(`Cannot convert ${this.kind} to boolean`);
|
|
757
|
+
}
|
|
758
|
+
return this._boolValue;
|
|
759
|
+
}
|
|
760
|
+
get asInt() {
|
|
761
|
+
if (this.kind !== "INT") {
|
|
762
|
+
throw new TypeError(`Cannot convert ${this.kind} to int`);
|
|
763
|
+
}
|
|
764
|
+
return this._intValue;
|
|
765
|
+
}
|
|
766
|
+
get asNumber() {
|
|
767
|
+
if (this.kind === "INT") {
|
|
768
|
+
return Number(this._intValue);
|
|
769
|
+
}
|
|
770
|
+
if (this.kind === "FLOAT") {
|
|
771
|
+
return this._floatValue;
|
|
772
|
+
}
|
|
773
|
+
if (this.kind === "DECIMAL") {
|
|
774
|
+
return this._decimalValue?.toNumber() ?? 0;
|
|
775
|
+
}
|
|
776
|
+
throw new TypeError(`Cannot convert ${this.kind} to number`);
|
|
777
|
+
}
|
|
778
|
+
get asFloat() {
|
|
779
|
+
if (this.kind !== "FLOAT") {
|
|
780
|
+
throw new TypeError(`Cannot convert ${this.kind} to float`);
|
|
781
|
+
}
|
|
782
|
+
return this._floatValue;
|
|
783
|
+
}
|
|
784
|
+
get asString() {
|
|
785
|
+
if (this.kind === "STRING" || this.kind === "UUID") {
|
|
786
|
+
return this._stringValue;
|
|
787
|
+
}
|
|
788
|
+
return this.toString();
|
|
789
|
+
}
|
|
790
|
+
get asDecimal() {
|
|
791
|
+
if (this.kind !== "DECIMAL") {
|
|
792
|
+
throw new TypeError(`Cannot convert ${this.kind} to decimal`);
|
|
793
|
+
}
|
|
794
|
+
return this._decimalValue;
|
|
795
|
+
}
|
|
796
|
+
get asArray() {
|
|
797
|
+
if (this.kind !== "ARRAY") {
|
|
798
|
+
throw new TypeError(`Cannot convert ${this.kind} to array`);
|
|
799
|
+
}
|
|
800
|
+
return this._arrayValue;
|
|
801
|
+
}
|
|
802
|
+
get asObject() {
|
|
803
|
+
if (this.kind !== "OBJECT" && this.kind !== "NODE" && this.kind !== "EDGE") {
|
|
804
|
+
throw new TypeError(`Cannot convert ${this.kind} to object`);
|
|
805
|
+
}
|
|
806
|
+
return this._objectValue;
|
|
807
|
+
}
|
|
808
|
+
get asBytes() {
|
|
809
|
+
if (this.kind !== "BYTEA") {
|
|
810
|
+
throw new TypeError(`Cannot convert ${this.kind} to bytes`);
|
|
811
|
+
}
|
|
812
|
+
return this._bytesValue;
|
|
813
|
+
}
|
|
814
|
+
get asDate() {
|
|
815
|
+
if (this.kind !== "DATE" && this.kind !== "TIME" && this.kind !== "TIMETZ" && this.kind !== "TIMESTAMP" && this.kind !== "TIMESTAMPTZ") {
|
|
816
|
+
throw new TypeError(`Cannot convert ${this.kind} to date`);
|
|
817
|
+
}
|
|
818
|
+
return this._dateValue;
|
|
819
|
+
}
|
|
820
|
+
get asNode() {
|
|
821
|
+
if (this.kind !== "NODE") {
|
|
822
|
+
throw new TypeError(`Cannot convert ${this.kind} to node`);
|
|
823
|
+
}
|
|
824
|
+
return this._nodeValue;
|
|
825
|
+
}
|
|
826
|
+
get asEdge() {
|
|
827
|
+
if (this.kind !== "EDGE") {
|
|
828
|
+
throw new TypeError(`Cannot convert ${this.kind} to edge`);
|
|
829
|
+
}
|
|
830
|
+
return this._edgeValue;
|
|
831
|
+
}
|
|
832
|
+
get asPath() {
|
|
833
|
+
if (this.kind !== "PATH") {
|
|
834
|
+
throw new TypeError(`Cannot convert ${this.kind} to path`);
|
|
835
|
+
}
|
|
836
|
+
return this._pathValue;
|
|
837
|
+
}
|
|
838
|
+
get asRange() {
|
|
839
|
+
if (this.kind !== "RANGE") {
|
|
840
|
+
throw new TypeError(`Cannot convert ${this.kind} to range`);
|
|
841
|
+
}
|
|
842
|
+
return this._rangeValue;
|
|
843
|
+
}
|
|
844
|
+
get asJSON() {
|
|
845
|
+
if (this.kind !== "JSON" && this.kind !== "JSONB") {
|
|
846
|
+
throw new TypeError(`Cannot convert ${this.kind} to JSON`);
|
|
847
|
+
}
|
|
848
|
+
return this._rawValue;
|
|
849
|
+
}
|
|
850
|
+
get raw() {
|
|
851
|
+
return this._rawValue;
|
|
852
|
+
}
|
|
853
|
+
toString() {
|
|
854
|
+
switch (this.kind) {
|
|
855
|
+
case "NULL":
|
|
856
|
+
return "null";
|
|
857
|
+
case "BOOL":
|
|
858
|
+
return this._boolValue.toString();
|
|
859
|
+
case "INT":
|
|
860
|
+
return this._intValue.toString();
|
|
861
|
+
case "FLOAT":
|
|
862
|
+
return this._floatValue.toString();
|
|
863
|
+
case "STRING":
|
|
864
|
+
case "UUID":
|
|
865
|
+
return this._stringValue;
|
|
866
|
+
case "DECIMAL":
|
|
867
|
+
return this._decimalValue?.toString() ?? "0";
|
|
868
|
+
case "ARRAY":
|
|
869
|
+
return `[${this._arrayValue.map((v) => v.toString()).join(", ")}]`;
|
|
870
|
+
case "OBJECT":
|
|
871
|
+
return `{${[...this._objectValue.entries()].map(([k, v]) => `${k}: ${v.toString()}`).join(", ")}}`;
|
|
872
|
+
case "BYTEA":
|
|
873
|
+
return `<bytes:${this._bytesValue.length}>`;
|
|
874
|
+
case "DATE":
|
|
875
|
+
case "TIME":
|
|
876
|
+
case "TIMETZ":
|
|
877
|
+
case "TIMESTAMP":
|
|
878
|
+
case "TIMESTAMPTZ":
|
|
879
|
+
return this._dateValue?.toISOString() ?? "";
|
|
880
|
+
case "NODE":
|
|
881
|
+
return `(${this._nodeValue?.labels.join(":")} {${JSON.stringify(this._nodeValue?.properties)}})`;
|
|
882
|
+
case "EDGE":
|
|
883
|
+
return `[${this._edgeValue?.type} {${JSON.stringify(this._edgeValue?.properties)}}]`;
|
|
884
|
+
case "PATH":
|
|
885
|
+
return `<path:${this._pathValue?.nodes.length} nodes>`;
|
|
886
|
+
case "JSON":
|
|
887
|
+
case "JSONB":
|
|
888
|
+
return JSON.stringify(this._rawValue);
|
|
889
|
+
default:
|
|
890
|
+
return String(this._rawValue ?? "");
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Convert to a plain JavaScript value.
|
|
895
|
+
*/
|
|
896
|
+
toJS() {
|
|
897
|
+
switch (this.kind) {
|
|
898
|
+
case "NULL":
|
|
899
|
+
return null;
|
|
900
|
+
case "BOOL":
|
|
901
|
+
return this._boolValue;
|
|
902
|
+
case "INT":
|
|
903
|
+
if (this._intValue >= Number.MIN_SAFE_INTEGER && this._intValue <= Number.MAX_SAFE_INTEGER) {
|
|
904
|
+
return Number(this._intValue);
|
|
905
|
+
}
|
|
906
|
+
return this._intValue;
|
|
907
|
+
case "FLOAT":
|
|
908
|
+
return this._floatValue;
|
|
909
|
+
case "STRING":
|
|
910
|
+
case "UUID":
|
|
911
|
+
return this._stringValue;
|
|
912
|
+
case "DECIMAL":
|
|
913
|
+
return this._decimalValue?.toString();
|
|
914
|
+
case "ARRAY":
|
|
915
|
+
return this._arrayValue.map((v) => v.toJS());
|
|
916
|
+
case "OBJECT":
|
|
917
|
+
return Object.fromEntries([...this._objectValue.entries()].map(([k, v]) => [k, v.toJS()]));
|
|
918
|
+
case "BYTEA":
|
|
919
|
+
return this._bytesValue;
|
|
920
|
+
case "DATE":
|
|
921
|
+
case "TIME":
|
|
922
|
+
case "TIMETZ":
|
|
923
|
+
case "TIMESTAMP":
|
|
924
|
+
case "TIMESTAMPTZ":
|
|
925
|
+
return this._dateValue;
|
|
926
|
+
case "NODE":
|
|
927
|
+
return this._nodeValue;
|
|
928
|
+
case "EDGE":
|
|
929
|
+
return this._edgeValue;
|
|
930
|
+
case "PATH":
|
|
931
|
+
return this._pathValue;
|
|
932
|
+
case "RANGE":
|
|
933
|
+
return this._rangeValue;
|
|
934
|
+
case "JSON":
|
|
935
|
+
case "JSONB":
|
|
936
|
+
return this._rawValue;
|
|
937
|
+
default:
|
|
938
|
+
return this._rawValue;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Convert to JSON-serializable format.
|
|
943
|
+
*/
|
|
944
|
+
toJSON() {
|
|
945
|
+
return this.toJS();
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// src/protocol.ts
|
|
952
|
+
function parseFrame(data) {
|
|
953
|
+
let str;
|
|
954
|
+
if (Buffer.isBuffer(data)) {
|
|
955
|
+
str = data.toString("utf-8");
|
|
956
|
+
} else {
|
|
957
|
+
str = data;
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
const frame = JSON.parse(str);
|
|
961
|
+
if (!frame.result || typeof frame.result.type !== "string") {
|
|
962
|
+
const topLevel = frame;
|
|
963
|
+
if (typeof topLevel.type === "string") {
|
|
964
|
+
return {
|
|
965
|
+
status_class: topLevel.status_class ?? "00000",
|
|
966
|
+
status_subclass: "000",
|
|
967
|
+
result: frame
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
if (typeof topLevel.status_class === "string") {
|
|
971
|
+
const resultType = Array.isArray(topLevel.columns) ? RespType.BINDINGS : RespType.STATUS;
|
|
972
|
+
return {
|
|
973
|
+
status_class: topLevel.status_class,
|
|
974
|
+
status_subclass: "000",
|
|
975
|
+
result: {
|
|
976
|
+
type: resultType,
|
|
977
|
+
...frame
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
throw new TransportError({
|
|
982
|
+
operation: "parse_frame",
|
|
983
|
+
cause: new Error(
|
|
984
|
+
`invalid frame structure: missing result.type. Received: ${str.slice(0, 200)}`
|
|
985
|
+
)
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
return frame;
|
|
989
|
+
} catch (e) {
|
|
990
|
+
if (e instanceof TransportError) {
|
|
991
|
+
throw e;
|
|
992
|
+
}
|
|
993
|
+
throw new TransportError({
|
|
994
|
+
operation: "parse_frame",
|
|
995
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function isErrorFrame(frame) {
|
|
1000
|
+
return frame.result.type === RespType.ERROR;
|
|
1001
|
+
}
|
|
1002
|
+
function isSchemaFrame(frame) {
|
|
1003
|
+
return frame.result.type === RespType.SCHEMA;
|
|
1004
|
+
}
|
|
1005
|
+
function isBindingsFrame(frame) {
|
|
1006
|
+
return frame.result.type === RespType.BINDINGS;
|
|
1007
|
+
}
|
|
1008
|
+
function isFinalFrame(frame) {
|
|
1009
|
+
return frame.result.final === true;
|
|
1010
|
+
}
|
|
1011
|
+
function frameToError(frame) {
|
|
1012
|
+
const result = frame.result;
|
|
1013
|
+
const additional = [];
|
|
1014
|
+
if (result.additional) {
|
|
1015
|
+
for (const info of result.additional) {
|
|
1016
|
+
if (info.message) {
|
|
1017
|
+
additional.push(info.message);
|
|
1018
|
+
} else if (info.code) {
|
|
1019
|
+
additional.push(info.code);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return new DriverError({
|
|
1024
|
+
statusClass: frame.status_class || StatusClass.SYSTEM,
|
|
1025
|
+
subclass: frame.status_subclass || "000",
|
|
1026
|
+
code: result.code ?? "UnknownError",
|
|
1027
|
+
message: result.message ?? "Unknown error",
|
|
1028
|
+
anchor: result.anchor,
|
|
1029
|
+
additional,
|
|
1030
|
+
findings: result.flagger_findings
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
function getColumnsFromSchema(frame) {
|
|
1034
|
+
if (!isSchemaFrame(frame)) {
|
|
1035
|
+
throw new Error("Expected SCHEMA frame");
|
|
1036
|
+
}
|
|
1037
|
+
const columns = frame.result.columns ?? [];
|
|
1038
|
+
return columns.map((col) => ({
|
|
1039
|
+
name: col.name,
|
|
1040
|
+
type: col.type,
|
|
1041
|
+
kind: parseGQLType(col.type)
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
1044
|
+
function buildHelloMessage(cfg) {
|
|
1045
|
+
const msg = {
|
|
1046
|
+
type: MsgType.HELLO,
|
|
1047
|
+
client_name: cfg.helloName,
|
|
1048
|
+
client_ver: cfg.helloVersion,
|
|
1049
|
+
wanted_conformance: cfg.conformance
|
|
1050
|
+
};
|
|
1051
|
+
if (cfg.username) {
|
|
1052
|
+
msg["username"] = cfg.username;
|
|
1053
|
+
}
|
|
1054
|
+
if (cfg.password) {
|
|
1055
|
+
msg["password"] = cfg.password;
|
|
1056
|
+
}
|
|
1057
|
+
return msg;
|
|
1058
|
+
}
|
|
1059
|
+
function buildRunGQLMessage(query2, params) {
|
|
1060
|
+
const msg = {
|
|
1061
|
+
type: MsgType.RUN_GQL,
|
|
1062
|
+
text: query2
|
|
1063
|
+
};
|
|
1064
|
+
if (params && Object.keys(params).length > 0) {
|
|
1065
|
+
msg["params"] = params;
|
|
1066
|
+
}
|
|
1067
|
+
return msg;
|
|
1068
|
+
}
|
|
1069
|
+
function buildPullMessage(requestId, pageSize) {
|
|
1070
|
+
return {
|
|
1071
|
+
type: MsgType.PULL,
|
|
1072
|
+
request_id: requestId,
|
|
1073
|
+
page_size: pageSize
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function buildBeginMessage() {
|
|
1077
|
+
return {
|
|
1078
|
+
type: MsgType.BEGIN
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function buildCommitMessage() {
|
|
1082
|
+
return {
|
|
1083
|
+
type: MsgType.COMMIT
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
function buildRollbackMessage() {
|
|
1087
|
+
return {
|
|
1088
|
+
type: MsgType.ROLLBACK
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function buildSavepointMessage(name) {
|
|
1092
|
+
return {
|
|
1093
|
+
type: MsgType.SAVEPOINT,
|
|
1094
|
+
name
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function buildRollbackToMessage(name) {
|
|
1098
|
+
return {
|
|
1099
|
+
type: MsgType.ROLLBACK_TO,
|
|
1100
|
+
name
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function buildPingMessage() {
|
|
1104
|
+
return {
|
|
1105
|
+
type: MsgType.PING
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function serializeMessage(msg) {
|
|
1109
|
+
const json = JSON.stringify(msg);
|
|
1110
|
+
return Buffer.from(json + "\n", "utf-8");
|
|
1111
|
+
}
|
|
1112
|
+
function rewritePlaceholders(query2, args) {
|
|
1113
|
+
const params = {};
|
|
1114
|
+
let argIndex = 0;
|
|
1115
|
+
let result = "";
|
|
1116
|
+
let i = 0;
|
|
1117
|
+
while (i < query2.length) {
|
|
1118
|
+
const ch = query2[i];
|
|
1119
|
+
if (ch === "'" || ch === '"') {
|
|
1120
|
+
const quote = ch;
|
|
1121
|
+
result += ch;
|
|
1122
|
+
i++;
|
|
1123
|
+
while (i < query2.length && query2[i] !== quote) {
|
|
1124
|
+
if (query2[i] === "\\" && i + 1 < query2.length) {
|
|
1125
|
+
result += query2[i];
|
|
1126
|
+
i++;
|
|
1127
|
+
}
|
|
1128
|
+
result += query2[i];
|
|
1129
|
+
i++;
|
|
1130
|
+
}
|
|
1131
|
+
if (i < query2.length) {
|
|
1132
|
+
result += query2[i];
|
|
1133
|
+
i++;
|
|
1134
|
+
}
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (ch === "?") {
|
|
1138
|
+
argIndex++;
|
|
1139
|
+
const paramName = `p${argIndex}`;
|
|
1140
|
+
result += "$" + paramName;
|
|
1141
|
+
if (argIndex <= args.length) {
|
|
1142
|
+
params[paramName] = args[argIndex - 1];
|
|
1143
|
+
}
|
|
1144
|
+
i++;
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
result += ch;
|
|
1148
|
+
i++;
|
|
1149
|
+
}
|
|
1150
|
+
return { query: result, params };
|
|
1151
|
+
}
|
|
1152
|
+
function countPlaceholders(query2) {
|
|
1153
|
+
let count = 0;
|
|
1154
|
+
let i = 0;
|
|
1155
|
+
while (i < query2.length) {
|
|
1156
|
+
const ch = query2[i];
|
|
1157
|
+
if (ch === "'" || ch === '"') {
|
|
1158
|
+
const quote = ch;
|
|
1159
|
+
i++;
|
|
1160
|
+
while (i < query2.length && query2[i] !== quote) {
|
|
1161
|
+
if (query2[i] === "\\") {
|
|
1162
|
+
i++;
|
|
1163
|
+
}
|
|
1164
|
+
i++;
|
|
1165
|
+
}
|
|
1166
|
+
i++;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (ch === "?") {
|
|
1170
|
+
count++;
|
|
1171
|
+
}
|
|
1172
|
+
i++;
|
|
1173
|
+
}
|
|
1174
|
+
return count;
|
|
1175
|
+
}
|
|
1176
|
+
function mergeParams(positionalParams, namedParams) {
|
|
1177
|
+
if (!namedParams || Object.keys(namedParams).length === 0) {
|
|
1178
|
+
return positionalParams;
|
|
1179
|
+
}
|
|
1180
|
+
return { ...positionalParams, ...namedParams };
|
|
1181
|
+
}
|
|
1182
|
+
var MsgType, RespType;
|
|
1183
|
+
var init_protocol = __esm({
|
|
1184
|
+
"src/protocol.ts"() {
|
|
1185
|
+
init_errors();
|
|
1186
|
+
init_types();
|
|
1187
|
+
MsgType = {
|
|
1188
|
+
HELLO: "HELLO",
|
|
1189
|
+
RUN_GQL: "RUN_GQL",
|
|
1190
|
+
PULL: "PULL",
|
|
1191
|
+
BEGIN: "BEGIN",
|
|
1192
|
+
COMMIT: "COMMIT",
|
|
1193
|
+
ROLLBACK: "ROLLBACK",
|
|
1194
|
+
SAVEPOINT: "SAVEPOINT",
|
|
1195
|
+
ROLLBACK_TO: "ROLLBACK_TO",
|
|
1196
|
+
PING: "PING"
|
|
1197
|
+
};
|
|
1198
|
+
RespType = {
|
|
1199
|
+
HELLO: "HELLO",
|
|
1200
|
+
SCHEMA: "SCHEMA",
|
|
1201
|
+
BINDINGS: "BINDINGS",
|
|
1202
|
+
ERROR: "ERROR",
|
|
1203
|
+
EXPLAIN: "EXPLAIN",
|
|
1204
|
+
PROFILE: "PROFILE",
|
|
1205
|
+
RESULT: "RESULT",
|
|
1206
|
+
STATUS: "STATUS"
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
// src/validate.ts
|
|
1212
|
+
function validateQuery(query2) {
|
|
1213
|
+
if (!query2) {
|
|
1214
|
+
throw new SecurityError({
|
|
1215
|
+
type: "input",
|
|
1216
|
+
message: "query cannot be empty"
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
if (query2.length > MAX_QUERY_LENGTH) {
|
|
1220
|
+
throw new SecurityError({
|
|
1221
|
+
type: "input",
|
|
1222
|
+
message: `query exceeds maximum length of ${MAX_QUERY_LENGTH} bytes`
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
if (query2.includes("\0")) {
|
|
1226
|
+
throw new SecurityError({
|
|
1227
|
+
type: "input",
|
|
1228
|
+
message: "query contains null bytes"
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(query2)) {
|
|
1232
|
+
throw new SecurityError({
|
|
1233
|
+
type: "input",
|
|
1234
|
+
message: "query contains invalid control characters"
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
function validateParamName(name) {
|
|
1239
|
+
if (!name) {
|
|
1240
|
+
throw new SecurityError({
|
|
1241
|
+
type: "input",
|
|
1242
|
+
message: "parameter name cannot be empty"
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
if (!PARAM_NAME_PATTERN.test(name)) {
|
|
1246
|
+
throw new SecurityError({
|
|
1247
|
+
type: "input",
|
|
1248
|
+
message: `invalid parameter name: ${name}`
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
if (RESERVED_KEYWORDS.has(name.toLowerCase())) {
|
|
1252
|
+
throw new SecurityError({
|
|
1253
|
+
type: "input",
|
|
1254
|
+
message: `parameter name is a reserved keyword: ${name}`
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
function validateParamValue(value) {
|
|
1259
|
+
try {
|
|
1260
|
+
JSON.stringify(value);
|
|
1261
|
+
} catch {
|
|
1262
|
+
throw new SecurityError({
|
|
1263
|
+
type: "input",
|
|
1264
|
+
message: "parameter value contains circular reference or is not serializable"
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
validateValueRecursive(value, 0);
|
|
1268
|
+
}
|
|
1269
|
+
function validateValueRecursive(value, depth) {
|
|
1270
|
+
const MAX_DEPTH = 32;
|
|
1271
|
+
if (depth > MAX_DEPTH) {
|
|
1272
|
+
throw new SecurityError({
|
|
1273
|
+
type: "input",
|
|
1274
|
+
message: `parameter value exceeds maximum nesting depth of ${MAX_DEPTH}`
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
if (value === null || value === void 0) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
if (typeof value === "string") {
|
|
1281
|
+
if (value.includes("\0")) {
|
|
1282
|
+
throw new SecurityError({
|
|
1283
|
+
type: "input",
|
|
1284
|
+
message: "parameter value contains null bytes"
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (Array.isArray(value)) {
|
|
1290
|
+
for (const item of value) {
|
|
1291
|
+
validateValueRecursive(item, depth + 1);
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (typeof value === "object") {
|
|
1296
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1297
|
+
validateParamName(key);
|
|
1298
|
+
validateValueRecursive(val, depth + 1);
|
|
1299
|
+
}
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function validateHostname(hostname) {
|
|
1304
|
+
if (!hostname) {
|
|
1305
|
+
throw new SecurityError({
|
|
1306
|
+
type: "input",
|
|
1307
|
+
message: "hostname cannot be empty"
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
if (hostname.length > 253) {
|
|
1311
|
+
throw new SecurityError({
|
|
1312
|
+
type: "input",
|
|
1313
|
+
message: "hostname exceeds maximum length"
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
if (!/^[a-zA-Z0-9.\-:[\]]+$/.test(hostname)) {
|
|
1317
|
+
throw new SecurityError({
|
|
1318
|
+
type: "input",
|
|
1319
|
+
message: "hostname contains invalid characters"
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
|
|
1323
|
+
const parts = hostname.split(".").map(Number);
|
|
1324
|
+
if (parts.some((p) => p < 0 || p > 255)) {
|
|
1325
|
+
throw new SecurityError({
|
|
1326
|
+
type: "input",
|
|
1327
|
+
message: "invalid IPv4 address"
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
if (hostname.includes(":") || hostname.startsWith("[")) {
|
|
1333
|
+
const addr = hostname.replace(/^\[|\]$/g, "");
|
|
1334
|
+
if (!/^[a-fA-F0-9:]+$/.test(addr)) {
|
|
1335
|
+
throw new SecurityError({
|
|
1336
|
+
type: "input",
|
|
1337
|
+
message: "invalid IPv6 address"
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const labels = hostname.split(".");
|
|
1343
|
+
for (const label of labels) {
|
|
1344
|
+
if (!label || label.length > 63) {
|
|
1345
|
+
throw new SecurityError({
|
|
1346
|
+
type: "input",
|
|
1347
|
+
message: "hostname label is invalid"
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(label)) {
|
|
1351
|
+
throw new SecurityError({
|
|
1352
|
+
type: "input",
|
|
1353
|
+
message: "hostname label contains invalid characters"
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function validatePort(port) {
|
|
1359
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1360
|
+
throw new SecurityError({
|
|
1361
|
+
type: "input",
|
|
1362
|
+
message: "port must be an integer between 1 and 65535"
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
function validatePageSize(pageSize) {
|
|
1367
|
+
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
1368
|
+
throw new SecurityError({
|
|
1369
|
+
type: "input",
|
|
1370
|
+
message: "page size must be a positive integer"
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
if (pageSize > MAX_PAGE_SIZE) {
|
|
1374
|
+
throw new SecurityError({
|
|
1375
|
+
type: "input",
|
|
1376
|
+
message: `page size must not exceed ${MAX_PAGE_SIZE}`
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function validateSavepointName(name) {
|
|
1381
|
+
if (!name) {
|
|
1382
|
+
throw new SecurityError({
|
|
1383
|
+
type: "input",
|
|
1384
|
+
message: "savepoint name cannot be empty"
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
if (name.length > 128) {
|
|
1388
|
+
throw new SecurityError({
|
|
1389
|
+
type: "input",
|
|
1390
|
+
message: "savepoint name exceeds maximum length of 128 characters"
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
1394
|
+
throw new SecurityError({
|
|
1395
|
+
type: "input",
|
|
1396
|
+
message: "savepoint name contains invalid characters"
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
function sanitizeForLog(value) {
|
|
1401
|
+
return value.replace(/password\s*[:=]\s*\S+/gi, "password=***").replace(/token\s*[:=]\s*\S+/gi, "token=***").replace(/secret\s*[:=]\s*\S+/gi, "secret=***").replace(/api[_-]?key\s*[:=]\s*\S+/gi, "api_key=***");
|
|
1402
|
+
}
|
|
1403
|
+
var PARAM_NAME_PATTERN, RESERVED_KEYWORDS;
|
|
1404
|
+
var init_validate = __esm({
|
|
1405
|
+
"src/validate.ts"() {
|
|
1406
|
+
init_errors();
|
|
1407
|
+
init_config();
|
|
1408
|
+
PARAM_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
|
|
1409
|
+
RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1410
|
+
"match",
|
|
1411
|
+
"create",
|
|
1412
|
+
"delete",
|
|
1413
|
+
"set",
|
|
1414
|
+
"remove",
|
|
1415
|
+
"return",
|
|
1416
|
+
"where",
|
|
1417
|
+
"with",
|
|
1418
|
+
"order",
|
|
1419
|
+
"by",
|
|
1420
|
+
"skip",
|
|
1421
|
+
"limit",
|
|
1422
|
+
"union",
|
|
1423
|
+
"all",
|
|
1424
|
+
"optional",
|
|
1425
|
+
"and",
|
|
1426
|
+
"or",
|
|
1427
|
+
"not",
|
|
1428
|
+
"xor",
|
|
1429
|
+
"null",
|
|
1430
|
+
"true",
|
|
1431
|
+
"false",
|
|
1432
|
+
"in",
|
|
1433
|
+
"is",
|
|
1434
|
+
"as",
|
|
1435
|
+
"case",
|
|
1436
|
+
"when",
|
|
1437
|
+
"then",
|
|
1438
|
+
"else",
|
|
1439
|
+
"end",
|
|
1440
|
+
"exists",
|
|
1441
|
+
"count",
|
|
1442
|
+
"sum",
|
|
1443
|
+
"avg",
|
|
1444
|
+
"min",
|
|
1445
|
+
"max",
|
|
1446
|
+
"collect",
|
|
1447
|
+
"distinct",
|
|
1448
|
+
"merge",
|
|
1449
|
+
"call",
|
|
1450
|
+
"yield",
|
|
1451
|
+
"unwind",
|
|
1452
|
+
"foreach",
|
|
1453
|
+
"detach",
|
|
1454
|
+
"drop",
|
|
1455
|
+
"graph",
|
|
1456
|
+
"use",
|
|
1457
|
+
"catalog",
|
|
1458
|
+
"constraint",
|
|
1459
|
+
"index",
|
|
1460
|
+
"create",
|
|
1461
|
+
"node",
|
|
1462
|
+
"relationship",
|
|
1463
|
+
"edge",
|
|
1464
|
+
"property",
|
|
1465
|
+
"on",
|
|
1466
|
+
"for",
|
|
1467
|
+
"if",
|
|
1468
|
+
"begin",
|
|
1469
|
+
"commit",
|
|
1470
|
+
"rollback",
|
|
1471
|
+
"transaction"
|
|
1472
|
+
]);
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
// src/prepared.ts
|
|
1477
|
+
var prepared_exports = {};
|
|
1478
|
+
__export(prepared_exports, {
|
|
1479
|
+
PreparedStatement: () => PreparedStatement,
|
|
1480
|
+
extractParameters: () => extractParameters,
|
|
1481
|
+
prepare: () => prepare
|
|
1482
|
+
});
|
|
1483
|
+
function extractParameters(query2) {
|
|
1484
|
+
const params = [];
|
|
1485
|
+
let i = 0;
|
|
1486
|
+
let position = 0;
|
|
1487
|
+
while (i < query2.length) {
|
|
1488
|
+
const ch = query2[i];
|
|
1489
|
+
if (ch === "'" || ch === '"') {
|
|
1490
|
+
const quote = ch;
|
|
1491
|
+
i++;
|
|
1492
|
+
while (i < query2.length && query2[i] !== quote) {
|
|
1493
|
+
if (query2[i] === "\\") {
|
|
1494
|
+
i++;
|
|
1495
|
+
}
|
|
1496
|
+
i++;
|
|
1497
|
+
}
|
|
1498
|
+
i++;
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (ch === "/" && i + 1 < query2.length && query2[i + 1] === "/") {
|
|
1502
|
+
while (i < query2.length && query2[i] !== "\n") {
|
|
1503
|
+
i++;
|
|
1504
|
+
}
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if (ch === "/" && i + 1 < query2.length && query2[i + 1] === "*") {
|
|
1508
|
+
i += 2;
|
|
1509
|
+
while (i + 1 < query2.length && !(query2[i] === "*" && query2[i + 1] === "/")) {
|
|
1510
|
+
i++;
|
|
1511
|
+
}
|
|
1512
|
+
i += 2;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (ch === "?") {
|
|
1516
|
+
position++;
|
|
1517
|
+
params.push({
|
|
1518
|
+
name: `p${position}`,
|
|
1519
|
+
position,
|
|
1520
|
+
positional: true
|
|
1521
|
+
});
|
|
1522
|
+
i++;
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
if (ch === "$") {
|
|
1526
|
+
i++;
|
|
1527
|
+
let name = "";
|
|
1528
|
+
while (i < query2.length && /[a-zA-Z0-9_]/.test(query2[i] ?? "")) {
|
|
1529
|
+
name += query2[i];
|
|
1530
|
+
i++;
|
|
1531
|
+
}
|
|
1532
|
+
if (name) {
|
|
1533
|
+
position++;
|
|
1534
|
+
const existing = params.find((p) => p.name === name);
|
|
1535
|
+
if (!existing) {
|
|
1536
|
+
params.push({
|
|
1537
|
+
name,
|
|
1538
|
+
position,
|
|
1539
|
+
positional: false
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
i++;
|
|
1546
|
+
}
|
|
1547
|
+
return params;
|
|
1548
|
+
}
|
|
1549
|
+
function prepare(conn, query2) {
|
|
1550
|
+
return new PreparedStatement(conn, query2);
|
|
1551
|
+
}
|
|
1552
|
+
var PreparedStatement;
|
|
1553
|
+
var init_prepared = __esm({
|
|
1554
|
+
"src/prepared.ts"() {
|
|
1555
|
+
init_errors();
|
|
1556
|
+
init_validate();
|
|
1557
|
+
PreparedStatement = class {
|
|
1558
|
+
_conn;
|
|
1559
|
+
_query;
|
|
1560
|
+
_parameters;
|
|
1561
|
+
_closed = false;
|
|
1562
|
+
/**
|
|
1563
|
+
* Create a new prepared statement.
|
|
1564
|
+
*
|
|
1565
|
+
* @internal Use Connection.prepare() instead.
|
|
1566
|
+
*/
|
|
1567
|
+
constructor(conn, query2) {
|
|
1568
|
+
validateQuery(query2);
|
|
1569
|
+
this._conn = conn;
|
|
1570
|
+
this._query = query2;
|
|
1571
|
+
this._parameters = extractParameters(query2);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Get the query text.
|
|
1575
|
+
*/
|
|
1576
|
+
get query() {
|
|
1577
|
+
return this._query;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Get parameter information.
|
|
1581
|
+
*/
|
|
1582
|
+
get parameters() {
|
|
1583
|
+
return this._parameters;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Get the number of parameters.
|
|
1587
|
+
*/
|
|
1588
|
+
get parameterCount() {
|
|
1589
|
+
return this._parameters.length;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Get positional parameter count (number of ? placeholders).
|
|
1593
|
+
*/
|
|
1594
|
+
get positionalCount() {
|
|
1595
|
+
return this._parameters.filter((p) => p.positional).length;
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Get named parameter names.
|
|
1599
|
+
*/
|
|
1600
|
+
get namedParameters() {
|
|
1601
|
+
return this._parameters.filter((p) => !p.positional).map((p) => p.name);
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Check if the statement is closed.
|
|
1605
|
+
*/
|
|
1606
|
+
get isClosed() {
|
|
1607
|
+
return this._closed;
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Execute the prepared statement and return results.
|
|
1611
|
+
*
|
|
1612
|
+
* @param params - Parameter values (object for named, array for positional)
|
|
1613
|
+
* @param options - Additional query options
|
|
1614
|
+
* @returns Query result
|
|
1615
|
+
*/
|
|
1616
|
+
async execute(params, options) {
|
|
1617
|
+
this.checkClosed();
|
|
1618
|
+
const resolvedParams = this.resolveParams(params);
|
|
1619
|
+
return this._conn.query(this._query, { ...options, params: resolvedParams });
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Execute the prepared statement and return all rows.
|
|
1623
|
+
*
|
|
1624
|
+
* @param params - Parameter values
|
|
1625
|
+
* @param options - Additional query options
|
|
1626
|
+
* @returns Array of row objects
|
|
1627
|
+
*/
|
|
1628
|
+
async executeAll(params, options) {
|
|
1629
|
+
this.checkClosed();
|
|
1630
|
+
const resolvedParams = this.resolveParams(params);
|
|
1631
|
+
return this._conn.queryAll(this._query, { ...options, params: resolvedParams });
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Execute the prepared statement without returning results.
|
|
1635
|
+
*
|
|
1636
|
+
* @param params - Parameter values
|
|
1637
|
+
* @param options - Additional query options
|
|
1638
|
+
*/
|
|
1639
|
+
async exec(params, options) {
|
|
1640
|
+
this.checkClosed();
|
|
1641
|
+
const resolvedParams = this.resolveParams(params);
|
|
1642
|
+
return this._conn.exec(this._query, { ...options, params: resolvedParams });
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Validate parameters without executing.
|
|
1646
|
+
*
|
|
1647
|
+
* @param params - Parameter values to validate
|
|
1648
|
+
* @throws SecurityError if validation fails
|
|
1649
|
+
*/
|
|
1650
|
+
validate(params) {
|
|
1651
|
+
this.checkClosed();
|
|
1652
|
+
this.resolveParams(params);
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Close the prepared statement.
|
|
1656
|
+
*
|
|
1657
|
+
* After closing, the statement cannot be executed.
|
|
1658
|
+
*/
|
|
1659
|
+
close() {
|
|
1660
|
+
this._closed = true;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Resolve and validate parameters.
|
|
1664
|
+
*/
|
|
1665
|
+
resolveParams(params) {
|
|
1666
|
+
if (!params) {
|
|
1667
|
+
if (this._parameters.length > 0) {
|
|
1668
|
+
throw new SecurityError({
|
|
1669
|
+
type: "validation",
|
|
1670
|
+
message: `Expected ${this._parameters.length} parameters, got 0`
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
return {};
|
|
1674
|
+
}
|
|
1675
|
+
if (Array.isArray(params)) {
|
|
1676
|
+
const positionalCount = this.positionalCount;
|
|
1677
|
+
if (params.length !== positionalCount) {
|
|
1678
|
+
throw new SecurityError({
|
|
1679
|
+
type: "validation",
|
|
1680
|
+
message: `Expected ${positionalCount} positional parameters, got ${params.length}`
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
const namedParams2 = {};
|
|
1684
|
+
let posIndex = 0;
|
|
1685
|
+
for (const p of this._parameters) {
|
|
1686
|
+
if (p.positional) {
|
|
1687
|
+
namedParams2[p.name] = params[posIndex];
|
|
1688
|
+
validateParamValue(params[posIndex]);
|
|
1689
|
+
posIndex++;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return namedParams2;
|
|
1693
|
+
}
|
|
1694
|
+
const namedParams = params;
|
|
1695
|
+
for (const p of this._parameters) {
|
|
1696
|
+
if (!p.positional && !(p.name in namedParams)) {
|
|
1697
|
+
throw new SecurityError({
|
|
1698
|
+
type: "validation",
|
|
1699
|
+
message: `Missing required parameter: ${p.name}`
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
for (const [key, value] of Object.entries(namedParams)) {
|
|
1704
|
+
validateParamName(key);
|
|
1705
|
+
validateParamValue(value);
|
|
1706
|
+
}
|
|
1707
|
+
return namedParams;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Check if statement is closed.
|
|
1711
|
+
*/
|
|
1712
|
+
checkClosed() {
|
|
1713
|
+
if (this._closed) {
|
|
1714
|
+
throw new StateError({
|
|
1715
|
+
currentState: "closed",
|
|
1716
|
+
operation: "execute",
|
|
1717
|
+
message: "PreparedStatement is closed"
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
// src/explain.ts
|
|
1726
|
+
var explain_exports = {};
|
|
1727
|
+
__export(explain_exports, {
|
|
1728
|
+
explain: () => explain,
|
|
1729
|
+
formatPlan: () => formatPlan,
|
|
1730
|
+
formatProfile: () => formatProfile,
|
|
1731
|
+
profile: () => profile
|
|
1732
|
+
});
|
|
1733
|
+
function parsePlanOperation(data) {
|
|
1734
|
+
if (!data || typeof data !== "object") {
|
|
1735
|
+
return { type: "Unknown", details: { raw: data } };
|
|
1736
|
+
}
|
|
1737
|
+
const obj = data;
|
|
1738
|
+
const op = {
|
|
1739
|
+
type: obj["type"] || obj["operator"] || "Unknown"
|
|
1740
|
+
};
|
|
1741
|
+
if ("operator" in obj) op.operator = obj["operator"];
|
|
1742
|
+
if ("cost" in obj) op.cost = obj["cost"];
|
|
1743
|
+
if ("rows" in obj) op.rows = obj["rows"];
|
|
1744
|
+
if ("estimated_rows" in obj) op.rows = obj["estimated_rows"];
|
|
1745
|
+
if ("estimated_cost" in obj) op.cost = obj["estimated_cost"];
|
|
1746
|
+
const details = {};
|
|
1747
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1748
|
+
if (![
|
|
1749
|
+
"type",
|
|
1750
|
+
"operator",
|
|
1751
|
+
"cost",
|
|
1752
|
+
"rows",
|
|
1753
|
+
"children",
|
|
1754
|
+
"estimated_rows",
|
|
1755
|
+
"estimated_cost"
|
|
1756
|
+
].includes(key)) {
|
|
1757
|
+
details[key] = value;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
if (Object.keys(details).length > 0) {
|
|
1761
|
+
op.details = details;
|
|
1762
|
+
}
|
|
1763
|
+
if (Array.isArray(obj["children"])) {
|
|
1764
|
+
op.children = obj["children"].map(parsePlanOperation);
|
|
1765
|
+
}
|
|
1766
|
+
return op;
|
|
1767
|
+
}
|
|
1768
|
+
function parseOperationTiming(data) {
|
|
1769
|
+
if (!data || typeof data !== "object") {
|
|
1770
|
+
return { type: "Unknown", wallTimeMs: 0 };
|
|
1771
|
+
}
|
|
1772
|
+
const obj = data;
|
|
1773
|
+
const timing = {
|
|
1774
|
+
type: obj["type"] || obj["operator"] || "Unknown",
|
|
1775
|
+
wallTimeMs: obj["wall_time_ms"] || obj["time_ms"] || 0
|
|
1776
|
+
};
|
|
1777
|
+
if ("cpu_time_ms" in obj) timing.cpuTimeMs = obj["cpu_time_ms"];
|
|
1778
|
+
if ("rows" in obj) timing.rows = obj["rows"];
|
|
1779
|
+
if ("actual_rows" in obj) timing.rows = obj["actual_rows"];
|
|
1780
|
+
if ("page_hits" in obj) timing.pageHits = obj["page_hits"];
|
|
1781
|
+
if ("page_faults" in obj) timing.pageFaults = obj["page_faults"];
|
|
1782
|
+
if (Array.isArray(obj["children"])) {
|
|
1783
|
+
timing.children = obj["children"].map(parseOperationTiming);
|
|
1784
|
+
}
|
|
1785
|
+
return timing;
|
|
1786
|
+
}
|
|
1787
|
+
async function explain(conn, query2, options) {
|
|
1788
|
+
validateQuery(query2);
|
|
1789
|
+
let finalQuery = query2;
|
|
1790
|
+
let params = options?.params ?? {};
|
|
1791
|
+
if (query2.includes("?") && Array.isArray(options?.params)) {
|
|
1792
|
+
const result = rewritePlaceholders(query2, options.params);
|
|
1793
|
+
finalQuery = result.query;
|
|
1794
|
+
params = result.params;
|
|
1795
|
+
}
|
|
1796
|
+
for (const [_key, value] of Object.entries(params)) {
|
|
1797
|
+
validateParamValue(value);
|
|
1798
|
+
}
|
|
1799
|
+
const explainQuery = options?.verbose ? `EXPLAIN VERBOSE ${finalQuery}` : `EXPLAIN ${finalQuery}`;
|
|
1800
|
+
const transport = conn._transport;
|
|
1801
|
+
await transport.send(buildRunGQLMessage(explainQuery, params), options?.signal);
|
|
1802
|
+
const data = await transport.receive(options?.signal);
|
|
1803
|
+
const frame = parseFrame(data);
|
|
1804
|
+
if (isErrorFrame(frame)) {
|
|
1805
|
+
throw frameToError(frame);
|
|
1806
|
+
}
|
|
1807
|
+
if (frame.result.type === RespType.EXPLAIN) {
|
|
1808
|
+
const raw = frame.result.json;
|
|
1809
|
+
const root = parsePlanOperation(raw);
|
|
1810
|
+
return {
|
|
1811
|
+
root,
|
|
1812
|
+
totalCost: calculateTotalCost(root),
|
|
1813
|
+
totalRows: root.rows,
|
|
1814
|
+
raw
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
return {
|
|
1818
|
+
root: { type: "Unknown", details: { response: frame.result } },
|
|
1819
|
+
raw: frame.result
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
async function profile(conn, query2, options) {
|
|
1823
|
+
validateQuery(query2);
|
|
1824
|
+
let finalQuery = query2;
|
|
1825
|
+
let params = options?.params ?? {};
|
|
1826
|
+
if (query2.includes("?") && Array.isArray(options?.params)) {
|
|
1827
|
+
const result = rewritePlaceholders(query2, options.params);
|
|
1828
|
+
finalQuery = result.query;
|
|
1829
|
+
params = result.params;
|
|
1830
|
+
}
|
|
1831
|
+
for (const [_key, value] of Object.entries(params)) {
|
|
1832
|
+
validateParamValue(value);
|
|
1833
|
+
}
|
|
1834
|
+
const profileQuery = options?.verbose ? `PROFILE VERBOSE ${finalQuery}` : `PROFILE ${finalQuery}`;
|
|
1835
|
+
const transport = conn._transport;
|
|
1836
|
+
await transport.send(buildRunGQLMessage(profileQuery, params), options?.signal);
|
|
1837
|
+
const data = await transport.receive(options?.signal);
|
|
1838
|
+
const frame = parseFrame(data);
|
|
1839
|
+
if (isErrorFrame(frame)) {
|
|
1840
|
+
throw frameToError(frame);
|
|
1841
|
+
}
|
|
1842
|
+
if (frame.result.type === RespType.PROFILE) {
|
|
1843
|
+
const raw = frame.result.json;
|
|
1844
|
+
const root = parseOperationTiming(raw);
|
|
1845
|
+
return {
|
|
1846
|
+
root,
|
|
1847
|
+
totalTimeMs: calculateTotalTime(root),
|
|
1848
|
+
totalRows: root.rows,
|
|
1849
|
+
planningTimeMs: raw?.["planning_time_ms"],
|
|
1850
|
+
executionTimeMs: raw?.["execution_time_ms"],
|
|
1851
|
+
memoryBytes: raw?.["memory_bytes"],
|
|
1852
|
+
raw
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
return {
|
|
1856
|
+
root: { type: "Unknown", wallTimeMs: 0 },
|
|
1857
|
+
totalTimeMs: 0,
|
|
1858
|
+
raw: frame.result
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
function calculateTotalCost(op) {
|
|
1862
|
+
let total = op.cost ?? 0;
|
|
1863
|
+
if (op.children) {
|
|
1864
|
+
for (const child of op.children) {
|
|
1865
|
+
total += calculateTotalCost(child);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return total;
|
|
1869
|
+
}
|
|
1870
|
+
function calculateTotalTime(timing) {
|
|
1871
|
+
let total = timing.wallTimeMs;
|
|
1872
|
+
if (timing.children) {
|
|
1873
|
+
for (const child of timing.children) {
|
|
1874
|
+
total = Math.max(total, calculateTotalTime(child));
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return total;
|
|
1878
|
+
}
|
|
1879
|
+
function formatPlan(plan, indent = 0) {
|
|
1880
|
+
const lines = [];
|
|
1881
|
+
function formatOp(op, depth) {
|
|
1882
|
+
const prefix = " ".repeat(depth);
|
|
1883
|
+
let line = `${prefix}${op.type}`;
|
|
1884
|
+
if (op.operator && op.operator !== op.type) {
|
|
1885
|
+
line += ` (${op.operator})`;
|
|
1886
|
+
}
|
|
1887
|
+
if (op.cost !== void 0) {
|
|
1888
|
+
line += ` cost=${op.cost.toFixed(2)}`;
|
|
1889
|
+
}
|
|
1890
|
+
if (op.rows !== void 0) {
|
|
1891
|
+
line += ` rows=${op.rows}`;
|
|
1892
|
+
}
|
|
1893
|
+
lines.push(line);
|
|
1894
|
+
if (op.details && Object.keys(op.details).length > 0) {
|
|
1895
|
+
for (const [key, value] of Object.entries(op.details)) {
|
|
1896
|
+
lines.push(`${prefix} ${key}: ${JSON.stringify(value)}`);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (op.children) {
|
|
1900
|
+
for (const child of op.children) {
|
|
1901
|
+
formatOp(child, depth + 1);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
formatOp(plan.root, indent);
|
|
1906
|
+
if (plan.totalCost !== void 0) {
|
|
1907
|
+
lines.push(`Total Cost: ${plan.totalCost.toFixed(2)}`);
|
|
1908
|
+
}
|
|
1909
|
+
if (plan.totalRows !== void 0) {
|
|
1910
|
+
lines.push(`Estimated Rows: ${plan.totalRows}`);
|
|
1911
|
+
}
|
|
1912
|
+
return lines.join("\n");
|
|
1913
|
+
}
|
|
1914
|
+
function formatProfile(prof, indent = 0) {
|
|
1915
|
+
const lines = [];
|
|
1916
|
+
function formatTiming(timing, depth) {
|
|
1917
|
+
const prefix = " ".repeat(depth);
|
|
1918
|
+
let line = `${prefix}${timing.type}`;
|
|
1919
|
+
line += ` ${timing.wallTimeMs.toFixed(2)}ms`;
|
|
1920
|
+
if (timing.rows !== void 0) {
|
|
1921
|
+
line += ` ${timing.rows} rows`;
|
|
1922
|
+
}
|
|
1923
|
+
if (timing.pageHits !== void 0 || timing.pageFaults !== void 0) {
|
|
1924
|
+
line += ` (hits=${timing.pageHits ?? 0}, faults=${timing.pageFaults ?? 0})`;
|
|
1925
|
+
}
|
|
1926
|
+
lines.push(line);
|
|
1927
|
+
if (timing.children) {
|
|
1928
|
+
for (const child of timing.children) {
|
|
1929
|
+
formatTiming(child, depth + 1);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
formatTiming(prof.root, indent);
|
|
1934
|
+
lines.push("---");
|
|
1935
|
+
lines.push(`Total Time: ${prof.totalTimeMs.toFixed(2)}ms`);
|
|
1936
|
+
if (prof.planningTimeMs !== void 0) {
|
|
1937
|
+
lines.push(`Planning: ${prof.planningTimeMs.toFixed(2)}ms`);
|
|
1938
|
+
}
|
|
1939
|
+
if (prof.executionTimeMs !== void 0) {
|
|
1940
|
+
lines.push(`Execution: ${prof.executionTimeMs.toFixed(2)}ms`);
|
|
1941
|
+
}
|
|
1942
|
+
if (prof.totalRows !== void 0) {
|
|
1943
|
+
lines.push(`Rows: ${prof.totalRows}`);
|
|
1944
|
+
}
|
|
1945
|
+
if (prof.memoryBytes !== void 0) {
|
|
1946
|
+
lines.push(`Memory: ${formatBytes(prof.memoryBytes)}`);
|
|
1947
|
+
}
|
|
1948
|
+
return lines.join("\n");
|
|
1949
|
+
}
|
|
1950
|
+
function formatBytes(bytes) {
|
|
1951
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
1952
|
+
let value = bytes;
|
|
1953
|
+
let unitIndex = 0;
|
|
1954
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
1955
|
+
value /= 1024;
|
|
1956
|
+
unitIndex++;
|
|
1957
|
+
}
|
|
1958
|
+
return `${value.toFixed(2)} ${units[unitIndex]}`;
|
|
1959
|
+
}
|
|
1960
|
+
var init_explain = __esm({
|
|
1961
|
+
"src/explain.ts"() {
|
|
1962
|
+
init_validate();
|
|
1963
|
+
init_protocol();
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
|
|
1967
|
+
// src/batch.ts
|
|
1968
|
+
var batch_exports = {};
|
|
1969
|
+
__export(batch_exports, {
|
|
1970
|
+
batch: () => batch,
|
|
1971
|
+
batchAll: () => batchAll,
|
|
1972
|
+
batchFirst: () => batchFirst,
|
|
1973
|
+
batchMap: () => batchMap,
|
|
1974
|
+
batchParallel: () => batchParallel
|
|
1975
|
+
});
|
|
1976
|
+
async function batch(conn, queries, options) {
|
|
1977
|
+
const results = [];
|
|
1978
|
+
const startTime = Date.now();
|
|
1979
|
+
let successful = 0;
|
|
1980
|
+
let failed = 0;
|
|
1981
|
+
const stopOnError = options?.stopOnError ?? false;
|
|
1982
|
+
const inTransaction = options?.transaction ?? false;
|
|
1983
|
+
let tx = null;
|
|
1984
|
+
if (inTransaction) {
|
|
1985
|
+
tx = await conn.begin(options?.signal);
|
|
1986
|
+
}
|
|
1987
|
+
try {
|
|
1988
|
+
for (let i = 0; i < queries.length; i++) {
|
|
1989
|
+
if (options?.signal?.aborted) {
|
|
1990
|
+
for (let j = i; j < queries.length; j++) {
|
|
1991
|
+
const q2 = queries[j];
|
|
1992
|
+
results.push({
|
|
1993
|
+
index: j,
|
|
1994
|
+
query: q2?.query ?? "",
|
|
1995
|
+
success: false,
|
|
1996
|
+
error: new Error("Batch execution cancelled")
|
|
1997
|
+
});
|
|
1998
|
+
failed++;
|
|
1999
|
+
}
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
const q = queries[i];
|
|
2003
|
+
if (!q) continue;
|
|
2004
|
+
const queryStart = Date.now();
|
|
2005
|
+
try {
|
|
2006
|
+
const queryResult = await conn.query(q.query, {
|
|
2007
|
+
params: q.params,
|
|
2008
|
+
signal: options?.signal,
|
|
2009
|
+
pageSize: options?.pageSize
|
|
2010
|
+
});
|
|
2011
|
+
const rows = [];
|
|
2012
|
+
for await (const row of queryResult) {
|
|
2013
|
+
rows.push(rowToObject(row));
|
|
2014
|
+
}
|
|
2015
|
+
results.push({
|
|
2016
|
+
index: i,
|
|
2017
|
+
query: q.query,
|
|
2018
|
+
success: true,
|
|
2019
|
+
rows,
|
|
2020
|
+
durationMs: Date.now() - queryStart
|
|
2021
|
+
});
|
|
2022
|
+
successful++;
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
results.push({
|
|
2025
|
+
index: i,
|
|
2026
|
+
query: q.query,
|
|
2027
|
+
success: false,
|
|
2028
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
2029
|
+
durationMs: Date.now() - queryStart
|
|
2030
|
+
});
|
|
2031
|
+
failed++;
|
|
2032
|
+
if (stopOnError) {
|
|
2033
|
+
for (let j = i + 1; j < queries.length; j++) {
|
|
2034
|
+
const skipped = queries[j];
|
|
2035
|
+
results.push({
|
|
2036
|
+
index: j,
|
|
2037
|
+
query: skipped?.query ?? "",
|
|
2038
|
+
success: false,
|
|
2039
|
+
error: new Error("Skipped due to previous error")
|
|
2040
|
+
});
|
|
2041
|
+
failed++;
|
|
2042
|
+
}
|
|
2043
|
+
break;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (tx && failed === 0) {
|
|
2048
|
+
await tx.commit();
|
|
2049
|
+
} else if (tx) {
|
|
2050
|
+
await tx.rollback();
|
|
2051
|
+
}
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
if (tx) {
|
|
2054
|
+
try {
|
|
2055
|
+
await tx.rollback();
|
|
2056
|
+
} catch {
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
throw error;
|
|
2060
|
+
}
|
|
2061
|
+
return {
|
|
2062
|
+
total: queries.length,
|
|
2063
|
+
successful,
|
|
2064
|
+
failed,
|
|
2065
|
+
totalDurationMs: Date.now() - startTime,
|
|
2066
|
+
results
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
async function batchAll(conn, queries, options) {
|
|
2070
|
+
const summary = await batch(conn, queries, options);
|
|
2071
|
+
return summary.results.filter(
|
|
2072
|
+
(r) => r.success && r.rows !== void 0
|
|
2073
|
+
).map((r) => r.rows);
|
|
2074
|
+
}
|
|
2075
|
+
async function batchFirst(conn, queries, options) {
|
|
2076
|
+
for (const q of queries) {
|
|
2077
|
+
try {
|
|
2078
|
+
const rows = await conn.queryAll(q.query, {
|
|
2079
|
+
params: q.params,
|
|
2080
|
+
signal: options?.signal,
|
|
2081
|
+
pageSize: options?.pageSize
|
|
2082
|
+
});
|
|
2083
|
+
return rows;
|
|
2084
|
+
} catch {
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
async function batchMap(conn, queryTemplate, items, options) {
|
|
2091
|
+
const queries = items.map((params) => ({
|
|
2092
|
+
query: queryTemplate,
|
|
2093
|
+
params
|
|
2094
|
+
}));
|
|
2095
|
+
return batch(conn, queries, options);
|
|
2096
|
+
}
|
|
2097
|
+
async function batchParallel(conn, queries, options) {
|
|
2098
|
+
const concurrency = options?.concurrency ?? 5;
|
|
2099
|
+
const results = [];
|
|
2100
|
+
const startTime = Date.now();
|
|
2101
|
+
let successful = 0;
|
|
2102
|
+
let failed = 0;
|
|
2103
|
+
async function executeQuery(index) {
|
|
2104
|
+
const q = queries[index];
|
|
2105
|
+
if (!q) return;
|
|
2106
|
+
const queryStart = Date.now();
|
|
2107
|
+
try {
|
|
2108
|
+
const queryResult = await conn.query(q.query, {
|
|
2109
|
+
params: q.params,
|
|
2110
|
+
signal: options?.signal,
|
|
2111
|
+
pageSize: options?.pageSize
|
|
2112
|
+
});
|
|
2113
|
+
const rows = [];
|
|
2114
|
+
for await (const row of queryResult) {
|
|
2115
|
+
rows.push(rowToObject(row));
|
|
2116
|
+
}
|
|
2117
|
+
results.push({
|
|
2118
|
+
index,
|
|
2119
|
+
query: q.query,
|
|
2120
|
+
success: true,
|
|
2121
|
+
rows,
|
|
2122
|
+
durationMs: Date.now() - queryStart
|
|
2123
|
+
});
|
|
2124
|
+
successful++;
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
results.push({
|
|
2127
|
+
index,
|
|
2128
|
+
query: q.query,
|
|
2129
|
+
success: false,
|
|
2130
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
2131
|
+
durationMs: Date.now() - queryStart
|
|
2132
|
+
});
|
|
2133
|
+
failed++;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
const executing = /* @__PURE__ */ new Set();
|
|
2137
|
+
for (let i = 0; i < queries.length; i++) {
|
|
2138
|
+
if (options?.signal?.aborted) {
|
|
2139
|
+
break;
|
|
2140
|
+
}
|
|
2141
|
+
while (executing.size >= concurrency) {
|
|
2142
|
+
await Promise.race(executing);
|
|
2143
|
+
}
|
|
2144
|
+
const promise = executeQuery(i).finally(() => {
|
|
2145
|
+
executing.delete(promise);
|
|
2146
|
+
});
|
|
2147
|
+
executing.add(promise);
|
|
2148
|
+
}
|
|
2149
|
+
await Promise.all(executing);
|
|
2150
|
+
results.sort((a, b) => a.index - b.index);
|
|
2151
|
+
return {
|
|
2152
|
+
total: queries.length,
|
|
2153
|
+
successful,
|
|
2154
|
+
failed,
|
|
2155
|
+
totalDurationMs: Date.now() - startTime,
|
|
2156
|
+
results
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
var init_batch = __esm({
|
|
2160
|
+
"src/batch.ts"() {
|
|
2161
|
+
init_types();
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
// src/auth.ts
|
|
2166
|
+
var auth_exports = {};
|
|
2167
|
+
__export(auth_exports, {
|
|
2168
|
+
AuthClient: () => AuthClient,
|
|
2169
|
+
createAuthClient: () => createAuthClient
|
|
2170
|
+
});
|
|
2171
|
+
function createAuthClient(conn) {
|
|
2172
|
+
return new AuthClient(conn);
|
|
2173
|
+
}
|
|
2174
|
+
var AuthClient;
|
|
2175
|
+
var init_auth = __esm({
|
|
2176
|
+
"src/auth.ts"() {
|
|
2177
|
+
init_errors();
|
|
2178
|
+
init_validate();
|
|
2179
|
+
AuthClient = class {
|
|
2180
|
+
_conn;
|
|
2181
|
+
/**
|
|
2182
|
+
* Create a new auth client.
|
|
2183
|
+
*
|
|
2184
|
+
* @param conn - Connection to use
|
|
2185
|
+
*/
|
|
2186
|
+
constructor(conn) {
|
|
2187
|
+
this._conn = conn;
|
|
2188
|
+
}
|
|
2189
|
+
// User Management
|
|
2190
|
+
/**
|
|
2191
|
+
* Create a new user.
|
|
2192
|
+
*
|
|
2193
|
+
* @param username - Username for the new user
|
|
2194
|
+
* @param options - User creation options
|
|
2195
|
+
*/
|
|
2196
|
+
async createUser(username, options) {
|
|
2197
|
+
this.validateUsername(username);
|
|
2198
|
+
this.validatePassword(options.password);
|
|
2199
|
+
const roles = options.roles?.join(", ") ?? "";
|
|
2200
|
+
const active = options.active !== false ? "true" : "false";
|
|
2201
|
+
const query2 = `
|
|
2202
|
+
CREATE USER $username
|
|
2203
|
+
SET password = $password
|
|
2204
|
+
${roles ? `, roles = [${roles}]` : ""}
|
|
2205
|
+
, active = ${active}
|
|
2206
|
+
`.trim();
|
|
2207
|
+
await this._conn.exec(query2, {
|
|
2208
|
+
params: {
|
|
2209
|
+
username,
|
|
2210
|
+
password: options.password
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Delete a user.
|
|
2216
|
+
*
|
|
2217
|
+
* @param username - Username to delete
|
|
2218
|
+
*/
|
|
2219
|
+
async deleteUser(username) {
|
|
2220
|
+
this.validateUsername(username);
|
|
2221
|
+
await this._conn.exec("DROP USER $username", {
|
|
2222
|
+
params: { username }
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Get user information.
|
|
2227
|
+
*
|
|
2228
|
+
* @param username - Username to look up
|
|
2229
|
+
* @returns User information or null if not found
|
|
2230
|
+
*/
|
|
2231
|
+
async getUser(username) {
|
|
2232
|
+
this.validateUsername(username);
|
|
2233
|
+
const result = await this._conn.queryAll("SHOW USER $username", { params: { username } });
|
|
2234
|
+
if (result.length === 0) {
|
|
2235
|
+
return null;
|
|
2236
|
+
}
|
|
2237
|
+
const row = result[0];
|
|
2238
|
+
if (!row) {
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
return {
|
|
2242
|
+
username: row["username"],
|
|
2243
|
+
roles: row["roles"] ?? [],
|
|
2244
|
+
active: row["active"] ?? true,
|
|
2245
|
+
createdAt: row["created_at"] ? new Date(row["created_at"]) : void 0,
|
|
2246
|
+
lastLoginAt: row["last_login"] ? new Date(row["last_login"]) : void 0,
|
|
2247
|
+
metadata: row["metadata"]
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* List all users.
|
|
2252
|
+
*
|
|
2253
|
+
* @returns Array of users
|
|
2254
|
+
*/
|
|
2255
|
+
async listUsers() {
|
|
2256
|
+
const result = await this._conn.queryAll("SHOW USERS");
|
|
2257
|
+
return result.map((row) => ({
|
|
2258
|
+
username: row["username"],
|
|
2259
|
+
roles: row["roles"] ?? [],
|
|
2260
|
+
active: row["active"] ?? true,
|
|
2261
|
+
createdAt: row["created_at"] ? new Date(row["created_at"]) : void 0,
|
|
2262
|
+
lastLoginAt: row["last_login"] ? new Date(row["last_login"]) : void 0
|
|
2263
|
+
}));
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Change a user's password.
|
|
2267
|
+
*
|
|
2268
|
+
* @param username - Username
|
|
2269
|
+
* @param newPassword - New password
|
|
2270
|
+
*/
|
|
2271
|
+
async changePassword(username, newPassword) {
|
|
2272
|
+
this.validateUsername(username);
|
|
2273
|
+
this.validatePassword(newPassword);
|
|
2274
|
+
await this._conn.exec("ALTER USER $username SET password = $password", {
|
|
2275
|
+
params: { username, password: newPassword }
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Activate a user.
|
|
2280
|
+
*
|
|
2281
|
+
* @param username - Username to activate
|
|
2282
|
+
*/
|
|
2283
|
+
async activateUser(username) {
|
|
2284
|
+
this.validateUsername(username);
|
|
2285
|
+
await this._conn.exec("ALTER USER $username SET active = true", {
|
|
2286
|
+
params: { username }
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Deactivate a user.
|
|
2291
|
+
*
|
|
2292
|
+
* @param username - Username to deactivate
|
|
2293
|
+
*/
|
|
2294
|
+
async deactivateUser(username) {
|
|
2295
|
+
this.validateUsername(username);
|
|
2296
|
+
await this._conn.exec("ALTER USER $username SET active = false", {
|
|
2297
|
+
params: { username }
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
// Role Management
|
|
2301
|
+
/**
|
|
2302
|
+
* Create a new role.
|
|
2303
|
+
*
|
|
2304
|
+
* @param name - Role name
|
|
2305
|
+
* @param options - Role creation options
|
|
2306
|
+
*/
|
|
2307
|
+
async createRole(name, options) {
|
|
2308
|
+
this.validateRoleName(name);
|
|
2309
|
+
let query2 = "CREATE ROLE $name";
|
|
2310
|
+
if (options?.description) {
|
|
2311
|
+
query2 += " SET description = $description";
|
|
2312
|
+
}
|
|
2313
|
+
await this._conn.exec(query2, {
|
|
2314
|
+
params: {
|
|
2315
|
+
name,
|
|
2316
|
+
description: options?.description
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
if (options?.permissions) {
|
|
2320
|
+
for (const perm of options.permissions) {
|
|
2321
|
+
await this.grantPermission(name, perm);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Delete a role.
|
|
2327
|
+
*
|
|
2328
|
+
* @param name - Role name to delete
|
|
2329
|
+
*/
|
|
2330
|
+
async deleteRole(name) {
|
|
2331
|
+
this.validateRoleName(name);
|
|
2332
|
+
await this._conn.exec("DROP ROLE $name", {
|
|
2333
|
+
params: { name }
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Get role information.
|
|
2338
|
+
*
|
|
2339
|
+
* @param name - Role name
|
|
2340
|
+
* @returns Role information or null if not found
|
|
2341
|
+
*/
|
|
2342
|
+
async getRole(name) {
|
|
2343
|
+
this.validateRoleName(name);
|
|
2344
|
+
const result = await this._conn.queryAll("SHOW ROLE $name", { params: { name } });
|
|
2345
|
+
const row = result[0];
|
|
2346
|
+
if (!row) {
|
|
2347
|
+
return null;
|
|
2348
|
+
}
|
|
2349
|
+
return {
|
|
2350
|
+
name: row["name"],
|
|
2351
|
+
description: row["description"],
|
|
2352
|
+
permissions: row["permissions"] ?? [],
|
|
2353
|
+
system: row["system"]
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* List all roles.
|
|
2358
|
+
*
|
|
2359
|
+
* @returns Array of roles
|
|
2360
|
+
*/
|
|
2361
|
+
async listRoles() {
|
|
2362
|
+
const result = await this._conn.queryAll("SHOW ROLES");
|
|
2363
|
+
return result.map((row) => ({
|
|
2364
|
+
name: row["name"],
|
|
2365
|
+
description: row["description"],
|
|
2366
|
+
permissions: row["permissions"] ?? [],
|
|
2367
|
+
system: row["system"]
|
|
2368
|
+
}));
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Assign a role to a user.
|
|
2372
|
+
*
|
|
2373
|
+
* @param username - Username
|
|
2374
|
+
* @param roleName - Role to assign
|
|
2375
|
+
*/
|
|
2376
|
+
async assignRole(username, roleName) {
|
|
2377
|
+
this.validateUsername(username);
|
|
2378
|
+
this.validateRoleName(roleName);
|
|
2379
|
+
await this._conn.exec("GRANT ROLE $role TO $user", {
|
|
2380
|
+
params: { role: roleName, user: username }
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Revoke a role from a user.
|
|
2385
|
+
*
|
|
2386
|
+
* @param username - Username
|
|
2387
|
+
* @param roleName - Role to revoke
|
|
2388
|
+
*/
|
|
2389
|
+
async revokeRole(username, roleName) {
|
|
2390
|
+
this.validateUsername(username);
|
|
2391
|
+
this.validateRoleName(roleName);
|
|
2392
|
+
await this._conn.exec("REVOKE ROLE $role FROM $user", {
|
|
2393
|
+
params: { role: roleName, user: username }
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Grant a permission to a role.
|
|
2398
|
+
*
|
|
2399
|
+
* @param roleName - Role name
|
|
2400
|
+
* @param permission - Permission to grant
|
|
2401
|
+
*/
|
|
2402
|
+
async grantPermission(roleName, permission) {
|
|
2403
|
+
this.validateRoleName(roleName);
|
|
2404
|
+
let query2 = `GRANT ${permission.action} ON ${permission.resource}`;
|
|
2405
|
+
if (permission.label) {
|
|
2406
|
+
query2 += `:${permission.label}`;
|
|
2407
|
+
}
|
|
2408
|
+
query2 += " TO $role";
|
|
2409
|
+
await this._conn.exec(query2, {
|
|
2410
|
+
params: { role: roleName }
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Revoke a permission from a role.
|
|
2415
|
+
*
|
|
2416
|
+
* @param roleName - Role name
|
|
2417
|
+
* @param permission - Permission to revoke
|
|
2418
|
+
*/
|
|
2419
|
+
async revokePermission(roleName, permission) {
|
|
2420
|
+
this.validateRoleName(roleName);
|
|
2421
|
+
let query2 = `REVOKE ${permission.action} ON ${permission.resource}`;
|
|
2422
|
+
if (permission.label) {
|
|
2423
|
+
query2 += `:${permission.label}`;
|
|
2424
|
+
}
|
|
2425
|
+
query2 += " FROM $role";
|
|
2426
|
+
await this._conn.exec(query2, {
|
|
2427
|
+
params: { role: roleName }
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
// Row-Level Security
|
|
2431
|
+
/**
|
|
2432
|
+
* Create an RLS policy.
|
|
2433
|
+
*
|
|
2434
|
+
* @param name - Policy name
|
|
2435
|
+
* @param label - Target label
|
|
2436
|
+
* @param action - Policy action
|
|
2437
|
+
* @param predicate - GQL predicate expression
|
|
2438
|
+
* @param options - Additional options
|
|
2439
|
+
*/
|
|
2440
|
+
async createRLSPolicy(name, label, action, predicate2, options) {
|
|
2441
|
+
this.validatePolicyName(name);
|
|
2442
|
+
validateQuery(predicate2);
|
|
2443
|
+
const enabled = options?.enabled !== false ? "ENABLE" : "DISABLE";
|
|
2444
|
+
const roles = options?.roles?.length ? `FOR ROLES (${options.roles.join(", ")})` : "";
|
|
2445
|
+
const query2 = `
|
|
2446
|
+
CREATE POLICY $name ON :${label}
|
|
2447
|
+
FOR ${action}
|
|
2448
|
+
${roles}
|
|
2449
|
+
USING (${predicate2})
|
|
2450
|
+
${enabled}
|
|
2451
|
+
`.trim();
|
|
2452
|
+
await this._conn.exec(query2, {
|
|
2453
|
+
params: { name }
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Delete an RLS policy.
|
|
2458
|
+
*
|
|
2459
|
+
* @param name - Policy name
|
|
2460
|
+
* @param label - Target label
|
|
2461
|
+
*/
|
|
2462
|
+
async deleteRLSPolicy(name, label) {
|
|
2463
|
+
this.validatePolicyName(name);
|
|
2464
|
+
await this._conn.exec(`DROP POLICY $name ON :${label}`, {
|
|
2465
|
+
params: { name }
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Get RLS policy information.
|
|
2470
|
+
*
|
|
2471
|
+
* @param name - Policy name
|
|
2472
|
+
* @returns Policy information or null if not found
|
|
2473
|
+
*/
|
|
2474
|
+
async getRLSPolicy(name) {
|
|
2475
|
+
this.validatePolicyName(name);
|
|
2476
|
+
const result = await this._conn.queryAll("SHOW POLICY $name", { params: { name } });
|
|
2477
|
+
const row = result[0];
|
|
2478
|
+
if (!row) {
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
return {
|
|
2482
|
+
name: row["name"],
|
|
2483
|
+
label: row["label"],
|
|
2484
|
+
action: row["action"],
|
|
2485
|
+
predicate: row["predicate"],
|
|
2486
|
+
roles: row["roles"],
|
|
2487
|
+
enabled: row["enabled"] ?? true
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* List all RLS policies.
|
|
2492
|
+
*
|
|
2493
|
+
* @param label - Optional label to filter by
|
|
2494
|
+
* @returns Array of policies
|
|
2495
|
+
*/
|
|
2496
|
+
async listRLSPolicies(label) {
|
|
2497
|
+
const query2 = label ? `SHOW POLICIES ON :${label}` : "SHOW POLICIES";
|
|
2498
|
+
const result = await this._conn.queryAll(query2);
|
|
2499
|
+
return result.map((row) => ({
|
|
2500
|
+
name: row["name"],
|
|
2501
|
+
label: row["label"],
|
|
2502
|
+
action: row["action"],
|
|
2503
|
+
predicate: row["predicate"],
|
|
2504
|
+
roles: row["roles"],
|
|
2505
|
+
enabled: row["enabled"] ?? true
|
|
2506
|
+
}));
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Enable an RLS policy.
|
|
2510
|
+
*
|
|
2511
|
+
* @param name - Policy name
|
|
2512
|
+
* @param label - Target label
|
|
2513
|
+
*/
|
|
2514
|
+
async enableRLSPolicy(name, label) {
|
|
2515
|
+
this.validatePolicyName(name);
|
|
2516
|
+
await this._conn.exec(`ALTER POLICY $name ON :${label} ENABLE`, {
|
|
2517
|
+
params: { name }
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Disable an RLS policy.
|
|
2522
|
+
*
|
|
2523
|
+
* @param name - Policy name
|
|
2524
|
+
* @param label - Target label
|
|
2525
|
+
*/
|
|
2526
|
+
async disableRLSPolicy(name, label) {
|
|
2527
|
+
this.validatePolicyName(name);
|
|
2528
|
+
await this._conn.exec(`ALTER POLICY $name ON :${label} DISABLE`, {
|
|
2529
|
+
params: { name }
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
// Session Management
|
|
2533
|
+
/**
|
|
2534
|
+
* Get the current session user.
|
|
2535
|
+
*
|
|
2536
|
+
* @returns Current username
|
|
2537
|
+
*/
|
|
2538
|
+
async currentUser() {
|
|
2539
|
+
const result = await this._conn.queryAll("RETURN current_user() AS user");
|
|
2540
|
+
return result[0]?.["user"] ?? "";
|
|
2541
|
+
}
|
|
2542
|
+
/**
|
|
2543
|
+
* Get the current session roles.
|
|
2544
|
+
*
|
|
2545
|
+
* @returns Current roles
|
|
2546
|
+
*/
|
|
2547
|
+
async currentRoles() {
|
|
2548
|
+
const result = await this._conn.queryAll("RETURN current_roles() AS roles");
|
|
2549
|
+
return result[0]?.["roles"] ?? [];
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Check if the current session has a specific permission.
|
|
2553
|
+
*
|
|
2554
|
+
* @param resource - Resource type
|
|
2555
|
+
* @param action - Action to check
|
|
2556
|
+
* @param label - Optional label filter
|
|
2557
|
+
* @returns Whether permission is granted
|
|
2558
|
+
*/
|
|
2559
|
+
async hasPermission(resource, action, label) {
|
|
2560
|
+
const target = label ? `${resource}:${label}` : resource;
|
|
2561
|
+
const result = await this._conn.queryAll("RETURN has_permission($target, $action) AS allowed", {
|
|
2562
|
+
params: { target, action }
|
|
2563
|
+
});
|
|
2564
|
+
return result[0]?.["allowed"] ?? false;
|
|
2565
|
+
}
|
|
2566
|
+
// Validation helpers
|
|
2567
|
+
validateUsername(username) {
|
|
2568
|
+
if (!username || username.length === 0) {
|
|
2569
|
+
throw new SecurityError({
|
|
2570
|
+
type: "validation",
|
|
2571
|
+
message: "Username cannot be empty"
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
if (username.length > 128) {
|
|
2575
|
+
throw new SecurityError({
|
|
2576
|
+
type: "validation",
|
|
2577
|
+
message: "Username too long (max 128 characters)"
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(username)) {
|
|
2581
|
+
throw new SecurityError({
|
|
2582
|
+
type: "validation",
|
|
2583
|
+
message: "Invalid username format"
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
validatePassword(password) {
|
|
2588
|
+
if (!password || password.length < 8) {
|
|
2589
|
+
throw new SecurityError({
|
|
2590
|
+
type: "validation",
|
|
2591
|
+
message: "Password must be at least 8 characters"
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
if (password.length > 256) {
|
|
2595
|
+
throw new SecurityError({
|
|
2596
|
+
type: "validation",
|
|
2597
|
+
message: "Password too long (max 256 characters)"
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
validateRoleName(name) {
|
|
2602
|
+
if (!name || name.length === 0) {
|
|
2603
|
+
throw new SecurityError({
|
|
2604
|
+
type: "validation",
|
|
2605
|
+
message: "Role name cannot be empty"
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
if (name.length > 128) {
|
|
2609
|
+
throw new SecurityError({
|
|
2610
|
+
type: "validation",
|
|
2611
|
+
message: "Role name too long (max 128 characters)"
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
2615
|
+
throw new SecurityError({
|
|
2616
|
+
type: "validation",
|
|
2617
|
+
message: "Invalid role name format"
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
validatePolicyName(name) {
|
|
2622
|
+
if (!name || name.length === 0) {
|
|
2623
|
+
throw new SecurityError({
|
|
2624
|
+
type: "validation",
|
|
2625
|
+
message: "Policy name cannot be empty"
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
if (name.length > 128) {
|
|
2629
|
+
throw new SecurityError({
|
|
2630
|
+
type: "validation",
|
|
2631
|
+
message: "Policy name too long (max 128 characters)"
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
2635
|
+
throw new SecurityError({
|
|
2636
|
+
type: "validation",
|
|
2637
|
+
message: "Invalid policy name format"
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2644
|
+
|
|
2645
|
+
// src/connection.ts
|
|
2646
|
+
init_config();
|
|
2647
|
+
init_errors();
|
|
2648
|
+
|
|
2649
|
+
// src/transport.ts
|
|
2650
|
+
init_config();
|
|
2651
|
+
init_errors();
|
|
2652
|
+
var GEODE_ALPN = ["geode/1"];
|
|
2653
|
+
async function buildTLSConfig(cfg) {
|
|
2654
|
+
const options = {
|
|
2655
|
+
minVersion: "TLSv1.3",
|
|
2656
|
+
maxVersion: "TLSv1.3"
|
|
2657
|
+
};
|
|
2658
|
+
if (cfg.tlsCACert) {
|
|
2659
|
+
options.ca = cfg.tlsCACert;
|
|
2660
|
+
} else if (cfg.tlsCA) {
|
|
2661
|
+
try {
|
|
2662
|
+
options.ca = await fs.promises.readFile(cfg.tlsCA, "utf-8");
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
throw new TransportError({
|
|
2665
|
+
operation: "load_ca_cert",
|
|
2666
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
if (cfg.tlsCertPEM) {
|
|
2671
|
+
options.cert = cfg.tlsCertPEM;
|
|
2672
|
+
} else if (cfg.tlsCert) {
|
|
2673
|
+
try {
|
|
2674
|
+
options.cert = await fs.promises.readFile(cfg.tlsCert, "utf-8");
|
|
2675
|
+
} catch (e) {
|
|
2676
|
+
throw new TransportError({
|
|
2677
|
+
operation: "load_client_cert",
|
|
2678
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
if (cfg.tlsKeyPEM) {
|
|
2683
|
+
options.key = cfg.tlsKeyPEM;
|
|
2684
|
+
} else if (cfg.tlsKey) {
|
|
2685
|
+
try {
|
|
2686
|
+
options.key = await fs.promises.readFile(cfg.tlsKey, "utf-8");
|
|
2687
|
+
} catch (e) {
|
|
2688
|
+
throw new TransportError({
|
|
2689
|
+
operation: "load_client_key",
|
|
2690
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return options;
|
|
2695
|
+
}
|
|
2696
|
+
var BaseTransport = class {
|
|
2697
|
+
_closed = false;
|
|
2698
|
+
_address;
|
|
2699
|
+
constructor(address) {
|
|
2700
|
+
this._address = address;
|
|
2701
|
+
}
|
|
2702
|
+
isClosed() {
|
|
2703
|
+
return this._closed;
|
|
2704
|
+
}
|
|
2705
|
+
getAddress() {
|
|
2706
|
+
return this._address;
|
|
2707
|
+
}
|
|
2708
|
+
checkClosed() {
|
|
2709
|
+
if (this._closed) {
|
|
2710
|
+
throw ErrClosed;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
var MatrixQuicTransport = class _MatrixQuicTransport extends BaseTransport {
|
|
2715
|
+
_client = null;
|
|
2716
|
+
_stream = null;
|
|
2717
|
+
_readBuffer = [];
|
|
2718
|
+
_lineBuffer = "";
|
|
2719
|
+
_pendingReads = [];
|
|
2720
|
+
constructor(address) {
|
|
2721
|
+
super(address);
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Connect to the Geode server using QUIC.
|
|
2725
|
+
*/
|
|
2726
|
+
static async connect(cfg) {
|
|
2727
|
+
const address = getAddress(cfg);
|
|
2728
|
+
const transport = new _MatrixQuicTransport(address);
|
|
2729
|
+
try {
|
|
2730
|
+
const { QUICClient } = await import('@matrixai/quic');
|
|
2731
|
+
const [host = "localhost", portStr] = address.includes("[") ? [address.slice(1, address.lastIndexOf("]")), address.slice(address.lastIndexOf(":") + 1)] : address.split(":");
|
|
2732
|
+
const port = parseInt(portStr ?? "3141", 10);
|
|
2733
|
+
const cryptoOps = {
|
|
2734
|
+
ops: {
|
|
2735
|
+
randomBytes: (data) => {
|
|
2736
|
+
const buf = Buffer.from(data);
|
|
2737
|
+
crypto.randomFillSync(buf);
|
|
2738
|
+
return Promise.resolve();
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
const tlsConfig = await buildTLSConfig(cfg);
|
|
2743
|
+
const config = {
|
|
2744
|
+
applicationProtos: GEODE_ALPN,
|
|
2745
|
+
maxIdleTimeout: cfg.maxIdleTime ?? 3e4,
|
|
2746
|
+
initialMaxData: 1e7,
|
|
2747
|
+
initialMaxStreamDataBidiLocal: 1e6,
|
|
2748
|
+
initialMaxStreamDataBidiRemote: 1e6,
|
|
2749
|
+
initialMaxStreamsBidi: 100
|
|
2750
|
+
};
|
|
2751
|
+
if (cfg.insecureSkipVerify) {
|
|
2752
|
+
config.verifyPeer = false;
|
|
2753
|
+
}
|
|
2754
|
+
if (tlsConfig.ca) {
|
|
2755
|
+
config.caCerts = [tlsConfig.ca];
|
|
2756
|
+
}
|
|
2757
|
+
if (tlsConfig.cert && tlsConfig.key) {
|
|
2758
|
+
config.cert = tlsConfig.cert;
|
|
2759
|
+
config.key = tlsConfig.key;
|
|
2760
|
+
}
|
|
2761
|
+
const client = await QUICClient.createQUICClient(
|
|
2762
|
+
{
|
|
2763
|
+
host,
|
|
2764
|
+
port,
|
|
2765
|
+
serverName: cfg.serverName ?? host,
|
|
2766
|
+
crypto: cryptoOps,
|
|
2767
|
+
config
|
|
2768
|
+
},
|
|
2769
|
+
{ timer: cfg.connectTimeout ?? 3e4 }
|
|
2770
|
+
);
|
|
2771
|
+
transport._client = client;
|
|
2772
|
+
const connection = client.connection;
|
|
2773
|
+
const stream = await connection.newStream();
|
|
2774
|
+
transport._stream = stream;
|
|
2775
|
+
transport.startReading();
|
|
2776
|
+
return transport;
|
|
2777
|
+
} catch (e) {
|
|
2778
|
+
await transport.close();
|
|
2779
|
+
throw new TransportError({
|
|
2780
|
+
operation: "connect",
|
|
2781
|
+
address,
|
|
2782
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Start reading from the stream in the background.
|
|
2788
|
+
*/
|
|
2789
|
+
startReading() {
|
|
2790
|
+
if (!this._stream) return;
|
|
2791
|
+
const stream = this._stream;
|
|
2792
|
+
const reader = stream.readable.getReader();
|
|
2793
|
+
const read = async () => {
|
|
2794
|
+
try {
|
|
2795
|
+
while (!this._closed) {
|
|
2796
|
+
const { done, value } = await reader.read();
|
|
2797
|
+
if (done) break;
|
|
2798
|
+
if (value) {
|
|
2799
|
+
this.processData(Buffer.from(value));
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
} catch {
|
|
2803
|
+
} finally {
|
|
2804
|
+
reader.releaseLock();
|
|
2805
|
+
}
|
|
2806
|
+
};
|
|
2807
|
+
void read();
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Process received data, splitting by newlines.
|
|
2811
|
+
*/
|
|
2812
|
+
processData(data) {
|
|
2813
|
+
this._lineBuffer += data.toString("utf-8");
|
|
2814
|
+
let newlineIdx;
|
|
2815
|
+
while ((newlineIdx = this._lineBuffer.indexOf("\n")) !== -1) {
|
|
2816
|
+
const line = this._lineBuffer.slice(0, newlineIdx);
|
|
2817
|
+
this._lineBuffer = this._lineBuffer.slice(newlineIdx + 1);
|
|
2818
|
+
if (line.length > 0) {
|
|
2819
|
+
const pending = this._pendingReads.shift();
|
|
2820
|
+
if (pending) {
|
|
2821
|
+
pending.resolve(Buffer.from(line, "utf-8"));
|
|
2822
|
+
} else {
|
|
2823
|
+
this._readBuffer.push(Buffer.from(line, "utf-8"));
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
async send(msg, signal) {
|
|
2829
|
+
this.checkClosed();
|
|
2830
|
+
if (signal?.aborted) {
|
|
2831
|
+
throw new TransportError({ operation: "send", cause: new Error("Aborted") });
|
|
2832
|
+
}
|
|
2833
|
+
if (!this._stream) {
|
|
2834
|
+
throw new TransportError({ operation: "send", cause: new Error("No stream") });
|
|
2835
|
+
}
|
|
2836
|
+
const data = Buffer.from(JSON.stringify(msg) + "\n", "utf-8");
|
|
2837
|
+
try {
|
|
2838
|
+
const stream = this._stream;
|
|
2839
|
+
const writer = stream.writable.getWriter();
|
|
2840
|
+
await writer.write(new Uint8Array(data));
|
|
2841
|
+
writer.releaseLock();
|
|
2842
|
+
} catch (e) {
|
|
2843
|
+
throw new TransportError({
|
|
2844
|
+
operation: "send",
|
|
2845
|
+
address: this._address,
|
|
2846
|
+
cause: e instanceof Error ? e : new Error(String(e))
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
async receive(signal) {
|
|
2851
|
+
this.checkClosed();
|
|
2852
|
+
if (signal?.aborted) {
|
|
2853
|
+
throw new TransportError({ operation: "receive", cause: new Error("Aborted") });
|
|
2854
|
+
}
|
|
2855
|
+
const buffered = this._readBuffer.shift();
|
|
2856
|
+
if (buffered) {
|
|
2857
|
+
return buffered;
|
|
2858
|
+
}
|
|
2859
|
+
return new Promise((resolve, reject) => {
|
|
2860
|
+
if (signal) {
|
|
2861
|
+
const onAbort = () => {
|
|
2862
|
+
reject(new TransportError({ operation: "receive", cause: new Error("Aborted") }));
|
|
2863
|
+
};
|
|
2864
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2865
|
+
}
|
|
2866
|
+
this._pendingReads.push({ resolve, reject });
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
async close() {
|
|
2870
|
+
if (this._closed) return;
|
|
2871
|
+
this._closed = true;
|
|
2872
|
+
for (const pending of this._pendingReads) {
|
|
2873
|
+
pending.reject(ErrClosed);
|
|
2874
|
+
}
|
|
2875
|
+
this._pendingReads = [];
|
|
2876
|
+
if (this._client) {
|
|
2877
|
+
try {
|
|
2878
|
+
const client = this._client;
|
|
2879
|
+
await client.destroy();
|
|
2880
|
+
} catch {
|
|
2881
|
+
}
|
|
2882
|
+
this._client = null;
|
|
2883
|
+
}
|
|
2884
|
+
this._stream = null;
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
var MockTransport = class extends BaseTransport {
|
|
2888
|
+
_sendQueue = [];
|
|
2889
|
+
_receiveQueue = [];
|
|
2890
|
+
_pendingReads = [];
|
|
2891
|
+
constructor(address = "mock:3141") {
|
|
2892
|
+
super(address);
|
|
2893
|
+
}
|
|
2894
|
+
/**
|
|
2895
|
+
* Queue a response to be returned by receive().
|
|
2896
|
+
*/
|
|
2897
|
+
queueResponse(response) {
|
|
2898
|
+
const data = Buffer.from(JSON.stringify(response), "utf-8");
|
|
2899
|
+
this._receiveQueue.push(data);
|
|
2900
|
+
const pending = this._pendingReads.shift();
|
|
2901
|
+
if (pending) {
|
|
2902
|
+
const queued = this._receiveQueue.shift();
|
|
2903
|
+
if (queued) {
|
|
2904
|
+
pending.resolve(queued);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Get all sent messages.
|
|
2910
|
+
*/
|
|
2911
|
+
getSentMessages() {
|
|
2912
|
+
return [...this._sendQueue];
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Clear sent messages.
|
|
2916
|
+
*/
|
|
2917
|
+
clearSentMessages() {
|
|
2918
|
+
this._sendQueue = [];
|
|
2919
|
+
}
|
|
2920
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2921
|
+
async send(msg, signal) {
|
|
2922
|
+
this.checkClosed();
|
|
2923
|
+
if (signal?.aborted) {
|
|
2924
|
+
throw new TransportError({ operation: "send", cause: new Error("Aborted") });
|
|
2925
|
+
}
|
|
2926
|
+
this._sendQueue.push(msg);
|
|
2927
|
+
}
|
|
2928
|
+
async receive(signal) {
|
|
2929
|
+
this.checkClosed();
|
|
2930
|
+
if (signal?.aborted) {
|
|
2931
|
+
throw new TransportError({ operation: "receive", cause: new Error("Aborted") });
|
|
2932
|
+
}
|
|
2933
|
+
const queued = this._receiveQueue.shift();
|
|
2934
|
+
if (queued) {
|
|
2935
|
+
return queued;
|
|
2936
|
+
}
|
|
2937
|
+
return new Promise((resolve, reject) => {
|
|
2938
|
+
if (signal) {
|
|
2939
|
+
const onAbort = () => {
|
|
2940
|
+
reject(new TransportError({ operation: "receive", cause: new Error("Aborted") }));
|
|
2941
|
+
};
|
|
2942
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2943
|
+
}
|
|
2944
|
+
this._pendingReads.push({ resolve, reject });
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2948
|
+
async close() {
|
|
2949
|
+
if (this._closed) return;
|
|
2950
|
+
this._closed = true;
|
|
2951
|
+
for (const pending of this._pendingReads) {
|
|
2952
|
+
pending.reject(ErrClosed);
|
|
2953
|
+
}
|
|
2954
|
+
this._pendingReads = [];
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
async function createTransport(cfg) {
|
|
2958
|
+
return MatrixQuicTransport.connect(cfg);
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// src/connection.ts
|
|
2962
|
+
init_protocol();
|
|
2963
|
+
init_validate();
|
|
2964
|
+
init_types();
|
|
2965
|
+
|
|
2966
|
+
// src/transaction.ts
|
|
2967
|
+
init_errors();
|
|
2968
|
+
var Transaction = class {
|
|
2969
|
+
_conn;
|
|
2970
|
+
_done = false;
|
|
2971
|
+
_savepoints = [];
|
|
2972
|
+
constructor(conn) {
|
|
2973
|
+
this._conn = conn;
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Check if transaction is complete (committed or rolled back).
|
|
2977
|
+
*/
|
|
2978
|
+
get isDone() {
|
|
2979
|
+
return this._done;
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Get the list of active savepoints.
|
|
2983
|
+
*/
|
|
2984
|
+
get savepoints() {
|
|
2985
|
+
return this._savepoints;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Execute a query within the transaction.
|
|
2989
|
+
*/
|
|
2990
|
+
async query(query2, options) {
|
|
2991
|
+
this.checkDone();
|
|
2992
|
+
return this._conn.query(query2, options);
|
|
2993
|
+
}
|
|
2994
|
+
/**
|
|
2995
|
+
* Execute a query and return all rows.
|
|
2996
|
+
*/
|
|
2997
|
+
async queryAll(query2, options) {
|
|
2998
|
+
this.checkDone();
|
|
2999
|
+
return this._conn.queryAll(query2, options);
|
|
3000
|
+
}
|
|
3001
|
+
/**
|
|
3002
|
+
* Execute a statement that doesn't return rows.
|
|
3003
|
+
*/
|
|
3004
|
+
async exec(query2, options) {
|
|
3005
|
+
this.checkDone();
|
|
3006
|
+
return this._conn.exec(query2, options);
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Create a savepoint.
|
|
3010
|
+
*/
|
|
3011
|
+
async savepoint(name, signal) {
|
|
3012
|
+
this.checkDone();
|
|
3013
|
+
await this._conn._savepoint(name, signal);
|
|
3014
|
+
this._savepoints.push(name);
|
|
3015
|
+
}
|
|
3016
|
+
/**
|
|
3017
|
+
* Rollback to a savepoint.
|
|
3018
|
+
*/
|
|
3019
|
+
async rollbackTo(name, signal) {
|
|
3020
|
+
this.checkDone();
|
|
3021
|
+
const idx = this._savepoints.indexOf(name);
|
|
3022
|
+
if (idx === -1) {
|
|
3023
|
+
throw new Error(`Savepoint not found: ${name}`);
|
|
3024
|
+
}
|
|
3025
|
+
await this._conn._rollbackTo(name, signal);
|
|
3026
|
+
this._savepoints = this._savepoints.slice(0, idx);
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Release a savepoint.
|
|
3030
|
+
*
|
|
3031
|
+
* Note: Geode doesn't have explicit RELEASE SAVEPOINT, but we track it client-side.
|
|
3032
|
+
*/
|
|
3033
|
+
release(name) {
|
|
3034
|
+
this.checkDone();
|
|
3035
|
+
const idx = this._savepoints.indexOf(name);
|
|
3036
|
+
if (idx === -1) {
|
|
3037
|
+
throw new Error(`Savepoint not found: ${name}`);
|
|
3038
|
+
}
|
|
3039
|
+
this._savepoints.splice(idx, 1);
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Commit the transaction.
|
|
3043
|
+
*/
|
|
3044
|
+
async commit(signal) {
|
|
3045
|
+
this.checkDone();
|
|
3046
|
+
this._done = true;
|
|
3047
|
+
await this._conn._commit(signal);
|
|
3048
|
+
this._savepoints = [];
|
|
3049
|
+
}
|
|
3050
|
+
/**
|
|
3051
|
+
* Rollback the transaction.
|
|
3052
|
+
*/
|
|
3053
|
+
async rollback(signal) {
|
|
3054
|
+
this.checkDone();
|
|
3055
|
+
this._done = true;
|
|
3056
|
+
await this._conn._rollback(signal);
|
|
3057
|
+
this._savepoints = [];
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Execute a function within the transaction with automatic commit/rollback.
|
|
3061
|
+
*/
|
|
3062
|
+
async run(fn) {
|
|
3063
|
+
this.checkDone();
|
|
3064
|
+
try {
|
|
3065
|
+
const result = await fn(this);
|
|
3066
|
+
await this.commit();
|
|
3067
|
+
return result;
|
|
3068
|
+
} catch (e) {
|
|
3069
|
+
if (!this._done) {
|
|
3070
|
+
await this.rollback();
|
|
3071
|
+
}
|
|
3072
|
+
throw e;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
/**
|
|
3076
|
+
* Check if transaction is already done.
|
|
3077
|
+
*/
|
|
3078
|
+
checkDone() {
|
|
3079
|
+
if (this._done) {
|
|
3080
|
+
throw ErrTxDone;
|
|
3081
|
+
}
|
|
3082
|
+
if (!this._conn.inTransaction) {
|
|
3083
|
+
throw ErrNoTx;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
async function withTransaction(conn, fn, signal) {
|
|
3088
|
+
const tx = await conn.begin(signal);
|
|
3089
|
+
return tx.run(fn);
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/result.ts
|
|
3093
|
+
init_types();
|
|
3094
|
+
var QueryResult = class {
|
|
3095
|
+
_conn;
|
|
3096
|
+
_columns;
|
|
3097
|
+
_buffer;
|
|
3098
|
+
_final;
|
|
3099
|
+
_ordered;
|
|
3100
|
+
_orderKeys;
|
|
3101
|
+
_pageSize;
|
|
3102
|
+
_signal;
|
|
3103
|
+
_closed = false;
|
|
3104
|
+
_bufferIndex = 0;
|
|
3105
|
+
_rowCount = 0;
|
|
3106
|
+
constructor(conn, columns, initialRows, final, ordered, orderKeys, pageSize, signal) {
|
|
3107
|
+
this._conn = conn;
|
|
3108
|
+
this._columns = columns;
|
|
3109
|
+
this._buffer = initialRows;
|
|
3110
|
+
this._final = final;
|
|
3111
|
+
this._ordered = ordered;
|
|
3112
|
+
this._orderKeys = orderKeys;
|
|
3113
|
+
this._pageSize = pageSize;
|
|
3114
|
+
this._signal = signal;
|
|
3115
|
+
}
|
|
3116
|
+
/**
|
|
3117
|
+
* Get column definitions.
|
|
3118
|
+
*/
|
|
3119
|
+
get columns() {
|
|
3120
|
+
return this._columns;
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Get column names.
|
|
3124
|
+
*/
|
|
3125
|
+
get columnNames() {
|
|
3126
|
+
return this._columns.map((c) => c.name);
|
|
3127
|
+
}
|
|
3128
|
+
/**
|
|
3129
|
+
* Check if results are ordered.
|
|
3130
|
+
*/
|
|
3131
|
+
get ordered() {
|
|
3132
|
+
return this._ordered;
|
|
3133
|
+
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Get order keys (if ordered).
|
|
3136
|
+
*/
|
|
3137
|
+
get orderKeys() {
|
|
3138
|
+
return this._orderKeys;
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* Check if all results have been fetched.
|
|
3142
|
+
*/
|
|
3143
|
+
get complete() {
|
|
3144
|
+
return this._final && this._bufferIndex >= this._buffer.length;
|
|
3145
|
+
}
|
|
3146
|
+
/**
|
|
3147
|
+
* Check if result is closed.
|
|
3148
|
+
*/
|
|
3149
|
+
get isClosed() {
|
|
3150
|
+
return this._closed;
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Get the number of rows returned so far.
|
|
3154
|
+
*/
|
|
3155
|
+
get rowCount() {
|
|
3156
|
+
return this._rowCount;
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Async iterator implementation.
|
|
3160
|
+
*/
|
|
3161
|
+
async *[Symbol.asyncIterator]() {
|
|
3162
|
+
try {
|
|
3163
|
+
while (!this._closed) {
|
|
3164
|
+
if (this._signal?.aborted) {
|
|
3165
|
+
break;
|
|
3166
|
+
}
|
|
3167
|
+
while (this._bufferIndex < this._buffer.length) {
|
|
3168
|
+
const rawRow = this._buffer[this._bufferIndex];
|
|
3169
|
+
if (rawRow) {
|
|
3170
|
+
this._bufferIndex++;
|
|
3171
|
+
this._rowCount++;
|
|
3172
|
+
yield parseRow(rawRow, this._columns);
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
if (this._final) {
|
|
3176
|
+
break;
|
|
3177
|
+
}
|
|
3178
|
+
const { rows, final } = await this._conn._fetchNextPage(this._pageSize, this._signal);
|
|
3179
|
+
this._buffer = rows;
|
|
3180
|
+
this._bufferIndex = 0;
|
|
3181
|
+
this._final = final;
|
|
3182
|
+
}
|
|
3183
|
+
} finally {
|
|
3184
|
+
this.close();
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
/**
|
|
3188
|
+
* Get all remaining rows as an array.
|
|
3189
|
+
*/
|
|
3190
|
+
async toArray() {
|
|
3191
|
+
const rows = [];
|
|
3192
|
+
for await (const row of this) {
|
|
3193
|
+
rows.push(row);
|
|
3194
|
+
}
|
|
3195
|
+
return rows;
|
|
3196
|
+
}
|
|
3197
|
+
/**
|
|
3198
|
+
* Get all remaining rows as plain objects.
|
|
3199
|
+
*/
|
|
3200
|
+
async toObjects() {
|
|
3201
|
+
const rows = [];
|
|
3202
|
+
for await (const row of this) {
|
|
3203
|
+
rows.push(rowToObject(row));
|
|
3204
|
+
}
|
|
3205
|
+
return rows;
|
|
3206
|
+
}
|
|
3207
|
+
/**
|
|
3208
|
+
* Get the first row (or null if empty).
|
|
3209
|
+
*/
|
|
3210
|
+
async first() {
|
|
3211
|
+
for await (const row of this) {
|
|
3212
|
+
this.close();
|
|
3213
|
+
return row;
|
|
3214
|
+
}
|
|
3215
|
+
return null;
|
|
3216
|
+
}
|
|
3217
|
+
/**
|
|
3218
|
+
* Get the first row as plain object (or null if empty).
|
|
3219
|
+
*/
|
|
3220
|
+
async firstObject() {
|
|
3221
|
+
const row = await this.first();
|
|
3222
|
+
return row ? rowToObject(row) : null;
|
|
3223
|
+
}
|
|
3224
|
+
/**
|
|
3225
|
+
* Apply a function to each row.
|
|
3226
|
+
*/
|
|
3227
|
+
async forEach(fn) {
|
|
3228
|
+
let index = 0;
|
|
3229
|
+
for await (const row of this) {
|
|
3230
|
+
await fn(row, index++);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Map rows to a new array.
|
|
3235
|
+
*/
|
|
3236
|
+
async map(fn) {
|
|
3237
|
+
const results = [];
|
|
3238
|
+
let index = 0;
|
|
3239
|
+
for await (const row of this) {
|
|
3240
|
+
results.push(await fn(row, index++));
|
|
3241
|
+
}
|
|
3242
|
+
return results;
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Filter rows.
|
|
3246
|
+
*/
|
|
3247
|
+
async filter(predicate2) {
|
|
3248
|
+
const results = [];
|
|
3249
|
+
let index = 0;
|
|
3250
|
+
for await (const row of this) {
|
|
3251
|
+
if (await predicate2(row, index++)) {
|
|
3252
|
+
results.push(row);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
return results;
|
|
3256
|
+
}
|
|
3257
|
+
/**
|
|
3258
|
+
* Reduce rows to a single value.
|
|
3259
|
+
*/
|
|
3260
|
+
async reduce(fn, initial) {
|
|
3261
|
+
let acc = initial;
|
|
3262
|
+
let index = 0;
|
|
3263
|
+
for await (const row of this) {
|
|
3264
|
+
acc = await fn(acc, row, index++);
|
|
3265
|
+
}
|
|
3266
|
+
return acc;
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Close the result set.
|
|
3270
|
+
*/
|
|
3271
|
+
close() {
|
|
3272
|
+
if (this._closed) {
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
this._closed = true;
|
|
3276
|
+
this._conn._releaseResult(this);
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Internal close (called by connection).
|
|
3280
|
+
*/
|
|
3281
|
+
_close() {
|
|
3282
|
+
this._closed = true;
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
var QueryResultIterator = class {
|
|
3286
|
+
_result;
|
|
3287
|
+
_iterator;
|
|
3288
|
+
constructor(result) {
|
|
3289
|
+
this._result = result;
|
|
3290
|
+
this._iterator = result[Symbol.asyncIterator]();
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* Get the next row.
|
|
3294
|
+
*/
|
|
3295
|
+
async next() {
|
|
3296
|
+
const result = await this._iterator.next();
|
|
3297
|
+
return {
|
|
3298
|
+
done: result.done ?? false,
|
|
3299
|
+
value: result.done ? void 0 : result.value
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Close the iterator.
|
|
3304
|
+
*/
|
|
3305
|
+
close() {
|
|
3306
|
+
this._result.close();
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
|
|
3310
|
+
// src/connection.ts
|
|
3311
|
+
var Connection = class _Connection {
|
|
3312
|
+
_config;
|
|
3313
|
+
_transport;
|
|
3314
|
+
_state = "idle";
|
|
3315
|
+
_inTransaction = false;
|
|
3316
|
+
_activeResult = null;
|
|
3317
|
+
_requestId = 0;
|
|
3318
|
+
constructor(config, transport) {
|
|
3319
|
+
this._config = config;
|
|
3320
|
+
this._transport = transport;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Create a new connection to the Geode server.
|
|
3324
|
+
*/
|
|
3325
|
+
static async connect(config) {
|
|
3326
|
+
validateConfig(config);
|
|
3327
|
+
const transport = await createTransport(config);
|
|
3328
|
+
const conn = new _Connection(config, transport);
|
|
3329
|
+
try {
|
|
3330
|
+
await conn.hello();
|
|
3331
|
+
return conn;
|
|
3332
|
+
} catch (e) {
|
|
3333
|
+
await transport.close();
|
|
3334
|
+
throw e;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* Create a connection with a custom transport (for testing).
|
|
3339
|
+
*/
|
|
3340
|
+
static async connectWithTransport(config, transport) {
|
|
3341
|
+
const conn = new _Connection(config, transport);
|
|
3342
|
+
await conn.hello();
|
|
3343
|
+
return conn;
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* Get connection configuration.
|
|
3347
|
+
*/
|
|
3348
|
+
get config() {
|
|
3349
|
+
return this._config;
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Get current connection state.
|
|
3353
|
+
*/
|
|
3354
|
+
get state() {
|
|
3355
|
+
return this._state;
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Check if connection is in a transaction.
|
|
3359
|
+
*/
|
|
3360
|
+
get inTransaction() {
|
|
3361
|
+
return this._inTransaction;
|
|
3362
|
+
}
|
|
3363
|
+
/**
|
|
3364
|
+
* Check if connection is closed.
|
|
3365
|
+
*/
|
|
3366
|
+
get isClosed() {
|
|
3367
|
+
return this._state === "closed" || this._transport.isClosed();
|
|
3368
|
+
}
|
|
3369
|
+
/**
|
|
3370
|
+
* Perform the HELLO handshake.
|
|
3371
|
+
*
|
|
3372
|
+
* Note: The server may return an ERROR frame with "Authentication required"
|
|
3373
|
+
* even when no authentication is configured. This is a known server behavior
|
|
3374
|
+
* that should be treated as a successful connection (matching other clients).
|
|
3375
|
+
*/
|
|
3376
|
+
async hello() {
|
|
3377
|
+
const msg = buildHelloMessage(this._config);
|
|
3378
|
+
await this._transport.send(msg);
|
|
3379
|
+
const data = await this._transport.receive();
|
|
3380
|
+
const frame = parseFrame(data);
|
|
3381
|
+
if (isErrorFrame(frame)) {
|
|
3382
|
+
const statusClass = frame.status_class || frame.result.code;
|
|
3383
|
+
if (statusClass !== "28000" || this._config.username || this._config.password) {
|
|
3384
|
+
throw frameToError(frame);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Execute a query that returns rows.
|
|
3390
|
+
*/
|
|
3391
|
+
async query(query2, options) {
|
|
3392
|
+
this.checkState("query");
|
|
3393
|
+
validateQuery(query2);
|
|
3394
|
+
let finalQuery = query2;
|
|
3395
|
+
let params = options?.params ?? {};
|
|
3396
|
+
if (query2.includes("?") && Array.isArray(options?.params)) {
|
|
3397
|
+
const result = rewritePlaceholders(query2, options.params);
|
|
3398
|
+
finalQuery = result.query;
|
|
3399
|
+
params = result.params;
|
|
3400
|
+
}
|
|
3401
|
+
for (const [_key, value] of Object.entries(params)) {
|
|
3402
|
+
validateParamValue(value);
|
|
3403
|
+
}
|
|
3404
|
+
const signal = options?.signal;
|
|
3405
|
+
try {
|
|
3406
|
+
this._state = "fetching";
|
|
3407
|
+
await this._transport.send(buildRunGQLMessage(finalQuery, params), signal);
|
|
3408
|
+
const schemaData = await this._transport.receive(signal);
|
|
3409
|
+
const schemaFrame = parseFrame(schemaData);
|
|
3410
|
+
if (isErrorFrame(schemaFrame)) {
|
|
3411
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3412
|
+
throw frameToError(schemaFrame);
|
|
3413
|
+
}
|
|
3414
|
+
if (!isSchemaFrame(schemaFrame)) {
|
|
3415
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3416
|
+
throw new TransportError({
|
|
3417
|
+
operation: "query",
|
|
3418
|
+
cause: new Error(`Expected SCHEMA frame, got ${schemaFrame.result.type}`)
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
const columns = getColumnsFromSchema(schemaFrame);
|
|
3422
|
+
const inlineFrame = await this._tryReceiveInlineFrame(signal);
|
|
3423
|
+
if (inlineFrame) {
|
|
3424
|
+
if (isErrorFrame(inlineFrame)) {
|
|
3425
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3426
|
+
throw frameToError(inlineFrame);
|
|
3427
|
+
}
|
|
3428
|
+
const result2 = new QueryResult(
|
|
3429
|
+
this,
|
|
3430
|
+
columns,
|
|
3431
|
+
inlineFrame.result.rows ?? [],
|
|
3432
|
+
inlineFrame.result.final ?? true,
|
|
3433
|
+
inlineFrame.result.ordered ?? false,
|
|
3434
|
+
inlineFrame.result.order_keys ?? [],
|
|
3435
|
+
options?.pageSize ?? this._config.pageSize,
|
|
3436
|
+
signal
|
|
3437
|
+
);
|
|
3438
|
+
this._activeResult = result2;
|
|
3439
|
+
return result2;
|
|
3440
|
+
}
|
|
3441
|
+
this._requestId++;
|
|
3442
|
+
const pageSize = options?.pageSize ?? this._config.pageSize;
|
|
3443
|
+
await this._transport.send(buildPullMessage(this._requestId, pageSize), signal);
|
|
3444
|
+
const bindingsData = await this._transport.receive(signal);
|
|
3445
|
+
const bindingsFrame = parseFrame(bindingsData);
|
|
3446
|
+
if (isErrorFrame(bindingsFrame)) {
|
|
3447
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3448
|
+
throw frameToError(bindingsFrame);
|
|
3449
|
+
}
|
|
3450
|
+
const result = new QueryResult(
|
|
3451
|
+
this,
|
|
3452
|
+
columns,
|
|
3453
|
+
bindingsFrame.result.rows ?? [],
|
|
3454
|
+
bindingsFrame.result.final ?? true,
|
|
3455
|
+
bindingsFrame.result.ordered ?? false,
|
|
3456
|
+
bindingsFrame.result.order_keys ?? [],
|
|
3457
|
+
pageSize,
|
|
3458
|
+
signal
|
|
3459
|
+
);
|
|
3460
|
+
this._activeResult = result;
|
|
3461
|
+
return result;
|
|
3462
|
+
} catch (e) {
|
|
3463
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3464
|
+
throw e;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* Execute a query and return all rows as an array.
|
|
3469
|
+
*/
|
|
3470
|
+
async queryAll(query2, options) {
|
|
3471
|
+
const result = await this.query(query2, options);
|
|
3472
|
+
const rows = [];
|
|
3473
|
+
for await (const row of result) {
|
|
3474
|
+
rows.push(rowToObject(row));
|
|
3475
|
+
}
|
|
3476
|
+
return rows;
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Execute a query that doesn't return rows.
|
|
3480
|
+
*/
|
|
3481
|
+
async exec(query2, options) {
|
|
3482
|
+
this.checkState("exec");
|
|
3483
|
+
validateQuery(query2);
|
|
3484
|
+
let finalQuery = query2;
|
|
3485
|
+
let params = options?.params ?? {};
|
|
3486
|
+
if (query2.includes("?") && Array.isArray(options?.params)) {
|
|
3487
|
+
const result = rewritePlaceholders(query2, options.params);
|
|
3488
|
+
finalQuery = result.query;
|
|
3489
|
+
params = result.params;
|
|
3490
|
+
}
|
|
3491
|
+
for (const [_key, value] of Object.entries(params)) {
|
|
3492
|
+
validateParamValue(value);
|
|
3493
|
+
}
|
|
3494
|
+
const signal = options?.signal;
|
|
3495
|
+
try {
|
|
3496
|
+
this._state = "executing";
|
|
3497
|
+
await this._transport.send(buildRunGQLMessage(finalQuery, params), signal);
|
|
3498
|
+
const data = await this._transport.receive(signal);
|
|
3499
|
+
const frame = parseFrame(data);
|
|
3500
|
+
if (isErrorFrame(frame)) {
|
|
3501
|
+
throw frameToError(frame);
|
|
3502
|
+
}
|
|
3503
|
+
if (frame.result.type === RespType.SCHEMA) {
|
|
3504
|
+
const inlineFrame = await this._tryReceiveInlineFrame(signal);
|
|
3505
|
+
if (inlineFrame && isErrorFrame(inlineFrame)) {
|
|
3506
|
+
throw frameToError(inlineFrame);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
} finally {
|
|
3510
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
/**
|
|
3514
|
+
* Fetch the next page of results (internal).
|
|
3515
|
+
*/
|
|
3516
|
+
async _fetchNextPage(pageSize, signal) {
|
|
3517
|
+
this._requestId++;
|
|
3518
|
+
await this._transport.send(buildPullMessage(this._requestId, pageSize), signal);
|
|
3519
|
+
const data = await this._transport.receive(signal);
|
|
3520
|
+
const frame = parseFrame(data);
|
|
3521
|
+
if (isErrorFrame(frame)) {
|
|
3522
|
+
throw frameToError(frame);
|
|
3523
|
+
}
|
|
3524
|
+
const resultType = frame.result.type;
|
|
3525
|
+
if (resultType === RespType.BINDINGS) {
|
|
3526
|
+
return {
|
|
3527
|
+
rows: frame.result.rows ?? [],
|
|
3528
|
+
final: frame.result.final ?? true
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
return {
|
|
3532
|
+
rows: [],
|
|
3533
|
+
final: true
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
async _tryReceiveInlineFrame(signal) {
|
|
3537
|
+
if (signal?.aborted) {
|
|
3538
|
+
return null;
|
|
3539
|
+
}
|
|
3540
|
+
const controller = new AbortController();
|
|
3541
|
+
const timeoutId = setTimeout(() => controller.abort(), 1);
|
|
3542
|
+
try {
|
|
3543
|
+
const data = await this._transport.receive(controller.signal);
|
|
3544
|
+
return parseFrame(data);
|
|
3545
|
+
} catch (err) {
|
|
3546
|
+
if (err instanceof TransportError && err.cause?.message === "Aborted") {
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
throw err;
|
|
3550
|
+
} finally {
|
|
3551
|
+
clearTimeout(timeoutId);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
/**
|
|
3555
|
+
* Release active result (internal).
|
|
3556
|
+
*/
|
|
3557
|
+
_releaseResult(result) {
|
|
3558
|
+
if (this._activeResult === result) {
|
|
3559
|
+
this._activeResult = null;
|
|
3560
|
+
this._state = this._inTransaction ? "in_transaction" : "idle";
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Begin a transaction.
|
|
3565
|
+
*/
|
|
3566
|
+
async begin(signal) {
|
|
3567
|
+
this.checkState("begin");
|
|
3568
|
+
if (this._inTransaction) {
|
|
3569
|
+
throw ErrTxInProgress;
|
|
3570
|
+
}
|
|
3571
|
+
await this._transport.send(buildBeginMessage(), signal);
|
|
3572
|
+
const data = await this._transport.receive(signal);
|
|
3573
|
+
const frame = parseFrame(data);
|
|
3574
|
+
if (isErrorFrame(frame)) {
|
|
3575
|
+
throw frameToError(frame);
|
|
3576
|
+
}
|
|
3577
|
+
this._inTransaction = true;
|
|
3578
|
+
this._state = "in_transaction";
|
|
3579
|
+
return new Transaction(this);
|
|
3580
|
+
}
|
|
3581
|
+
/**
|
|
3582
|
+
* Commit the current transaction (internal).
|
|
3583
|
+
*/
|
|
3584
|
+
async _commit(signal) {
|
|
3585
|
+
await this._transport.send(buildCommitMessage(), signal);
|
|
3586
|
+
const data = await this._transport.receive(signal);
|
|
3587
|
+
const frame = parseFrame(data);
|
|
3588
|
+
if (isErrorFrame(frame)) {
|
|
3589
|
+
throw frameToError(frame);
|
|
3590
|
+
}
|
|
3591
|
+
this._inTransaction = false;
|
|
3592
|
+
this._state = "idle";
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3595
|
+
* Rollback the current transaction (internal).
|
|
3596
|
+
*/
|
|
3597
|
+
async _rollback(signal) {
|
|
3598
|
+
await this._transport.send(buildRollbackMessage(), signal);
|
|
3599
|
+
const data = await this._transport.receive(signal);
|
|
3600
|
+
const frame = parseFrame(data);
|
|
3601
|
+
if (isErrorFrame(frame)) {
|
|
3602
|
+
throw frameToError(frame);
|
|
3603
|
+
}
|
|
3604
|
+
this._inTransaction = false;
|
|
3605
|
+
this._state = "idle";
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Create a savepoint (internal).
|
|
3609
|
+
*/
|
|
3610
|
+
async _savepoint(name, signal) {
|
|
3611
|
+
validateSavepointName(name);
|
|
3612
|
+
await this._transport.send(buildSavepointMessage(name), signal);
|
|
3613
|
+
const data = await this._transport.receive(signal);
|
|
3614
|
+
const frame = parseFrame(data);
|
|
3615
|
+
if (isErrorFrame(frame)) {
|
|
3616
|
+
throw frameToError(frame);
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
/**
|
|
3620
|
+
* Rollback to a savepoint (internal).
|
|
3621
|
+
*/
|
|
3622
|
+
async _rollbackTo(name, signal) {
|
|
3623
|
+
validateSavepointName(name);
|
|
3624
|
+
await this._transport.send(buildRollbackToMessage(name), signal);
|
|
3625
|
+
const data = await this._transport.receive(signal);
|
|
3626
|
+
const frame = parseFrame(data);
|
|
3627
|
+
if (isErrorFrame(frame)) {
|
|
3628
|
+
throw frameToError(frame);
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
/**
|
|
3632
|
+
* Ping the server to check connection health.
|
|
3633
|
+
*/
|
|
3634
|
+
async ping(signal) {
|
|
3635
|
+
if (this._state === "closed") {
|
|
3636
|
+
throw ErrClosed;
|
|
3637
|
+
}
|
|
3638
|
+
await this._transport.send(buildPingMessage(), signal);
|
|
3639
|
+
const data = await this._transport.receive(signal);
|
|
3640
|
+
const frame = parseFrame(data);
|
|
3641
|
+
if (isErrorFrame(frame)) {
|
|
3642
|
+
throw frameToError(frame);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
/**
|
|
3646
|
+
* Reset the connection session.
|
|
3647
|
+
*/
|
|
3648
|
+
async reset(signal) {
|
|
3649
|
+
if (this._state === "closed") {
|
|
3650
|
+
throw ErrClosed;
|
|
3651
|
+
}
|
|
3652
|
+
if (this._inTransaction) {
|
|
3653
|
+
await this._rollback(signal);
|
|
3654
|
+
}
|
|
3655
|
+
this._state = "idle";
|
|
3656
|
+
this._activeResult = null;
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Close the connection.
|
|
3660
|
+
*/
|
|
3661
|
+
async close() {
|
|
3662
|
+
if (this._state === "closed") {
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
this._state = "closed";
|
|
3666
|
+
if (this._activeResult) {
|
|
3667
|
+
this._activeResult._close();
|
|
3668
|
+
this._activeResult = null;
|
|
3669
|
+
}
|
|
3670
|
+
await this._transport.close();
|
|
3671
|
+
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Create a prepared statement.
|
|
3674
|
+
*
|
|
3675
|
+
* @param query - Query text with parameters
|
|
3676
|
+
* @returns Prepared statement
|
|
3677
|
+
*/
|
|
3678
|
+
async prepare(query2) {
|
|
3679
|
+
const { PreparedStatement: PreparedStatement2 } = await Promise.resolve().then(() => (init_prepared(), prepared_exports));
|
|
3680
|
+
return new PreparedStatement2(this, query2);
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* Get the query execution plan without executing.
|
|
3684
|
+
*
|
|
3685
|
+
* @param query - Query to explain
|
|
3686
|
+
* @param options - Options including parameters
|
|
3687
|
+
* @returns Query execution plan
|
|
3688
|
+
*/
|
|
3689
|
+
async explain(query2, options) {
|
|
3690
|
+
const { explain: doExplain } = await Promise.resolve().then(() => (init_explain(), explain_exports));
|
|
3691
|
+
return doExplain(this, query2, options);
|
|
3692
|
+
}
|
|
3693
|
+
/**
|
|
3694
|
+
* Execute a query with profiling.
|
|
3695
|
+
*
|
|
3696
|
+
* @param query - Query to profile
|
|
3697
|
+
* @param options - Options including parameters
|
|
3698
|
+
* @returns Query execution profile
|
|
3699
|
+
*/
|
|
3700
|
+
async profile(query2, options) {
|
|
3701
|
+
const { profile: doProfile } = await Promise.resolve().then(() => (init_explain(), explain_exports));
|
|
3702
|
+
return doProfile(this, query2, options);
|
|
3703
|
+
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Execute multiple queries in a batch.
|
|
3706
|
+
*
|
|
3707
|
+
* @param queries - Array of queries to execute
|
|
3708
|
+
* @param options - Batch execution options
|
|
3709
|
+
* @returns Batch execution summary
|
|
3710
|
+
*/
|
|
3711
|
+
async batch(queries, options) {
|
|
3712
|
+
const { batch: doBatch } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
3713
|
+
return doBatch(this, queries, options);
|
|
3714
|
+
}
|
|
3715
|
+
/**
|
|
3716
|
+
* Check connection state before operation.
|
|
3717
|
+
*/
|
|
3718
|
+
checkState(operation) {
|
|
3719
|
+
if (this._state === "closed") {
|
|
3720
|
+
throw ErrClosed;
|
|
3721
|
+
}
|
|
3722
|
+
if (this._state === "fetching" || this._activeResult) {
|
|
3723
|
+
throw ErrQueryInProgress;
|
|
3724
|
+
}
|
|
3725
|
+
if (operation === "begin" && this._inTransaction) {
|
|
3726
|
+
throw ErrTxInProgress;
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
|
|
3731
|
+
// src/pool.ts
|
|
3732
|
+
init_errors();
|
|
3733
|
+
var ConnectionPool = class _ConnectionPool {
|
|
3734
|
+
_config;
|
|
3735
|
+
_connections = [];
|
|
3736
|
+
_waitQueue = [];
|
|
3737
|
+
_closed = false;
|
|
3738
|
+
_maintenanceInterval;
|
|
3739
|
+
// Pool configuration with defaults
|
|
3740
|
+
_minConnections;
|
|
3741
|
+
_maxConnections;
|
|
3742
|
+
_acquireTimeout;
|
|
3743
|
+
_idleTimeout;
|
|
3744
|
+
constructor(config) {
|
|
3745
|
+
this._config = config;
|
|
3746
|
+
this._minConnections = config.minConnections ?? 2;
|
|
3747
|
+
this._maxConnections = config.maxConnections ?? 10;
|
|
3748
|
+
this._acquireTimeout = config.acquireTimeout ?? 3e4;
|
|
3749
|
+
this._idleTimeout = config.idleTimeout ?? 6e4;
|
|
3750
|
+
this._maintenanceInterval = setInterval(() => {
|
|
3751
|
+
this.maintenance();
|
|
3752
|
+
}, 1e4);
|
|
3753
|
+
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Create a new connection pool.
|
|
3756
|
+
*/
|
|
3757
|
+
static async create(config) {
|
|
3758
|
+
const pool = new _ConnectionPool(config);
|
|
3759
|
+
const initPromises = [];
|
|
3760
|
+
for (let i = 0; i < pool._minConnections; i++) {
|
|
3761
|
+
initPromises.push(pool.addConnection());
|
|
3762
|
+
}
|
|
3763
|
+
try {
|
|
3764
|
+
await Promise.all(initPromises);
|
|
3765
|
+
} catch (e) {
|
|
3766
|
+
await pool.close();
|
|
3767
|
+
throw e;
|
|
3768
|
+
}
|
|
3769
|
+
return pool;
|
|
3770
|
+
}
|
|
3771
|
+
/**
|
|
3772
|
+
* Get pool statistics.
|
|
3773
|
+
*/
|
|
3774
|
+
get stats() {
|
|
3775
|
+
const available = this._connections.filter((c) => !c.inUse).length;
|
|
3776
|
+
const inUse = this._connections.filter((c) => c.inUse).length;
|
|
3777
|
+
return {
|
|
3778
|
+
total: this._connections.length,
|
|
3779
|
+
available,
|
|
3780
|
+
inUse,
|
|
3781
|
+
waiting: this._waitQueue.length
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Check if pool is closed.
|
|
3786
|
+
*/
|
|
3787
|
+
get isClosed() {
|
|
3788
|
+
return this._closed;
|
|
3789
|
+
}
|
|
3790
|
+
/**
|
|
3791
|
+
* Acquire a connection from the pool.
|
|
3792
|
+
*/
|
|
3793
|
+
async acquire(signal) {
|
|
3794
|
+
if (this._closed) {
|
|
3795
|
+
throw ErrClosed;
|
|
3796
|
+
}
|
|
3797
|
+
if (signal?.aborted) {
|
|
3798
|
+
throw new TransportError({ operation: "acquire", cause: new Error("Aborted") });
|
|
3799
|
+
}
|
|
3800
|
+
const available = this._connections.find((c) => !c.inUse && !c.connection.isClosed);
|
|
3801
|
+
if (available) {
|
|
3802
|
+
available.inUse = true;
|
|
3803
|
+
available.lastUsedAt = Date.now();
|
|
3804
|
+
return available.connection;
|
|
3805
|
+
}
|
|
3806
|
+
if (this._connections.length < this._maxConnections) {
|
|
3807
|
+
try {
|
|
3808
|
+
await this.addConnection();
|
|
3809
|
+
const newConn = this._connections.find((c) => !c.inUse && !c.connection.isClosed);
|
|
3810
|
+
if (newConn) {
|
|
3811
|
+
newConn.inUse = true;
|
|
3812
|
+
newConn.lastUsedAt = Date.now();
|
|
3813
|
+
return newConn.connection;
|
|
3814
|
+
}
|
|
3815
|
+
} catch {
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
return new Promise((resolve, reject) => {
|
|
3819
|
+
const timeout = setTimeout(() => {
|
|
3820
|
+
const idx = this._waitQueue.findIndex((w) => w.resolve === resolve && w.reject === reject);
|
|
3821
|
+
if (idx !== -1) {
|
|
3822
|
+
this._waitQueue.splice(idx, 1);
|
|
3823
|
+
}
|
|
3824
|
+
reject(
|
|
3825
|
+
new TransportError({
|
|
3826
|
+
operation: "acquire",
|
|
3827
|
+
cause: new Error("Connection pool acquire timeout")
|
|
3828
|
+
})
|
|
3829
|
+
);
|
|
3830
|
+
}, this._acquireTimeout);
|
|
3831
|
+
const cleanup = () => {
|
|
3832
|
+
clearTimeout(timeout);
|
|
3833
|
+
};
|
|
3834
|
+
if (signal) {
|
|
3835
|
+
signal.addEventListener(
|
|
3836
|
+
"abort",
|
|
3837
|
+
() => {
|
|
3838
|
+
cleanup();
|
|
3839
|
+
const idx = this._waitQueue.findIndex(
|
|
3840
|
+
(w) => w.resolve === resolve && w.reject === reject
|
|
3841
|
+
);
|
|
3842
|
+
if (idx !== -1) {
|
|
3843
|
+
this._waitQueue.splice(idx, 1);
|
|
3844
|
+
}
|
|
3845
|
+
reject(new TransportError({ operation: "acquire", cause: new Error("Aborted") }));
|
|
3846
|
+
},
|
|
3847
|
+
{ once: true }
|
|
3848
|
+
);
|
|
3849
|
+
}
|
|
3850
|
+
this._waitQueue.push({
|
|
3851
|
+
resolve: (conn) => {
|
|
3852
|
+
cleanup();
|
|
3853
|
+
resolve(conn);
|
|
3854
|
+
},
|
|
3855
|
+
reject: (err) => {
|
|
3856
|
+
cleanup();
|
|
3857
|
+
reject(err);
|
|
3858
|
+
},
|
|
3859
|
+
signal
|
|
3860
|
+
});
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
/**
|
|
3864
|
+
* Release a connection back to the pool.
|
|
3865
|
+
*/
|
|
3866
|
+
async release(conn) {
|
|
3867
|
+
const pooledConn = this._connections.find((c) => c.connection === conn);
|
|
3868
|
+
if (!pooledConn) {
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3871
|
+
try {
|
|
3872
|
+
if (conn.inTransaction) {
|
|
3873
|
+
await conn.reset();
|
|
3874
|
+
}
|
|
3875
|
+
} catch {
|
|
3876
|
+
await this.removeConnection(pooledConn);
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
pooledConn.inUse = false;
|
|
3880
|
+
pooledConn.lastUsedAt = Date.now();
|
|
3881
|
+
const waiter = this._waitQueue.shift();
|
|
3882
|
+
if (waiter) {
|
|
3883
|
+
pooledConn.inUse = true;
|
|
3884
|
+
waiter.resolve(conn);
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Execute a function with a connection from the pool.
|
|
3889
|
+
*/
|
|
3890
|
+
async withConnection(fn, signal) {
|
|
3891
|
+
const conn = await this.acquire(signal);
|
|
3892
|
+
try {
|
|
3893
|
+
return await fn(conn);
|
|
3894
|
+
} finally {
|
|
3895
|
+
await this.release(conn);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
/**
|
|
3899
|
+
* Execute a query using a pooled connection.
|
|
3900
|
+
*/
|
|
3901
|
+
async query(query2, options) {
|
|
3902
|
+
return this.withConnection((conn) => conn.query(query2, options), options?.signal);
|
|
3903
|
+
}
|
|
3904
|
+
/**
|
|
3905
|
+
* Execute a query and return all rows.
|
|
3906
|
+
*/
|
|
3907
|
+
async queryAll(query2, options) {
|
|
3908
|
+
return this.withConnection((conn) => conn.queryAll(query2, options), options?.signal);
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* Execute a statement that doesn't return rows.
|
|
3912
|
+
*/
|
|
3913
|
+
async exec(query2, options) {
|
|
3914
|
+
return this.withConnection((conn) => conn.exec(query2, options), options?.signal);
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Execute a function within a transaction.
|
|
3918
|
+
*/
|
|
3919
|
+
async withTransaction(fn, signal) {
|
|
3920
|
+
return this.withConnection((conn) => withTransaction(conn, fn, signal), signal);
|
|
3921
|
+
}
|
|
3922
|
+
/**
|
|
3923
|
+
* Ping to verify pool health.
|
|
3924
|
+
*/
|
|
3925
|
+
async ping(signal) {
|
|
3926
|
+
return this.withConnection((conn) => conn.ping(signal), signal);
|
|
3927
|
+
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Close the pool and all connections.
|
|
3930
|
+
*/
|
|
3931
|
+
async close() {
|
|
3932
|
+
if (this._closed) {
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
this._closed = true;
|
|
3936
|
+
if (this._maintenanceInterval) {
|
|
3937
|
+
clearInterval(this._maintenanceInterval);
|
|
3938
|
+
this._maintenanceInterval = void 0;
|
|
3939
|
+
}
|
|
3940
|
+
for (const waiter of this._waitQueue) {
|
|
3941
|
+
waiter.reject(ErrClosed);
|
|
3942
|
+
}
|
|
3943
|
+
this._waitQueue = [];
|
|
3944
|
+
const closePromises = this._connections.map(async (c) => {
|
|
3945
|
+
try {
|
|
3946
|
+
await c.connection.close();
|
|
3947
|
+
} catch {
|
|
3948
|
+
}
|
|
3949
|
+
});
|
|
3950
|
+
await Promise.all(closePromises);
|
|
3951
|
+
this._connections = [];
|
|
3952
|
+
}
|
|
3953
|
+
/**
|
|
3954
|
+
* Add a new connection to the pool.
|
|
3955
|
+
*/
|
|
3956
|
+
async addConnection() {
|
|
3957
|
+
const conn = await Connection.connect(this._config);
|
|
3958
|
+
this._connections.push({
|
|
3959
|
+
connection: conn,
|
|
3960
|
+
createdAt: Date.now(),
|
|
3961
|
+
lastUsedAt: Date.now(),
|
|
3962
|
+
inUse: false
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Remove a connection from the pool.
|
|
3967
|
+
*/
|
|
3968
|
+
async removeConnection(pooledConn) {
|
|
3969
|
+
const idx = this._connections.indexOf(pooledConn);
|
|
3970
|
+
if (idx !== -1) {
|
|
3971
|
+
this._connections.splice(idx, 1);
|
|
3972
|
+
}
|
|
3973
|
+
try {
|
|
3974
|
+
await pooledConn.connection.close();
|
|
3975
|
+
} catch {
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Perform pool maintenance.
|
|
3980
|
+
*/
|
|
3981
|
+
maintenance() {
|
|
3982
|
+
if (this._closed) {
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
const now = Date.now();
|
|
3986
|
+
const idleConnections = this._connections.filter((c) => !c.inUse && now - c.lastUsedAt > this._idleTimeout).sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
3987
|
+
const toRemove = Math.max(0, idleConnections.length - this._minConnections);
|
|
3988
|
+
for (let i = 0; i < toRemove; i++) {
|
|
3989
|
+
const conn = idleConnections[i];
|
|
3990
|
+
if (conn) {
|
|
3991
|
+
this.removeConnection(conn).catch(() => {
|
|
3992
|
+
});
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
for (const conn of this._connections.filter((c) => c.connection.isClosed)) {
|
|
3996
|
+
this.removeConnection(conn).catch(() => {
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3999
|
+
const available = this._connections.filter((c) => !c.connection.isClosed).length;
|
|
4000
|
+
const toCreate = this._minConnections - available;
|
|
4001
|
+
for (let i = 0; i < toCreate; i++) {
|
|
4002
|
+
this.addConnection().catch(() => {
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
};
|
|
4007
|
+
|
|
4008
|
+
// src/client.ts
|
|
4009
|
+
init_config();
|
|
4010
|
+
var GeodeClient = class _GeodeClient {
|
|
4011
|
+
_config;
|
|
4012
|
+
_pool;
|
|
4013
|
+
_connection;
|
|
4014
|
+
constructor(config) {
|
|
4015
|
+
this._config = config;
|
|
4016
|
+
}
|
|
4017
|
+
/**
|
|
4018
|
+
* Create a new client from a DSN string.
|
|
4019
|
+
*/
|
|
4020
|
+
static async connect(dsn, options) {
|
|
4021
|
+
const config = parseDSN(dsn);
|
|
4022
|
+
if (options) {
|
|
4023
|
+
Object.assign(config, options);
|
|
4024
|
+
}
|
|
4025
|
+
const usePool = options?.pooling ?? true;
|
|
4026
|
+
const client = new _GeodeClient(config);
|
|
4027
|
+
if (usePool) {
|
|
4028
|
+
const poolConfig = {
|
|
4029
|
+
...config,
|
|
4030
|
+
minConnections: options?.pool?.min,
|
|
4031
|
+
maxConnections: options?.pool?.max,
|
|
4032
|
+
acquireTimeout: options?.pool?.acquireTimeout,
|
|
4033
|
+
idleTimeout: options?.pool?.idleTimeout
|
|
4034
|
+
};
|
|
4035
|
+
client._pool = await ConnectionPool.create(poolConfig);
|
|
4036
|
+
} else {
|
|
4037
|
+
client._connection = await Connection.connect(config);
|
|
4038
|
+
}
|
|
4039
|
+
return client;
|
|
4040
|
+
}
|
|
4041
|
+
/**
|
|
4042
|
+
* Create a new client from configuration.
|
|
4043
|
+
*/
|
|
4044
|
+
static async connectWithConfig(config, options) {
|
|
4045
|
+
const usePool = options?.pooling ?? true;
|
|
4046
|
+
const client = new _GeodeClient(config);
|
|
4047
|
+
if (usePool) {
|
|
4048
|
+
const poolConfig = {
|
|
4049
|
+
...config,
|
|
4050
|
+
minConnections: options?.pool?.min,
|
|
4051
|
+
maxConnections: options?.pool?.max,
|
|
4052
|
+
acquireTimeout: options?.pool?.acquireTimeout,
|
|
4053
|
+
idleTimeout: options?.pool?.idleTimeout
|
|
4054
|
+
};
|
|
4055
|
+
client._pool = await ConnectionPool.create(poolConfig);
|
|
4056
|
+
} else {
|
|
4057
|
+
client._connection = await Connection.connect(config);
|
|
4058
|
+
}
|
|
4059
|
+
return client;
|
|
4060
|
+
}
|
|
4061
|
+
/**
|
|
4062
|
+
* Get the client configuration.
|
|
4063
|
+
*/
|
|
4064
|
+
get config() {
|
|
4065
|
+
return this._config;
|
|
4066
|
+
}
|
|
4067
|
+
/**
|
|
4068
|
+
* Check if client is closed.
|
|
4069
|
+
*/
|
|
4070
|
+
get isClosed() {
|
|
4071
|
+
if (this._pool) {
|
|
4072
|
+
return this._pool.isClosed;
|
|
4073
|
+
}
|
|
4074
|
+
return this._connection?.isClosed ?? true;
|
|
4075
|
+
}
|
|
4076
|
+
/**
|
|
4077
|
+
* Get pool statistics (if using pooling).
|
|
4078
|
+
*/
|
|
4079
|
+
get poolStats() {
|
|
4080
|
+
return this._pool?.stats ?? null;
|
|
4081
|
+
}
|
|
4082
|
+
/**
|
|
4083
|
+
* Execute a query and return results.
|
|
4084
|
+
*/
|
|
4085
|
+
async query(query2, options) {
|
|
4086
|
+
if (this._pool) {
|
|
4087
|
+
return this._pool.query(query2, options);
|
|
4088
|
+
}
|
|
4089
|
+
if (!this._connection) {
|
|
4090
|
+
throw new Error("Client is not connected");
|
|
4091
|
+
}
|
|
4092
|
+
return this._connection.query(query2, options);
|
|
4093
|
+
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Execute a query and return all rows as objects.
|
|
4096
|
+
*/
|
|
4097
|
+
async queryAll(query2, options) {
|
|
4098
|
+
if (this._pool) {
|
|
4099
|
+
return this._pool.queryAll(query2, options);
|
|
4100
|
+
}
|
|
4101
|
+
if (!this._connection) {
|
|
4102
|
+
throw new Error("Client is not connected");
|
|
4103
|
+
}
|
|
4104
|
+
return this._connection.queryAll(query2, options);
|
|
4105
|
+
}
|
|
4106
|
+
/**
|
|
4107
|
+
* Execute a query and return the first row.
|
|
4108
|
+
*/
|
|
4109
|
+
async queryFirst(query2, options) {
|
|
4110
|
+
const result = await this.query(query2, options);
|
|
4111
|
+
return result.firstObject();
|
|
4112
|
+
}
|
|
4113
|
+
/**
|
|
4114
|
+
* Execute a query and return a scalar value.
|
|
4115
|
+
*/
|
|
4116
|
+
async queryScalar(query2, column, options) {
|
|
4117
|
+
const row = await this.queryFirst(query2, options);
|
|
4118
|
+
if (!row) {
|
|
4119
|
+
return null;
|
|
4120
|
+
}
|
|
4121
|
+
return row[column];
|
|
4122
|
+
}
|
|
4123
|
+
/**
|
|
4124
|
+
* Execute a statement that doesn't return rows.
|
|
4125
|
+
*/
|
|
4126
|
+
async exec(query2, options) {
|
|
4127
|
+
if (this._pool) {
|
|
4128
|
+
return this._pool.exec(query2, options);
|
|
4129
|
+
}
|
|
4130
|
+
if (!this._connection) {
|
|
4131
|
+
throw new Error("Client is not connected");
|
|
4132
|
+
}
|
|
4133
|
+
return this._connection.exec(query2, options);
|
|
4134
|
+
}
|
|
4135
|
+
/**
|
|
4136
|
+
* Execute multiple statements.
|
|
4137
|
+
*/
|
|
4138
|
+
async execBatch(queries) {
|
|
4139
|
+
for (const q of queries) {
|
|
4140
|
+
await this.exec(q.query, { params: q.params });
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
/**
|
|
4144
|
+
* Execute a function within a transaction.
|
|
4145
|
+
*/
|
|
4146
|
+
async withTransaction(fn, signal) {
|
|
4147
|
+
if (this._pool) {
|
|
4148
|
+
return this._pool.withTransaction(fn, signal);
|
|
4149
|
+
}
|
|
4150
|
+
if (!this._connection) {
|
|
4151
|
+
throw new Error("Client is not connected");
|
|
4152
|
+
}
|
|
4153
|
+
return withTransaction(this._connection, fn, signal);
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Get a raw connection from the pool (or the single connection).
|
|
4157
|
+
*
|
|
4158
|
+
* The connection must be released after use.
|
|
4159
|
+
*/
|
|
4160
|
+
async getConnection(signal) {
|
|
4161
|
+
if (this._pool) {
|
|
4162
|
+
return this._pool.acquire(signal);
|
|
4163
|
+
}
|
|
4164
|
+
if (!this._connection) {
|
|
4165
|
+
throw new Error("Client is not connected");
|
|
4166
|
+
}
|
|
4167
|
+
return this._connection;
|
|
4168
|
+
}
|
|
4169
|
+
/**
|
|
4170
|
+
* Release a connection back to the pool.
|
|
4171
|
+
*
|
|
4172
|
+
* Only needed when using getConnection() with pooling.
|
|
4173
|
+
*/
|
|
4174
|
+
async releaseConnection(conn) {
|
|
4175
|
+
if (this._pool) {
|
|
4176
|
+
return this._pool.release(conn);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* Execute a function with a dedicated connection.
|
|
4181
|
+
*/
|
|
4182
|
+
async withConnection(fn, signal) {
|
|
4183
|
+
if (this._pool) {
|
|
4184
|
+
return this._pool.withConnection(fn, signal);
|
|
4185
|
+
}
|
|
4186
|
+
if (!this._connection) {
|
|
4187
|
+
throw new Error("Client is not connected");
|
|
4188
|
+
}
|
|
4189
|
+
return fn(this._connection);
|
|
4190
|
+
}
|
|
4191
|
+
/**
|
|
4192
|
+
* Ping the server to check connectivity.
|
|
4193
|
+
*/
|
|
4194
|
+
async ping(signal) {
|
|
4195
|
+
if (this._pool) {
|
|
4196
|
+
return this._pool.ping(signal);
|
|
4197
|
+
}
|
|
4198
|
+
if (!this._connection) {
|
|
4199
|
+
throw new Error("Client is not connected");
|
|
4200
|
+
}
|
|
4201
|
+
return this._connection.ping(signal);
|
|
4202
|
+
}
|
|
4203
|
+
/**
|
|
4204
|
+
* Close the client and release all resources.
|
|
4205
|
+
*/
|
|
4206
|
+
async close() {
|
|
4207
|
+
if (this._pool) {
|
|
4208
|
+
await this._pool.close();
|
|
4209
|
+
this._pool = void 0;
|
|
4210
|
+
}
|
|
4211
|
+
if (this._connection) {
|
|
4212
|
+
await this._connection.close();
|
|
4213
|
+
this._connection = void 0;
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
// Convenience methods for common graph operations
|
|
4217
|
+
/**
|
|
4218
|
+
* Create a node with labels and properties.
|
|
4219
|
+
*/
|
|
4220
|
+
async createNode(labels, properties) {
|
|
4221
|
+
const labelStr = Array.isArray(labels) ? labels.join(":") : labels;
|
|
4222
|
+
const propsStr = properties ? " $props" : "";
|
|
4223
|
+
const result = await this.queryFirst(
|
|
4224
|
+
`CREATE (n:${labelStr}${propsStr}) RETURN n`,
|
|
4225
|
+
properties ? { params: { props: properties } } : void 0
|
|
4226
|
+
);
|
|
4227
|
+
return result?.["n"];
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* Find nodes by label and optional property filter.
|
|
4231
|
+
*/
|
|
4232
|
+
async findNodes(label, filter, options) {
|
|
4233
|
+
let query2 = `MATCH (n:${label})`;
|
|
4234
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
4235
|
+
const conditions = Object.keys(filter).map((k) => `n.${k} = $${k}`).join(" AND ");
|
|
4236
|
+
query2 += ` WHERE ${conditions}`;
|
|
4237
|
+
}
|
|
4238
|
+
query2 += " RETURN n";
|
|
4239
|
+
if (options?.skip) {
|
|
4240
|
+
query2 += ` SKIP ${options.skip}`;
|
|
4241
|
+
}
|
|
4242
|
+
if (options?.limit) {
|
|
4243
|
+
query2 += ` LIMIT ${options.limit}`;
|
|
4244
|
+
}
|
|
4245
|
+
const rows = await this.queryAll(query2, { params: filter });
|
|
4246
|
+
return rows.map((r) => r["n"]);
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Find a single node by label and property.
|
|
4250
|
+
*/
|
|
4251
|
+
async findNode(label, property, value) {
|
|
4252
|
+
const rows = await this.findNodes(label, { [property]: value }, { limit: 1 });
|
|
4253
|
+
return rows[0] ?? null;
|
|
4254
|
+
}
|
|
4255
|
+
/**
|
|
4256
|
+
* Create a relationship between two nodes.
|
|
4257
|
+
*/
|
|
4258
|
+
async createRelationship(startLabel, startProp, startValue, type, endLabel, endProp, endValue, properties) {
|
|
4259
|
+
const propsStr = properties ? " $props" : "";
|
|
4260
|
+
const result = await this.queryFirst(
|
|
4261
|
+
`MATCH (a:${startLabel}), (b:${endLabel})
|
|
4262
|
+
WHERE a.${startProp} = $startValue AND b.${endProp} = $endValue
|
|
4263
|
+
CREATE (a)-[r:${type}${propsStr}]->(b)
|
|
4264
|
+
RETURN r`,
|
|
4265
|
+
{
|
|
4266
|
+
params: {
|
|
4267
|
+
startValue,
|
|
4268
|
+
endValue,
|
|
4269
|
+
...properties ? { props: properties } : {}
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
);
|
|
4273
|
+
return result?.["r"];
|
|
4274
|
+
}
|
|
4275
|
+
/**
|
|
4276
|
+
* Delete a node by label and property.
|
|
4277
|
+
*/
|
|
4278
|
+
async deleteNode(label, property, value) {
|
|
4279
|
+
const result = await this.queryFirst(
|
|
4280
|
+
`MATCH (n:${label}) WHERE n.${property} = $value DETACH DELETE n RETURN count(n) AS deleted`,
|
|
4281
|
+
{ params: { value } }
|
|
4282
|
+
);
|
|
4283
|
+
return result?.["deleted"] ?? 0;
|
|
4284
|
+
}
|
|
4285
|
+
/**
|
|
4286
|
+
* Update node properties.
|
|
4287
|
+
*/
|
|
4288
|
+
async updateNode(label, matchProp, matchValue, updates) {
|
|
4289
|
+
const setStatements = Object.keys(updates).map((k) => `n.${k} = $${k}`).join(", ");
|
|
4290
|
+
const result = await this.queryFirst(
|
|
4291
|
+
`MATCH (n:${label}) WHERE n.${matchProp} = $matchValue SET ${setStatements} RETURN n`,
|
|
4292
|
+
{ params: { matchValue, ...updates } }
|
|
4293
|
+
);
|
|
4294
|
+
return result?.["n"];
|
|
4295
|
+
}
|
|
4296
|
+
/**
|
|
4297
|
+
* Count nodes by label.
|
|
4298
|
+
*/
|
|
4299
|
+
async countNodes(label, filter) {
|
|
4300
|
+
let query2 = `MATCH (n:${label})`;
|
|
4301
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
4302
|
+
const conditions = Object.keys(filter).map((k) => `n.${k} = $${k}`).join(" AND ");
|
|
4303
|
+
query2 += ` WHERE ${conditions}`;
|
|
4304
|
+
}
|
|
4305
|
+
query2 += " RETURN count(n) AS count";
|
|
4306
|
+
const result = await this.queryFirst(query2, { params: filter });
|
|
4307
|
+
return result?.["count"] ?? 0;
|
|
4308
|
+
}
|
|
4309
|
+
// Advanced operations
|
|
4310
|
+
/**
|
|
4311
|
+
* Create a prepared statement.
|
|
4312
|
+
*
|
|
4313
|
+
* @param query - Query text with parameters
|
|
4314
|
+
* @returns Prepared statement
|
|
4315
|
+
*
|
|
4316
|
+
* @example
|
|
4317
|
+
* ```typescript
|
|
4318
|
+
* const stmt = client.prepare('MATCH (n:Person {name: $name}) RETURN n');
|
|
4319
|
+
* const result1 = await stmt.query({ name: 'Alice' });
|
|
4320
|
+
* const result2 = await stmt.query({ name: 'Bob' });
|
|
4321
|
+
* stmt.close();
|
|
4322
|
+
* ```
|
|
4323
|
+
*/
|
|
4324
|
+
async prepare(query2) {
|
|
4325
|
+
const conn = await this.getConnection();
|
|
4326
|
+
try {
|
|
4327
|
+
return conn.prepare(query2);
|
|
4328
|
+
} finally {
|
|
4329
|
+
await this.releaseConnection(conn);
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
/**
|
|
4333
|
+
* Get the query execution plan without executing.
|
|
4334
|
+
*
|
|
4335
|
+
* @param query - Query to explain
|
|
4336
|
+
* @param options - Options including parameters
|
|
4337
|
+
* @returns Query execution plan
|
|
4338
|
+
*
|
|
4339
|
+
* @example
|
|
4340
|
+
* ```typescript
|
|
4341
|
+
* const plan = await client.explain('MATCH (n:Person)-[:KNOWS]->(m) RETURN n, m');
|
|
4342
|
+
* console.log('Estimated cost:', plan.totalCost);
|
|
4343
|
+
* ```
|
|
4344
|
+
*/
|
|
4345
|
+
async explain(query2, options) {
|
|
4346
|
+
const conn = await this.getConnection(options?.signal);
|
|
4347
|
+
try {
|
|
4348
|
+
return await conn.explain(query2, options);
|
|
4349
|
+
} finally {
|
|
4350
|
+
await this.releaseConnection(conn);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
/**
|
|
4354
|
+
* Execute a query with profiling.
|
|
4355
|
+
*
|
|
4356
|
+
* @param query - Query to profile
|
|
4357
|
+
* @param options - Options including parameters
|
|
4358
|
+
* @returns Query execution profile
|
|
4359
|
+
*
|
|
4360
|
+
* @example
|
|
4361
|
+
* ```typescript
|
|
4362
|
+
* const profile = await client.profile('MATCH (n:Person) RETURN count(n)');
|
|
4363
|
+
* console.log('Execution time:', profile.totalTimeMs, 'ms');
|
|
4364
|
+
* ```
|
|
4365
|
+
*/
|
|
4366
|
+
async profile(query2, options) {
|
|
4367
|
+
const conn = await this.getConnection(options?.signal);
|
|
4368
|
+
try {
|
|
4369
|
+
return await conn.profile(query2, options);
|
|
4370
|
+
} finally {
|
|
4371
|
+
await this.releaseConnection(conn);
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
/**
|
|
4375
|
+
* Execute multiple queries in a batch.
|
|
4376
|
+
*
|
|
4377
|
+
* @param queries - Array of queries to execute
|
|
4378
|
+
* @param options - Batch execution options
|
|
4379
|
+
* @returns Batch execution summary
|
|
4380
|
+
*
|
|
4381
|
+
* @example
|
|
4382
|
+
* ```typescript
|
|
4383
|
+
* const summary = await client.batch([
|
|
4384
|
+
* { query: 'CREATE (a:Person {name: "Alice"})' },
|
|
4385
|
+
* { query: 'CREATE (b:Person {name: "Bob"})' },
|
|
4386
|
+
* ]);
|
|
4387
|
+
* console.log(`${summary.successful}/${summary.total} succeeded`);
|
|
4388
|
+
* ```
|
|
4389
|
+
*/
|
|
4390
|
+
async batch(queries, options) {
|
|
4391
|
+
const conn = await this.getConnection(options?.signal);
|
|
4392
|
+
try {
|
|
4393
|
+
return await conn.batch(queries, options);
|
|
4394
|
+
} finally {
|
|
4395
|
+
await this.releaseConnection(conn);
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Get an auth client for user and role management.
|
|
4400
|
+
*
|
|
4401
|
+
* @returns Auth client
|
|
4402
|
+
*
|
|
4403
|
+
* @example
|
|
4404
|
+
* ```typescript
|
|
4405
|
+
* const auth = await client.auth();
|
|
4406
|
+
* await auth.createUser('alice', { password: 'secret123' });
|
|
4407
|
+
* await auth.assignRole('alice', 'analyst');
|
|
4408
|
+
* ```
|
|
4409
|
+
*/
|
|
4410
|
+
async auth() {
|
|
4411
|
+
const { AuthClient: AuthClient2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
4412
|
+
const conn = await this.getConnection();
|
|
4413
|
+
return new AuthClient2(conn);
|
|
4414
|
+
}
|
|
4415
|
+
};
|
|
4416
|
+
async function createClient(dsn, options) {
|
|
4417
|
+
return GeodeClient.connect(dsn, options);
|
|
4418
|
+
}
|
|
4419
|
+
async function createClientWithConfig(config, options) {
|
|
4420
|
+
return GeodeClient.connectWithConfig(config, options);
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
// src/index.ts
|
|
4424
|
+
init_config();
|
|
4425
|
+
init_types();
|
|
4426
|
+
init_errors();
|
|
4427
|
+
init_protocol();
|
|
4428
|
+
init_validate();
|
|
4429
|
+
|
|
4430
|
+
// src/query-builder.ts
|
|
4431
|
+
var NodePatternBuilder = class {
|
|
4432
|
+
_variable;
|
|
4433
|
+
_labels = [];
|
|
4434
|
+
_properties = {};
|
|
4435
|
+
/**
|
|
4436
|
+
* Set the variable name.
|
|
4437
|
+
*/
|
|
4438
|
+
as(variable) {
|
|
4439
|
+
this._variable = variable;
|
|
4440
|
+
return this;
|
|
4441
|
+
}
|
|
4442
|
+
/**
|
|
4443
|
+
* Add a label.
|
|
4444
|
+
*/
|
|
4445
|
+
label(label) {
|
|
4446
|
+
this._labels.push(label);
|
|
4447
|
+
return this;
|
|
4448
|
+
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Add multiple labels.
|
|
4451
|
+
*/
|
|
4452
|
+
labels(...labels) {
|
|
4453
|
+
this._labels.push(...labels);
|
|
4454
|
+
return this;
|
|
4455
|
+
}
|
|
4456
|
+
/**
|
|
4457
|
+
* Set a property.
|
|
4458
|
+
*/
|
|
4459
|
+
prop(key, value) {
|
|
4460
|
+
this._properties[key] = value;
|
|
4461
|
+
return this;
|
|
4462
|
+
}
|
|
4463
|
+
/**
|
|
4464
|
+
* Set multiple properties.
|
|
4465
|
+
*/
|
|
4466
|
+
props(properties) {
|
|
4467
|
+
Object.assign(this._properties, properties);
|
|
4468
|
+
return this;
|
|
4469
|
+
}
|
|
4470
|
+
/**
|
|
4471
|
+
* Build the pattern.
|
|
4472
|
+
*/
|
|
4473
|
+
build() {
|
|
4474
|
+
return {
|
|
4475
|
+
variable: this._variable,
|
|
4476
|
+
labels: this._labels.length > 0 ? this._labels : void 0,
|
|
4477
|
+
properties: Object.keys(this._properties).length > 0 ? this._properties : void 0
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
/**
|
|
4481
|
+
* Convert to GQL string.
|
|
4482
|
+
*/
|
|
4483
|
+
toGQL() {
|
|
4484
|
+
let result = "(";
|
|
4485
|
+
if (this._variable) {
|
|
4486
|
+
result += this._variable;
|
|
4487
|
+
}
|
|
4488
|
+
if (this._labels.length > 0) {
|
|
4489
|
+
result += ":" + this._labels.join(":");
|
|
4490
|
+
}
|
|
4491
|
+
if (Object.keys(this._properties).length > 0) {
|
|
4492
|
+
result += " " + propsToGQL(this._properties);
|
|
4493
|
+
}
|
|
4494
|
+
return result + ")";
|
|
4495
|
+
}
|
|
4496
|
+
};
|
|
4497
|
+
var EdgePatternBuilder = class {
|
|
4498
|
+
_variable;
|
|
4499
|
+
_type;
|
|
4500
|
+
_direction = "outgoing";
|
|
4501
|
+
_properties = {};
|
|
4502
|
+
_minHops;
|
|
4503
|
+
_maxHops;
|
|
4504
|
+
/**
|
|
4505
|
+
* Set the variable name.
|
|
4506
|
+
*/
|
|
4507
|
+
as(variable) {
|
|
4508
|
+
this._variable = variable;
|
|
4509
|
+
return this;
|
|
4510
|
+
}
|
|
4511
|
+
/**
|
|
4512
|
+
* Set the relationship type.
|
|
4513
|
+
*/
|
|
4514
|
+
type(type) {
|
|
4515
|
+
this._type = type;
|
|
4516
|
+
return this;
|
|
4517
|
+
}
|
|
4518
|
+
/**
|
|
4519
|
+
* Set outgoing direction.
|
|
4520
|
+
*/
|
|
4521
|
+
outgoing() {
|
|
4522
|
+
this._direction = "outgoing";
|
|
4523
|
+
return this;
|
|
4524
|
+
}
|
|
4525
|
+
/**
|
|
4526
|
+
* Set incoming direction.
|
|
4527
|
+
*/
|
|
4528
|
+
incoming() {
|
|
4529
|
+
this._direction = "incoming";
|
|
4530
|
+
return this;
|
|
4531
|
+
}
|
|
4532
|
+
/**
|
|
4533
|
+
* Set bidirectional.
|
|
4534
|
+
*/
|
|
4535
|
+
both() {
|
|
4536
|
+
this._direction = "both";
|
|
4537
|
+
return this;
|
|
4538
|
+
}
|
|
4539
|
+
/**
|
|
4540
|
+
* Set direction.
|
|
4541
|
+
*/
|
|
4542
|
+
direction(dir) {
|
|
4543
|
+
this._direction = dir;
|
|
4544
|
+
return this;
|
|
4545
|
+
}
|
|
4546
|
+
/**
|
|
4547
|
+
* Set a property.
|
|
4548
|
+
*/
|
|
4549
|
+
prop(key, value) {
|
|
4550
|
+
this._properties[key] = value;
|
|
4551
|
+
return this;
|
|
4552
|
+
}
|
|
4553
|
+
/**
|
|
4554
|
+
* Set multiple properties.
|
|
4555
|
+
*/
|
|
4556
|
+
props(properties) {
|
|
4557
|
+
Object.assign(this._properties, properties);
|
|
4558
|
+
return this;
|
|
4559
|
+
}
|
|
4560
|
+
/**
|
|
4561
|
+
* Set variable length path.
|
|
4562
|
+
*/
|
|
4563
|
+
hops(min, max) {
|
|
4564
|
+
this._minHops = min;
|
|
4565
|
+
this._maxHops = max;
|
|
4566
|
+
return this;
|
|
4567
|
+
}
|
|
4568
|
+
/**
|
|
4569
|
+
* Build the pattern.
|
|
4570
|
+
*/
|
|
4571
|
+
build() {
|
|
4572
|
+
return {
|
|
4573
|
+
variable: this._variable,
|
|
4574
|
+
type: this._type,
|
|
4575
|
+
direction: this._direction,
|
|
4576
|
+
properties: Object.keys(this._properties).length > 0 ? this._properties : void 0,
|
|
4577
|
+
minHops: this._minHops,
|
|
4578
|
+
maxHops: this._maxHops
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* Convert to GQL string.
|
|
4583
|
+
*/
|
|
4584
|
+
toGQL() {
|
|
4585
|
+
const hasContent = this._variable || this._type || Object.keys(this._properties).length > 0;
|
|
4586
|
+
let inner = "";
|
|
4587
|
+
if (hasContent) {
|
|
4588
|
+
inner = "[";
|
|
4589
|
+
if (this._variable) {
|
|
4590
|
+
inner += this._variable;
|
|
4591
|
+
}
|
|
4592
|
+
if (this._type) {
|
|
4593
|
+
inner += ":" + this._type;
|
|
4594
|
+
}
|
|
4595
|
+
if (this._minHops !== void 0 || this._maxHops !== void 0) {
|
|
4596
|
+
inner += "*";
|
|
4597
|
+
if (this._minHops !== void 0) {
|
|
4598
|
+
inner += this._minHops;
|
|
4599
|
+
}
|
|
4600
|
+
inner += "..";
|
|
4601
|
+
if (this._maxHops !== void 0) {
|
|
4602
|
+
inner += this._maxHops;
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
if (Object.keys(this._properties).length > 0) {
|
|
4606
|
+
inner += " " + propsToGQL(this._properties);
|
|
4607
|
+
}
|
|
4608
|
+
inner += "]";
|
|
4609
|
+
}
|
|
4610
|
+
switch (this._direction) {
|
|
4611
|
+
case "outgoing":
|
|
4612
|
+
return `-${inner}->`;
|
|
4613
|
+
case "incoming":
|
|
4614
|
+
return `<-${inner}-`;
|
|
4615
|
+
case "both":
|
|
4616
|
+
return `-${inner}-`;
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4620
|
+
var PatternBuilder = class {
|
|
4621
|
+
_elements = [];
|
|
4622
|
+
/**
|
|
4623
|
+
* Add a node to the pattern.
|
|
4624
|
+
*/
|
|
4625
|
+
node(builder) {
|
|
4626
|
+
const nodeBuilder = new NodePatternBuilder();
|
|
4627
|
+
if (builder) {
|
|
4628
|
+
builder(nodeBuilder);
|
|
4629
|
+
}
|
|
4630
|
+
this._elements.push({ kind: "node", pattern: nodeBuilder.build() });
|
|
4631
|
+
return this;
|
|
4632
|
+
}
|
|
4633
|
+
/**
|
|
4634
|
+
* Add an outgoing edge to the pattern.
|
|
4635
|
+
*/
|
|
4636
|
+
outgoing(builder) {
|
|
4637
|
+
const edgeBuilder = new EdgePatternBuilder().outgoing();
|
|
4638
|
+
if (builder) {
|
|
4639
|
+
builder(edgeBuilder);
|
|
4640
|
+
}
|
|
4641
|
+
this._elements.push({ kind: "edge", pattern: edgeBuilder.build() });
|
|
4642
|
+
return this;
|
|
4643
|
+
}
|
|
4644
|
+
/**
|
|
4645
|
+
* Add an incoming edge to the pattern.
|
|
4646
|
+
*/
|
|
4647
|
+
incoming(builder) {
|
|
4648
|
+
const edgeBuilder = new EdgePatternBuilder().incoming();
|
|
4649
|
+
if (builder) {
|
|
4650
|
+
builder(edgeBuilder);
|
|
4651
|
+
}
|
|
4652
|
+
this._elements.push({ kind: "edge", pattern: edgeBuilder.build() });
|
|
4653
|
+
return this;
|
|
4654
|
+
}
|
|
4655
|
+
/**
|
|
4656
|
+
* Add a bidirectional edge to the pattern.
|
|
4657
|
+
*/
|
|
4658
|
+
related(builder) {
|
|
4659
|
+
const edgeBuilder = new EdgePatternBuilder().both();
|
|
4660
|
+
if (builder) {
|
|
4661
|
+
builder(edgeBuilder);
|
|
4662
|
+
}
|
|
4663
|
+
this._elements.push({ kind: "edge", pattern: edgeBuilder.build() });
|
|
4664
|
+
return this;
|
|
4665
|
+
}
|
|
4666
|
+
/**
|
|
4667
|
+
* Convert to GQL string.
|
|
4668
|
+
*/
|
|
4669
|
+
toGQL() {
|
|
4670
|
+
let result = "";
|
|
4671
|
+
for (const element of this._elements) {
|
|
4672
|
+
if (element.kind === "node") {
|
|
4673
|
+
result += nodePatternToGQL(element.pattern);
|
|
4674
|
+
} else {
|
|
4675
|
+
result += edgePatternToGQL(element.pattern);
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
return result;
|
|
4679
|
+
}
|
|
4680
|
+
};
|
|
4681
|
+
var PredicateBuilder = class {
|
|
4682
|
+
_predicates = [];
|
|
4683
|
+
_params = {};
|
|
4684
|
+
_paramCounter = 0;
|
|
4685
|
+
/**
|
|
4686
|
+
* Add a comparison predicate.
|
|
4687
|
+
*/
|
|
4688
|
+
compare(left, op, right) {
|
|
4689
|
+
if (op === "IS NULL") {
|
|
4690
|
+
this._predicates.push(`${left} IS NULL`);
|
|
4691
|
+
} else if (op === "IS NOT NULL") {
|
|
4692
|
+
this._predicates.push(`${left} IS NOT NULL`);
|
|
4693
|
+
} else {
|
|
4694
|
+
const paramName = this.nextParam();
|
|
4695
|
+
this._predicates.push(`${left} ${op} $${paramName}`);
|
|
4696
|
+
this._params[paramName] = right;
|
|
4697
|
+
}
|
|
4698
|
+
return this;
|
|
4699
|
+
}
|
|
4700
|
+
/**
|
|
4701
|
+
* Add an equality predicate.
|
|
4702
|
+
*/
|
|
4703
|
+
eq(left, right) {
|
|
4704
|
+
return this.compare(left, "=", right);
|
|
4705
|
+
}
|
|
4706
|
+
/**
|
|
4707
|
+
* Add a not-equal predicate.
|
|
4708
|
+
*/
|
|
4709
|
+
neq(left, right) {
|
|
4710
|
+
return this.compare(left, "<>", right);
|
|
4711
|
+
}
|
|
4712
|
+
/**
|
|
4713
|
+
* Add a less-than predicate.
|
|
4714
|
+
*/
|
|
4715
|
+
lt(left, right) {
|
|
4716
|
+
return this.compare(left, "<", right);
|
|
4717
|
+
}
|
|
4718
|
+
/**
|
|
4719
|
+
* Add a less-than-or-equal predicate.
|
|
4720
|
+
*/
|
|
4721
|
+
lte(left, right) {
|
|
4722
|
+
return this.compare(left, "<=", right);
|
|
4723
|
+
}
|
|
4724
|
+
/**
|
|
4725
|
+
* Add a greater-than predicate.
|
|
4726
|
+
*/
|
|
4727
|
+
gt(left, right) {
|
|
4728
|
+
return this.compare(left, ">", right);
|
|
4729
|
+
}
|
|
4730
|
+
/**
|
|
4731
|
+
* Add a greater-than-or-equal predicate.
|
|
4732
|
+
*/
|
|
4733
|
+
gte(left, right) {
|
|
4734
|
+
return this.compare(left, ">=", right);
|
|
4735
|
+
}
|
|
4736
|
+
/**
|
|
4737
|
+
* Add an IN predicate.
|
|
4738
|
+
*/
|
|
4739
|
+
in(left, values) {
|
|
4740
|
+
const paramName = this.nextParam();
|
|
4741
|
+
this._predicates.push(`${left} IN $${paramName}`);
|
|
4742
|
+
this._params[paramName] = values;
|
|
4743
|
+
return this;
|
|
4744
|
+
}
|
|
4745
|
+
/**
|
|
4746
|
+
* Add a NOT IN predicate.
|
|
4747
|
+
*/
|
|
4748
|
+
notIn(left, values) {
|
|
4749
|
+
const paramName = this.nextParam();
|
|
4750
|
+
this._predicates.push(`${left} NOT IN $${paramName}`);
|
|
4751
|
+
this._params[paramName] = values;
|
|
4752
|
+
return this;
|
|
4753
|
+
}
|
|
4754
|
+
/**
|
|
4755
|
+
* Add a CONTAINS predicate.
|
|
4756
|
+
*/
|
|
4757
|
+
contains(left, value) {
|
|
4758
|
+
return this.compare(left, "CONTAINS", value);
|
|
4759
|
+
}
|
|
4760
|
+
/**
|
|
4761
|
+
* Add a STARTS WITH predicate.
|
|
4762
|
+
*/
|
|
4763
|
+
startsWith(left, value) {
|
|
4764
|
+
return this.compare(left, "STARTS WITH", value);
|
|
4765
|
+
}
|
|
4766
|
+
/**
|
|
4767
|
+
* Add an ENDS WITH predicate.
|
|
4768
|
+
*/
|
|
4769
|
+
endsWith(left, value) {
|
|
4770
|
+
return this.compare(left, "ENDS WITH", value);
|
|
4771
|
+
}
|
|
4772
|
+
/**
|
|
4773
|
+
* Add an IS NULL predicate.
|
|
4774
|
+
*/
|
|
4775
|
+
isNull(expr) {
|
|
4776
|
+
return this.compare(expr, "IS NULL", null);
|
|
4777
|
+
}
|
|
4778
|
+
/**
|
|
4779
|
+
* Add an IS NOT NULL predicate.
|
|
4780
|
+
*/
|
|
4781
|
+
isNotNull(expr) {
|
|
4782
|
+
return this.compare(expr, "IS NOT NULL", null);
|
|
4783
|
+
}
|
|
4784
|
+
/**
|
|
4785
|
+
* Add a raw predicate expression.
|
|
4786
|
+
*/
|
|
4787
|
+
raw(expr, params) {
|
|
4788
|
+
this._predicates.push(expr);
|
|
4789
|
+
if (params) {
|
|
4790
|
+
Object.assign(this._params, params);
|
|
4791
|
+
}
|
|
4792
|
+
return this;
|
|
4793
|
+
}
|
|
4794
|
+
/**
|
|
4795
|
+
* Combine predicates with AND.
|
|
4796
|
+
*/
|
|
4797
|
+
and(builder) {
|
|
4798
|
+
const sub = builder.build();
|
|
4799
|
+
if (sub.predicate) {
|
|
4800
|
+
this._predicates.push(`(${sub.predicate})`);
|
|
4801
|
+
Object.assign(this._params, sub.params);
|
|
4802
|
+
}
|
|
4803
|
+
return this;
|
|
4804
|
+
}
|
|
4805
|
+
/**
|
|
4806
|
+
* Combine predicates with OR.
|
|
4807
|
+
*/
|
|
4808
|
+
or(builder) {
|
|
4809
|
+
const sub = builder.build();
|
|
4810
|
+
if (sub.predicate) {
|
|
4811
|
+
if (this._predicates.length > 0) {
|
|
4812
|
+
const current = this._predicates.join(" AND ");
|
|
4813
|
+
this._predicates = [`(${current}) OR (${sub.predicate})`];
|
|
4814
|
+
} else {
|
|
4815
|
+
this._predicates.push(sub.predicate);
|
|
4816
|
+
}
|
|
4817
|
+
Object.assign(this._params, sub.params);
|
|
4818
|
+
}
|
|
4819
|
+
return this;
|
|
4820
|
+
}
|
|
4821
|
+
/**
|
|
4822
|
+
* Build the predicate.
|
|
4823
|
+
*/
|
|
4824
|
+
build() {
|
|
4825
|
+
return {
|
|
4826
|
+
predicate: this._predicates.join(" AND "),
|
|
4827
|
+
params: this._params
|
|
4828
|
+
};
|
|
4829
|
+
}
|
|
4830
|
+
nextParam() {
|
|
4831
|
+
return `pred_${++this._paramCounter}`;
|
|
4832
|
+
}
|
|
4833
|
+
};
|
|
4834
|
+
var QueryBuilder = class {
|
|
4835
|
+
_clauses = [];
|
|
4836
|
+
_params = {};
|
|
4837
|
+
/**
|
|
4838
|
+
* Add a MATCH clause.
|
|
4839
|
+
*/
|
|
4840
|
+
match(pattern2) {
|
|
4841
|
+
const p = typeof pattern2 === "string" ? pattern2 : pattern2.toGQL();
|
|
4842
|
+
this._clauses.push(`MATCH ${p}`);
|
|
4843
|
+
return this;
|
|
4844
|
+
}
|
|
4845
|
+
/**
|
|
4846
|
+
* Add an OPTIONAL MATCH clause.
|
|
4847
|
+
*/
|
|
4848
|
+
optionalMatch(pattern2) {
|
|
4849
|
+
const p = typeof pattern2 === "string" ? pattern2 : pattern2.toGQL();
|
|
4850
|
+
this._clauses.push(`OPTIONAL MATCH ${p}`);
|
|
4851
|
+
return this;
|
|
4852
|
+
}
|
|
4853
|
+
/**
|
|
4854
|
+
* Add a WHERE clause.
|
|
4855
|
+
*/
|
|
4856
|
+
where(predicate2, params) {
|
|
4857
|
+
if (typeof predicate2 === "string") {
|
|
4858
|
+
this._clauses.push(`WHERE ${predicate2}`);
|
|
4859
|
+
if (params) {
|
|
4860
|
+
Object.assign(this._params, params);
|
|
4861
|
+
}
|
|
4862
|
+
} else {
|
|
4863
|
+
const built = predicate2.build();
|
|
4864
|
+
this._clauses.push(`WHERE ${built.predicate}`);
|
|
4865
|
+
Object.assign(this._params, built.params);
|
|
4866
|
+
}
|
|
4867
|
+
return this;
|
|
4868
|
+
}
|
|
4869
|
+
/**
|
|
4870
|
+
* Add a WITH clause.
|
|
4871
|
+
*/
|
|
4872
|
+
with(...expressions) {
|
|
4873
|
+
this._clauses.push(`WITH ${expressions.join(", ")}`);
|
|
4874
|
+
return this;
|
|
4875
|
+
}
|
|
4876
|
+
/**
|
|
4877
|
+
* Add a RETURN clause.
|
|
4878
|
+
*/
|
|
4879
|
+
return(...expressions) {
|
|
4880
|
+
this._clauses.push(`RETURN ${expressions.join(", ")}`);
|
|
4881
|
+
return this;
|
|
4882
|
+
}
|
|
4883
|
+
/**
|
|
4884
|
+
* Add RETURN DISTINCT.
|
|
4885
|
+
*/
|
|
4886
|
+
returnDistinct(...expressions) {
|
|
4887
|
+
this._clauses.push(`RETURN DISTINCT ${expressions.join(", ")}`);
|
|
4888
|
+
return this;
|
|
4889
|
+
}
|
|
4890
|
+
/**
|
|
4891
|
+
* Add an ORDER BY clause.
|
|
4892
|
+
*/
|
|
4893
|
+
orderBy(...expressions) {
|
|
4894
|
+
const parts = expressions.map((e) => {
|
|
4895
|
+
if (typeof e === "string") {
|
|
4896
|
+
return e;
|
|
4897
|
+
}
|
|
4898
|
+
return `${e[0]} ${e[1]}`;
|
|
4899
|
+
});
|
|
4900
|
+
this._clauses.push(`ORDER BY ${parts.join(", ")}`);
|
|
4901
|
+
return this;
|
|
4902
|
+
}
|
|
4903
|
+
/**
|
|
4904
|
+
* Add a SKIP clause.
|
|
4905
|
+
*/
|
|
4906
|
+
skip(count) {
|
|
4907
|
+
this._clauses.push(`SKIP ${count}`);
|
|
4908
|
+
return this;
|
|
4909
|
+
}
|
|
4910
|
+
/**
|
|
4911
|
+
* Add a LIMIT clause.
|
|
4912
|
+
*/
|
|
4913
|
+
limit(count) {
|
|
4914
|
+
this._clauses.push(`LIMIT ${count}`);
|
|
4915
|
+
return this;
|
|
4916
|
+
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Add a CREATE clause.
|
|
4919
|
+
*/
|
|
4920
|
+
create(pattern2) {
|
|
4921
|
+
const p = typeof pattern2 === "string" ? pattern2 : pattern2.toGQL();
|
|
4922
|
+
this._clauses.push(`CREATE ${p}`);
|
|
4923
|
+
return this;
|
|
4924
|
+
}
|
|
4925
|
+
/**
|
|
4926
|
+
* Add a MERGE clause.
|
|
4927
|
+
*/
|
|
4928
|
+
merge(pattern2) {
|
|
4929
|
+
const p = typeof pattern2 === "string" ? pattern2 : pattern2.toGQL();
|
|
4930
|
+
this._clauses.push(`MERGE ${p}`);
|
|
4931
|
+
return this;
|
|
4932
|
+
}
|
|
4933
|
+
/**
|
|
4934
|
+
* Add a DELETE clause.
|
|
4935
|
+
*/
|
|
4936
|
+
delete(...variables) {
|
|
4937
|
+
this._clauses.push(`DELETE ${variables.join(", ")}`);
|
|
4938
|
+
return this;
|
|
4939
|
+
}
|
|
4940
|
+
/**
|
|
4941
|
+
* Add a DETACH DELETE clause.
|
|
4942
|
+
*/
|
|
4943
|
+
detachDelete(...variables) {
|
|
4944
|
+
this._clauses.push(`DETACH DELETE ${variables.join(", ")}`);
|
|
4945
|
+
return this;
|
|
4946
|
+
}
|
|
4947
|
+
/**
|
|
4948
|
+
* Add a SET clause.
|
|
4949
|
+
*/
|
|
4950
|
+
set(...assignments) {
|
|
4951
|
+
this._clauses.push(`SET ${assignments.join(", ")}`);
|
|
4952
|
+
return this;
|
|
4953
|
+
}
|
|
4954
|
+
/**
|
|
4955
|
+
* Add a REMOVE clause.
|
|
4956
|
+
*/
|
|
4957
|
+
remove(...items) {
|
|
4958
|
+
this._clauses.push(`REMOVE ${items.join(", ")}`);
|
|
4959
|
+
return this;
|
|
4960
|
+
}
|
|
4961
|
+
/**
|
|
4962
|
+
* Add raw clause.
|
|
4963
|
+
*/
|
|
4964
|
+
raw(clause, params) {
|
|
4965
|
+
this._clauses.push(clause);
|
|
4966
|
+
if (params) {
|
|
4967
|
+
Object.assign(this._params, params);
|
|
4968
|
+
}
|
|
4969
|
+
return this;
|
|
4970
|
+
}
|
|
4971
|
+
/**
|
|
4972
|
+
* Add a parameter.
|
|
4973
|
+
*/
|
|
4974
|
+
param(name, value) {
|
|
4975
|
+
this._params[name] = value;
|
|
4976
|
+
return this;
|
|
4977
|
+
}
|
|
4978
|
+
/**
|
|
4979
|
+
* Add multiple parameters.
|
|
4980
|
+
*/
|
|
4981
|
+
params(params) {
|
|
4982
|
+
Object.assign(this._params, params);
|
|
4983
|
+
return this;
|
|
4984
|
+
}
|
|
4985
|
+
/**
|
|
4986
|
+
* Build the query string.
|
|
4987
|
+
*/
|
|
4988
|
+
build() {
|
|
4989
|
+
return {
|
|
4990
|
+
query: this._clauses.join("\n"),
|
|
4991
|
+
params: this._params
|
|
4992
|
+
};
|
|
4993
|
+
}
|
|
4994
|
+
/**
|
|
4995
|
+
* Get the query string.
|
|
4996
|
+
*/
|
|
4997
|
+
toGQL() {
|
|
4998
|
+
return this._clauses.join("\n");
|
|
4999
|
+
}
|
|
5000
|
+
/**
|
|
5001
|
+
* Get the parameters.
|
|
5002
|
+
*/
|
|
5003
|
+
getParams() {
|
|
5004
|
+
return { ...this._params };
|
|
5005
|
+
}
|
|
5006
|
+
};
|
|
5007
|
+
function propsToGQL(props) {
|
|
5008
|
+
const entries = Object.entries(props).map(([k, v]) => `${k}: ${valueToGQL(v)}`);
|
|
5009
|
+
return `{${entries.join(", ")}}`;
|
|
5010
|
+
}
|
|
5011
|
+
function valueToGQL(value) {
|
|
5012
|
+
if (value === null || value === void 0) {
|
|
5013
|
+
return "null";
|
|
5014
|
+
}
|
|
5015
|
+
if (typeof value === "string") {
|
|
5016
|
+
return `'${value.replace(/'/g, "\\'")}'`;
|
|
5017
|
+
}
|
|
5018
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
5019
|
+
return String(value);
|
|
5020
|
+
}
|
|
5021
|
+
if (Array.isArray(value)) {
|
|
5022
|
+
return `[${value.map(valueToGQL).join(", ")}]`;
|
|
5023
|
+
}
|
|
5024
|
+
if (typeof value === "object") {
|
|
5025
|
+
return propsToGQL(value);
|
|
5026
|
+
}
|
|
5027
|
+
return String(value);
|
|
5028
|
+
}
|
|
5029
|
+
function nodePatternToGQL(pattern2) {
|
|
5030
|
+
let result = "(";
|
|
5031
|
+
if (pattern2.variable) {
|
|
5032
|
+
result += pattern2.variable;
|
|
5033
|
+
}
|
|
5034
|
+
if (pattern2.labels && pattern2.labels.length > 0) {
|
|
5035
|
+
result += ":" + pattern2.labels.join(":");
|
|
5036
|
+
}
|
|
5037
|
+
if (pattern2.properties && Object.keys(pattern2.properties).length > 0) {
|
|
5038
|
+
result += " " + propsToGQL(pattern2.properties);
|
|
5039
|
+
}
|
|
5040
|
+
return result + ")";
|
|
5041
|
+
}
|
|
5042
|
+
function edgePatternToGQL(pattern2) {
|
|
5043
|
+
const hasContent = pattern2.variable || pattern2.type || pattern2.properties && Object.keys(pattern2.properties).length > 0;
|
|
5044
|
+
let inner = "";
|
|
5045
|
+
if (hasContent) {
|
|
5046
|
+
inner = "[";
|
|
5047
|
+
if (pattern2.variable) {
|
|
5048
|
+
inner += pattern2.variable;
|
|
5049
|
+
}
|
|
5050
|
+
if (pattern2.type) {
|
|
5051
|
+
inner += ":" + pattern2.type;
|
|
5052
|
+
}
|
|
5053
|
+
if (pattern2.minHops !== void 0 || pattern2.maxHops !== void 0) {
|
|
5054
|
+
inner += "*";
|
|
5055
|
+
if (pattern2.minHops !== void 0) {
|
|
5056
|
+
inner += pattern2.minHops;
|
|
5057
|
+
}
|
|
5058
|
+
inner += "..";
|
|
5059
|
+
if (pattern2.maxHops !== void 0) {
|
|
5060
|
+
inner += pattern2.maxHops;
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
if (pattern2.properties && Object.keys(pattern2.properties).length > 0) {
|
|
5064
|
+
inner += " " + propsToGQL(pattern2.properties);
|
|
5065
|
+
}
|
|
5066
|
+
inner += "]";
|
|
5067
|
+
}
|
|
5068
|
+
switch (pattern2.direction) {
|
|
5069
|
+
case "outgoing":
|
|
5070
|
+
return `-${inner}->`;
|
|
5071
|
+
case "incoming":
|
|
5072
|
+
return `<-${inner}-`;
|
|
5073
|
+
case "both":
|
|
5074
|
+
return `-${inner}-`;
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
function query() {
|
|
5078
|
+
return new QueryBuilder();
|
|
5079
|
+
}
|
|
5080
|
+
function pattern() {
|
|
5081
|
+
return new PatternBuilder();
|
|
5082
|
+
}
|
|
5083
|
+
function predicate() {
|
|
5084
|
+
return new PredicateBuilder();
|
|
5085
|
+
}
|
|
5086
|
+
function node() {
|
|
5087
|
+
return new NodePatternBuilder();
|
|
5088
|
+
}
|
|
5089
|
+
function edge() {
|
|
5090
|
+
return new EdgePatternBuilder();
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
// src/index.ts
|
|
5094
|
+
init_prepared();
|
|
5095
|
+
init_explain();
|
|
5096
|
+
init_batch();
|
|
5097
|
+
init_auth();
|
|
5098
|
+
|
|
5099
|
+
export { AuthClient, BaseTransport, ConfigError, Connection, ConnectionPool, DEFAULT_CONFORMANCE, DEFAULT_HELLO_NAME, DEFAULT_HELLO_VERSION, DEFAULT_PAGE_SIZE, DEFAULT_PORT, DriverError, EdgePatternBuilder, ErrBadConn, ErrClosed, ErrNoTx, ErrQueryInProgress, ErrRowsClosed, ErrTxDone, ErrTxInProgress, GQLValue, GeodeClient, MAX_PAGE_SIZE, MAX_QUERY_LENGTH, MockTransport, MsgType, NodePatternBuilder, PatternBuilder, PredicateBuilder, PreparedStatement, QueryBuilder, QueryResult, QueryResultIterator, MatrixQuicTransport as QuicheTransport, RespType, SecurityError, StateError, StatusClass, Transaction, TransportError, batch, batchAll, batchFirst, batchMap, batchParallel, buildBeginMessage, buildCommitMessage, buildHelloMessage, buildPingMessage, buildPullMessage, buildRollbackMessage, buildRollbackToMessage, buildRunGQLMessage, buildSavepointMessage, buildTLSConfig, cloneConfig, countPlaceholders, createAuthClient, createClient, createClientWithConfig, createTransport, defaultConfig, edge, explain, extractParameters, formatPlan, formatProfile, frameToError, fromJSON, getAddress, getColumnsFromSchema, isBindingsFrame, isDriverError, isErrorFrame, isFinalFrame, isGeodeError, isRetryableError, isSchemaFrame, mergeParams, node, parseDSN, parseFrame, parseGQLType, parseRow, pattern, predicate, prepare, profile, query, rewritePlaceholders, rowToObject, sanitizeForLog, serializeMessage, validateConfig, validateHostname, validatePageSize, validateParamName, validateParamValue, validatePort, validateQuery, validateSavepointName, withTransaction };
|
|
5100
|
+
//# sourceMappingURL=index.mjs.map
|
|
5101
|
+
//# sourceMappingURL=index.mjs.map
|