@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/README.md +124 -2
- 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/errors.d.ts +27 -0
- package/dist/errors.js +30 -0
- package/dist/index.d.ts +137 -4
- package/dist/index.js +255 -77
- package/dist/pool.d.ts +18 -1
- package/dist/pool.js +18 -1
- package/dist/script.d.ts +25 -0
- package/dist/script.js +66 -0
- package/package.json +18 -6
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PowDB wire protocol.
|
|
4
|
+
*
|
|
5
|
+
* Frame format: [type(1)][flags(1)][len(4 LE)][payload]
|
|
6
|
+
* Strings are encoded as [len(4 LE)][utf-8 bytes].
|
|
7
|
+
*
|
|
8
|
+
* This mirrors crates/server/src/protocol.rs — keep them in sync.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.MAX_SYNC_PULL_BYTES = exports.MAX_SYNC_PULL_UNITS = exports.MAX_SYNC_UNITS = exports.MAX_PARAMS = exports.MAX_COLUMNS = exports.MAX_ROWS = exports.MAX_PAYLOAD_SIZE = exports.MSG_PONG = exports.MSG_PING = exports.MSG_DISCONNECT = exports.MSG_RESULT_MSG = exports.MSG_ERROR = exports.MSG_RESULT_OK = exports.MSG_RESULT_SCALAR = exports.MSG_RESULT_ROWS = exports.MSG_SYNC_ACK_RESULT = exports.MSG_SYNC_PULL_RESULT = exports.MSG_SYNC_STATUS_RESULT = exports.MSG_SYNC_ACK = exports.MSG_SYNC_PULL = exports.MSG_SYNC_STATUS = exports.MSG_QUERY_SQL = exports.MSG_QUERY_PARAMS = exports.MSG_QUERY = exports.MSG_CONNECT_OK = exports.MSG_CONNECT = void 0;
|
|
12
|
+
exports.encode = encode;
|
|
13
|
+
exports.tryDecode = tryDecode;
|
|
14
|
+
exports.MSG_CONNECT = 0x01;
|
|
15
|
+
exports.MSG_CONNECT_OK = 0x02;
|
|
16
|
+
exports.MSG_QUERY = 0x03;
|
|
17
|
+
/**
|
|
18
|
+
* Query carrying positional `$N` parameters. Pure protocol addition: old
|
|
19
|
+
* servers reject it with the existing "unknown message type" error, and the
|
|
20
|
+
* plain `MSG_QUERY` frame is unchanged. Requires powdb-server ≥ 0.4.7.
|
|
21
|
+
*/
|
|
22
|
+
exports.MSG_QUERY_PARAMS = 0x04;
|
|
23
|
+
/** SQL query frame. Plain Query remains PowQL for backward compatibility. */
|
|
24
|
+
exports.MSG_QUERY_SQL = 0x05;
|
|
25
|
+
/** Private embedded-sync control frames. Requires authenticated server support. */
|
|
26
|
+
exports.MSG_SYNC_STATUS = 0x20;
|
|
27
|
+
exports.MSG_SYNC_PULL = 0x21;
|
|
28
|
+
exports.MSG_SYNC_ACK = 0x22;
|
|
29
|
+
exports.MSG_SYNC_STATUS_RESULT = 0x23;
|
|
30
|
+
exports.MSG_SYNC_PULL_RESULT = 0x24;
|
|
31
|
+
exports.MSG_SYNC_ACK_RESULT = 0x25;
|
|
32
|
+
exports.MSG_RESULT_ROWS = 0x07;
|
|
33
|
+
exports.MSG_RESULT_SCALAR = 0x08;
|
|
34
|
+
exports.MSG_RESULT_OK = 0x09;
|
|
35
|
+
exports.MSG_ERROR = 0x0a;
|
|
36
|
+
exports.MSG_RESULT_MSG = 0x0b;
|
|
37
|
+
exports.MSG_DISCONNECT = 0x10;
|
|
38
|
+
exports.MSG_PING = 0x11;
|
|
39
|
+
exports.MSG_PONG = 0x12;
|
|
40
|
+
// ───── Size limits (mirror crates/server/src/protocol.rs) ──────────────────
|
|
41
|
+
/** Maximum payload size accepted from the wire (64 MB). */
|
|
42
|
+
exports.MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
|
|
43
|
+
/** Maximum number of rows allowed in a single result message. */
|
|
44
|
+
exports.MAX_ROWS = 10_000_000;
|
|
45
|
+
/** Maximum number of columns allowed in a result set. */
|
|
46
|
+
exports.MAX_COLUMNS = 4096;
|
|
47
|
+
/** Maximum number of bound parameters in a QueryWithParams message. */
|
|
48
|
+
exports.MAX_PARAMS = 4096;
|
|
49
|
+
/** Maximum retained units accepted in one sync pull result. */
|
|
50
|
+
exports.MAX_SYNC_UNITS = 4096;
|
|
51
|
+
/** Maximum retained units accepted by the server for one sync pull request. */
|
|
52
|
+
exports.MAX_SYNC_PULL_UNITS = 4096;
|
|
53
|
+
/** Maximum retained-unit payload budget accepted by the server for one sync pull. */
|
|
54
|
+
exports.MAX_SYNC_PULL_BYTES = 16 * 1024 * 1024;
|
|
55
|
+
// ───── Encoding ────────────────────────────────────────────────────────────
|
|
56
|
+
function encode(msg) {
|
|
57
|
+
let msgType;
|
|
58
|
+
let payload;
|
|
59
|
+
switch (msg.type) {
|
|
60
|
+
case "Connect": {
|
|
61
|
+
const dbBuf = encodeString(msg.dbName);
|
|
62
|
+
const pwBuf = msg.password === null ? u32LE(0) : encodeString(msg.password);
|
|
63
|
+
// Username rides after the password (append-only extension, mirrors
|
|
64
|
+
// crates/server/src/protocol.rs). The server reads it only when bytes
|
|
65
|
+
// remain after the password, so omitting it when null keeps the frame
|
|
66
|
+
// byte-identical to the legacy 0.3.x shape.
|
|
67
|
+
const parts = [dbBuf, pwBuf];
|
|
68
|
+
if (msg.username !== null) {
|
|
69
|
+
parts.push(encodeString(msg.username));
|
|
70
|
+
}
|
|
71
|
+
payload = Buffer.concat(parts);
|
|
72
|
+
msgType = exports.MSG_CONNECT;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "ConnectOk":
|
|
76
|
+
payload = encodeString(msg.version);
|
|
77
|
+
msgType = exports.MSG_CONNECT_OK;
|
|
78
|
+
break;
|
|
79
|
+
case "Query":
|
|
80
|
+
payload = encodeString(msg.query);
|
|
81
|
+
msgType = exports.MSG_QUERY;
|
|
82
|
+
break;
|
|
83
|
+
case "QuerySql":
|
|
84
|
+
payload = encodeString(msg.query);
|
|
85
|
+
msgType = exports.MSG_QUERY_SQL;
|
|
86
|
+
break;
|
|
87
|
+
case "QueryWithParams": {
|
|
88
|
+
const parts = [encodeString(msg.query)];
|
|
89
|
+
const count = Buffer.alloc(2);
|
|
90
|
+
count.writeUInt16LE(msg.params.length, 0);
|
|
91
|
+
parts.push(count);
|
|
92
|
+
for (const p of msg.params) {
|
|
93
|
+
switch (p.tag) {
|
|
94
|
+
case "null":
|
|
95
|
+
parts.push(Buffer.from([0]));
|
|
96
|
+
break;
|
|
97
|
+
case "int": {
|
|
98
|
+
const b = Buffer.alloc(9);
|
|
99
|
+
b.writeUInt8(1, 0);
|
|
100
|
+
b.writeBigInt64LE(p.value, 1);
|
|
101
|
+
parts.push(b);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "float": {
|
|
105
|
+
const b = Buffer.alloc(9);
|
|
106
|
+
b.writeUInt8(2, 0);
|
|
107
|
+
b.writeDoubleLE(p.value, 1);
|
|
108
|
+
parts.push(b);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "bool":
|
|
112
|
+
parts.push(Buffer.from([3, p.value ? 1 : 0]));
|
|
113
|
+
break;
|
|
114
|
+
case "str":
|
|
115
|
+
parts.push(Buffer.from([4]), encodeString(p.value));
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
payload = Buffer.concat(parts);
|
|
120
|
+
msgType = exports.MSG_QUERY_PARAMS;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "SyncStatus":
|
|
124
|
+
payload = encodeString(msg.replicaId);
|
|
125
|
+
msgType = exports.MSG_SYNC_STATUS;
|
|
126
|
+
break;
|
|
127
|
+
case "SyncPull": {
|
|
128
|
+
if (msg.databaseId.length !== 16) {
|
|
129
|
+
throw new Error(`sync databaseId must be exactly 16 bytes, got ${msg.databaseId.length}`);
|
|
130
|
+
}
|
|
131
|
+
payload = Buffer.concat([
|
|
132
|
+
encodeString(msg.replicaId),
|
|
133
|
+
u64LE(msg.sinceLsn),
|
|
134
|
+
u32LE(msg.maxUnits),
|
|
135
|
+
u64LE(msg.maxBytes),
|
|
136
|
+
msg.databaseId,
|
|
137
|
+
u64LE(msg.primaryGeneration),
|
|
138
|
+
u16LE(msg.walFormatVersion),
|
|
139
|
+
u16LE(msg.catalogVersion),
|
|
140
|
+
u16LE(msg.segmentFormatVersion),
|
|
141
|
+
]);
|
|
142
|
+
msgType = exports.MSG_SYNC_PULL;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "SyncAck":
|
|
146
|
+
payload = Buffer.concat([
|
|
147
|
+
encodeString(msg.replicaId),
|
|
148
|
+
u64LE(msg.appliedLsn),
|
|
149
|
+
u64LE(msg.remoteLsn),
|
|
150
|
+
]);
|
|
151
|
+
msgType = exports.MSG_SYNC_ACK;
|
|
152
|
+
break;
|
|
153
|
+
case "SyncStatusResult":
|
|
154
|
+
payload = encodeSyncStatus(msg.status);
|
|
155
|
+
msgType = exports.MSG_SYNC_STATUS_RESULT;
|
|
156
|
+
break;
|
|
157
|
+
case "SyncPullResult": {
|
|
158
|
+
const parts = [
|
|
159
|
+
encodeSyncStatus(msg.status),
|
|
160
|
+
u32LE(msg.units.length),
|
|
161
|
+
];
|
|
162
|
+
for (const unit of msg.units) {
|
|
163
|
+
parts.push(encodeRetainedUnit(unit));
|
|
164
|
+
}
|
|
165
|
+
parts.push(Buffer.from([msg.hasMore ? 1 : 0]));
|
|
166
|
+
payload = Buffer.concat(parts);
|
|
167
|
+
msgType = exports.MSG_SYNC_PULL_RESULT;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "SyncAckResult":
|
|
171
|
+
payload = Buffer.concat([
|
|
172
|
+
u64LE(msg.previousAppliedLsn),
|
|
173
|
+
u64LE(msg.appliedLsn),
|
|
174
|
+
u64LE(msg.remoteLsn),
|
|
175
|
+
Buffer.from([msg.advanced ? 1 : 0]),
|
|
176
|
+
encodeSyncStatus(msg.status),
|
|
177
|
+
]);
|
|
178
|
+
msgType = exports.MSG_SYNC_ACK_RESULT;
|
|
179
|
+
break;
|
|
180
|
+
case "ResultRows": {
|
|
181
|
+
const parts = [];
|
|
182
|
+
const colCount = Buffer.alloc(2);
|
|
183
|
+
colCount.writeUInt16LE(msg.columns.length, 0);
|
|
184
|
+
parts.push(colCount);
|
|
185
|
+
for (const col of msg.columns)
|
|
186
|
+
parts.push(encodeString(col));
|
|
187
|
+
parts.push(u32LE(msg.rows.length));
|
|
188
|
+
for (const row of msg.rows) {
|
|
189
|
+
for (const val of row)
|
|
190
|
+
parts.push(encodeString(val));
|
|
191
|
+
}
|
|
192
|
+
payload = Buffer.concat(parts);
|
|
193
|
+
msgType = exports.MSG_RESULT_ROWS;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case "ResultScalar":
|
|
197
|
+
payload = encodeString(msg.value);
|
|
198
|
+
msgType = exports.MSG_RESULT_SCALAR;
|
|
199
|
+
break;
|
|
200
|
+
case "ResultOk": {
|
|
201
|
+
payload = Buffer.alloc(8);
|
|
202
|
+
payload.writeBigUInt64LE(msg.affected, 0);
|
|
203
|
+
msgType = exports.MSG_RESULT_OK;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case "ResultMessage":
|
|
207
|
+
payload = encodeString(msg.message);
|
|
208
|
+
msgType = exports.MSG_RESULT_MSG;
|
|
209
|
+
break;
|
|
210
|
+
case "Error":
|
|
211
|
+
payload = encodeString(msg.message);
|
|
212
|
+
msgType = exports.MSG_ERROR;
|
|
213
|
+
break;
|
|
214
|
+
case "Disconnect":
|
|
215
|
+
payload = Buffer.alloc(0);
|
|
216
|
+
msgType = exports.MSG_DISCONNECT;
|
|
217
|
+
break;
|
|
218
|
+
case "Ping":
|
|
219
|
+
payload = Buffer.alloc(0);
|
|
220
|
+
msgType = exports.MSG_PING;
|
|
221
|
+
break;
|
|
222
|
+
case "Pong":
|
|
223
|
+
payload = Buffer.alloc(0);
|
|
224
|
+
msgType = exports.MSG_PONG;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
const frame = Buffer.alloc(6 + payload.length);
|
|
228
|
+
frame.writeUInt8(msgType, 0);
|
|
229
|
+
frame.writeUInt8(0, 1); // flags
|
|
230
|
+
frame.writeUInt32LE(payload.length, 2);
|
|
231
|
+
payload.copy(frame, 6);
|
|
232
|
+
return frame;
|
|
233
|
+
}
|
|
234
|
+
// ───── Decoding ────────────────────────────────────────────────────────────
|
|
235
|
+
/**
|
|
236
|
+
* Attempts to parse a single frame from the start of `buf`. Returns the parsed
|
|
237
|
+
* message and the number of bytes consumed, or `null` if the buffer does not
|
|
238
|
+
* yet contain a complete frame.
|
|
239
|
+
*/
|
|
240
|
+
function tryDecode(buf) {
|
|
241
|
+
if (buf.length < 6)
|
|
242
|
+
return null;
|
|
243
|
+
const msgType = buf.readUInt8(0);
|
|
244
|
+
// flags byte at offset 1 is currently unused
|
|
245
|
+
const payloadLen = buf.readUInt32LE(2);
|
|
246
|
+
if (payloadLen > exports.MAX_PAYLOAD_SIZE) {
|
|
247
|
+
throw new Error(`payload too large: ${payloadLen} bytes (max ${exports.MAX_PAYLOAD_SIZE})`);
|
|
248
|
+
}
|
|
249
|
+
if (buf.length < 6 + payloadLen)
|
|
250
|
+
return null;
|
|
251
|
+
const payload = buf.subarray(6, 6 + payloadLen);
|
|
252
|
+
const msg = decodePayload(msgType, payload);
|
|
253
|
+
return { msg, consumed: 6 + payloadLen };
|
|
254
|
+
}
|
|
255
|
+
function decodePayload(msgType, payload) {
|
|
256
|
+
const cursor = { pos: 0 };
|
|
257
|
+
switch (msgType) {
|
|
258
|
+
case exports.MSG_CONNECT: {
|
|
259
|
+
const dbName = decodeString(payload, cursor);
|
|
260
|
+
let password = null;
|
|
261
|
+
if (cursor.pos < payload.length) {
|
|
262
|
+
const p = decodeString(payload, cursor);
|
|
263
|
+
password = p.length === 0 ? null : p;
|
|
264
|
+
}
|
|
265
|
+
// Optional trailing username (mirror the server: present only when
|
|
266
|
+
// bytes remain after the password; empty string means null).
|
|
267
|
+
let username = null;
|
|
268
|
+
if (cursor.pos < payload.length) {
|
|
269
|
+
const u = decodeString(payload, cursor);
|
|
270
|
+
username = u.length === 0 ? null : u;
|
|
271
|
+
}
|
|
272
|
+
return { type: "Connect", dbName, password, username };
|
|
273
|
+
}
|
|
274
|
+
case exports.MSG_CONNECT_OK:
|
|
275
|
+
return { type: "ConnectOk", version: decodeString(payload, cursor) };
|
|
276
|
+
case exports.MSG_QUERY:
|
|
277
|
+
return { type: "Query", query: decodeString(payload, cursor) };
|
|
278
|
+
case exports.MSG_QUERY_SQL: {
|
|
279
|
+
const query = decodeString(payload, cursor);
|
|
280
|
+
return { type: "QuerySql", query };
|
|
281
|
+
}
|
|
282
|
+
case exports.MSG_QUERY_PARAMS: {
|
|
283
|
+
const query = decodeString(payload, cursor);
|
|
284
|
+
const count = readU16(payload, cursor, "param count");
|
|
285
|
+
if (count > exports.MAX_PARAMS) {
|
|
286
|
+
throw new Error(`too many parameters: ${count} (max ${exports.MAX_PARAMS})`);
|
|
287
|
+
}
|
|
288
|
+
const params = [];
|
|
289
|
+
for (let i = 0; i < count; i++) {
|
|
290
|
+
const tag = readU8(payload, cursor, "param tag");
|
|
291
|
+
switch (tag) {
|
|
292
|
+
case 0:
|
|
293
|
+
params.push({ tag: "null" });
|
|
294
|
+
break;
|
|
295
|
+
case 1:
|
|
296
|
+
params.push({ tag: "int", value: readI64(payload, cursor, "int param") });
|
|
297
|
+
break;
|
|
298
|
+
case 2:
|
|
299
|
+
params.push({ tag: "float", value: readF64(payload, cursor, "float param") });
|
|
300
|
+
break;
|
|
301
|
+
case 3:
|
|
302
|
+
params.push({ tag: "bool", value: readU8(payload, cursor, "bool param") !== 0 });
|
|
303
|
+
break;
|
|
304
|
+
case 4:
|
|
305
|
+
params.push({ tag: "str", value: decodeString(payload, cursor) });
|
|
306
|
+
break;
|
|
307
|
+
default:
|
|
308
|
+
throw new Error(`unknown param tag: ${tag}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { type: "QueryWithParams", query, params };
|
|
312
|
+
}
|
|
313
|
+
case exports.MSG_SYNC_STATUS:
|
|
314
|
+
return { type: "SyncStatus", replicaId: decodeString(payload, cursor) };
|
|
315
|
+
case exports.MSG_SYNC_PULL: {
|
|
316
|
+
const replicaId = decodeString(payload, cursor);
|
|
317
|
+
const sinceLsn = readU64(payload, cursor, "sync pull since LSN");
|
|
318
|
+
const maxUnits = readU32(payload, cursor, "sync pull max units");
|
|
319
|
+
const maxBytes = readU64(payload, cursor, "sync pull max bytes");
|
|
320
|
+
const databaseId = readFixedBytes(payload, cursor, 16, "sync database id");
|
|
321
|
+
const primaryGeneration = readU64(payload, cursor, "sync primary generation");
|
|
322
|
+
const walFormatVersion = readU16(payload, cursor, "sync WAL format version");
|
|
323
|
+
const catalogVersion = readU16(payload, cursor, "sync catalog version");
|
|
324
|
+
const segmentFormatVersion = readU16(payload, cursor, "sync segment format version");
|
|
325
|
+
return {
|
|
326
|
+
type: "SyncPull",
|
|
327
|
+
replicaId,
|
|
328
|
+
sinceLsn,
|
|
329
|
+
maxUnits,
|
|
330
|
+
maxBytes,
|
|
331
|
+
databaseId,
|
|
332
|
+
primaryGeneration,
|
|
333
|
+
walFormatVersion,
|
|
334
|
+
catalogVersion,
|
|
335
|
+
segmentFormatVersion,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
case exports.MSG_SYNC_ACK: {
|
|
339
|
+
const replicaId = decodeString(payload, cursor);
|
|
340
|
+
const appliedLsn = readU64(payload, cursor, "sync ack applied LSN");
|
|
341
|
+
const remoteLsn = readU64(payload, cursor, "sync ack remote LSN");
|
|
342
|
+
return { type: "SyncAck", replicaId, appliedLsn, remoteLsn };
|
|
343
|
+
}
|
|
344
|
+
case exports.MSG_SYNC_STATUS_RESULT:
|
|
345
|
+
return {
|
|
346
|
+
type: "SyncStatusResult",
|
|
347
|
+
status: decodeSyncStatus(payload, cursor),
|
|
348
|
+
};
|
|
349
|
+
case exports.MSG_SYNC_PULL_RESULT: {
|
|
350
|
+
const status = decodeSyncStatus(payload, cursor);
|
|
351
|
+
const count = readU32(payload, cursor, "sync retained unit count");
|
|
352
|
+
if (count > exports.MAX_SYNC_UNITS) {
|
|
353
|
+
throw new Error(`too many retained units: ${count} (max ${exports.MAX_SYNC_UNITS})`);
|
|
354
|
+
}
|
|
355
|
+
const units = [];
|
|
356
|
+
for (let i = 0; i < count; i++) {
|
|
357
|
+
units.push(decodeRetainedUnit(payload, cursor));
|
|
358
|
+
}
|
|
359
|
+
const hasMore = readBool(payload, cursor, "sync has_more");
|
|
360
|
+
return { type: "SyncPullResult", status, units, hasMore };
|
|
361
|
+
}
|
|
362
|
+
case exports.MSG_SYNC_ACK_RESULT: {
|
|
363
|
+
const previousAppliedLsn = readU64(payload, cursor, "previous applied LSN");
|
|
364
|
+
const appliedLsn = readU64(payload, cursor, "applied LSN");
|
|
365
|
+
const remoteLsn = readU64(payload, cursor, "remote LSN");
|
|
366
|
+
const advanced = readBool(payload, cursor, "sync ack advanced");
|
|
367
|
+
const status = decodeSyncStatus(payload, cursor);
|
|
368
|
+
return {
|
|
369
|
+
type: "SyncAckResult",
|
|
370
|
+
previousAppliedLsn,
|
|
371
|
+
appliedLsn,
|
|
372
|
+
remoteLsn,
|
|
373
|
+
advanced,
|
|
374
|
+
status,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
case exports.MSG_RESULT_ROWS: {
|
|
378
|
+
const colCount = readU16(payload, cursor, "column count");
|
|
379
|
+
if (colCount > exports.MAX_COLUMNS) {
|
|
380
|
+
throw new Error(`too many columns: ${colCount} (max ${exports.MAX_COLUMNS})`);
|
|
381
|
+
}
|
|
382
|
+
const columns = [];
|
|
383
|
+
for (let i = 0; i < colCount; i++) {
|
|
384
|
+
columns.push(decodeString(payload, cursor));
|
|
385
|
+
}
|
|
386
|
+
const rowCount = readU32(payload, cursor, "row count");
|
|
387
|
+
if (rowCount > exports.MAX_ROWS) {
|
|
388
|
+
throw new Error(`too many rows: ${rowCount} (max ${exports.MAX_ROWS})`);
|
|
389
|
+
}
|
|
390
|
+
if (colCount === 0 && rowCount > 0) {
|
|
391
|
+
throw new Error("nonzero row count with zero columns");
|
|
392
|
+
}
|
|
393
|
+
const remaining = BigInt(payload.length - cursor.pos);
|
|
394
|
+
const minRowBytes = BigInt(rowCount) * BigInt(colCount) * 4n;
|
|
395
|
+
if (remaining < minRowBytes) {
|
|
396
|
+
throw new Error(`row data too short for declared shape: ${rowCount} rows x ${colCount} columns`);
|
|
397
|
+
}
|
|
398
|
+
const rows = [];
|
|
399
|
+
for (let r = 0; r < rowCount; r++) {
|
|
400
|
+
const row = [];
|
|
401
|
+
for (let c = 0; c < colCount; c++) {
|
|
402
|
+
row.push(decodeString(payload, cursor));
|
|
403
|
+
}
|
|
404
|
+
rows.push(row);
|
|
405
|
+
}
|
|
406
|
+
return { type: "ResultRows", columns, rows };
|
|
407
|
+
}
|
|
408
|
+
case exports.MSG_RESULT_SCALAR:
|
|
409
|
+
return { type: "ResultScalar", value: decodeString(payload, cursor) };
|
|
410
|
+
case exports.MSG_RESULT_OK: {
|
|
411
|
+
const affected = readU64(payload, cursor, "affected count");
|
|
412
|
+
return { type: "ResultOk", affected };
|
|
413
|
+
}
|
|
414
|
+
case exports.MSG_RESULT_MSG:
|
|
415
|
+
return { type: "ResultMessage", message: decodeString(payload, cursor) };
|
|
416
|
+
case exports.MSG_ERROR:
|
|
417
|
+
return { type: "Error", message: decodeString(payload, cursor) };
|
|
418
|
+
case exports.MSG_DISCONNECT:
|
|
419
|
+
return { type: "Disconnect" };
|
|
420
|
+
case exports.MSG_PING:
|
|
421
|
+
return { type: "Ping" };
|
|
422
|
+
case exports.MSG_PONG:
|
|
423
|
+
return { type: "Pong" };
|
|
424
|
+
default:
|
|
425
|
+
throw new Error(`unknown message type: 0x${msgType.toString(16)}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function encodeSyncStatus(status) {
|
|
429
|
+
return Buffer.concat([
|
|
430
|
+
encodeString(status.replicaId),
|
|
431
|
+
Buffer.from([status.active ? 1 : 0]),
|
|
432
|
+
encodeOptionalU64(status.lastAppliedLsn),
|
|
433
|
+
u64LE(status.remoteLsn),
|
|
434
|
+
encodeOptionalU64(status.servableLsn),
|
|
435
|
+
encodeOptionalU64(status.unarchivedLsn),
|
|
436
|
+
encodeOptionalU64(status.lagLsn),
|
|
437
|
+
encodeOptionalU64(status.lagBytes),
|
|
438
|
+
encodeOptionalU64(status.lagMs),
|
|
439
|
+
Buffer.from([
|
|
440
|
+
status.stale ? 1 : 0,
|
|
441
|
+
encodeRepairAction(status.repairAction),
|
|
442
|
+
]),
|
|
443
|
+
encodeOptionalString(status.lastSyncError),
|
|
444
|
+
]);
|
|
445
|
+
}
|
|
446
|
+
function decodeSyncStatus(buf, cursor) {
|
|
447
|
+
const replicaId = decodeString(buf, cursor);
|
|
448
|
+
const active = readBool(buf, cursor, "sync status active");
|
|
449
|
+
const lastAppliedLsn = readOptionalU64(buf, cursor, "sync status last applied LSN");
|
|
450
|
+
const remoteLsn = readU64(buf, cursor, "sync status remote LSN");
|
|
451
|
+
const servableLsn = readOptionalU64(buf, cursor, "sync status servable LSN");
|
|
452
|
+
const unarchivedLsn = readOptionalU64(buf, cursor, "sync status unarchived LSN");
|
|
453
|
+
const lagLsn = readOptionalU64(buf, cursor, "sync status lag LSN");
|
|
454
|
+
const lagBytes = readOptionalU64(buf, cursor, "sync status lag bytes");
|
|
455
|
+
const lagMs = readOptionalU64(buf, cursor, "sync status lag milliseconds");
|
|
456
|
+
const stale = readBool(buf, cursor, "sync status stale");
|
|
457
|
+
const repairAction = decodeRepairAction(readU8(buf, cursor, "sync repair action"));
|
|
458
|
+
const lastSyncError = readOptionalString(buf, cursor, "sync status last error");
|
|
459
|
+
return {
|
|
460
|
+
replicaId,
|
|
461
|
+
active,
|
|
462
|
+
lastAppliedLsn,
|
|
463
|
+
remoteLsn,
|
|
464
|
+
servableLsn,
|
|
465
|
+
unarchivedLsn,
|
|
466
|
+
lagLsn,
|
|
467
|
+
lagBytes,
|
|
468
|
+
lagMs,
|
|
469
|
+
stale,
|
|
470
|
+
repairAction,
|
|
471
|
+
lastSyncError,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function encodeRetainedUnit(unit) {
|
|
475
|
+
return Buffer.concat([
|
|
476
|
+
u64LE(unit.txId),
|
|
477
|
+
Buffer.from([u8(unit.recordType, "sync retained unit record type")]),
|
|
478
|
+
u64LE(unit.lsn),
|
|
479
|
+
encodeBytes(unit.data),
|
|
480
|
+
]);
|
|
481
|
+
}
|
|
482
|
+
function decodeRetainedUnit(buf, cursor) {
|
|
483
|
+
const txId = readU64(buf, cursor, "sync retained unit tx id");
|
|
484
|
+
const recordType = readU8(buf, cursor, "sync retained unit record type");
|
|
485
|
+
const lsn = readU64(buf, cursor, "sync retained unit LSN");
|
|
486
|
+
const data = readBytes(buf, cursor, "sync retained unit payload");
|
|
487
|
+
return { txId, recordType, lsn, data };
|
|
488
|
+
}
|
|
489
|
+
function encodeRepairAction(action) {
|
|
490
|
+
switch (action) {
|
|
491
|
+
case "none":
|
|
492
|
+
return 0;
|
|
493
|
+
case "pull":
|
|
494
|
+
return 1;
|
|
495
|
+
case "awaitArchive":
|
|
496
|
+
return 2;
|
|
497
|
+
case "rebootstrap":
|
|
498
|
+
return 3;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function decodeRepairAction(raw) {
|
|
502
|
+
switch (raw) {
|
|
503
|
+
case 0:
|
|
504
|
+
return "none";
|
|
505
|
+
case 1:
|
|
506
|
+
return "pull";
|
|
507
|
+
case 2:
|
|
508
|
+
return "awaitArchive";
|
|
509
|
+
case 3:
|
|
510
|
+
return "rebootstrap";
|
|
511
|
+
default:
|
|
512
|
+
throw new Error(`unknown sync repair action: ${raw}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function encodeOptionalU64(value) {
|
|
516
|
+
return value === null
|
|
517
|
+
? Buffer.from([0])
|
|
518
|
+
: Buffer.concat([Buffer.from([1]), u64LE(value)]);
|
|
519
|
+
}
|
|
520
|
+
function readOptionalU64(buf, cursor, label) {
|
|
521
|
+
return readBool(buf, cursor, label) ? readU64(buf, cursor, label) : null;
|
|
522
|
+
}
|
|
523
|
+
function encodeOptionalString(value) {
|
|
524
|
+
return value === null
|
|
525
|
+
? Buffer.from([0])
|
|
526
|
+
: Buffer.concat([Buffer.from([1]), encodeString(value)]);
|
|
527
|
+
}
|
|
528
|
+
function readOptionalString(buf, cursor, label) {
|
|
529
|
+
return readBool(buf, cursor, label) ? decodeString(buf, cursor) : null;
|
|
530
|
+
}
|
|
531
|
+
function encodeBytes(bytes) {
|
|
532
|
+
return Buffer.concat([u32LE(bytes.length), bytes]);
|
|
533
|
+
}
|
|
534
|
+
// ───── String helpers ──────────────────────────────────────────────────────
|
|
535
|
+
function encodeString(s) {
|
|
536
|
+
const bytes = Buffer.from(s, "utf8");
|
|
537
|
+
const out = Buffer.alloc(4 + bytes.length);
|
|
538
|
+
out.writeUInt32LE(bytes.length, 0);
|
|
539
|
+
bytes.copy(out, 4);
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
function decodeString(buf, cursor) {
|
|
543
|
+
if (cursor.pos + 4 > buf.length) {
|
|
544
|
+
throw new Error("truncated string length");
|
|
545
|
+
}
|
|
546
|
+
const len = readU32(buf, cursor, "string length");
|
|
547
|
+
if (cursor.pos + len > buf.length) {
|
|
548
|
+
throw new Error("truncated string data");
|
|
549
|
+
}
|
|
550
|
+
const s = buf.toString("utf8", cursor.pos, cursor.pos + len);
|
|
551
|
+
cursor.pos += len;
|
|
552
|
+
return s;
|
|
553
|
+
}
|
|
554
|
+
function readU8(buf, cursor, label) {
|
|
555
|
+
if (cursor.pos + 1 > buf.length) {
|
|
556
|
+
throw new Error(`truncated ${label}`);
|
|
557
|
+
}
|
|
558
|
+
const value = buf.readUInt8(cursor.pos);
|
|
559
|
+
cursor.pos += 1;
|
|
560
|
+
return value;
|
|
561
|
+
}
|
|
562
|
+
function readBool(buf, cursor, label) {
|
|
563
|
+
const raw = readU8(buf, cursor, label);
|
|
564
|
+
if (raw === 0)
|
|
565
|
+
return false;
|
|
566
|
+
if (raw === 1)
|
|
567
|
+
return true;
|
|
568
|
+
throw new Error(`invalid boolean for ${label}: ${raw}`);
|
|
569
|
+
}
|
|
570
|
+
function readI64(buf, cursor, label) {
|
|
571
|
+
if (cursor.pos + 8 > buf.length) {
|
|
572
|
+
throw new Error(`truncated ${label}`);
|
|
573
|
+
}
|
|
574
|
+
const value = buf.readBigInt64LE(cursor.pos);
|
|
575
|
+
cursor.pos += 8;
|
|
576
|
+
return value;
|
|
577
|
+
}
|
|
578
|
+
function readF64(buf, cursor, label) {
|
|
579
|
+
if (cursor.pos + 8 > buf.length) {
|
|
580
|
+
throw new Error(`truncated ${label}`);
|
|
581
|
+
}
|
|
582
|
+
const value = buf.readDoubleLE(cursor.pos);
|
|
583
|
+
cursor.pos += 8;
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
function readU16(buf, cursor, label) {
|
|
587
|
+
if (cursor.pos + 2 > buf.length) {
|
|
588
|
+
throw new Error(`truncated ${label}`);
|
|
589
|
+
}
|
|
590
|
+
const value = buf.readUInt16LE(cursor.pos);
|
|
591
|
+
cursor.pos += 2;
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
594
|
+
function readU32(buf, cursor, label) {
|
|
595
|
+
if (cursor.pos + 4 > buf.length) {
|
|
596
|
+
throw new Error(`truncated ${label}`);
|
|
597
|
+
}
|
|
598
|
+
const value = buf.readUInt32LE(cursor.pos);
|
|
599
|
+
cursor.pos += 4;
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
function readU64(buf, cursor, label) {
|
|
603
|
+
if (cursor.pos + 8 > buf.length) {
|
|
604
|
+
throw new Error(`truncated ${label}`);
|
|
605
|
+
}
|
|
606
|
+
const value = buf.readBigUInt64LE(cursor.pos);
|
|
607
|
+
cursor.pos += 8;
|
|
608
|
+
return value;
|
|
609
|
+
}
|
|
610
|
+
function readFixedBytes(buf, cursor, len, label) {
|
|
611
|
+
if (cursor.pos + len > buf.length) {
|
|
612
|
+
throw new Error(`truncated ${label}`);
|
|
613
|
+
}
|
|
614
|
+
const value = Buffer.from(buf.subarray(cursor.pos, cursor.pos + len));
|
|
615
|
+
cursor.pos += len;
|
|
616
|
+
return value;
|
|
617
|
+
}
|
|
618
|
+
function readBytes(buf, cursor, label) {
|
|
619
|
+
const len = readU32(buf, cursor, label);
|
|
620
|
+
return readFixedBytes(buf, cursor, len, label);
|
|
621
|
+
}
|
|
622
|
+
function u16LE(n) {
|
|
623
|
+
const b = Buffer.alloc(2);
|
|
624
|
+
b.writeUInt16LE(n, 0);
|
|
625
|
+
return b;
|
|
626
|
+
}
|
|
627
|
+
function u8(n, label) {
|
|
628
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xff) {
|
|
629
|
+
throw new Error(`${label} must fit in u8`);
|
|
630
|
+
}
|
|
631
|
+
return n;
|
|
632
|
+
}
|
|
633
|
+
function u32LE(n) {
|
|
634
|
+
const b = Buffer.alloc(4);
|
|
635
|
+
b.writeUInt32LE(n, 0);
|
|
636
|
+
return b;
|
|
637
|
+
}
|
|
638
|
+
function u64LE(n) {
|
|
639
|
+
if (n < 0n || n > 0xffffffffffffffffn) {
|
|
640
|
+
throw new Error(`u64 value out of range: ${n}`);
|
|
641
|
+
}
|
|
642
|
+
const b = Buffer.alloc(8);
|
|
643
|
+
b.writeBigUInt64LE(n, 0);
|
|
644
|
+
return b;
|
|
645
|
+
}
|
|
@@ -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[];
|