@zvndev/powdb-client 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,10 +18,20 @@ import * as net from "node:net";
18
18
  import * as tls from "node:tls";
19
19
  import { EventEmitter } from "node:events";
20
20
  import { encode, tryDecode, MAX_SYNC_PULL_BYTES, MAX_SYNC_PULL_UNITS, } from "./protocol.js";
21
- import { PowDBError } from "./errors.js";
21
+ import { PowDBError, PowDBScriptError, isPowDBError } from "./errors.js";
22
+ import { splitStatements } from "./script.js";
22
23
  import { coerceRows, } from "./typed.js";
23
24
  /** Client library version. Compared to the server's reported version. */
24
- export const CLIENT_VERSION = "0.8.1";
25
+ export const CLIENT_VERSION = "0.10.0";
26
+ /** Transaction-control statements a `transactional` script may not contain. */
27
+ const TX_CONTROL_RE = /^(begin|commit|rollback)\b/i;
28
+ /**
29
+ * Leading trivia (whitespace and `#` line comments) that may precede a
30
+ * statement's first real token. Must be stripped before matching
31
+ * {@link TX_CONTROL_RE}: the server's lexer skips comments, so
32
+ * `"# note\ncommit"` executes a commit — the guard has to see it too.
33
+ */
34
+ const LEADING_TRIVIA_RE = /^(?:\s+|#[^\n]*)+/;
25
35
  function socketChunkToBuffer(chunk) {
26
36
  return typeof chunk === "string" ? Buffer.from(chunk) : chunk;
27
37
  }
@@ -123,91 +133,99 @@ export class Client extends EventEmitter {
123
133
  pending = [];
124
134
  closed = false;
125
135
  closeError = null;
126
- serverVersion;
127
- constructor(socket, serverVersion) {
136
+ /** Settled once the Connect→ConnectOk handshake completes (or fails). */
137
+ handshake = Promise.resolve();
138
+ /** True once ConnectOk has been received. */
139
+ handshakeComplete = false;
140
+ _serverVersion = "";
141
+ /**
142
+ * Server version from the ConnectOk frame. For a client opened with
143
+ * `eager: true` this is `""` until the handshake reply arrives (await
144
+ * {@link ready} to guarantee it is populated).
145
+ */
146
+ get serverVersion() {
147
+ return this._serverVersion;
148
+ }
149
+ constructor(socket) {
128
150
  super();
129
151
  this.socket = socket;
130
- this.serverVersion = serverVersion;
131
152
  this.socket.on("data", (chunk) => this.onData(socketChunkToBuffer(chunk)));
132
153
  this.socket.on("error", (err) => this.onClose(err));
133
154
  this.socket.on("close", () => this.onClose(null));
134
155
  }
135
- /** Open a connection, send Connect, wait for ConnectOk. */
156
+ /**
157
+ * Open a connection, send Connect, and wait for ConnectOk.
158
+ *
159
+ * With `eager: true`, resolve as soon as the Connect frame is written
160
+ * instead — queries may be issued immediately and are pipelined behind
161
+ * the handshake (see {@link ClientOptions.eager} and {@link ready}).
162
+ */
136
163
  static async connect(opts) {
137
- const { host, port, path, dbName = "default", password = null, user, connectTimeoutMs = 5000, tls: tlsOpt = false, } = opts;
164
+ const { host, port, path, dbName = "default", password = null, user, connectTimeoutMs = 5000, tls: tlsOpt = false, eager = false, } = opts;
138
165
  if (path === undefined && (host === undefined || port === undefined)) {
139
166
  throw new PowDBError("connect requires either { path } (Unix socket) or { host, port } (TCP)", "connect_failed");
140
167
  }
141
168
  const socket = await openSocket({ host, port, path }, connectTimeoutMs, tlsOpt);
142
- // We need to read the initial ConnectOk before wiring up the normal
143
- // pending-queue machinery, so we do a one-shot handshake here.
144
- const handshake = new Promise((resolve, reject) => {
145
- let scratch = Buffer.alloc(0);
146
- const cleanup = () => {
147
- socket.removeListener("data", onData);
148
- socket.removeListener("error", onError);
149
- socket.removeListener("close", onClose);
150
- };
151
- const onData = (chunk) => {
152
- scratch = Buffer.concat([scratch, socketChunkToBuffer(chunk)]);
153
- let decoded;
154
- try {
155
- decoded = tryDecode(scratch);
156
- }
157
- catch (err) {
158
- cleanup();
159
- reject(err);
160
- return;
161
- }
162
- if (decoded !== null) {
163
- cleanup();
164
- // Any bytes past the handshake frame belong to later responses.
165
- // This should not happen in practice, but handle it defensively.
166
- const leftover = scratch.subarray(decoded.consumed);
167
- if (leftover.length > 0) {
168
- socket.unshift(leftover);
169
- }
170
- resolve(decoded.msg);
169
+ const client = new Client(socket);
170
+ client.startHandshake({ type: "Connect", dbName, password, username: user ?? null }, path ?? `${host}:${port}`);
171
+ if (!eager) {
172
+ await client.ready();
173
+ }
174
+ return client;
175
+ }
176
+ /**
177
+ * Resolves once the Connect→ConnectOk handshake has completed; rejects
178
+ * with the handshake error if it failed. For non-eager clients this has
179
+ * already settled by the time {@link connect} returns. Eager callers can
180
+ * await it to learn the handshake outcome without issuing a query.
181
+ */
182
+ ready() {
183
+ return this.handshake;
184
+ }
185
+ /**
186
+ * Write the Connect frame and register the handshake as the first entry
187
+ * in the pending queue. The reply-matching machinery is strictly FIFO, so
188
+ * the ConnectOk (or Error) frame is matched to the handshake before any
189
+ * pipelined query sees a reply. On failure, every queued query is
190
+ * rejected with the handshake error and the socket is torn down.
191
+ */
192
+ startHandshake(connect, versionWarnKey) {
193
+ this.handshake = this.send(connect).then((reply) => {
194
+ if (reply.type === "Error") {
195
+ throw new PowDBError(`connect failed: ${reply.message}`, "auth_failed");
196
+ }
197
+ if (reply.type !== "ConnectOk") {
198
+ throw new PowDBError(`expected ConnectOk, got ${reply.type}`, "protocol_error");
199
+ }
200
+ this.handshakeComplete = true;
201
+ this._serverVersion = reply.version;
202
+ // Advisory: warn once per host:port if the server's major differs
203
+ // from the client's. Do not throw or close — this is best-effort.
204
+ const serverMajor = majorOf(reply.version);
205
+ const clientMajor = majorOf(CLIENT_VERSION);
206
+ if (serverMajor !== clientMajor) {
207
+ if (!versionWarnings.has(versionWarnKey)) {
208
+ versionWarnings.add(versionWarnKey);
209
+ console.warn(`[powdb] server version ${reply.version} major (${serverMajor}) ` +
210
+ `differs from client ${CLIENT_VERSION} major (${clientMajor}); ` +
211
+ `behaviour may be inconsistent.`);
171
212
  }
172
- };
173
- const onError = (err) => {
174
- cleanup();
175
- reject(err);
176
- };
177
- // Without this, a peer that FINs the connection mid-handshake (no
178
- // explicit error event) would leave this promise pending forever.
179
- const onClose = () => {
180
- cleanup();
181
- reject(new PowDBError("connection closed during handshake", "connect_failed"));
182
- };
183
- socket.on("data", onData);
184
- socket.on("error", onError);
185
- socket.on("close", onClose);
213
+ }
214
+ }, (err) => {
215
+ // The connection dropped before a handshake reply — surface it as a
216
+ // connect failure (transient, retryable) rather than a bare close.
217
+ if (isPowDBError(err) && err.code === "closed") {
218
+ throw new PowDBError("connection closed during handshake", "connect_failed", { cause: err });
219
+ }
220
+ throw err;
221
+ });
222
+ // On handshake failure, reject everything queued behind it and tear the
223
+ // socket down. The extra no-op catch keeps an eager caller that never
224
+ // awaits ready() from tripping an unhandled-rejection crash.
225
+ this.handshake.catch((err) => {
226
+ this.onClose(err);
227
+ this.socket.destroy();
186
228
  });
187
- socket.write(encode({ type: "Connect", dbName, password, username: user ?? null }));
188
- const reply = await handshake;
189
- if (reply.type === "Error") {
190
- socket.destroy();
191
- throw new PowDBError(`connect failed: ${reply.message}`, "auth_failed");
192
- }
193
- if (reply.type !== "ConnectOk") {
194
- socket.destroy();
195
- throw new PowDBError(`expected ConnectOk, got ${reply.type}`, "protocol_error");
196
- }
197
- // Advisory: warn once per host:port if the server's major differs
198
- // from the client's. Do not throw or close — this is best-effort.
199
- const serverMajor = majorOf(reply.version);
200
- const clientMajor = majorOf(CLIENT_VERSION);
201
- if (serverMajor !== clientMajor) {
202
- const key = path ?? `${host}:${port}`;
203
- if (!versionWarnings.has(key)) {
204
- versionWarnings.add(key);
205
- console.warn(`[powdb] server version ${reply.version} major (${serverMajor}) ` +
206
- `differs from client ${CLIENT_VERSION} major (${clientMajor}); ` +
207
- `behaviour may be inconsistent.`);
208
- }
209
- }
210
- return new Client(socket, reply.version);
211
229
  }
212
230
  /**
213
231
  * Run a PowQL statement and return the typed result.
@@ -471,6 +489,136 @@ export class Client extends EventEmitter {
471
489
  }
472
490
  return coerceRows(result.columns, result.rows, schema);
473
491
  }
492
+ async execScript(script, opts) {
493
+ const statements = splitStatements(script);
494
+ const continueOnError = opts?.continueOnError === true;
495
+ const transactional = opts?.transactional === true;
496
+ const signal = opts?.signal;
497
+ if (transactional && continueOnError) {
498
+ throw new PowDBError("execScript: `transactional` and `continueOnError` are mutually exclusive", "protocol_error");
499
+ }
500
+ if (transactional) {
501
+ for (let i = 0; i < statements.length; i++) {
502
+ if (TX_CONTROL_RE.test(statements[i].replace(LEADING_TRIVIA_RE, ""))) {
503
+ throw new PowDBError(`execScript: a transactional script may not contain its own transaction control (statement ${i + 1}: ${statements[i]})`, "protocol_error");
504
+ }
505
+ }
506
+ // Open the transaction and WAIT for its reply before dispatching
507
+ // anything: if `begin` fails there must be nothing else on the wire.
508
+ // Deliberately no signal — an abort after `begin` is on the wire
509
+ // would leave the transaction open with no rollback; an
510
+ // already-aborted signal stops the loop below at statement 0 and
511
+ // takes the rollback path.
512
+ await this.query("begin");
513
+ }
514
+ const outcomes = new Array(statements.length);
515
+ const inFlight = [];
516
+ let firstFailureIndex = -1;
517
+ let firstFailureError = null;
518
+ let dispatched = 0;
519
+ for (let i = 0; i < statements.length; i++) {
520
+ // Stop dispatching when the script is already doomed: fail-fast saw
521
+ // an error, the caller aborted, or the connection is gone.
522
+ if (this.closed || signal?.aborted)
523
+ break;
524
+ if (!continueOnError && firstFailureIndex !== -1)
525
+ break;
526
+ const statement = statements[i];
527
+ const idx = i;
528
+ dispatched++;
529
+ // query() writes the frame synchronously before its first await, so
530
+ // this loop puts every statement on the wire without waiting for any
531
+ // reply; the FIFO pending queue matches replies back in order.
532
+ inFlight.push(this.query(statement, signal ? { signal } : undefined).then((result) => {
533
+ outcomes[idx] = { statement, ok: true, result };
534
+ }, (error) => {
535
+ const err = error instanceof Error ? error : new Error(String(error));
536
+ outcomes[idx] = { statement, ok: false, error: err };
537
+ // Replies are FIFO so failures normally arrive in statement
538
+ // order; keep the earliest defensively regardless.
539
+ if (firstFailureIndex === -1 || idx < firstFailureIndex) {
540
+ firstFailureIndex = idx;
541
+ firstFailureError = err;
542
+ }
543
+ }));
544
+ // Backpressure: only yield when the kernel buffer is full. These
545
+ // yields are also the windows in which an already-arrived error reply
546
+ // or abort can stop further dispatch (the checks at the loop head).
547
+ if (this.socket.writableNeedDrain)
548
+ await this.drained();
549
+ }
550
+ // Every dispatched statement has a handler attached, so this never
551
+ // rejects; it resolves once all in-flight replies have settled.
552
+ await Promise.all(inFlight);
553
+ if (!continueOnError) {
554
+ if (firstFailureIndex === -1 && dispatched < statements.length) {
555
+ // Dispatch stopped without any statement failing — e.g. a signal
556
+ // that was already aborted before the first write. Treat the first
557
+ // undispatched statement as the failure point.
558
+ firstFailureIndex = dispatched;
559
+ firstFailureError = signal?.aborted
560
+ ? abortError(signal)
561
+ : (this.closeError ?? new PowDBError("client is closed", "closed"));
562
+ }
563
+ if (firstFailureIndex !== -1) {
564
+ if (transactional) {
565
+ // Every dispatched reply has settled, so the connection is quiet
566
+ // and the transaction is still open. Best-effort rollback — the
567
+ // throw below is what callers act on either way, and if the
568
+ // connection died the server aborts the transaction itself.
569
+ try {
570
+ await this.query("rollback");
571
+ }
572
+ catch {
573
+ /* connection may already be gone */
574
+ }
575
+ }
576
+ const cause = firstFailureError;
577
+ const results = [];
578
+ for (let i = 0; i < firstFailureIndex; i++) {
579
+ const o = outcomes[i];
580
+ if (o !== undefined && o.ok)
581
+ results.push(o.result);
582
+ }
583
+ throw new PowDBScriptError(`script failed at statement ${firstFailureIndex + 1}/${statements.length}: ${cause.message}`, isPowDBError(cause) ? cause.code : "query_failed", {
584
+ statementIndex: firstFailureIndex,
585
+ statement: statements[firstFailureIndex],
586
+ results,
587
+ cause,
588
+ });
589
+ }
590
+ if (transactional) {
591
+ // Commit only after every statement's reply settled successfully —
592
+ // this is the all-or-nothing guarantee. The commit reply is not
593
+ // part of the returned results. No signal here either: aborting a
594
+ // commit already on the wire buys nothing but ambiguity.
595
+ try {
596
+ await this.query("commit");
597
+ }
598
+ catch (err) {
599
+ try {
600
+ await this.query("rollback");
601
+ }
602
+ catch {
603
+ /* connection may already be gone */
604
+ }
605
+ throw err;
606
+ }
607
+ }
608
+ return outcomes.map((o) => o.result);
609
+ }
610
+ // continueOnError: synthesize outcomes for statements that were never
611
+ // dispatched (abort or connection loss) so the array stays dense.
612
+ if (dispatched < statements.length) {
613
+ const reason = signal?.aborted
614
+ ? abortError(signal)
615
+ : (this.closeError ?? new PowDBError("client is closed", "closed"));
616
+ for (let i = dispatched; i < statements.length; i++) {
617
+ outcomes[i] = { statement: statements[i], ok: false, error: reason };
618
+ }
619
+ }
620
+ return outcomes;
621
+ }
474
622
  /**
475
623
  * Poll a query on an interval and invoke `onRows` for every successful
476
624
  * run. Returns an unsubscribe function. Does NOT deduplicate results —
@@ -636,6 +784,26 @@ export class Client extends EventEmitter {
636
784
  });
637
785
  });
638
786
  }
787
+ /**
788
+ * Resolve once the socket's write buffer has drained. Also resolves if
789
+ * the client closes while waiting, so a dead peer cannot hang a
790
+ * backpressured `execScript` dispatch loop forever.
791
+ */
792
+ drained() {
793
+ return new Promise((resolve) => {
794
+ if (!this.socket.writableNeedDrain || this.closed) {
795
+ resolve();
796
+ return;
797
+ }
798
+ const done = () => {
799
+ this.socket.removeListener("drain", done);
800
+ this.removeListener("close", done);
801
+ resolve();
802
+ };
803
+ this.socket.on("drain", done);
804
+ this.on("close", done);
805
+ });
806
+ }
639
807
  onData(chunk) {
640
808
  // Append to the chunk queue — O(1) — and lazily concat only when
641
809
  // we actually need contiguous bytes to decode.
@@ -739,8 +907,17 @@ export class Client extends EventEmitter {
739
907
  if (this.closed && err === null)
740
908
  return;
741
909
  this.closed = true;
742
- this.closeError = err;
743
- const error = err ?? new PowDBError("connection closed", "closed");
910
+ let error = err ?? new PowDBError("connection closed", "closed");
911
+ // A teardown before ConnectOk is a handshake failure: everything queued
912
+ // (the handshake entry and any eagerly pipelined queries) rejects with
913
+ // the handshake error. Auth/protocol errors already carry the precise
914
+ // cause; anything else becomes a retryable connect failure.
915
+ if (!this.handshakeComplete &&
916
+ !(isPowDBError(error) &&
917
+ (error.code === "auth_failed" || error.code === "protocol_error"))) {
918
+ error = new PowDBError("connection closed during handshake", "connect_failed", { cause: err ?? undefined });
919
+ }
920
+ this.closeError = error;
744
921
  while (this.pending.length > 0) {
745
922
  const entry = this.pending.shift();
746
923
  if (!entry.settled) {
@@ -804,5 +981,6 @@ export { encode, tryDecode } from "./protocol.js";
804
981
  export { MAX_PAYLOAD_SIZE, MAX_ROWS, MAX_COLUMNS, MAX_PARAMS, MAX_SYNC_UNITS, MAX_SYNC_PULL_UNITS, MAX_SYNC_PULL_BYTES, } from "./protocol.js";
805
982
  export { escapeLiteral, escapeIdent, ident, powql, PowqlIdent, } from "./escape.js";
806
983
  export { Pool } from "./pool.js";
807
- export { PowDBError, isPowDBError } from "./errors.js";
984
+ export { splitStatements } from "./script.js";
985
+ export { PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, } from "./errors.js";
808
986
  export { coerceValue, coerceRow, coerceRows, } from "./typed.js";
package/dist/pool.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * await pool.close();
21
21
  */
22
- import { Client, type ClientOptions } from "./index.js";
22
+ import { Client, type ClientOptions, type QueryResult, type ScriptStatementOutcome } from "./index.js";
23
23
  export interface PoolOptions extends ClientOptions {
24
24
  /** Maximum concurrent clients. Default 10. */
25
25
  max?: number;
@@ -110,6 +110,23 @@ export declare class Pool {
110
110
  * to leak a client on an unhandled exception.
111
111
  */
112
112
  withClient<T>(fn: (c: Client) => Promise<T>): Promise<T>;
113
+ /**
114
+ * Run a multi-statement PowQL script on ONE pooled connection, pipelined.
115
+ *
116
+ * Checks out a single client for the whole script (statement order —
117
+ * and `transactional: true` — therefore holds), delegates to
118
+ * {@link Client.execScript}, and releases the client on success or
119
+ * destroys it on error — same lifecycle as {@link withClient}.
120
+ */
121
+ execScript(script: string, opts?: {
122
+ continueOnError?: false;
123
+ transactional?: boolean;
124
+ signal?: AbortSignal;
125
+ }): Promise<QueryResult[]>;
126
+ execScript(script: string, opts: {
127
+ continueOnError: true;
128
+ signal?: AbortSignal;
129
+ }): Promise<ScriptStatementOutcome[]>;
113
130
  /**
114
131
  * Close the pool. Rejects all pending waiters, awaits `close()` on every
115
132
  * idle client, and marks the pool closed. Subsequent `acquire` calls
package/dist/pool.js CHANGED
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * await pool.close();
21
21
  */
22
- import { Client } from "./index.js";
22
+ import { Client, } from "./index.js";
23
23
  import { isPowDBError } from "./errors.js";
24
24
  /**
25
25
  * Is this error worth retrying? Only the transient connect-phase errors
@@ -251,6 +251,23 @@ export class Pool {
251
251
  throw err;
252
252
  }
253
253
  }
254
+ async execScript(script, opts) {
255
+ if (opts?.continueOnError === true) {
256
+ return this.withClient((c) => c.execScript(script, {
257
+ continueOnError: true,
258
+ ...(opts.transactional !== undefined
259
+ ? { transactional: opts.transactional }
260
+ : {}),
261
+ ...(opts.signal ? { signal: opts.signal } : {}),
262
+ }));
263
+ }
264
+ return this.withClient((c) => c.execScript(script, {
265
+ ...(opts?.transactional !== undefined
266
+ ? { transactional: opts.transactional }
267
+ : {}),
268
+ ...(opts?.signal ? { signal: opts.signal } : {}),
269
+ }));
270
+ }
254
271
  /**
255
272
  * Close the pool. Rejects all pending waiters, awaits `close()` on every
256
273
  * idle client, and marks the pool closed. Subsequent `acquire` calls
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Statement-aware PowQL script splitting.
3
+ *
4
+ * Mirrors the server-side splitter (`powdb_query::lexer::split_statements`)
5
+ * exactly, so a script file behaves the same whether it is fed to the CLI's
6
+ * `--exec` / `.powql` path or to {@link Client.execScript} over the wire:
7
+ *
8
+ * - `;` splits statements only at the top level.
9
+ * - `;` inside a `"..."` string literal never splits. A backslash inside a
10
+ * string consumes the next character unconditionally (mirroring the
11
+ * PowQL lexer), so `\"` and `\;` stay inside the string.
12
+ * - `#` starts a comment that runs to end-of-line; a `;` inside a comment
13
+ * never splits.
14
+ * - Segments are trimmed; empty segments (leading/doubled/trailing `;`,
15
+ * blank lines) are dropped.
16
+ * - Never errors: an unterminated string simply makes the rest of the
17
+ * input the final segment.
18
+ */
19
+ /**
20
+ * Split a PowQL script into individual statements.
21
+ *
22
+ * String-literal- and `#`-comment-aware `;` splitting with the exact
23
+ * semantics of the CLI/server splitter (see module docs above).
24
+ */
25
+ export declare function splitStatements(input: string): string[];
package/dist/script.js ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Statement-aware PowQL script splitting.
3
+ *
4
+ * Mirrors the server-side splitter (`powdb_query::lexer::split_statements`)
5
+ * exactly, so a script file behaves the same whether it is fed to the CLI's
6
+ * `--exec` / `.powql` path or to {@link Client.execScript} over the wire:
7
+ *
8
+ * - `;` splits statements only at the top level.
9
+ * - `;` inside a `"..."` string literal never splits. A backslash inside a
10
+ * string consumes the next character unconditionally (mirroring the
11
+ * PowQL lexer), so `\"` and `\;` stay inside the string.
12
+ * - `#` starts a comment that runs to end-of-line; a `;` inside a comment
13
+ * never splits.
14
+ * - Segments are trimmed; empty segments (leading/doubled/trailing `;`,
15
+ * blank lines) are dropped.
16
+ * - Never errors: an unterminated string simply makes the rest of the
17
+ * input the final segment.
18
+ */
19
+ /**
20
+ * Split a PowQL script into individual statements.
21
+ *
22
+ * String-literal- and `#`-comment-aware `;` splitting with the exact
23
+ * semantics of the CLI/server splitter (see module docs above).
24
+ */
25
+ export function splitStatements(input) {
26
+ const out = [];
27
+ let start = 0;
28
+ let state = "normal";
29
+ for (let i = 0; i < input.length; i++) {
30
+ const c = input[i];
31
+ switch (state) {
32
+ case "normal":
33
+ if (c === ";") {
34
+ const seg = input.slice(start, i).trim();
35
+ if (seg.length > 0)
36
+ out.push(seg);
37
+ start = i + 1;
38
+ }
39
+ else if (c === '"') {
40
+ state = "in-string";
41
+ }
42
+ else if (c === "#") {
43
+ state = "in-comment";
44
+ }
45
+ break;
46
+ case "in-string":
47
+ // Mirror the lexer: a backslash consumes the next char
48
+ // unconditionally, so `\"` and `\;` stay inside the string.
49
+ if (c === "\\") {
50
+ i++;
51
+ }
52
+ else if (c === '"') {
53
+ state = "normal";
54
+ }
55
+ break;
56
+ case "in-comment":
57
+ if (c === "\n")
58
+ state = "normal";
59
+ break;
60
+ }
61
+ }
62
+ const seg = input.slice(start).trim();
63
+ if (seg.length > 0)
64
+ out.push(seg);
65
+ return out;
66
+ }
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@zvndev/powdb-client",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "TypeScript client for PowDB (PowQL wire protocol)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.29.3",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
7
+ "main": "./dist/cjs/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
9
10
  "exports": {
10
11
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js"
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/cjs/index.d.ts",
18
+ "default": "./dist/cjs/index.js"
19
+ }
13
20
  }
14
21
  },
15
22
  "files": ["dist", "README.md", "LICENSE", "CHANGELOG.md"],
@@ -47,7 +54,9 @@
47
54
  "@types/node": { "optional": true }
48
55
  },
49
56
  "scripts": {
50
- "build": "tsc -p tsconfig.json",
57
+ "build": "npm run build:esm && npm run build:cjs",
58
+ "build:esm": "tsc -p tsconfig.json",
59
+ "build:cjs": "tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }) + '\\n')\"",
51
60
  "test": "tsx test/run-with-server.ts test/client.test.ts",
52
61
  "test:escape": "tsx test/escape.test.ts",
53
62
  "test:protocol": "tsx test/protocol.test.ts",
@@ -56,6 +65,9 @@
56
65
  "test:pool": "tsx test/run-with-server.ts test/pool.test.ts",
57
66
  "test:typed": "tsx test/typed.test.ts",
58
67
  "test:errors": "tsx test/errors.test.ts",
68
+ "test:script": "tsx test/script.test.ts",
69
+ "test:eager": "tsx test/run-with-server.ts test/eager.test.ts",
70
+ "test:exec-script": "tsx test/run-with-server.ts test/exec-script.test.ts",
59
71
  "demo": "tsx demo/demo.ts",
60
72
  "clean": "rm -rf dist",
61
73
  "prepublishOnly": "npm run build"