@zvndev/powdb-client 0.9.0 → 0.11.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/cjs/errors.d.ts +70 -0
- package/dist/cjs/errors.js +68 -0
- package/dist/cjs/escape.d.ts +54 -0
- package/dist/cjs/escape.js +131 -0
- package/dist/cjs/index.d.ts +393 -0
- package/dist/cjs/index.js +1046 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pool.d.ts +140 -0
- package/dist/cjs/pool.js +335 -0
- package/dist/cjs/protocol.d.ts +185 -0
- package/dist/cjs/protocol.js +645 -0
- package/dist/cjs/script.d.ts +25 -0
- package/dist/cjs/script.js +69 -0
- package/dist/cjs/typed.d.ts +49 -0
- package/dist/cjs/typed.js +105 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +15 -6
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PowDB TypeScript client.
|
|
3
|
+
*
|
|
4
|
+
* Thin async wrapper around a TCP (or TLS) socket speaking the PowDB wire
|
|
5
|
+
* protocol.
|
|
6
|
+
*
|
|
7
|
+
* const client = await Client.connect({
|
|
8
|
+
* host: "127.0.0.1",
|
|
9
|
+
* port: 5433,
|
|
10
|
+
* dbName: "default",
|
|
11
|
+
* password: process.env.POWDB_PASSWORD,
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* const result = await client.query("User filter .age > 27 { .name, .age }");
|
|
15
|
+
* await client.close();
|
|
16
|
+
*/
|
|
17
|
+
import * as tls from "node:tls";
|
|
18
|
+
import { EventEmitter } from "node:events";
|
|
19
|
+
import { type SyncRepairAction, type WireRetainedUnit, type WireSyncStatus } from "./protocol.js";
|
|
20
|
+
import { type TypedRow, type TypedSchema } from "./typed.js";
|
|
21
|
+
/** Client library version. Compared to the server's reported version. */
|
|
22
|
+
export declare const CLIENT_VERSION = "0.11.0";
|
|
23
|
+
export type QueryResult = {
|
|
24
|
+
kind: "rows";
|
|
25
|
+
columns: string[];
|
|
26
|
+
rows: string[][];
|
|
27
|
+
} | {
|
|
28
|
+
kind: "scalar";
|
|
29
|
+
value: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "ok";
|
|
32
|
+
affected: bigint;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "message";
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A value bound to a positional `$N` placeholder in {@link Client.query}.
|
|
39
|
+
*
|
|
40
|
+
* The server binds these at the token level — a string is substituted as a
|
|
41
|
+
* literal token, never interpolated — so injection-shaped input is inert.
|
|
42
|
+
* Numbers bind as ints when integral and floats otherwise; `bigint` always
|
|
43
|
+
* binds as an int; `null` binds PowQL `null`.
|
|
44
|
+
*/
|
|
45
|
+
export type QueryParam = string | number | bigint | boolean | null;
|
|
46
|
+
/** Options for {@link Client.execScript} / {@link Pool.execScript}. */
|
|
47
|
+
export interface ExecScriptOptions {
|
|
48
|
+
/**
|
|
49
|
+
* When `true`, keep dispatching statements after one fails and return a
|
|
50
|
+
* per-statement outcome array instead of throwing. Defaults to `false`
|
|
51
|
+
* (fail-fast: stop dispatching and reject with a {@link PowDBScriptError}
|
|
52
|
+
* carrying the failing statement's index and the results so far).
|
|
53
|
+
*/
|
|
54
|
+
continueOnError?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* When `true`, run the whole script atomically: `execScript` opens a
|
|
57
|
+
* transaction before dispatching and sends `commit` only after EVERY
|
|
58
|
+
* statement's reply has arrived successfully — on any failure it sends
|
|
59
|
+
* `rollback` instead, so no statement's effect survives. This is the only
|
|
60
|
+
* all-or-nothing mode: embedding your own `begin`/`commit` in a pipelined
|
|
61
|
+
* script is NOT safe (a trailing `commit` is already on the wire when an
|
|
62
|
+
* earlier error reply arrives, so partial work would commit), and a
|
|
63
|
+
* transactional script containing its own transaction-control statements
|
|
64
|
+
* is rejected up front. Mutually exclusive with `continueOnError`.
|
|
65
|
+
*/
|
|
66
|
+
transactional?: boolean;
|
|
67
|
+
/** Abort the remaining statements (see {@link Client.query}). */
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Per-statement outcome from `execScript(script, { continueOnError: true })`.
|
|
72
|
+
* Array order matches statement order in the script.
|
|
73
|
+
*/
|
|
74
|
+
export type ScriptStatementOutcome = {
|
|
75
|
+
statement: string;
|
|
76
|
+
ok: true;
|
|
77
|
+
result: QueryResult;
|
|
78
|
+
} | {
|
|
79
|
+
statement: string;
|
|
80
|
+
ok: false;
|
|
81
|
+
error: Error;
|
|
82
|
+
};
|
|
83
|
+
/** Unsigned 64-bit sync protocol value. Numbers must be safe non-negative integers. */
|
|
84
|
+
export type SyncU64 = bigint | number;
|
|
85
|
+
/**
|
|
86
|
+
* Database identity carried by the sync protocol. Strings are the 32-hex-char
|
|
87
|
+
* form stored in `.powdb-sync/identity.json`; byte arrays must be exactly 16B.
|
|
88
|
+
*/
|
|
89
|
+
export type SyncDatabaseId = string | Uint8Array;
|
|
90
|
+
export interface SyncPullRequest {
|
|
91
|
+
replicaId: string;
|
|
92
|
+
sinceLsn: SyncU64;
|
|
93
|
+
maxUnits: number;
|
|
94
|
+
maxBytes: SyncU64;
|
|
95
|
+
databaseId: SyncDatabaseId;
|
|
96
|
+
primaryGeneration: SyncU64;
|
|
97
|
+
walFormatVersion: number;
|
|
98
|
+
catalogVersion: number;
|
|
99
|
+
segmentFormatVersion: number;
|
|
100
|
+
}
|
|
101
|
+
export interface SyncAckRequest {
|
|
102
|
+
replicaId: string;
|
|
103
|
+
appliedLsn: SyncU64;
|
|
104
|
+
remoteLsn: SyncU64;
|
|
105
|
+
}
|
|
106
|
+
export interface SyncPullResult {
|
|
107
|
+
status: WireSyncStatus;
|
|
108
|
+
units: WireRetainedUnit[];
|
|
109
|
+
hasMore: boolean;
|
|
110
|
+
}
|
|
111
|
+
export interface SyncAckResult {
|
|
112
|
+
previousAppliedLsn: bigint;
|
|
113
|
+
appliedLsn: bigint;
|
|
114
|
+
remoteLsn: bigint;
|
|
115
|
+
advanced: boolean;
|
|
116
|
+
status: WireSyncStatus;
|
|
117
|
+
}
|
|
118
|
+
export interface ClientOptions {
|
|
119
|
+
/** TCP host. Required unless `path` (a Unix domain socket) is given. */
|
|
120
|
+
host?: string;
|
|
121
|
+
/** TCP port. Required unless `path` (a Unix domain socket) is given. */
|
|
122
|
+
port?: number;
|
|
123
|
+
/**
|
|
124
|
+
* Path to a Unix domain socket. When set, the client connects over the
|
|
125
|
+
* socket instead of TCP (same-host, ~2× lower round-trip latency) and
|
|
126
|
+
* `host`/`port`/`tls` are ignored. Requires a server started with
|
|
127
|
+
* `--socket <path>`.
|
|
128
|
+
*/
|
|
129
|
+
path?: string;
|
|
130
|
+
dbName?: string;
|
|
131
|
+
password?: string | null;
|
|
132
|
+
/**
|
|
133
|
+
* User name for multi-user authentication. Servers ≥0.4.5 with named users
|
|
134
|
+
* defined require a `(user, password)` pair; role enforcement (readonly vs
|
|
135
|
+
* readwrite) requires server ≥0.4.6. Omit for legacy shared-password or
|
|
136
|
+
* no-auth servers — the Connect frame is then byte-identical to 0.3.x.
|
|
137
|
+
*/
|
|
138
|
+
user?: string;
|
|
139
|
+
/** Connection timeout in ms. Defaults to 5000. */
|
|
140
|
+
connectTimeoutMs?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Enable TLS. When `true`, connect over TLS with system defaults
|
|
143
|
+
* (servername is taken from `host`). When an object, passed through to
|
|
144
|
+
* `tls.connect(port, host, options)`. Defaults to plain TCP.
|
|
145
|
+
*/
|
|
146
|
+
tls?: boolean | tls.ConnectionOptions;
|
|
147
|
+
/**
|
|
148
|
+
* Pipelined ("eager") connect. When `true`, {@link Client.connect}
|
|
149
|
+
* resolves as soon as the socket is open and the Connect frame has been
|
|
150
|
+
* written — it does NOT wait for the server's ConnectOk. Queries issued
|
|
151
|
+
* immediately are queued and written right behind the Connect frame (the
|
|
152
|
+
* server reads frames sequentially, so this is valid on the wire), saving
|
|
153
|
+
* a full round trip on every fresh connection.
|
|
154
|
+
*
|
|
155
|
+
* If the handshake then fails (bad password, protocol mismatch), every
|
|
156
|
+
* queued query rejects with the handshake error and the client closes.
|
|
157
|
+
* `serverVersion` is `""` until the ConnectOk arrives; await
|
|
158
|
+
* {@link Client.ready} if you need the handshake settled. Defaults to
|
|
159
|
+
* `false` (connect blocks until ConnectOk, exactly as before).
|
|
160
|
+
*/
|
|
161
|
+
eager?: boolean;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Event map for {@link Client}. Typed so `client.on("query", ...)` gets
|
|
165
|
+
* inference for the payload.
|
|
166
|
+
*
|
|
167
|
+
* - `"query"`: one emission per completed (or failed) query attempt,
|
|
168
|
+
* whether via `query` or `queryTyped`. `durationMs` is the client-side
|
|
169
|
+
* round-trip including abort handling. `ok=false` means the server
|
|
170
|
+
* returned an Error frame or the client rejected locally.
|
|
171
|
+
* - `"close"`: the underlying socket has been fully torn down. Emitted
|
|
172
|
+
* once per client.
|
|
173
|
+
*/
|
|
174
|
+
export interface ClientEvents {
|
|
175
|
+
query: [
|
|
176
|
+
{
|
|
177
|
+
query: string;
|
|
178
|
+
durationMs: number;
|
|
179
|
+
ok: boolean;
|
|
180
|
+
kind?: "rows" | "scalar" | "ok" | "message";
|
|
181
|
+
error?: Error;
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
sync: [
|
|
185
|
+
{
|
|
186
|
+
operation: "status" | "pull" | "ack";
|
|
187
|
+
replicaId: string;
|
|
188
|
+
durationMs: number;
|
|
189
|
+
ok: boolean;
|
|
190
|
+
status?: WireSyncStatus;
|
|
191
|
+
stale?: boolean;
|
|
192
|
+
repairAction?: SyncRepairAction;
|
|
193
|
+
units?: number;
|
|
194
|
+
advanced?: boolean;
|
|
195
|
+
error?: Error;
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
close: [{
|
|
199
|
+
error: Error | null;
|
|
200
|
+
}];
|
|
201
|
+
}
|
|
202
|
+
export declare class Client extends EventEmitter<ClientEvents> {
|
|
203
|
+
private readonly socket;
|
|
204
|
+
/** FIFO of raw chunks; concatenated lazily when we try to decode. */
|
|
205
|
+
private readonly chunks;
|
|
206
|
+
/** Cached length of everything currently in `chunks`. */
|
|
207
|
+
private totalLen;
|
|
208
|
+
private readonly pending;
|
|
209
|
+
private closed;
|
|
210
|
+
private closeError;
|
|
211
|
+
/** Settled once the Connect→ConnectOk handshake completes (or fails). */
|
|
212
|
+
private handshake;
|
|
213
|
+
/** True once ConnectOk has been received. */
|
|
214
|
+
private handshakeComplete;
|
|
215
|
+
private _serverVersion;
|
|
216
|
+
/**
|
|
217
|
+
* Server version from the ConnectOk frame. For a client opened with
|
|
218
|
+
* `eager: true` this is `""` until the handshake reply arrives (await
|
|
219
|
+
* {@link ready} to guarantee it is populated).
|
|
220
|
+
*/
|
|
221
|
+
get serverVersion(): string;
|
|
222
|
+
private constructor();
|
|
223
|
+
/**
|
|
224
|
+
* Open a connection, send Connect, and wait for ConnectOk.
|
|
225
|
+
*
|
|
226
|
+
* With `eager: true`, resolve as soon as the Connect frame is written
|
|
227
|
+
* instead — queries may be issued immediately and are pipelined behind
|
|
228
|
+
* the handshake (see {@link ClientOptions.eager} and {@link ready}).
|
|
229
|
+
*/
|
|
230
|
+
static connect(opts: ClientOptions): Promise<Client>;
|
|
231
|
+
/**
|
|
232
|
+
* Resolves once the Connect→ConnectOk handshake has completed; rejects
|
|
233
|
+
* with the handshake error if it failed. For non-eager clients this has
|
|
234
|
+
* already settled by the time {@link connect} returns. Eager callers can
|
|
235
|
+
* await it to learn the handshake outcome without issuing a query.
|
|
236
|
+
*/
|
|
237
|
+
ready(): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Write the Connect frame and register the handshake as the first entry
|
|
240
|
+
* in the pending queue. The reply-matching machinery is strictly FIFO, so
|
|
241
|
+
* the ConnectOk (or Error) frame is matched to the handshake before any
|
|
242
|
+
* pipelined query sees a reply. On failure, every queued query is
|
|
243
|
+
* rejected with the handshake error and the socket is torn down.
|
|
244
|
+
*/
|
|
245
|
+
private startHandshake;
|
|
246
|
+
/**
|
|
247
|
+
* Run a PowQL statement and return the typed result.
|
|
248
|
+
*
|
|
249
|
+
* When `opts.signal` is provided and fires, the returned promise rejects
|
|
250
|
+
* with the signal's `reason` (or an `AbortError`). The socket is NOT
|
|
251
|
+
* destroyed — the server will still eventually send its reply, which we
|
|
252
|
+
* silently discard so other in-flight queries keep working.
|
|
253
|
+
*/
|
|
254
|
+
query(query: string, paramsOrOpts?: QueryParam[] | {
|
|
255
|
+
signal?: AbortSignal;
|
|
256
|
+
}, maybeOpts?: {
|
|
257
|
+
signal?: AbortSignal;
|
|
258
|
+
}): Promise<QueryResult>;
|
|
259
|
+
/**
|
|
260
|
+
* Run a SQL statement through the server-side SQL frontend. The plain
|
|
261
|
+
* {@link query} method remains PowQL for wire compatibility.
|
|
262
|
+
*/
|
|
263
|
+
querySql(query: string, opts?: {
|
|
264
|
+
signal?: AbortSignal;
|
|
265
|
+
}): Promise<QueryResult>;
|
|
266
|
+
/**
|
|
267
|
+
* Fetch primary-side sync status for one embedded replica cursor.
|
|
268
|
+
*
|
|
269
|
+
* This speaks the private authenticated sync frame added for the embedded
|
|
270
|
+
* replica product. Servers without sync support return a protocol/query
|
|
271
|
+
* error; unauthenticated or readonly users are rejected server-side.
|
|
272
|
+
*/
|
|
273
|
+
syncStatus(replicaId: string, opts?: {
|
|
274
|
+
signal?: AbortSignal;
|
|
275
|
+
}): Promise<WireSyncStatus>;
|
|
276
|
+
/**
|
|
277
|
+
* Pull a bounded retained-unit chunk after this replica's primary-side cursor.
|
|
278
|
+
*
|
|
279
|
+
* The caller supplies the database identity and format versions from the
|
|
280
|
+
* sync bootstrap metadata. The server rejects mismatches and non-applyable
|
|
281
|
+
* transaction cuts instead of returning ambiguous history.
|
|
282
|
+
*/
|
|
283
|
+
syncPull(request: SyncPullRequest, opts?: {
|
|
284
|
+
signal?: AbortSignal;
|
|
285
|
+
}): Promise<SyncPullResult>;
|
|
286
|
+
/** Acknowledge that the replica applied retained history through `appliedLsn`. */
|
|
287
|
+
syncAck(request: SyncAckRequest, opts?: {
|
|
288
|
+
signal?: AbortSignal;
|
|
289
|
+
}): Promise<SyncAckResult>;
|
|
290
|
+
/**
|
|
291
|
+
* Like {@link query}, but coerces string result columns to typed JS values
|
|
292
|
+
* using the caller-supplied schema. See `./typed.ts` for the coercion
|
|
293
|
+
* rules and supported column types.
|
|
294
|
+
*
|
|
295
|
+
* Returns a `TypedRow[]` — an array of objects keyed by column name.
|
|
296
|
+
* Throws `PowDBError(code="query_failed")` if the query is not a
|
|
297
|
+
* rows-returning query.
|
|
298
|
+
*/
|
|
299
|
+
queryTyped(query: string, schema: TypedSchema, opts?: {
|
|
300
|
+
signal?: AbortSignal;
|
|
301
|
+
}): Promise<TypedRow[]>;
|
|
302
|
+
/**
|
|
303
|
+
* Execute a multi-statement PowQL script on this connection, pipelined.
|
|
304
|
+
*
|
|
305
|
+
* The script is split with the same statement-aware semantics as the CLI
|
|
306
|
+
* (see {@link splitStatements}: `;` inside string literals and `#`
|
|
307
|
+
* comments never splits; empty statements are dropped). All statements
|
|
308
|
+
* are then written down the single connection back-to-back WITHOUT
|
|
309
|
+
* waiting for each reply — the server reads frames sequentially, so an
|
|
310
|
+
* N-statement script costs one round trip instead of N. Results are
|
|
311
|
+
* collected in statement order.
|
|
312
|
+
*
|
|
313
|
+
* Error handling:
|
|
314
|
+
* - Default (fail-fast): resolve with `QueryResult[]` (one entry per
|
|
315
|
+
* statement). On the first failed statement, stop dispatching further
|
|
316
|
+
* statements and reject with a {@link PowDBScriptError} carrying the
|
|
317
|
+
* failing `statementIndex`, the `statement` text, and the successful
|
|
318
|
+
* `results` so far. NOTE: because dispatch is pipelined, statements
|
|
319
|
+
* already written when the error reply arrives (typically the whole
|
|
320
|
+
* script) still execute server-side. If you need all-or-nothing
|
|
321
|
+
* behavior use `transactional: true` — do NOT embed `begin`/`commit`
|
|
322
|
+
* in the script yourself: the trailing `commit` is already on the
|
|
323
|
+
* wire when an error reply arrives, so it commits the partial work.
|
|
324
|
+
* - `transactional: true`: `execScript` opens the transaction itself,
|
|
325
|
+
* waits for every statement's reply, and only then sends `commit`
|
|
326
|
+
* (or `rollback` if any statement failed), so no statement's effect
|
|
327
|
+
* survives a failure. On failure the {@link PowDBScriptError}'s
|
|
328
|
+
* `results` are the replies received before rollback — their effects
|
|
329
|
+
* are NOT persisted.
|
|
330
|
+
* - `continueOnError: true`: dispatch every statement regardless of
|
|
331
|
+
* failures and resolve with a dense {@link ScriptStatementOutcome}
|
|
332
|
+
* array recording each statement's result or error.
|
|
333
|
+
*
|
|
334
|
+
* Each statement emits the usual `"query"` event.
|
|
335
|
+
*/
|
|
336
|
+
execScript(script: string, opts?: {
|
|
337
|
+
continueOnError?: false;
|
|
338
|
+
transactional?: boolean;
|
|
339
|
+
signal?: AbortSignal;
|
|
340
|
+
}): Promise<QueryResult[]>;
|
|
341
|
+
execScript(script: string, opts: {
|
|
342
|
+
continueOnError: true;
|
|
343
|
+
signal?: AbortSignal;
|
|
344
|
+
}): Promise<ScriptStatementOutcome[]>;
|
|
345
|
+
/**
|
|
346
|
+
* Poll a query on an interval and invoke `onRows` for every successful
|
|
347
|
+
* run. Returns an unsubscribe function. Does NOT deduplicate results —
|
|
348
|
+
* `onRows` fires every interval, even if the rows are unchanged.
|
|
349
|
+
*
|
|
350
|
+
* Pragmatic first-cut live-data. If a query takes longer than
|
|
351
|
+
* `intervalMs`, the next tick waits for the in-flight one to finish
|
|
352
|
+
* (no pile-up). Errors fire `onError` (if provided) without stopping
|
|
353
|
+
* the watcher unless `stopOnError: true`.
|
|
354
|
+
*/
|
|
355
|
+
watch(query: string, opts: {
|
|
356
|
+
intervalMs: number;
|
|
357
|
+
onRows: (rows: QueryResult) => void;
|
|
358
|
+
onError?: (err: Error) => void;
|
|
359
|
+
stopOnError?: boolean;
|
|
360
|
+
}): {
|
|
361
|
+
stop: () => void;
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* Send Disconnect and tear down the socket. Waits for the remote FIN-ack
|
|
365
|
+
* (`socket.end` callback) but falls back to `destroy()` after a bounded
|
|
366
|
+
* timeout so an unresponsive peer cannot make `close()` hang forever.
|
|
367
|
+
*/
|
|
368
|
+
close(): Promise<void>;
|
|
369
|
+
private send;
|
|
370
|
+
/**
|
|
371
|
+
* Resolve once the socket's write buffer has drained. Also resolves if
|
|
372
|
+
* the client closes while waiting, so a dead peer cannot hang a
|
|
373
|
+
* backpressured `execScript` dispatch loop forever.
|
|
374
|
+
*/
|
|
375
|
+
private drained;
|
|
376
|
+
private onData;
|
|
377
|
+
/** Collapse the chunk queue into a single Buffer. */
|
|
378
|
+
private coalesce;
|
|
379
|
+
/** Drop the first `n` bytes off the chunk queue. */
|
|
380
|
+
private consume;
|
|
381
|
+
private onClose;
|
|
382
|
+
}
|
|
383
|
+
export { encode, tryDecode } from "./protocol.js";
|
|
384
|
+
export type { Message, SyncRepairAction, WireParam, WireRetainedUnit, WireSyncStatus, } from "./protocol.js";
|
|
385
|
+
export { MAX_PAYLOAD_SIZE, MAX_ROWS, MAX_COLUMNS, MAX_PARAMS, MAX_SYNC_UNITS, MAX_SYNC_PULL_UNITS, MAX_SYNC_PULL_BYTES, } from "./protocol.js";
|
|
386
|
+
export { escapeLiteral, escapeIdent, ident, powql, PowqlIdent, } from "./escape.js";
|
|
387
|
+
export { Pool } from "./pool.js";
|
|
388
|
+
export type { PoolOptions } from "./pool.js";
|
|
389
|
+
export { splitStatements } from "./script.js";
|
|
390
|
+
export { PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, } from "./errors.js";
|
|
391
|
+
export type { PowDBErrorCode } from "./errors.js";
|
|
392
|
+
export { coerceValue, coerceRow, coerceRows, } from "./typed.js";
|
|
393
|
+
export type { ColumnType, TypedSchema, TypedRow, Coerced, } from "./typed.js";
|