@scriptdb/client 1.0.9 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,837 @@
1
+ // src/index.ts
2
+ import * as net from "net";
3
+ import * as tls from "tls";
4
+ import { URL } from "url";
5
+ import * as crypto from "crypto";
6
+ var noopLogger = {
7
+ debug: () => {
8
+ },
9
+ info: () => {
10
+ },
11
+ warn: () => {
12
+ },
13
+ error: () => {
14
+ }
15
+ };
16
+ var ScriptDBClient = class {
17
+ /**
18
+ * Create a new client. Do NOT auto-connect in constructor — call connect()
19
+ * @param uri - Connection URI
20
+ * @param options - Client options
21
+ * @param options.secure - use TLS
22
+ * @param options.logger - { debug, info, warn, error }
23
+ * @param options.requestTimeout - Request timeout in ms
24
+ * @param options.retries - Reconnection retries
25
+ * @param options.retryDelay - Initial retry delay in ms
26
+ * @param options.tlsOptions - Passed to tls.connect when secure
27
+ */
28
+ constructor(uri, options = {}) {
29
+ this.socketTimeout = 0;
30
+ this.maxMessageSize = 0;
31
+ this._mask = () => {
32
+ };
33
+ this._maskArgs = () => [];
34
+ this.logger = {};
35
+ this.secure = true;
36
+ this.requestTimeout = 0;
37
+ this.retries = 0;
38
+ this.retryDelay = 0;
39
+ this.frame = "ndjson";
40
+ this.uri = "";
41
+ this.protocolName = "";
42
+ this.username = null;
43
+ this.password = null;
44
+ this.host = "";
45
+ this.port = 0;
46
+ this.database = null;
47
+ this.client = null;
48
+ this.buffer = Buffer.alloc(0);
49
+ this._nextId = 1;
50
+ this._pending = /* @__PURE__ */ new Map();
51
+ this._maxPending = 0;
52
+ this._maxQueue = 0;
53
+ this._pendingQueue = [];
54
+ this._connected = false;
55
+ this._authenticating = false;
56
+ this.token = null;
57
+ this._currentRetries = 0;
58
+ this.tokenExpiry = null;
59
+ this._destroyed = false;
60
+ this._reconnectTimer = null;
61
+ this.signing = null;
62
+ this._stringify = JSON.stringify;
63
+ this.ready = Promise.resolve();
64
+ this._resolveReadyFn = null;
65
+ this._rejectReadyFn = null;
66
+ this._connecting = null;
67
+ this._authPendingId = null;
68
+ if (!uri || typeof uri !== "string") throw new Error("uri required");
69
+ this.options = Object.assign({}, options);
70
+ this.socketTimeout = Number.isFinite(this.options.socketTimeout) ? this.options.socketTimeout : 0;
71
+ this.maxMessageSize = Number.isFinite(this.options.maxMessageSize) ? this.options.maxMessageSize : 5 * 1024 * 1024;
72
+ this._mask = (obj) => {
73
+ try {
74
+ if (!obj || typeof obj !== "object") return obj;
75
+ const copy = Array.isArray(obj) ? obj.slice() : Object.assign({}, obj);
76
+ if (copy.token) copy.token = "****";
77
+ if (copy.password) copy.password = "****";
78
+ if (copy.data && copy.data.password) copy.data.password = "****";
79
+ return copy;
80
+ } catch (e) {
81
+ return obj;
82
+ }
83
+ };
84
+ const rawLogger = this.options.logger && typeof this.options.logger === "object" ? this.options.logger : noopLogger;
85
+ this._maskArgs = (args) => {
86
+ return args.map((a) => {
87
+ if (!a) return a;
88
+ if (typeof a === "string") return a;
89
+ return this._mask(a);
90
+ });
91
+ };
92
+ this.logger = {
93
+ debug: (...args) => rawLogger.debug && rawLogger.debug(...this._maskArgs(args)),
94
+ info: (...args) => rawLogger.info && rawLogger.info(...this._maskArgs(args)),
95
+ warn: (...args) => rawLogger.warn && rawLogger.warn(...this._maskArgs(args)),
96
+ error: (...args) => rawLogger.error && rawLogger.error(...this._maskArgs(args))
97
+ };
98
+ this.secure = typeof this.options.secure === "boolean" ? !!this.options.secure : true;
99
+ if (!this.secure)
100
+ this.logger.warn?.(
101
+ "Warning: connecting in insecure mode (secure=false). This is not recommended."
102
+ );
103
+ this.requestTimeout = Number.isFinite(this.options.requestTimeout) ? this.options.requestTimeout : 0;
104
+ this.retries = Number.isFinite(this.options.retries) ? this.options.retries : 3;
105
+ this.retryDelay = Number.isFinite(this.options.retryDelay) ? this.options.retryDelay : 1e3;
106
+ this.frame = this.options.frame === "length-prefix" || this.options.preferLengthPrefix ? "length-prefix" : "ndjson";
107
+ let parsed;
108
+ try {
109
+ parsed = new URL(uri);
110
+ } catch (e) {
111
+ throw new Error("Invalid uri");
112
+ }
113
+ if (parsed.protocol !== "scriptdb:") {
114
+ throw new Error("URI must use scriptdb:// protocol");
115
+ }
116
+ this.uri = uri;
117
+ this.protocolName = parsed.protocol ? parsed.protocol.replace(":", "") : "scriptdb";
118
+ this.username = (typeof this.options.username === "string" ? this.options.username : parsed.username) || null;
119
+ this.password = (typeof this.options.password === "string" ? this.options.password : parsed.password) || null;
120
+ if (parsed.username && !(typeof this.options.username === "string")) {
121
+ this.logger.warn?.(
122
+ "Credentials found in URI \u2014 consider passing credentials via options instead of embedding in URI"
123
+ );
124
+ }
125
+ try {
126
+ parsed.username = "";
127
+ parsed.password = "";
128
+ this.uri = parsed.toString();
129
+ } catch (e) {
130
+ this.uri = uri;
131
+ }
132
+ this.host = parsed.hostname || "localhost";
133
+ this.port = parsed.port ? parseInt(parsed.port, 10) : 1234;
134
+ this.database = parsed.pathname && parsed.pathname.length > 1 ? parsed.pathname.slice(1) : null;
135
+ this.client = null;
136
+ this.buffer = Buffer.alloc(0);
137
+ this._nextId = 1;
138
+ this._pending = /* @__PURE__ */ new Map();
139
+ this._maxPending = Number.isFinite(this.options.maxPending) ? this.options.maxPending : 100;
140
+ this._maxQueue = Number.isFinite(this.options.maxQueue) ? this.options.maxQueue : 1e3;
141
+ this._pendingQueue = [];
142
+ this._connected = false;
143
+ this._authenticating = false;
144
+ this.token = null;
145
+ this._currentRetries = 0;
146
+ this.tokenExpiry = null;
147
+ this._destroyed = false;
148
+ this._reconnectTimer = null;
149
+ this.signing = this.options.signing && this.options.signing.secret ? this.options.signing : null;
150
+ this._stringify = typeof this.options.stringify === "function" ? this.options.stringify : JSON.stringify;
151
+ this._createReady();
152
+ }
153
+ _createReady() {
154
+ this.ready = new Promise((resolve, reject) => {
155
+ this._resolveReadyFn = resolve;
156
+ this._rejectReadyFn = reject;
157
+ });
158
+ }
159
+ _resolveReady(value) {
160
+ if (this._resolveReadyFn) {
161
+ try {
162
+ this._resolveReadyFn(value);
163
+ } catch (e) {
164
+ }
165
+ this._resolveReadyFn = null;
166
+ this._rejectReadyFn = null;
167
+ }
168
+ }
169
+ _rejectReady(err) {
170
+ if (this._rejectReadyFn) {
171
+ const rejectFn = this._rejectReadyFn;
172
+ this._resolveReadyFn = null;
173
+ this._rejectReadyFn = null;
174
+ process.nextTick(() => {
175
+ try {
176
+ rejectFn(err);
177
+ } catch (e) {
178
+ }
179
+ });
180
+ }
181
+ }
182
+ /**
183
+ * Check if connected
184
+ */
185
+ get connected() {
186
+ return this._connected;
187
+ }
188
+ /**
189
+ * Connect to server and authenticate. Returns a Promise that resolves when authenticated.
190
+ */
191
+ connect() {
192
+ if (this._connecting) return this._connecting;
193
+ this._connecting = new Promise((resolve, reject) => {
194
+ const opts = { host: this.host, port: this.port };
195
+ const onConnect = () => {
196
+ console.log("ScriptDBClient: Connected to server at", opts.host, opts.port);
197
+ this.logger?.info?.("Connected to server");
198
+ this._connected = true;
199
+ this._currentRetries = 0;
200
+ console.log("ScriptDBClient: Setting up listeners...");
201
+ this._setupListeners();
202
+ console.log("ScriptDBClient: Authenticating...");
203
+ this.authenticate().then(() => {
204
+ this._processQueue();
205
+ resolve(this);
206
+ }).catch((err) => {
207
+ reject(err);
208
+ });
209
+ };
210
+ try {
211
+ if (this.secure) {
212
+ const connectionOpts = { host: opts.host, port: opts.port };
213
+ const tlsOptions = Object.assign(
214
+ {},
215
+ this.options.tlsOptions || {},
216
+ connectionOpts
217
+ );
218
+ if (typeof tlsOptions.rejectUnauthorized === "undefined")
219
+ tlsOptions.rejectUnauthorized = true;
220
+ this.client = tls.connect(tlsOptions, onConnect);
221
+ } else {
222
+ this.client = net.createConnection(opts, onConnect);
223
+ }
224
+ } catch (e) {
225
+ const error = e;
226
+ this.logger?.error?.("Connection failed", error.message);
227
+ this._rejectReady(error);
228
+ this._createReady();
229
+ this._connecting = null;
230
+ return reject(error);
231
+ }
232
+ if (!this.client) {
233
+ const error = new Error("Failed to create client socket");
234
+ this.logger?.error?.("Connection failed", error.message);
235
+ this._connecting = null;
236
+ return reject(error);
237
+ }
238
+ const onError = (err) => {
239
+ this.logger?.error?.(
240
+ "Client socket error:",
241
+ err && err.message ? err.message : err
242
+ );
243
+ this._handleDisconnect(err);
244
+ };
245
+ const onClose = (hadError) => {
246
+ this.logger?.info?.("Server closed connection");
247
+ this._handleDisconnect(null);
248
+ };
249
+ this.client.on("error", onError);
250
+ this.client.on("close", onClose);
251
+ if (this.socketTimeout > 0 && this.client) {
252
+ this.client.setTimeout(this.socketTimeout);
253
+ this.client.on("timeout", () => {
254
+ this.logger?.warn?.("Socket timeout, destroying connection");
255
+ try {
256
+ this.client?.destroy();
257
+ } catch (e) {
258
+ }
259
+ });
260
+ }
261
+ }).catch((err) => {
262
+ this._connecting = null;
263
+ throw err;
264
+ });
265
+ return this._connecting;
266
+ }
267
+ _setupListeners() {
268
+ if (!this.client) return;
269
+ console.log("ScriptDBClient _setupListeners: called, client exists:", !!this.client);
270
+ this.client.removeAllListeners("data");
271
+ this.buffer = Buffer.alloc(0);
272
+ console.log("ScriptDBClient _setupListeners: frame mode:", this.frame);
273
+ if (this.frame === "length-prefix") {
274
+ this.client.on("data", (chunk) => {
275
+ if (!Buffer.isBuffer(chunk)) {
276
+ try {
277
+ chunk = Buffer.from(chunk);
278
+ } catch (e) {
279
+ return;
280
+ }
281
+ }
282
+ this.buffer = Buffer.concat([this.buffer, chunk]);
283
+ while (this.buffer.length >= 4) {
284
+ const len = this.buffer.readUInt32BE(0);
285
+ if (len > this.maxMessageSize) {
286
+ this.logger?.error?.(
287
+ "Incoming length-prefixed frame exceeds maxMessageSize \u2014 closing connection"
288
+ );
289
+ try {
290
+ this.client?.destroy();
291
+ } catch (e) {
292
+ }
293
+ return;
294
+ }
295
+ if (this.buffer.length < 4 + len) break;
296
+ const payload = this.buffer.slice(4, 4 + len);
297
+ this.buffer = this.buffer.slice(4 + len);
298
+ let msg;
299
+ try {
300
+ msg = JSON.parse(payload.toString("utf8"));
301
+ } catch (e) {
302
+ this.logger?.error?.(
303
+ "Invalid JSON frame",
304
+ e && e.message ? e.message : e
305
+ );
306
+ continue;
307
+ }
308
+ if (!msg || typeof msg !== "object") continue;
309
+ const validSchema = (typeof msg.id === "undefined" || typeof msg.id === "number") && (typeof msg.action === "string" || typeof msg.action === "undefined") && (typeof msg.command === "string" || typeof msg.command === "undefined") && (typeof msg.message === "string" || typeof msg.message === "undefined") && (typeof msg.data === "object" || typeof msg.data === "undefined" || msg.data === null);
310
+ if (!validSchema) {
311
+ this.logger?.warn?.("Message failed schema validation \u2014 ignoring");
312
+ continue;
313
+ }
314
+ this._handleMessage(msg);
315
+ }
316
+ });
317
+ } else {
318
+ console.log("ScriptDBClient _setupListeners: Setting up NDJSON data listener");
319
+ this.client.on("data", (chunk) => {
320
+ console.log("ScriptDBClient: Received data chunk, length:", chunk.length);
321
+ if (!Buffer.isBuffer(chunk)) {
322
+ try {
323
+ chunk = Buffer.from(chunk);
324
+ } catch (e) {
325
+ return;
326
+ }
327
+ }
328
+ const idxLastNewline = chunk.indexOf(10);
329
+ if (this.buffer.length === 0 && idxLastNewline === chunk.length - 1) {
330
+ let start = 0;
331
+ let idx2;
332
+ while ((idx2 = chunk.indexOf(10, start)) !== -1) {
333
+ const lineBuf = chunk.slice(start, idx2);
334
+ start = idx2 + 1;
335
+ if (lineBuf.length === 0) continue;
336
+ let msg;
337
+ try {
338
+ msg = JSON.parse(lineBuf.toString("utf8"));
339
+ } catch (e) {
340
+ this.logger?.error?.(
341
+ "Invalid JSON from server",
342
+ e && e.message ? e.message : e
343
+ );
344
+ continue;
345
+ }
346
+ if (!msg || typeof msg !== "object") continue;
347
+ const validSchema = (typeof msg.id === "undefined" || typeof msg.id === "number") && (typeof msg.action === "string" || typeof msg.action === "undefined") && (typeof msg.command === "string" || typeof msg.command === "undefined") && (typeof msg.message === "string" || typeof msg.message === "undefined");
348
+ if (!validSchema) continue;
349
+ this._handleMessage(msg);
350
+ }
351
+ return;
352
+ }
353
+ this.buffer = Buffer.concat([this.buffer, chunk]);
354
+ if (this.buffer.length > this.maxMessageSize) {
355
+ this.logger?.error?.(
356
+ "Incoming message exceeds maxMessageSize \u2014 closing connection"
357
+ );
358
+ try {
359
+ this.client?.destroy();
360
+ } catch (e) {
361
+ }
362
+ return;
363
+ }
364
+ let idx;
365
+ while ((idx = this.buffer.indexOf(10)) !== -1) {
366
+ const lineBuf = this.buffer.slice(0, idx);
367
+ this.buffer = this.buffer.slice(idx + 1);
368
+ if (lineBuf.length === 0) continue;
369
+ let msg;
370
+ try {
371
+ msg = JSON.parse(lineBuf.toString("utf8"));
372
+ } catch (e) {
373
+ this.logger?.error?.(
374
+ "Invalid JSON from server",
375
+ e && e.message ? e.message : e
376
+ );
377
+ continue;
378
+ }
379
+ const validSchema = (typeof msg.id === "undefined" || typeof msg.id === "number") && (typeof msg.action === "string" || typeof msg.action === "undefined") && (typeof msg.command === "string" || typeof msg.command === "undefined") && (typeof msg.message === "string" || typeof msg.message === "undefined");
380
+ if (!validSchema) {
381
+ this.logger?.warn?.("Message failed schema validation \u2014 ignoring");
382
+ continue;
383
+ }
384
+ this._handleMessage(msg);
385
+ }
386
+ });
387
+ }
388
+ }
389
+ // build the final buffer for sending using current token and signing settings
390
+ _buildFinalBuffer(payloadBase, id) {
391
+ const payloadObj = Object.assign(
392
+ { id, action: payloadBase.action },
393
+ payloadBase.data !== void 0 ? { data: payloadBase.data } : {}
394
+ );
395
+ if (this.token) payloadObj.token = this.token;
396
+ const payloadStr = this._stringify(payloadObj);
397
+ if (this.signing && this.signing.secret) {
398
+ const hmac = crypto.createHmac(
399
+ this.signing.algorithm || "sha256",
400
+ this.signing.secret
401
+ );
402
+ hmac.update(payloadStr);
403
+ const sig = hmac.digest("hex");
404
+ const envelope = { id, signature: sig, payload: payloadObj };
405
+ const envelopeStr = this._stringify(envelope);
406
+ if (this.frame === "length-prefix") {
407
+ const body = Buffer.from(envelopeStr, "utf8");
408
+ const buf = Buffer.allocUnsafe(4 + body.length);
409
+ buf.writeUInt32BE(body.length, 0);
410
+ body.copy(buf, 4);
411
+ return buf;
412
+ }
413
+ return Buffer.from(envelopeStr + "\n", "utf8");
414
+ }
415
+ if (this.frame === "length-prefix") {
416
+ const body = Buffer.from(payloadStr, "utf8");
417
+ const buf = Buffer.allocUnsafe(4 + body.length);
418
+ buf.writeUInt32BE(body.length, 0);
419
+ body.copy(buf, 4);
420
+ return buf;
421
+ }
422
+ return Buffer.from(payloadStr + "\n", "utf8");
423
+ }
424
+ _handleMessage(msg) {
425
+ console.log("ScriptDBClient _handleMessage:", JSON.stringify(msg));
426
+ console.log("ScriptDBClient _handleMessage msg.id:", msg.id, "msg.action:", msg.action, "msg.command:", msg.command, "msg.message:", msg.message);
427
+ if (msg && typeof msg.id !== "undefined") {
428
+ console.log("Handling message with id:", msg.id, "action:", msg.action, "message:", msg.message);
429
+ const pending = this._pending.get(msg.id);
430
+ if (!pending) {
431
+ console.log("No pending request for id", msg.id, "pending map size:", this._pending.size);
432
+ this.logger?.debug?.("No pending request for id", msg.id);
433
+ return;
434
+ }
435
+ const { resolve, reject, timer } = pending;
436
+ if (timer) clearTimeout(timer);
437
+ this._pending.delete(msg.id);
438
+ this._processQueue();
439
+ if (msg.action === "login" || msg.command === "login") {
440
+ console.log("Processing login response with id");
441
+ if (msg.message === "AUTH OK") {
442
+ console.log("AUTH OK - setting token and resolving");
443
+ this.token = msg.data && msg.data.token ? msg.data.token : null;
444
+ this._resolveReady(null);
445
+ return resolve(msg.data);
446
+ } else {
447
+ console.log("AUTH FAILED:", msg.data);
448
+ this._rejectReady(new Error("Authentication failed"));
449
+ const errorMsg = msg.data || "Authentication failed";
450
+ try {
451
+ this.client?.end();
452
+ } catch (e) {
453
+ }
454
+ return reject(new Error(typeof errorMsg === "string" ? errorMsg : "Authentication failed"));
455
+ }
456
+ }
457
+ if ((msg.action === "script-code" || msg.command === "script-code") && msg.message === "OK")
458
+ return resolve(msg.data);
459
+ if ((msg.action === "script-code" || msg.command === "script-code") && msg.message === "ERROR")
460
+ return reject(new Error(typeof msg.data === "string" ? msg.data : "Server returned ERROR"));
461
+ if (msg.action === "create-db" && msg.message === "SUCCESS")
462
+ return resolve(msg.data);
463
+ if (msg.action === "create-db" && msg.message === "ERROR")
464
+ return reject(new Error(typeof msg.data === "string" ? msg.data : "Failed to create database"));
465
+ if (msg.message === "OK" || msg.message === "SUCCESS") {
466
+ return resolve(msg.data);
467
+ } else if (msg.message === "ERROR") {
468
+ return reject(new Error(typeof msg.data === "string" ? msg.data : "Request failed"));
469
+ }
470
+ return reject(new Error("Invalid response from server"));
471
+ }
472
+ console.log("Unhandled message:", msg);
473
+ this.logger?.debug?.("Unhandled message from server", this._mask(msg));
474
+ }
475
+ authenticate() {
476
+ if (this._authenticating)
477
+ return Promise.reject(new Error("Already authenticating"));
478
+ this._authenticating = true;
479
+ return new Promise((resolve, reject) => {
480
+ const id = this._nextId++;
481
+ const payload = {
482
+ id,
483
+ action: "login",
484
+ data: { username: this.username, password: this.password }
485
+ };
486
+ let timer = null;
487
+ if (this.requestTimeout > 0) {
488
+ timer = setTimeout(() => {
489
+ this._pending.delete(id);
490
+ this._authenticating = false;
491
+ reject(new Error("Auth timeout"));
492
+ this.close();
493
+ }, this.requestTimeout);
494
+ }
495
+ this._pending.set(id, {
496
+ resolve: (data) => {
497
+ if (timer) clearTimeout(timer);
498
+ this._authenticating = false;
499
+ if (data && data.token) this.token = data.token;
500
+ resolve(data);
501
+ },
502
+ reject: (err) => {
503
+ if (timer) clearTimeout(timer);
504
+ this._authenticating = false;
505
+ reject(err);
506
+ },
507
+ timer
508
+ });
509
+ try {
510
+ const buf = Buffer.from(JSON.stringify(payload) + "\n", "utf8");
511
+ this._write(buf).catch((err) => {
512
+ if (timer) clearTimeout(timer);
513
+ this._pending.delete(id);
514
+ this._authenticating = false;
515
+ reject(err);
516
+ });
517
+ } catch (e) {
518
+ clearTimeout(timer);
519
+ this._pending.delete(id);
520
+ this._authenticating = false;
521
+ reject(e);
522
+ }
523
+ }).then((data) => {
524
+ if (data && data.token) {
525
+ this.token = data.token;
526
+ this._resolveReady(null);
527
+ }
528
+ return data;
529
+ }).catch((err) => {
530
+ this._rejectReady(err);
531
+ throw err;
532
+ });
533
+ }
534
+ // internal write helper that respects backpressure
535
+ _write(buf) {
536
+ return new Promise((resolve, reject) => {
537
+ if (!this.client || !this._connected)
538
+ return reject(new Error("Not connected"));
539
+ try {
540
+ const ok = this.client.write(buf, (err) => {
541
+ if (err) return reject(err);
542
+ resolve(void 0);
543
+ });
544
+ if (!ok) {
545
+ this.client.once("drain", () => resolve(void 0));
546
+ }
547
+ } catch (e) {
548
+ reject(e);
549
+ }
550
+ });
551
+ }
552
+ async _processQueue() {
553
+ while (this._pending.size < this._maxPending && this._pendingQueue.length > 0) {
554
+ const item = this._pendingQueue.shift();
555
+ const { payloadBase, id, resolve, reject, timer } = item;
556
+ if (this.tokenExpiry && Date.now() >= this.tokenExpiry) {
557
+ const refreshed = await this._maybeRefreshToken();
558
+ if (!refreshed) {
559
+ clearTimeout(timer);
560
+ try {
561
+ reject(new Error("Token expired and refresh failed"));
562
+ } catch (e) {
563
+ }
564
+ continue;
565
+ }
566
+ }
567
+ let buf;
568
+ try {
569
+ buf = this._buildFinalBuffer(payloadBase, id);
570
+ } catch (e) {
571
+ clearTimeout(timer);
572
+ try {
573
+ reject(e);
574
+ } catch (er) {
575
+ }
576
+ continue;
577
+ }
578
+ this._pending.set(id, { resolve, reject, timer });
579
+ try {
580
+ await this._write(buf);
581
+ } catch (e) {
582
+ clearTimeout(timer);
583
+ this._pending.delete(id);
584
+ try {
585
+ reject(e);
586
+ } catch (er) {
587
+ }
588
+ }
589
+ }
590
+ }
591
+ /**
592
+ * Execute a command. Supports concurrent requests via request id mapping.
593
+ * Returns a Promise resolved with response.data or rejected on ERROR/timeout.
594
+ */
595
+ async execute(payload) {
596
+ try {
597
+ await this.ready;
598
+ } catch (err) {
599
+ throw new Error(
600
+ "Not authenticated: " + (err && err.message ? err.message : err)
601
+ );
602
+ }
603
+ if (!this.token) throw new Error("Not authenticated");
604
+ const id = this._nextId++;
605
+ const payloadBase = { action: payload.action, data: payload.data };
606
+ if (this.tokenExpiry && Date.now() >= this.tokenExpiry) {
607
+ const refreshed = await this._maybeRefreshToken();
608
+ if (!refreshed) throw new Error("Token expired");
609
+ }
610
+ return new Promise((resolve, reject) => {
611
+ let timer = null;
612
+ if (this.requestTimeout > 0) {
613
+ timer = setTimeout(() => {
614
+ if (this._pending.has(id)) {
615
+ this._pending.delete(id);
616
+ reject(new Error("Request timeout"));
617
+ }
618
+ }, this.requestTimeout);
619
+ }
620
+ if (this._pending.size >= this._maxPending) {
621
+ if (this._pendingQueue.length >= this._maxQueue) {
622
+ if (timer) clearTimeout(timer);
623
+ return reject(new Error("Pending queue full"));
624
+ }
625
+ this._pendingQueue.push({ payloadBase, id, resolve, reject, timer });
626
+ this._processQueue().catch(() => {
627
+ });
628
+ return;
629
+ }
630
+ let finalBuf;
631
+ try {
632
+ finalBuf = this._buildFinalBuffer(payloadBase, id);
633
+ } catch (e) {
634
+ clearTimeout(timer);
635
+ return reject(e);
636
+ }
637
+ this._pending.set(id, { resolve, reject, timer });
638
+ this._write(finalBuf).catch((e) => {
639
+ if (timer) clearTimeout(timer);
640
+ this._pending.delete(id);
641
+ reject(e);
642
+ });
643
+ });
644
+ }
645
+ /**
646
+ * Attempt to refresh token using provided tokenRefresh option if token expired.
647
+ * options.tokenRefresh should be async function that returns { token, expiresAt }
648
+ */
649
+ async _maybeRefreshToken() {
650
+ if (!this.options.tokenRefresh || typeof this.options.tokenRefresh !== "function")
651
+ return false;
652
+ try {
653
+ const res = await this.options.tokenRefresh();
654
+ if (res && res.token) {
655
+ this.token = res.token;
656
+ if (res.expiresAt) this.tokenExpiry = res.expiresAt;
657
+ return true;
658
+ }
659
+ } catch (e) {
660
+ this.logger?.error?.(
661
+ "Token refresh failed",
662
+ e && e.message ? e.message : String(e)
663
+ );
664
+ }
665
+ return false;
666
+ }
667
+ destroy() {
668
+ this._destroyed = true;
669
+ if (this._reconnectTimer) {
670
+ clearTimeout(this._reconnectTimer);
671
+ this._reconnectTimer = null;
672
+ }
673
+ this._rejectReady(new Error("Client destroyed"));
674
+ this._pending.forEach((pending, id) => {
675
+ if (pending.timer) clearTimeout(pending.timer);
676
+ try {
677
+ pending.reject(new Error("Client destroyed"));
678
+ } catch (e) {
679
+ }
680
+ this._pending.delete(id);
681
+ });
682
+ this._pendingQueue = [];
683
+ try {
684
+ if (this.client) this.client.destroy();
685
+ } catch (e) {
686
+ }
687
+ this.client = null;
688
+ }
689
+ _handleDisconnect(err) {
690
+ if (!this._connected && !this._authenticating) {
691
+ return;
692
+ }
693
+ const wasAuthenticating = this._authenticating;
694
+ this._pending.forEach((pending, id) => {
695
+ if (pending.timer) clearTimeout(pending.timer);
696
+ const errorMsg = wasAuthenticating ? "Authentication failed - credentials may be required" : err && err.message ? err.message : "Disconnected";
697
+ process.nextTick(() => {
698
+ try {
699
+ pending.reject(new Error(errorMsg));
700
+ } catch (e) {
701
+ }
702
+ });
703
+ this._pending.delete(id);
704
+ });
705
+ this._connected = false;
706
+ this._connecting = null;
707
+ this._authenticating = false;
708
+ if (wasAuthenticating && this.retries === 0) {
709
+ const rejectErr = err || new Error("Authentication failed and no retries configured");
710
+ try {
711
+ this._rejectReady(rejectErr);
712
+ } catch (e) {
713
+ }
714
+ return;
715
+ }
716
+ if (this._currentRetries < this.retries) {
717
+ this._createReady();
718
+ const base = Math.min(
719
+ this.retryDelay * Math.pow(2, this._currentRetries),
720
+ 3e4
721
+ );
722
+ const jitter = Math.floor(Math.random() * Math.min(1e3, base));
723
+ const delay = base + jitter;
724
+ this._currentRetries += 1;
725
+ this.logger?.info?.(
726
+ "Attempting reconnect in " + delay + "ms (attempt " + this._currentRetries + ")"
727
+ );
728
+ this.token = null;
729
+ setTimeout(() => {
730
+ this.connect().catch((e) => {
731
+ this.logger?.error?.("Reconnect failed", e && e.message ? e.message : e);
732
+ });
733
+ }, delay);
734
+ } else {
735
+ try {
736
+ this._rejectReady(err || new Error("Disconnected and no retries left"));
737
+ } catch (e) {
738
+ }
739
+ }
740
+ }
741
+ close() {
742
+ if (!this.client) return;
743
+ try {
744
+ this.client.removeAllListeners("data");
745
+ this.client.removeAllListeners("error");
746
+ this.client.removeAllListeners("close");
747
+ this.client.end();
748
+ } catch (e) {
749
+ }
750
+ this.client = null;
751
+ }
752
+ /**
753
+ * Disconnect (alias for close)
754
+ */
755
+ disconnect() {
756
+ this.close();
757
+ }
758
+ /**
759
+ * Send request helper (for public API methods)
760
+ */
761
+ async sendRequest(action, data = {}) {
762
+ return this.execute({ action, data });
763
+ }
764
+ // ========== Public API Methods ==========
765
+ /**
766
+ * Login with username and password
767
+ */
768
+ async login(username, password) {
769
+ return this.sendRequest("login", { username, password });
770
+ }
771
+ /**
772
+ * Logout from server
773
+ */
774
+ async logout() {
775
+ return this.sendRequest("logout", {});
776
+ }
777
+ /**
778
+ * List all databases
779
+ */
780
+ async listDatabases() {
781
+ return this.sendRequest("list-dbs", {});
782
+ }
783
+ /**
784
+ * Create a new database
785
+ */
786
+ async createDatabase(name) {
787
+ return this.sendRequest("create-db", { databaseName: name });
788
+ }
789
+ /**
790
+ * Remove a database
791
+ */
792
+ async removeDatabase(name) {
793
+ return this.sendRequest("remove-db", { databaseName: name });
794
+ }
795
+ /**
796
+ * Rename a database
797
+ */
798
+ async renameDatabase(oldName, newName) {
799
+ return this.sendRequest("rename-db", { databaseName: oldName, newName });
800
+ }
801
+ /**
802
+ * Execute code in a database
803
+ */
804
+ async run(code, databaseName) {
805
+ return this.sendRequest("script-code", { code, databaseName });
806
+ }
807
+ /**
808
+ * Save a database to disk
809
+ */
810
+ async saveDatabase(databaseName) {
811
+ return this.sendRequest("save-db", { databaseName });
812
+ }
813
+ /**
814
+ * Update database metadata
815
+ */
816
+ async updateDatabase(databaseName, data) {
817
+ return this.sendRequest("update-db", { databaseName, ...data });
818
+ }
819
+ /**
820
+ * Get server information
821
+ */
822
+ async getInfo() {
823
+ return this.sendRequest("get-info", {});
824
+ }
825
+ /**
826
+ * Execute shell command on server
827
+ */
828
+ async executeShell(command) {
829
+ return this.sendRequest("shell-command", { command });
830
+ }
831
+ };
832
+ var index_default = ScriptDBClient;
833
+ export {
834
+ ScriptDBClient,
835
+ index_default as default
836
+ };
837
+ //# sourceMappingURL=index.mjs.map