@crashlab/pg-mock 0.1.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 +3 -0
- package/dist/index.cjs +432 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +399 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +26 -0
- package/dist/protocol.d.ts.map +1 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# @crashlab/pg-mock
|
|
2
|
+
|
|
3
|
+
PostgreSQL wire protocol v3 mock for CrashLab. Supports startup handshake (no SSL), simple query protocol, RowDescription/DataRow/CommandComplete, BEGIN/COMMIT/ROLLBACK, and basic SQL patterns (SELECT/INSERT/UPDATE/DELETE with WHERE). Throws `CrashLabUnsupportedPGFeature` for unsupported features. Plugs into `@crashlab/tcp` as a handler.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
PgMock: () => PgMock,
|
|
34
|
+
SimNodeUnsupportedPGFeature: () => SimNodeUnsupportedPGFeature,
|
|
35
|
+
proto: () => protocol_exports
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/protocol.ts
|
|
40
|
+
var protocol_exports = {};
|
|
41
|
+
__export(protocol_exports, {
|
|
42
|
+
authOk: () => authOk,
|
|
43
|
+
backendKeyData: () => backendKeyData,
|
|
44
|
+
bindComplete: () => bindComplete,
|
|
45
|
+
commandComplete: () => commandComplete,
|
|
46
|
+
dataRow: () => dataRow,
|
|
47
|
+
errorResponse: () => errorResponse,
|
|
48
|
+
noData: () => noData,
|
|
49
|
+
paramStatus: () => paramStatus,
|
|
50
|
+
parseComplete: () => parseComplete,
|
|
51
|
+
parseExecuteMsg: () => parseExecuteMsg,
|
|
52
|
+
parseQueryMsg: () => parseQueryMsg,
|
|
53
|
+
parseStartupMsg: () => parseStartupMsg,
|
|
54
|
+
readyForQuery: () => readyForQuery,
|
|
55
|
+
rowDescription: () => rowDescription,
|
|
56
|
+
startupResponse: () => startupResponse
|
|
57
|
+
});
|
|
58
|
+
var int32 = (n) => {
|
|
59
|
+
const b = Buffer.alloc(4);
|
|
60
|
+
b.writeInt32BE(n);
|
|
61
|
+
return b;
|
|
62
|
+
};
|
|
63
|
+
var int16 = (n) => {
|
|
64
|
+
const b = Buffer.alloc(2);
|
|
65
|
+
b.writeInt16BE(n);
|
|
66
|
+
return b;
|
|
67
|
+
};
|
|
68
|
+
var cstr = (s) => Buffer.from(s + "\0", "utf8");
|
|
69
|
+
function pgMsg(type, payload) {
|
|
70
|
+
return Buffer.concat([Buffer.from(type), int32(payload.length + 4), payload]);
|
|
71
|
+
}
|
|
72
|
+
var authOk = () => pgMsg("R", int32(0));
|
|
73
|
+
var paramStatus = (k, v) => pgMsg("S", Buffer.concat([cstr(k), cstr(v)]));
|
|
74
|
+
var backendKeyData = () => pgMsg("K", Buffer.concat([int32(1), int32(1)]));
|
|
75
|
+
var readyForQuery = (s) => pgMsg("Z", Buffer.from(s));
|
|
76
|
+
function rowDescription(cols) {
|
|
77
|
+
const parts = [int16(cols.length)];
|
|
78
|
+
for (const c of cols) {
|
|
79
|
+
const typeSizeBuf = Buffer.alloc(2);
|
|
80
|
+
typeSizeBuf.writeInt16BE(-1);
|
|
81
|
+
parts.push(cstr(c), int32(0), int16(0), int32(25), typeSizeBuf, int32(-1), int16(0));
|
|
82
|
+
}
|
|
83
|
+
return pgMsg("T", Buffer.concat(parts));
|
|
84
|
+
}
|
|
85
|
+
function dataRow(vals) {
|
|
86
|
+
const parts = [int16(vals.length)];
|
|
87
|
+
for (const v of vals) {
|
|
88
|
+
if (v === null) {
|
|
89
|
+
parts.push(int32(-1));
|
|
90
|
+
} else {
|
|
91
|
+
const b = Buffer.from(v, "utf8");
|
|
92
|
+
parts.push(int32(b.length), b);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return pgMsg("D", Buffer.concat(parts));
|
|
96
|
+
}
|
|
97
|
+
var commandComplete = (tag) => pgMsg("C", cstr(tag));
|
|
98
|
+
function errorResponse(msg, code = "42000") {
|
|
99
|
+
return pgMsg("E", Buffer.concat([
|
|
100
|
+
Buffer.from("S"),
|
|
101
|
+
cstr("ERROR"),
|
|
102
|
+
Buffer.from("C"),
|
|
103
|
+
cstr(code),
|
|
104
|
+
Buffer.from("M"),
|
|
105
|
+
cstr(msg),
|
|
106
|
+
Buffer.from([0])
|
|
107
|
+
]));
|
|
108
|
+
}
|
|
109
|
+
function startupResponse() {
|
|
110
|
+
return Buffer.concat([
|
|
111
|
+
authOk(),
|
|
112
|
+
paramStatus("server_version", "15.0"),
|
|
113
|
+
paramStatus("client_encoding", "UTF8"),
|
|
114
|
+
backendKeyData(),
|
|
115
|
+
readyForQuery("I")
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
var parseComplete = () => pgMsg("1", Buffer.alloc(0));
|
|
119
|
+
var bindComplete = () => pgMsg("2", Buffer.alloc(0));
|
|
120
|
+
var noData = () => pgMsg("n", Buffer.alloc(0));
|
|
121
|
+
function parseExecuteMsg(payload) {
|
|
122
|
+
const nul = payload.indexOf(0);
|
|
123
|
+
return nul > 0 ? payload.toString("utf8", 0, nul) : "";
|
|
124
|
+
}
|
|
125
|
+
function parseStartupMsg(data) {
|
|
126
|
+
if (data.length >= 8 && data.readInt32BE(4) === 80877103) return { isSSL: true };
|
|
127
|
+
let off = 8;
|
|
128
|
+
const params = {};
|
|
129
|
+
while (off < data.length - 1) {
|
|
130
|
+
const kEnd = data.indexOf(0, off);
|
|
131
|
+
if (kEnd < 0) break;
|
|
132
|
+
const key = data.toString("utf8", off, kEnd);
|
|
133
|
+
off = kEnd + 1;
|
|
134
|
+
const vEnd = data.indexOf(0, off);
|
|
135
|
+
if (vEnd < 0) break;
|
|
136
|
+
params[key] = data.toString("utf8", off, vEnd);
|
|
137
|
+
off = vEnd + 1;
|
|
138
|
+
}
|
|
139
|
+
return { user: params.user ?? "unknown", database: params.database ?? "unknown" };
|
|
140
|
+
}
|
|
141
|
+
function parseQueryMsg(data) {
|
|
142
|
+
const nul = data.indexOf(0, 5);
|
|
143
|
+
return data.toString("utf8", 5, nul >= 0 ? nul : data.length);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/index.ts
|
|
147
|
+
var SimNodeUnsupportedPGFeature = class extends Error {
|
|
148
|
+
constructor(detail) {
|
|
149
|
+
super(`SimNode: Unsupported PostgreSQL feature: ${detail}`);
|
|
150
|
+
this.name = "SimNodeUnsupportedPGFeature";
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function createPGliteInstance() {
|
|
154
|
+
return import("@electric-sql/pglite").then(({ PGlite }) => new PGlite());
|
|
155
|
+
}
|
|
156
|
+
function inferTag(sql, rowCount, affected) {
|
|
157
|
+
const verb = sql.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
|
|
158
|
+
switch (verb) {
|
|
159
|
+
case "SELECT":
|
|
160
|
+
return `SELECT ${rowCount}`;
|
|
161
|
+
case "INSERT":
|
|
162
|
+
return `INSERT 0 ${affected ?? rowCount}`;
|
|
163
|
+
case "UPDATE":
|
|
164
|
+
return `UPDATE ${affected ?? rowCount}`;
|
|
165
|
+
case "DELETE":
|
|
166
|
+
return `DELETE ${affected ?? rowCount}`;
|
|
167
|
+
case "CREATE":
|
|
168
|
+
return "CREATE TABLE";
|
|
169
|
+
case "DROP":
|
|
170
|
+
return "DROP TABLE";
|
|
171
|
+
case "BEGIN":
|
|
172
|
+
return "BEGIN";
|
|
173
|
+
case "COMMIT":
|
|
174
|
+
return "COMMIT";
|
|
175
|
+
case "ROLLBACK":
|
|
176
|
+
return "ROLLBACK";
|
|
177
|
+
default:
|
|
178
|
+
return verb;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
var PgConnection = class {
|
|
182
|
+
constructor(_pglite) {
|
|
183
|
+
this._pglite = _pglite;
|
|
184
|
+
}
|
|
185
|
+
_phase = "startup";
|
|
186
|
+
_txState = "I";
|
|
187
|
+
_buf = Buffer.alloc(0);
|
|
188
|
+
/** Prepared statements: statement name → SQL. '' = unnamed statement. */
|
|
189
|
+
_statements = /* @__PURE__ */ new Map();
|
|
190
|
+
/** Bound portals: portal name → { sql, params }. '' = unnamed portal. */
|
|
191
|
+
_portals = /* @__PURE__ */ new Map();
|
|
192
|
+
async processData(data) {
|
|
193
|
+
this._buf = this._buf.length > 0 ? Buffer.concat([this._buf, data]) : data;
|
|
194
|
+
if (this._phase === "startup") {
|
|
195
|
+
if (this._buf.length < 4) return Buffer.alloc(0);
|
|
196
|
+
const msgLen = this._buf.readInt32BE(0);
|
|
197
|
+
if (this._buf.length < msgLen) return Buffer.alloc(0);
|
|
198
|
+
const msg = this._buf.subarray(0, msgLen);
|
|
199
|
+
this._buf = this._buf.subarray(msgLen);
|
|
200
|
+
const parsed = parseStartupMsg(msg);
|
|
201
|
+
if ("isSSL" in parsed) return Buffer.from("N");
|
|
202
|
+
this._phase = "ready";
|
|
203
|
+
return startupResponse();
|
|
204
|
+
}
|
|
205
|
+
const responses = [];
|
|
206
|
+
while (this._buf.length >= 5) {
|
|
207
|
+
const msgType = String.fromCharCode(this._buf[0]);
|
|
208
|
+
const msgLen = this._buf.readInt32BE(1);
|
|
209
|
+
const totalLen = 1 + msgLen;
|
|
210
|
+
if (this._buf.length < totalLen) break;
|
|
211
|
+
const payload = this._buf.subarray(5, totalLen);
|
|
212
|
+
this._buf = this._buf.subarray(totalLen);
|
|
213
|
+
if (msgType === "Q") {
|
|
214
|
+
const nul = payload.indexOf(0);
|
|
215
|
+
const sql = payload.toString("utf8", 0, nul >= 0 ? nul : payload.length);
|
|
216
|
+
responses.push(await this._execQuery(sql));
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
switch (msgType) {
|
|
220
|
+
case "P": {
|
|
221
|
+
const nameEnd = payload.indexOf(0);
|
|
222
|
+
const stmtName = nameEnd > 0 ? payload.toString("utf8", 0, nameEnd) : "";
|
|
223
|
+
const queryStart = nameEnd + 1;
|
|
224
|
+
const queryEnd = payload.indexOf(0, queryStart);
|
|
225
|
+
const sql = payload.toString("utf8", queryStart, queryEnd >= 0 ? queryEnd : payload.length);
|
|
226
|
+
this._statements.set(stmtName, sql);
|
|
227
|
+
responses.push(parseComplete());
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "B": {
|
|
231
|
+
const { portal, statement, params } = this._parseBind(payload);
|
|
232
|
+
const boundSql = this._statements.get(statement) ?? "";
|
|
233
|
+
this._portals.set(portal, { sql: boundSql, params });
|
|
234
|
+
responses.push(bindComplete());
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "D": {
|
|
238
|
+
responses.push(noData());
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "E": {
|
|
242
|
+
const portalEnd = payload.indexOf(0);
|
|
243
|
+
const portalName = portalEnd > 0 ? payload.toString("utf8", 0, portalEnd) : "";
|
|
244
|
+
const bound = this._portals.get(portalName);
|
|
245
|
+
if (bound && bound.sql) {
|
|
246
|
+
const r = await this._execQueryWithParams(bound.sql, bound.params);
|
|
247
|
+
responses.push(r);
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "S": {
|
|
252
|
+
responses.push(readyForQuery(this._txState));
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case "X": {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
default:
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return responses.length > 0 ? Buffer.concat(responses) : Buffer.alloc(0);
|
|
263
|
+
}
|
|
264
|
+
/** Parse a Bind message payload into portal, statement, and parameter values. */
|
|
265
|
+
_parseBind(payload) {
|
|
266
|
+
let off = 0;
|
|
267
|
+
const portalEnd = payload.indexOf(0, off);
|
|
268
|
+
const portal = portalEnd > off ? payload.toString("utf8", off, portalEnd) : "";
|
|
269
|
+
off = portalEnd + 1;
|
|
270
|
+
const stmtEnd = payload.indexOf(0, off);
|
|
271
|
+
const statement = stmtEnd > off ? payload.toString("utf8", off, stmtEnd) : "";
|
|
272
|
+
off = stmtEnd + 1;
|
|
273
|
+
const numFormats = payload.readInt16BE(off);
|
|
274
|
+
off += 2;
|
|
275
|
+
off += numFormats * 2;
|
|
276
|
+
const numParams = payload.readInt16BE(off);
|
|
277
|
+
off += 2;
|
|
278
|
+
const params = [];
|
|
279
|
+
for (let i = 0; i < numParams; i++) {
|
|
280
|
+
const len = payload.readInt32BE(off);
|
|
281
|
+
off += 4;
|
|
282
|
+
if (len === -1) {
|
|
283
|
+
params.push("NULL");
|
|
284
|
+
} else {
|
|
285
|
+
params.push(payload.toString("utf8", off, off + len));
|
|
286
|
+
off += len;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { portal, statement, params };
|
|
290
|
+
}
|
|
291
|
+
/** Execute a parameterized query — substitutes $1, $2, … and runs via PGlite. */
|
|
292
|
+
async _execQueryWithParams(sql, params) {
|
|
293
|
+
const trimmed = sql.trim();
|
|
294
|
+
const upper = trimmed.toUpperCase();
|
|
295
|
+
if (upper === "BEGIN") {
|
|
296
|
+
this._txState = "T";
|
|
297
|
+
return commandComplete("BEGIN");
|
|
298
|
+
}
|
|
299
|
+
if (upper === "COMMIT") {
|
|
300
|
+
this._txState = "I";
|
|
301
|
+
return commandComplete("COMMIT");
|
|
302
|
+
}
|
|
303
|
+
if (upper === "ROLLBACK") {
|
|
304
|
+
this._txState = "I";
|
|
305
|
+
return commandComplete("ROLLBACK");
|
|
306
|
+
}
|
|
307
|
+
const db = await this._pglite;
|
|
308
|
+
try {
|
|
309
|
+
const result = await db.query(trimmed, params);
|
|
310
|
+
const fields = result.fields ?? [];
|
|
311
|
+
const rows = result.rows ?? [];
|
|
312
|
+
const bufs = [];
|
|
313
|
+
if (fields.length > 0) {
|
|
314
|
+
bufs.push(rowDescription(fields.map((f) => f.name)));
|
|
315
|
+
for (const row of rows) {
|
|
316
|
+
bufs.push(dataRow(fields.map((f) => {
|
|
317
|
+
const v = row[f.name];
|
|
318
|
+
return v === null || v === void 0 ? null : String(v);
|
|
319
|
+
})));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const tag = inferTag(trimmed, rows.length, result.affectedRows);
|
|
323
|
+
bufs.push(commandComplete(tag));
|
|
324
|
+
return Buffer.concat(bufs);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async _execQuery(sql) {
|
|
330
|
+
const trimmed = sql.trim();
|
|
331
|
+
const upper = trimmed.toUpperCase();
|
|
332
|
+
if (upper === "BEGIN") {
|
|
333
|
+
this._txState = "T";
|
|
334
|
+
return Buffer.concat([commandComplete("BEGIN"), readyForQuery("T")]);
|
|
335
|
+
}
|
|
336
|
+
if (upper === "COMMIT") {
|
|
337
|
+
this._txState = "I";
|
|
338
|
+
return Buffer.concat([commandComplete("COMMIT"), readyForQuery("I")]);
|
|
339
|
+
}
|
|
340
|
+
if (upper === "ROLLBACK") {
|
|
341
|
+
this._txState = "I";
|
|
342
|
+
return Buffer.concat([commandComplete("ROLLBACK"), readyForQuery("I")]);
|
|
343
|
+
}
|
|
344
|
+
const db = await this._pglite;
|
|
345
|
+
try {
|
|
346
|
+
const result = await db.query(trimmed);
|
|
347
|
+
const fields = result.fields ?? [];
|
|
348
|
+
const rows = result.rows ?? [];
|
|
349
|
+
const bufs = [];
|
|
350
|
+
if (fields.length > 0) {
|
|
351
|
+
bufs.push(rowDescription(fields.map((f) => f.name)));
|
|
352
|
+
for (const row of rows) {
|
|
353
|
+
bufs.push(dataRow(fields.map((f) => {
|
|
354
|
+
const v = row[f.name];
|
|
355
|
+
return v === null || v === void 0 ? null : String(v);
|
|
356
|
+
})));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const tag = inferTag(trimmed, rows.length, result.affectedRows);
|
|
360
|
+
if (tag === "BEGIN") this._txState = "T";
|
|
361
|
+
else if (tag === "COMMIT" || tag === "ROLLBACK") this._txState = "I";
|
|
362
|
+
bufs.push(commandComplete(tag));
|
|
363
|
+
bufs.push(readyForQuery(this._txState));
|
|
364
|
+
return Buffer.concat(bufs);
|
|
365
|
+
} catch (err) {
|
|
366
|
+
return Buffer.concat([
|
|
367
|
+
errorResponse(err instanceof Error ? err.message : String(err)),
|
|
368
|
+
readyForQuery(this._txState === "T" ? "E" : "I")
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
var PgMock = class {
|
|
374
|
+
/** Shared PGlite instance (one per PgMock, lazy-initialised). */
|
|
375
|
+
_pglite;
|
|
376
|
+
/** Tracks all in-flight seed operations so ready() can await them. */
|
|
377
|
+
_seedPromise = Promise.resolve();
|
|
378
|
+
_connections = /* @__PURE__ */ new Map();
|
|
379
|
+
constructor() {
|
|
380
|
+
this._pglite = createPGliteInstance();
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Resolves once PGlite is initialised AND all pending seedData() calls have
|
|
384
|
+
* been mirrored into PGlite. Await this before making wire-protocol queries
|
|
385
|
+
* in tests that call seedData().
|
|
386
|
+
*/
|
|
387
|
+
async ready() {
|
|
388
|
+
await this._pglite;
|
|
389
|
+
await this._seedPromise;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Seed data directly into PGlite.
|
|
393
|
+
* Creates a simple text-column table with the supplied rows.
|
|
394
|
+
*/
|
|
395
|
+
seedData(table, rows) {
|
|
396
|
+
this._seedPromise = this._seedPromise.then(() => this._seedPGlite(table, rows));
|
|
397
|
+
}
|
|
398
|
+
async _seedPGlite(table, rows) {
|
|
399
|
+
if (rows.length === 0) return;
|
|
400
|
+
const db = await this._pglite;
|
|
401
|
+
const cols = Object.keys(rows[0]);
|
|
402
|
+
const colDefs = cols.map((c) => `"${c}" TEXT`).join(", ");
|
|
403
|
+
await db.exec(`CREATE TABLE IF NOT EXISTS "${table}" (${colDefs})`);
|
|
404
|
+
for (const row of rows) {
|
|
405
|
+
const vals = cols.map((c) => row[c] === null ? "NULL" : `'${String(row[c]).replace(/'/g, "''")}'`).join(", ");
|
|
406
|
+
await db.exec(`INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${vals})`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Execute a raw SQL query against the embedded PGlite instance.
|
|
411
|
+
* Returns rows as plain objects keyed by column name.
|
|
412
|
+
*/
|
|
413
|
+
async query(sql) {
|
|
414
|
+
const db = await this._pglite;
|
|
415
|
+
return db.query(sql);
|
|
416
|
+
}
|
|
417
|
+
createHandler() {
|
|
418
|
+
return async (data, ctx) => {
|
|
419
|
+
if (!this._connections.has(ctx.socketId)) {
|
|
420
|
+
this._connections.set(ctx.socketId, new PgConnection(this._pglite));
|
|
421
|
+
}
|
|
422
|
+
return this._connections.get(ctx.socketId).processData(data);
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
427
|
+
0 && (module.exports = {
|
|
428
|
+
PgMock,
|
|
429
|
+
SimNodeUnsupportedPGFeature,
|
|
430
|
+
proto
|
|
431
|
+
});
|
|
432
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/protocol.ts"],"sourcesContent":["import type { TcpMockHandler, TcpMockContext, TcpHandlerResult } from '@crashlab/tcp';\nimport * as proto from './protocol.js';\n\nexport class SimNodeUnsupportedPGFeature extends Error {\n constructor(detail: string) {\n super(`SimNode: Unsupported PostgreSQL feature: ${detail}`);\n this.name = 'SimNodeUnsupportedPGFeature';\n }\n}\n\n// ── PGlite helpers ────────────────────────────────────────────────────────────\n\n// Dynamically imported so the package remains optional at load time.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PGliteInstance = any;\n\nfunction createPGliteInstance(): Promise<PGliteInstance> {\n return import('@electric-sql/pglite').then(({ PGlite }) => new PGlite());\n}\n\n/** Infer a PostgreSQL command tag from the SQL statement and affected-row count. */\nfunction inferTag(sql: string, rowCount: number, affected?: number): string {\n const verb = sql.trim().split(/\\s+/)[0]?.toUpperCase() ?? '';\n switch (verb) {\n case 'SELECT': return `SELECT ${rowCount}`;\n case 'INSERT': return `INSERT 0 ${affected ?? rowCount}`;\n case 'UPDATE': return `UPDATE ${affected ?? rowCount}`;\n case 'DELETE': return `DELETE ${affected ?? rowCount}`;\n case 'CREATE': return 'CREATE TABLE';\n case 'DROP': return 'DROP TABLE';\n case 'BEGIN': return 'BEGIN';\n case 'COMMIT': return 'COMMIT';\n case 'ROLLBACK': return 'ROLLBACK';\n default: return verb;\n }\n}\n\n// ── PgConnection ──────────────────────────────────────────────────────────────\n\nclass PgConnection {\n private _phase: 'startup' | 'ready' = 'startup';\n private _txState: 'I' | 'T' | 'E' = 'I';\n private _buf: Buffer = Buffer.alloc(0);\n\n /** Prepared statements: statement name → SQL. '' = unnamed statement. */\n private _statements = new Map<string, string>();\n /** Bound portals: portal name → { sql, params }. '' = unnamed portal. */\n private _portals = new Map<string, { sql: string; params: string[] }>();\n\n constructor(private _pglite: Promise<PGliteInstance>) {}\n\n async processData(data: Buffer): Promise<Buffer> {\n this._buf = this._buf.length > 0 ? Buffer.concat([this._buf, data]) : data;\n\n // ── Startup / SSL handshake ───────────────────────────────────────────────\n if (this._phase === 'startup') {\n // SSL probe is exactly 8 bytes; startup message has a 4-byte length prefix\n if (this._buf.length < 4) return Buffer.alloc(0);\n const msgLen = this._buf.readInt32BE(0);\n if (this._buf.length < msgLen) return Buffer.alloc(0);\n\n const msg = this._buf.subarray(0, msgLen);\n this._buf = this._buf.subarray(msgLen);\n\n const parsed = proto.parseStartupMsg(msg);\n if ('isSSL' in parsed) return Buffer.from('N');\n this._phase = 'ready';\n return proto.startupResponse();\n }\n\n // ── Ready phase: consume complete framed messages ──────────────────────────\n const responses: Buffer[] = [];\n\n while (this._buf.length >= 5) {\n const msgType = String.fromCharCode(this._buf[0]);\n const msgLen = this._buf.readInt32BE(1); // includes self (4 bytes) but not type byte\n const totalLen = 1 + msgLen;\n if (this._buf.length < totalLen) break; // incomplete — wait for more data\n\n const payload = this._buf.subarray(5, totalLen);\n this._buf = this._buf.subarray(totalLen);\n\n // Simple Query ('Q')\n if (msgType === 'Q') {\n const nul = payload.indexOf(0);\n const sql = payload.toString('utf8', 0, nul >= 0 ? nul : payload.length);\n responses.push(await this._execQuery(sql));\n continue;\n }\n\n switch (msgType) {\n case 'P': { // Parse: statement_name\\0 + query\\0 + Int16(numParams) + ...\n const nameEnd = payload.indexOf(0);\n const stmtName = nameEnd > 0 ? payload.toString('utf8', 0, nameEnd) : '';\n const queryStart = nameEnd + 1;\n const queryEnd = payload.indexOf(0, queryStart);\n const sql = payload.toString('utf8', queryStart, queryEnd >= 0 ? queryEnd : payload.length);\n this._statements.set(stmtName, sql);\n responses.push(proto.parseComplete());\n break;\n }\n case 'B': { // Bind: portal\\0 + statement\\0 + formats + params + result_formats\n const { portal, statement, params } = this._parseBind(payload);\n const boundSql = this._statements.get(statement) ?? '';\n this._portals.set(portal, { sql: boundSql, params });\n responses.push(proto.bindComplete());\n break;\n }\n case 'D': { // Describe\n responses.push(proto.noData());\n break;\n }\n case 'E': { // Execute: portal\\0 + maxRows(Int32)\n const portalEnd = payload.indexOf(0);\n const portalName = portalEnd > 0 ? payload.toString('utf8', 0, portalEnd) : '';\n const bound = this._portals.get(portalName);\n if (bound && bound.sql) {\n const r = await this._execQueryWithParams(bound.sql, bound.params);\n responses.push(r);\n }\n break;\n }\n case 'S': { // Sync\n responses.push(proto.readyForQuery(this._txState));\n break;\n }\n case 'X': { // Terminate\n break;\n }\n default:\n // Silently ignore unknown message types\n break;\n }\n }\n\n return responses.length > 0 ? Buffer.concat(responses) : Buffer.alloc(0);\n }\n\n /** Parse a Bind message payload into portal, statement, and parameter values. */\n private _parseBind(payload: Buffer): { portal: string; statement: string; params: string[] } {\n let off = 0;\n // portal name \\0\n const portalEnd = payload.indexOf(0, off);\n const portal = portalEnd > off ? payload.toString('utf8', off, portalEnd) : '';\n off = portalEnd + 1;\n // statement name \\0\n const stmtEnd = payload.indexOf(0, off);\n const statement = stmtEnd > off ? payload.toString('utf8', off, stmtEnd) : '';\n off = stmtEnd + 1;\n // Int16 num format codes + format codes (skip)\n const numFormats = payload.readInt16BE(off); off += 2;\n off += numFormats * 2; // skip format codes\n // Int16 num params\n const numParams = payload.readInt16BE(off); off += 2;\n const params: string[] = [];\n for (let i = 0; i < numParams; i++) {\n const len = payload.readInt32BE(off); off += 4;\n if (len === -1) {\n params.push('NULL');\n } else {\n params.push(payload.toString('utf8', off, off + len));\n off += len;\n }\n }\n return { portal, statement, params };\n }\n\n /** Execute a parameterized query — substitutes $1, $2, … and runs via PGlite. */\n private async _execQueryWithParams(sql: string, params: string[]): Promise<Buffer> {\n const trimmed = sql.trim();\n const upper = trimmed.toUpperCase();\n\n if (upper === 'BEGIN') { this._txState = 'T'; return proto.commandComplete('BEGIN'); }\n if (upper === 'COMMIT') { this._txState = 'I'; return proto.commandComplete('COMMIT'); }\n if (upper === 'ROLLBACK') { this._txState = 'I'; return proto.commandComplete('ROLLBACK'); }\n\n const db = await this._pglite;\n try {\n // PGlite supports parameterized queries directly\n const result = await db.query(trimmed, params);\n const fields: Array<{ name: string }> = result.fields ?? [];\n const rows: Array<Record<string, unknown>> = result.rows ?? [];\n\n const bufs: Buffer[] = [];\n if (fields.length > 0) {\n bufs.push(proto.rowDescription(fields.map((f: { name: string }) => f.name)));\n for (const row of rows) {\n bufs.push(proto.dataRow(fields.map((f: { name: string }) => {\n const v = row[f.name];\n return v === null || v === undefined ? null : String(v);\n })));\n }\n }\n\n const tag = inferTag(trimmed, rows.length, result.affectedRows as number | undefined);\n bufs.push(proto.commandComplete(tag));\n return Buffer.concat(bufs);\n } catch (err) {\n return proto.errorResponse(err instanceof Error ? err.message : String(err));\n }\n }\n\n private async _execQuery(sql: string): Promise<Buffer> {\n const trimmed = sql.trim();\n const upper = trimmed.toUpperCase();\n\n if (upper === 'BEGIN') { this._txState = 'T'; return Buffer.concat([proto.commandComplete('BEGIN'), proto.readyForQuery('T')]); }\n if (upper === 'COMMIT') { this._txState = 'I'; return Buffer.concat([proto.commandComplete('COMMIT'), proto.readyForQuery('I')]); }\n if (upper === 'ROLLBACK') { this._txState = 'I'; return Buffer.concat([proto.commandComplete('ROLLBACK'), proto.readyForQuery('I')]); }\n\n const db = await this._pglite;\n try {\n const result = await db.query(trimmed);\n const fields: Array<{ name: string }> = result.fields ?? [];\n const rows: Array<Record<string, unknown>> = result.rows ?? [];\n\n const bufs: Buffer[] = [];\n\n if (fields.length > 0) {\n bufs.push(proto.rowDescription(fields.map((f: { name: string }) => f.name)));\n for (const row of rows) {\n bufs.push(proto.dataRow(fields.map((f: { name: string }) => {\n const v = row[f.name];\n return v === null || v === undefined ? null : String(v);\n })));\n }\n }\n\n const tag = inferTag(trimmed, rows.length, result.affectedRows as number | undefined);\n if (tag === 'BEGIN') this._txState = 'T';\n else if (tag === 'COMMIT' || tag === 'ROLLBACK') this._txState = 'I';\n\n bufs.push(proto.commandComplete(tag));\n bufs.push(proto.readyForQuery(this._txState));\n return Buffer.concat(bufs);\n } catch (err) {\n return Buffer.concat([\n proto.errorResponse(err instanceof Error ? err.message : String(err)),\n proto.readyForQuery(this._txState === 'T' ? 'E' : 'I'),\n ]);\n }\n }\n}\n\n// ── PgMock ────────────────────────────────────────────────────────────────────\n\nexport class PgMock {\n /** Shared PGlite instance (one per PgMock, lazy-initialised). */\n private _pglite: Promise<PGliteInstance>;\n /** Tracks all in-flight seed operations so ready() can await them. */\n private _seedPromise: Promise<void> = Promise.resolve();\n private _connections = new Map<number, PgConnection>();\n\n constructor() {\n this._pglite = createPGliteInstance();\n }\n\n /**\n * Resolves once PGlite is initialised AND all pending seedData() calls have\n * been mirrored into PGlite. Await this before making wire-protocol queries\n * in tests that call seedData().\n */\n async ready(): Promise<void> {\n await this._pglite;\n await this._seedPromise;\n }\n\n /**\n * Seed data directly into PGlite.\n * Creates a simple text-column table with the supplied rows.\n */\n seedData(table: string, rows: Array<Record<string, string | null>>): void {\n // Write directly to PGlite (no legacy sync store).\n this._seedPromise = this._seedPromise.then(() => this._seedPGlite(table, rows));\n }\n\n private async _seedPGlite(table: string, rows: Array<Record<string, string | null>>): Promise<void> {\n if (rows.length === 0) return;\n const db = await this._pglite;\n const cols = Object.keys(rows[0]);\n const colDefs = cols.map(c => `\"${c}\" TEXT`).join(', ');\n await db.exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${colDefs})`);\n for (const row of rows) {\n const vals = cols.map(c => row[c] === null ? 'NULL' : `'${String(row[c]).replace(/'/g, \"''\")}'`).join(', ');\n await db.exec(`INSERT INTO \"${table}\" (${cols.map(c => `\"${c}\"`).join(', ')}) VALUES (${vals})`);\n }\n }\n\n /**\n * Execute a raw SQL query against the embedded PGlite instance.\n * Returns rows as plain objects keyed by column name.\n */\n async query<T = Record<string, unknown>>(sql: string): Promise<{ rows: T[]; fields: Array<{ name: string }> }> {\n const db = await this._pglite;\n return db.query(sql) as Promise<{ rows: T[]; fields: Array<{ name: string }> }>;\n }\n\n createHandler(): TcpMockHandler {\n return async (data: Buffer, ctx: TcpMockContext): Promise<TcpHandlerResult> => {\n if (!this._connections.has(ctx.socketId)) {\n this._connections.set(ctx.socketId, new PgConnection(this._pglite));\n }\n return this._connections.get(ctx.socketId)!.processData(data);\n };\n }\n}\n\nexport { proto };\n","// PG wire protocol v3 encoding helpers\nconst int32 = (n: number): Buffer => { const b = Buffer.alloc(4); b.writeInt32BE(n); return b; };\nconst int16 = (n: number): Buffer => { const b = Buffer.alloc(2); b.writeInt16BE(n); return b; };\nconst cstr = (s: string): Buffer => Buffer.from(s + '\\0', 'utf8');\n\nfunction pgMsg(type: string, payload: Buffer): Buffer {\n return Buffer.concat([Buffer.from(type), int32(payload.length + 4), payload]);\n}\n\nexport const authOk = (): Buffer => pgMsg('R', int32(0));\nexport const paramStatus = (k: string, v: string): Buffer => pgMsg('S', Buffer.concat([cstr(k), cstr(v)]));\nexport const backendKeyData = (): Buffer => pgMsg('K', Buffer.concat([int32(1), int32(1)]));\nexport const readyForQuery = (s: 'I' | 'T' | 'E'): Buffer => pgMsg('Z', Buffer.from(s));\n\nexport function rowDescription(cols: string[]): Buffer {\n const parts: Buffer[] = [int16(cols.length)];\n for (const c of cols) {\n // name, tableOID, colAttrNum, typeOID(text=25), typeSize(-1), typeMod(-1), formatCode(0=text)\n const typeSizeBuf = Buffer.alloc(2);\n typeSizeBuf.writeInt16BE(-1);\n parts.push(cstr(c), int32(0), int16(0), int32(25), typeSizeBuf, int32(-1), int16(0));\n }\n return pgMsg('T', Buffer.concat(parts));\n}\n\nexport function dataRow(vals: (string | null)[]): Buffer {\n const parts: Buffer[] = [int16(vals.length)];\n for (const v of vals) {\n if (v === null) { parts.push(int32(-1)); }\n else { const b = Buffer.from(v, 'utf8'); parts.push(int32(b.length), b); }\n }\n return pgMsg('D', Buffer.concat(parts));\n}\n\nexport const commandComplete = (tag: string): Buffer => pgMsg('C', cstr(tag));\n\nexport function errorResponse(msg: string, code = '42000'): Buffer {\n return pgMsg('E', Buffer.concat([\n Buffer.from('S'), cstr('ERROR'),\n Buffer.from('C'), cstr(code),\n Buffer.from('M'), cstr(msg),\n Buffer.from([0]),\n ]));\n}\n\nexport function startupResponse(): Buffer {\n return Buffer.concat([\n authOk(),\n paramStatus('server_version', '15.0'),\n paramStatus('client_encoding', 'UTF8'),\n backendKeyData(),\n readyForQuery('I'),\n ]);\n}\n\n// Extended protocol response messages\nexport const parseComplete = (): Buffer => pgMsg('1', Buffer.alloc(0));\nexport const bindComplete = (): Buffer => pgMsg('2', Buffer.alloc(0));\nexport const noData = (): Buffer => pgMsg('n', Buffer.alloc(0));\n\n/**\n * Extract the portal name from an Execute message payload so that\n * PgConnection can look up the prepared SQL. Returns empty string\n * (unnamed portal) when no explicit portal name was given.\n */\nexport function parseExecuteMsg(payload: Buffer): string {\n // Execute: portal-name\\0 + maxRows(Int32)\n const nul = payload.indexOf(0);\n return nul > 0 ? payload.toString('utf8', 0, nul) : '';\n}\n\nexport function parseStartupMsg(data: Buffer): { isSSL: boolean } | { user: string; database: string } {\n if (data.length >= 8 && data.readInt32BE(4) === 80877103) return { isSSL: true };\n let off = 8;\n const params: Record<string, string> = {};\n while (off < data.length - 1) {\n const kEnd = data.indexOf(0, off); if (kEnd < 0) break;\n const key = data.toString('utf8', off, kEnd); off = kEnd + 1;\n const vEnd = data.indexOf(0, off); if (vEnd < 0) break;\n params[key] = data.toString('utf8', off, vEnd); off = vEnd + 1;\n }\n return { user: params.user ?? 'unknown', database: params.database ?? 'unknown' };\n}\n\nexport function parseQueryMsg(data: Buffer): string {\n // 'Q' + Int32 length + query\\0\n const nul = data.indexOf(0, 5);\n return data.toString('utf8', 5, nul >= 0 ? nul : data.length);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TcpMockHandler } from '@crashlab/tcp';
|
|
2
|
+
import * as proto from './protocol.js';
|
|
3
|
+
export declare class SimNodeUnsupportedPGFeature extends Error {
|
|
4
|
+
constructor(detail: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class PgMock {
|
|
7
|
+
/** Shared PGlite instance (one per PgMock, lazy-initialised). */
|
|
8
|
+
private _pglite;
|
|
9
|
+
/** Tracks all in-flight seed operations so ready() can await them. */
|
|
10
|
+
private _seedPromise;
|
|
11
|
+
private _connections;
|
|
12
|
+
constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Resolves once PGlite is initialised AND all pending seedData() calls have
|
|
15
|
+
* been mirrored into PGlite. Await this before making wire-protocol queries
|
|
16
|
+
* in tests that call seedData().
|
|
17
|
+
*/
|
|
18
|
+
ready(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Seed data directly into PGlite.
|
|
21
|
+
* Creates a simple text-column table with the supplied rows.
|
|
22
|
+
*/
|
|
23
|
+
seedData(table: string, rows: Array<Record<string, string | null>>): void;
|
|
24
|
+
private _seedPGlite;
|
|
25
|
+
/**
|
|
26
|
+
* Execute a raw SQL query against the embedded PGlite instance.
|
|
27
|
+
* Returns rows as plain objects keyed by column name.
|
|
28
|
+
*/
|
|
29
|
+
query<T = Record<string, unknown>>(sql: string): Promise<{
|
|
30
|
+
rows: T[];
|
|
31
|
+
fields: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
}>;
|
|
34
|
+
}>;
|
|
35
|
+
createHandler(): TcpMockHandler;
|
|
36
|
+
}
|
|
37
|
+
export { proto };
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAoC,MAAM,eAAe,CAAC;AACtF,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AAEvC,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,MAAM,EAAE,MAAM;CAI3B;AA8OD,qBAAa,MAAM;IACjB,iEAAiE;IACjE,OAAO,CAAC,OAAO,CAA0B;IACzC,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,YAAY,CAAmC;;IAMvD;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI;YAK3D,WAAW;IAYzB;;;OAGG;IACG,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAK9G,aAAa,IAAI,cAAc;CAQhC;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/protocol.ts
|
|
8
|
+
var protocol_exports = {};
|
|
9
|
+
__export(protocol_exports, {
|
|
10
|
+
authOk: () => authOk,
|
|
11
|
+
backendKeyData: () => backendKeyData,
|
|
12
|
+
bindComplete: () => bindComplete,
|
|
13
|
+
commandComplete: () => commandComplete,
|
|
14
|
+
dataRow: () => dataRow,
|
|
15
|
+
errorResponse: () => errorResponse,
|
|
16
|
+
noData: () => noData,
|
|
17
|
+
paramStatus: () => paramStatus,
|
|
18
|
+
parseComplete: () => parseComplete,
|
|
19
|
+
parseExecuteMsg: () => parseExecuteMsg,
|
|
20
|
+
parseQueryMsg: () => parseQueryMsg,
|
|
21
|
+
parseStartupMsg: () => parseStartupMsg,
|
|
22
|
+
readyForQuery: () => readyForQuery,
|
|
23
|
+
rowDescription: () => rowDescription,
|
|
24
|
+
startupResponse: () => startupResponse
|
|
25
|
+
});
|
|
26
|
+
var int32 = (n) => {
|
|
27
|
+
const b = Buffer.alloc(4);
|
|
28
|
+
b.writeInt32BE(n);
|
|
29
|
+
return b;
|
|
30
|
+
};
|
|
31
|
+
var int16 = (n) => {
|
|
32
|
+
const b = Buffer.alloc(2);
|
|
33
|
+
b.writeInt16BE(n);
|
|
34
|
+
return b;
|
|
35
|
+
};
|
|
36
|
+
var cstr = (s) => Buffer.from(s + "\0", "utf8");
|
|
37
|
+
function pgMsg(type, payload) {
|
|
38
|
+
return Buffer.concat([Buffer.from(type), int32(payload.length + 4), payload]);
|
|
39
|
+
}
|
|
40
|
+
var authOk = () => pgMsg("R", int32(0));
|
|
41
|
+
var paramStatus = (k, v) => pgMsg("S", Buffer.concat([cstr(k), cstr(v)]));
|
|
42
|
+
var backendKeyData = () => pgMsg("K", Buffer.concat([int32(1), int32(1)]));
|
|
43
|
+
var readyForQuery = (s) => pgMsg("Z", Buffer.from(s));
|
|
44
|
+
function rowDescription(cols) {
|
|
45
|
+
const parts = [int16(cols.length)];
|
|
46
|
+
for (const c of cols) {
|
|
47
|
+
const typeSizeBuf = Buffer.alloc(2);
|
|
48
|
+
typeSizeBuf.writeInt16BE(-1);
|
|
49
|
+
parts.push(cstr(c), int32(0), int16(0), int32(25), typeSizeBuf, int32(-1), int16(0));
|
|
50
|
+
}
|
|
51
|
+
return pgMsg("T", Buffer.concat(parts));
|
|
52
|
+
}
|
|
53
|
+
function dataRow(vals) {
|
|
54
|
+
const parts = [int16(vals.length)];
|
|
55
|
+
for (const v of vals) {
|
|
56
|
+
if (v === null) {
|
|
57
|
+
parts.push(int32(-1));
|
|
58
|
+
} else {
|
|
59
|
+
const b = Buffer.from(v, "utf8");
|
|
60
|
+
parts.push(int32(b.length), b);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return pgMsg("D", Buffer.concat(parts));
|
|
64
|
+
}
|
|
65
|
+
var commandComplete = (tag) => pgMsg("C", cstr(tag));
|
|
66
|
+
function errorResponse(msg, code = "42000") {
|
|
67
|
+
return pgMsg("E", Buffer.concat([
|
|
68
|
+
Buffer.from("S"),
|
|
69
|
+
cstr("ERROR"),
|
|
70
|
+
Buffer.from("C"),
|
|
71
|
+
cstr(code),
|
|
72
|
+
Buffer.from("M"),
|
|
73
|
+
cstr(msg),
|
|
74
|
+
Buffer.from([0])
|
|
75
|
+
]));
|
|
76
|
+
}
|
|
77
|
+
function startupResponse() {
|
|
78
|
+
return Buffer.concat([
|
|
79
|
+
authOk(),
|
|
80
|
+
paramStatus("server_version", "15.0"),
|
|
81
|
+
paramStatus("client_encoding", "UTF8"),
|
|
82
|
+
backendKeyData(),
|
|
83
|
+
readyForQuery("I")
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
var parseComplete = () => pgMsg("1", Buffer.alloc(0));
|
|
87
|
+
var bindComplete = () => pgMsg("2", Buffer.alloc(0));
|
|
88
|
+
var noData = () => pgMsg("n", Buffer.alloc(0));
|
|
89
|
+
function parseExecuteMsg(payload) {
|
|
90
|
+
const nul = payload.indexOf(0);
|
|
91
|
+
return nul > 0 ? payload.toString("utf8", 0, nul) : "";
|
|
92
|
+
}
|
|
93
|
+
function parseStartupMsg(data) {
|
|
94
|
+
if (data.length >= 8 && data.readInt32BE(4) === 80877103) return { isSSL: true };
|
|
95
|
+
let off = 8;
|
|
96
|
+
const params = {};
|
|
97
|
+
while (off < data.length - 1) {
|
|
98
|
+
const kEnd = data.indexOf(0, off);
|
|
99
|
+
if (kEnd < 0) break;
|
|
100
|
+
const key = data.toString("utf8", off, kEnd);
|
|
101
|
+
off = kEnd + 1;
|
|
102
|
+
const vEnd = data.indexOf(0, off);
|
|
103
|
+
if (vEnd < 0) break;
|
|
104
|
+
params[key] = data.toString("utf8", off, vEnd);
|
|
105
|
+
off = vEnd + 1;
|
|
106
|
+
}
|
|
107
|
+
return { user: params.user ?? "unknown", database: params.database ?? "unknown" };
|
|
108
|
+
}
|
|
109
|
+
function parseQueryMsg(data) {
|
|
110
|
+
const nul = data.indexOf(0, 5);
|
|
111
|
+
return data.toString("utf8", 5, nul >= 0 ? nul : data.length);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/index.ts
|
|
115
|
+
var SimNodeUnsupportedPGFeature = class extends Error {
|
|
116
|
+
constructor(detail) {
|
|
117
|
+
super(`SimNode: Unsupported PostgreSQL feature: ${detail}`);
|
|
118
|
+
this.name = "SimNodeUnsupportedPGFeature";
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
function createPGliteInstance() {
|
|
122
|
+
return import("@electric-sql/pglite").then(({ PGlite }) => new PGlite());
|
|
123
|
+
}
|
|
124
|
+
function inferTag(sql, rowCount, affected) {
|
|
125
|
+
const verb = sql.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
|
|
126
|
+
switch (verb) {
|
|
127
|
+
case "SELECT":
|
|
128
|
+
return `SELECT ${rowCount}`;
|
|
129
|
+
case "INSERT":
|
|
130
|
+
return `INSERT 0 ${affected ?? rowCount}`;
|
|
131
|
+
case "UPDATE":
|
|
132
|
+
return `UPDATE ${affected ?? rowCount}`;
|
|
133
|
+
case "DELETE":
|
|
134
|
+
return `DELETE ${affected ?? rowCount}`;
|
|
135
|
+
case "CREATE":
|
|
136
|
+
return "CREATE TABLE";
|
|
137
|
+
case "DROP":
|
|
138
|
+
return "DROP TABLE";
|
|
139
|
+
case "BEGIN":
|
|
140
|
+
return "BEGIN";
|
|
141
|
+
case "COMMIT":
|
|
142
|
+
return "COMMIT";
|
|
143
|
+
case "ROLLBACK":
|
|
144
|
+
return "ROLLBACK";
|
|
145
|
+
default:
|
|
146
|
+
return verb;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
var PgConnection = class {
|
|
150
|
+
constructor(_pglite) {
|
|
151
|
+
this._pglite = _pglite;
|
|
152
|
+
}
|
|
153
|
+
_phase = "startup";
|
|
154
|
+
_txState = "I";
|
|
155
|
+
_buf = Buffer.alloc(0);
|
|
156
|
+
/** Prepared statements: statement name → SQL. '' = unnamed statement. */
|
|
157
|
+
_statements = /* @__PURE__ */ new Map();
|
|
158
|
+
/** Bound portals: portal name → { sql, params }. '' = unnamed portal. */
|
|
159
|
+
_portals = /* @__PURE__ */ new Map();
|
|
160
|
+
async processData(data) {
|
|
161
|
+
this._buf = this._buf.length > 0 ? Buffer.concat([this._buf, data]) : data;
|
|
162
|
+
if (this._phase === "startup") {
|
|
163
|
+
if (this._buf.length < 4) return Buffer.alloc(0);
|
|
164
|
+
const msgLen = this._buf.readInt32BE(0);
|
|
165
|
+
if (this._buf.length < msgLen) return Buffer.alloc(0);
|
|
166
|
+
const msg = this._buf.subarray(0, msgLen);
|
|
167
|
+
this._buf = this._buf.subarray(msgLen);
|
|
168
|
+
const parsed = parseStartupMsg(msg);
|
|
169
|
+
if ("isSSL" in parsed) return Buffer.from("N");
|
|
170
|
+
this._phase = "ready";
|
|
171
|
+
return startupResponse();
|
|
172
|
+
}
|
|
173
|
+
const responses = [];
|
|
174
|
+
while (this._buf.length >= 5) {
|
|
175
|
+
const msgType = String.fromCharCode(this._buf[0]);
|
|
176
|
+
const msgLen = this._buf.readInt32BE(1);
|
|
177
|
+
const totalLen = 1 + msgLen;
|
|
178
|
+
if (this._buf.length < totalLen) break;
|
|
179
|
+
const payload = this._buf.subarray(5, totalLen);
|
|
180
|
+
this._buf = this._buf.subarray(totalLen);
|
|
181
|
+
if (msgType === "Q") {
|
|
182
|
+
const nul = payload.indexOf(0);
|
|
183
|
+
const sql = payload.toString("utf8", 0, nul >= 0 ? nul : payload.length);
|
|
184
|
+
responses.push(await this._execQuery(sql));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
switch (msgType) {
|
|
188
|
+
case "P": {
|
|
189
|
+
const nameEnd = payload.indexOf(0);
|
|
190
|
+
const stmtName = nameEnd > 0 ? payload.toString("utf8", 0, nameEnd) : "";
|
|
191
|
+
const queryStart = nameEnd + 1;
|
|
192
|
+
const queryEnd = payload.indexOf(0, queryStart);
|
|
193
|
+
const sql = payload.toString("utf8", queryStart, queryEnd >= 0 ? queryEnd : payload.length);
|
|
194
|
+
this._statements.set(stmtName, sql);
|
|
195
|
+
responses.push(parseComplete());
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "B": {
|
|
199
|
+
const { portal, statement, params } = this._parseBind(payload);
|
|
200
|
+
const boundSql = this._statements.get(statement) ?? "";
|
|
201
|
+
this._portals.set(portal, { sql: boundSql, params });
|
|
202
|
+
responses.push(bindComplete());
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "D": {
|
|
206
|
+
responses.push(noData());
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "E": {
|
|
210
|
+
const portalEnd = payload.indexOf(0);
|
|
211
|
+
const portalName = portalEnd > 0 ? payload.toString("utf8", 0, portalEnd) : "";
|
|
212
|
+
const bound = this._portals.get(portalName);
|
|
213
|
+
if (bound && bound.sql) {
|
|
214
|
+
const r = await this._execQueryWithParams(bound.sql, bound.params);
|
|
215
|
+
responses.push(r);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "S": {
|
|
220
|
+
responses.push(readyForQuery(this._txState));
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "X": {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
default:
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return responses.length > 0 ? Buffer.concat(responses) : Buffer.alloc(0);
|
|
231
|
+
}
|
|
232
|
+
/** Parse a Bind message payload into portal, statement, and parameter values. */
|
|
233
|
+
_parseBind(payload) {
|
|
234
|
+
let off = 0;
|
|
235
|
+
const portalEnd = payload.indexOf(0, off);
|
|
236
|
+
const portal = portalEnd > off ? payload.toString("utf8", off, portalEnd) : "";
|
|
237
|
+
off = portalEnd + 1;
|
|
238
|
+
const stmtEnd = payload.indexOf(0, off);
|
|
239
|
+
const statement = stmtEnd > off ? payload.toString("utf8", off, stmtEnd) : "";
|
|
240
|
+
off = stmtEnd + 1;
|
|
241
|
+
const numFormats = payload.readInt16BE(off);
|
|
242
|
+
off += 2;
|
|
243
|
+
off += numFormats * 2;
|
|
244
|
+
const numParams = payload.readInt16BE(off);
|
|
245
|
+
off += 2;
|
|
246
|
+
const params = [];
|
|
247
|
+
for (let i = 0; i < numParams; i++) {
|
|
248
|
+
const len = payload.readInt32BE(off);
|
|
249
|
+
off += 4;
|
|
250
|
+
if (len === -1) {
|
|
251
|
+
params.push("NULL");
|
|
252
|
+
} else {
|
|
253
|
+
params.push(payload.toString("utf8", off, off + len));
|
|
254
|
+
off += len;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { portal, statement, params };
|
|
258
|
+
}
|
|
259
|
+
/** Execute a parameterized query — substitutes $1, $2, … and runs via PGlite. */
|
|
260
|
+
async _execQueryWithParams(sql, params) {
|
|
261
|
+
const trimmed = sql.trim();
|
|
262
|
+
const upper = trimmed.toUpperCase();
|
|
263
|
+
if (upper === "BEGIN") {
|
|
264
|
+
this._txState = "T";
|
|
265
|
+
return commandComplete("BEGIN");
|
|
266
|
+
}
|
|
267
|
+
if (upper === "COMMIT") {
|
|
268
|
+
this._txState = "I";
|
|
269
|
+
return commandComplete("COMMIT");
|
|
270
|
+
}
|
|
271
|
+
if (upper === "ROLLBACK") {
|
|
272
|
+
this._txState = "I";
|
|
273
|
+
return commandComplete("ROLLBACK");
|
|
274
|
+
}
|
|
275
|
+
const db = await this._pglite;
|
|
276
|
+
try {
|
|
277
|
+
const result = await db.query(trimmed, params);
|
|
278
|
+
const fields = result.fields ?? [];
|
|
279
|
+
const rows = result.rows ?? [];
|
|
280
|
+
const bufs = [];
|
|
281
|
+
if (fields.length > 0) {
|
|
282
|
+
bufs.push(rowDescription(fields.map((f) => f.name)));
|
|
283
|
+
for (const row of rows) {
|
|
284
|
+
bufs.push(dataRow(fields.map((f) => {
|
|
285
|
+
const v = row[f.name];
|
|
286
|
+
return v === null || v === void 0 ? null : String(v);
|
|
287
|
+
})));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const tag = inferTag(trimmed, rows.length, result.affectedRows);
|
|
291
|
+
bufs.push(commandComplete(tag));
|
|
292
|
+
return Buffer.concat(bufs);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async _execQuery(sql) {
|
|
298
|
+
const trimmed = sql.trim();
|
|
299
|
+
const upper = trimmed.toUpperCase();
|
|
300
|
+
if (upper === "BEGIN") {
|
|
301
|
+
this._txState = "T";
|
|
302
|
+
return Buffer.concat([commandComplete("BEGIN"), readyForQuery("T")]);
|
|
303
|
+
}
|
|
304
|
+
if (upper === "COMMIT") {
|
|
305
|
+
this._txState = "I";
|
|
306
|
+
return Buffer.concat([commandComplete("COMMIT"), readyForQuery("I")]);
|
|
307
|
+
}
|
|
308
|
+
if (upper === "ROLLBACK") {
|
|
309
|
+
this._txState = "I";
|
|
310
|
+
return Buffer.concat([commandComplete("ROLLBACK"), readyForQuery("I")]);
|
|
311
|
+
}
|
|
312
|
+
const db = await this._pglite;
|
|
313
|
+
try {
|
|
314
|
+
const result = await db.query(trimmed);
|
|
315
|
+
const fields = result.fields ?? [];
|
|
316
|
+
const rows = result.rows ?? [];
|
|
317
|
+
const bufs = [];
|
|
318
|
+
if (fields.length > 0) {
|
|
319
|
+
bufs.push(rowDescription(fields.map((f) => f.name)));
|
|
320
|
+
for (const row of rows) {
|
|
321
|
+
bufs.push(dataRow(fields.map((f) => {
|
|
322
|
+
const v = row[f.name];
|
|
323
|
+
return v === null || v === void 0 ? null : String(v);
|
|
324
|
+
})));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const tag = inferTag(trimmed, rows.length, result.affectedRows);
|
|
328
|
+
if (tag === "BEGIN") this._txState = "T";
|
|
329
|
+
else if (tag === "COMMIT" || tag === "ROLLBACK") this._txState = "I";
|
|
330
|
+
bufs.push(commandComplete(tag));
|
|
331
|
+
bufs.push(readyForQuery(this._txState));
|
|
332
|
+
return Buffer.concat(bufs);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return Buffer.concat([
|
|
335
|
+
errorResponse(err instanceof Error ? err.message : String(err)),
|
|
336
|
+
readyForQuery(this._txState === "T" ? "E" : "I")
|
|
337
|
+
]);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var PgMock = class {
|
|
342
|
+
/** Shared PGlite instance (one per PgMock, lazy-initialised). */
|
|
343
|
+
_pglite;
|
|
344
|
+
/** Tracks all in-flight seed operations so ready() can await them. */
|
|
345
|
+
_seedPromise = Promise.resolve();
|
|
346
|
+
_connections = /* @__PURE__ */ new Map();
|
|
347
|
+
constructor() {
|
|
348
|
+
this._pglite = createPGliteInstance();
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Resolves once PGlite is initialised AND all pending seedData() calls have
|
|
352
|
+
* been mirrored into PGlite. Await this before making wire-protocol queries
|
|
353
|
+
* in tests that call seedData().
|
|
354
|
+
*/
|
|
355
|
+
async ready() {
|
|
356
|
+
await this._pglite;
|
|
357
|
+
await this._seedPromise;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Seed data directly into PGlite.
|
|
361
|
+
* Creates a simple text-column table with the supplied rows.
|
|
362
|
+
*/
|
|
363
|
+
seedData(table, rows) {
|
|
364
|
+
this._seedPromise = this._seedPromise.then(() => this._seedPGlite(table, rows));
|
|
365
|
+
}
|
|
366
|
+
async _seedPGlite(table, rows) {
|
|
367
|
+
if (rows.length === 0) return;
|
|
368
|
+
const db = await this._pglite;
|
|
369
|
+
const cols = Object.keys(rows[0]);
|
|
370
|
+
const colDefs = cols.map((c) => `"${c}" TEXT`).join(", ");
|
|
371
|
+
await db.exec(`CREATE TABLE IF NOT EXISTS "${table}" (${colDefs})`);
|
|
372
|
+
for (const row of rows) {
|
|
373
|
+
const vals = cols.map((c) => row[c] === null ? "NULL" : `'${String(row[c]).replace(/'/g, "''")}'`).join(", ");
|
|
374
|
+
await db.exec(`INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${vals})`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Execute a raw SQL query against the embedded PGlite instance.
|
|
379
|
+
* Returns rows as plain objects keyed by column name.
|
|
380
|
+
*/
|
|
381
|
+
async query(sql) {
|
|
382
|
+
const db = await this._pglite;
|
|
383
|
+
return db.query(sql);
|
|
384
|
+
}
|
|
385
|
+
createHandler() {
|
|
386
|
+
return async (data, ctx) => {
|
|
387
|
+
if (!this._connections.has(ctx.socketId)) {
|
|
388
|
+
this._connections.set(ctx.socketId, new PgConnection(this._pglite));
|
|
389
|
+
}
|
|
390
|
+
return this._connections.get(ctx.socketId).processData(data);
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
export {
|
|
395
|
+
PgMock,
|
|
396
|
+
SimNodeUnsupportedPGFeature,
|
|
397
|
+
protocol_exports as proto
|
|
398
|
+
};
|
|
399
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts","../src/index.ts"],"sourcesContent":["// PG wire protocol v3 encoding helpers\nconst int32 = (n: number): Buffer => { const b = Buffer.alloc(4); b.writeInt32BE(n); return b; };\nconst int16 = (n: number): Buffer => { const b = Buffer.alloc(2); b.writeInt16BE(n); return b; };\nconst cstr = (s: string): Buffer => Buffer.from(s + '\\0', 'utf8');\n\nfunction pgMsg(type: string, payload: Buffer): Buffer {\n return Buffer.concat([Buffer.from(type), int32(payload.length + 4), payload]);\n}\n\nexport const authOk = (): Buffer => pgMsg('R', int32(0));\nexport const paramStatus = (k: string, v: string): Buffer => pgMsg('S', Buffer.concat([cstr(k), cstr(v)]));\nexport const backendKeyData = (): Buffer => pgMsg('K', Buffer.concat([int32(1), int32(1)]));\nexport const readyForQuery = (s: 'I' | 'T' | 'E'): Buffer => pgMsg('Z', Buffer.from(s));\n\nexport function rowDescription(cols: string[]): Buffer {\n const parts: Buffer[] = [int16(cols.length)];\n for (const c of cols) {\n // name, tableOID, colAttrNum, typeOID(text=25), typeSize(-1), typeMod(-1), formatCode(0=text)\n const typeSizeBuf = Buffer.alloc(2);\n typeSizeBuf.writeInt16BE(-1);\n parts.push(cstr(c), int32(0), int16(0), int32(25), typeSizeBuf, int32(-1), int16(0));\n }\n return pgMsg('T', Buffer.concat(parts));\n}\n\nexport function dataRow(vals: (string | null)[]): Buffer {\n const parts: Buffer[] = [int16(vals.length)];\n for (const v of vals) {\n if (v === null) { parts.push(int32(-1)); }\n else { const b = Buffer.from(v, 'utf8'); parts.push(int32(b.length), b); }\n }\n return pgMsg('D', Buffer.concat(parts));\n}\n\nexport const commandComplete = (tag: string): Buffer => pgMsg('C', cstr(tag));\n\nexport function errorResponse(msg: string, code = '42000'): Buffer {\n return pgMsg('E', Buffer.concat([\n Buffer.from('S'), cstr('ERROR'),\n Buffer.from('C'), cstr(code),\n Buffer.from('M'), cstr(msg),\n Buffer.from([0]),\n ]));\n}\n\nexport function startupResponse(): Buffer {\n return Buffer.concat([\n authOk(),\n paramStatus('server_version', '15.0'),\n paramStatus('client_encoding', 'UTF8'),\n backendKeyData(),\n readyForQuery('I'),\n ]);\n}\n\n// Extended protocol response messages\nexport const parseComplete = (): Buffer => pgMsg('1', Buffer.alloc(0));\nexport const bindComplete = (): Buffer => pgMsg('2', Buffer.alloc(0));\nexport const noData = (): Buffer => pgMsg('n', Buffer.alloc(0));\n\n/**\n * Extract the portal name from an Execute message payload so that\n * PgConnection can look up the prepared SQL. Returns empty string\n * (unnamed portal) when no explicit portal name was given.\n */\nexport function parseExecuteMsg(payload: Buffer): string {\n // Execute: portal-name\\0 + maxRows(Int32)\n const nul = payload.indexOf(0);\n return nul > 0 ? payload.toString('utf8', 0, nul) : '';\n}\n\nexport function parseStartupMsg(data: Buffer): { isSSL: boolean } | { user: string; database: string } {\n if (data.length >= 8 && data.readInt32BE(4) === 80877103) return { isSSL: true };\n let off = 8;\n const params: Record<string, string> = {};\n while (off < data.length - 1) {\n const kEnd = data.indexOf(0, off); if (kEnd < 0) break;\n const key = data.toString('utf8', off, kEnd); off = kEnd + 1;\n const vEnd = data.indexOf(0, off); if (vEnd < 0) break;\n params[key] = data.toString('utf8', off, vEnd); off = vEnd + 1;\n }\n return { user: params.user ?? 'unknown', database: params.database ?? 'unknown' };\n}\n\nexport function parseQueryMsg(data: Buffer): string {\n // 'Q' + Int32 length + query\\0\n const nul = data.indexOf(0, 5);\n return data.toString('utf8', 5, nul >= 0 ? nul : data.length);\n}\n","import type { TcpMockHandler, TcpMockContext, TcpHandlerResult } from '@crashlab/tcp';\nimport * as proto from './protocol.js';\n\nexport class SimNodeUnsupportedPGFeature extends Error {\n constructor(detail: string) {\n super(`SimNode: Unsupported PostgreSQL feature: ${detail}`);\n this.name = 'SimNodeUnsupportedPGFeature';\n }\n}\n\n// ── PGlite helpers ────────────────────────────────────────────────────────────\n\n// Dynamically imported so the package remains optional at load time.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PGliteInstance = any;\n\nfunction createPGliteInstance(): Promise<PGliteInstance> {\n return import('@electric-sql/pglite').then(({ PGlite }) => new PGlite());\n}\n\n/** Infer a PostgreSQL command tag from the SQL statement and affected-row count. */\nfunction inferTag(sql: string, rowCount: number, affected?: number): string {\n const verb = sql.trim().split(/\\s+/)[0]?.toUpperCase() ?? '';\n switch (verb) {\n case 'SELECT': return `SELECT ${rowCount}`;\n case 'INSERT': return `INSERT 0 ${affected ?? rowCount}`;\n case 'UPDATE': return `UPDATE ${affected ?? rowCount}`;\n case 'DELETE': return `DELETE ${affected ?? rowCount}`;\n case 'CREATE': return 'CREATE TABLE';\n case 'DROP': return 'DROP TABLE';\n case 'BEGIN': return 'BEGIN';\n case 'COMMIT': return 'COMMIT';\n case 'ROLLBACK': return 'ROLLBACK';\n default: return verb;\n }\n}\n\n// ── PgConnection ──────────────────────────────────────────────────────────────\n\nclass PgConnection {\n private _phase: 'startup' | 'ready' = 'startup';\n private _txState: 'I' | 'T' | 'E' = 'I';\n private _buf: Buffer = Buffer.alloc(0);\n\n /** Prepared statements: statement name → SQL. '' = unnamed statement. */\n private _statements = new Map<string, string>();\n /** Bound portals: portal name → { sql, params }. '' = unnamed portal. */\n private _portals = new Map<string, { sql: string; params: string[] }>();\n\n constructor(private _pglite: Promise<PGliteInstance>) {}\n\n async processData(data: Buffer): Promise<Buffer> {\n this._buf = this._buf.length > 0 ? Buffer.concat([this._buf, data]) : data;\n\n // ── Startup / SSL handshake ───────────────────────────────────────────────\n if (this._phase === 'startup') {\n // SSL probe is exactly 8 bytes; startup message has a 4-byte length prefix\n if (this._buf.length < 4) return Buffer.alloc(0);\n const msgLen = this._buf.readInt32BE(0);\n if (this._buf.length < msgLen) return Buffer.alloc(0);\n\n const msg = this._buf.subarray(0, msgLen);\n this._buf = this._buf.subarray(msgLen);\n\n const parsed = proto.parseStartupMsg(msg);\n if ('isSSL' in parsed) return Buffer.from('N');\n this._phase = 'ready';\n return proto.startupResponse();\n }\n\n // ── Ready phase: consume complete framed messages ──────────────────────────\n const responses: Buffer[] = [];\n\n while (this._buf.length >= 5) {\n const msgType = String.fromCharCode(this._buf[0]);\n const msgLen = this._buf.readInt32BE(1); // includes self (4 bytes) but not type byte\n const totalLen = 1 + msgLen;\n if (this._buf.length < totalLen) break; // incomplete — wait for more data\n\n const payload = this._buf.subarray(5, totalLen);\n this._buf = this._buf.subarray(totalLen);\n\n // Simple Query ('Q')\n if (msgType === 'Q') {\n const nul = payload.indexOf(0);\n const sql = payload.toString('utf8', 0, nul >= 0 ? nul : payload.length);\n responses.push(await this._execQuery(sql));\n continue;\n }\n\n switch (msgType) {\n case 'P': { // Parse: statement_name\\0 + query\\0 + Int16(numParams) + ...\n const nameEnd = payload.indexOf(0);\n const stmtName = nameEnd > 0 ? payload.toString('utf8', 0, nameEnd) : '';\n const queryStart = nameEnd + 1;\n const queryEnd = payload.indexOf(0, queryStart);\n const sql = payload.toString('utf8', queryStart, queryEnd >= 0 ? queryEnd : payload.length);\n this._statements.set(stmtName, sql);\n responses.push(proto.parseComplete());\n break;\n }\n case 'B': { // Bind: portal\\0 + statement\\0 + formats + params + result_formats\n const { portal, statement, params } = this._parseBind(payload);\n const boundSql = this._statements.get(statement) ?? '';\n this._portals.set(portal, { sql: boundSql, params });\n responses.push(proto.bindComplete());\n break;\n }\n case 'D': { // Describe\n responses.push(proto.noData());\n break;\n }\n case 'E': { // Execute: portal\\0 + maxRows(Int32)\n const portalEnd = payload.indexOf(0);\n const portalName = portalEnd > 0 ? payload.toString('utf8', 0, portalEnd) : '';\n const bound = this._portals.get(portalName);\n if (bound && bound.sql) {\n const r = await this._execQueryWithParams(bound.sql, bound.params);\n responses.push(r);\n }\n break;\n }\n case 'S': { // Sync\n responses.push(proto.readyForQuery(this._txState));\n break;\n }\n case 'X': { // Terminate\n break;\n }\n default:\n // Silently ignore unknown message types\n break;\n }\n }\n\n return responses.length > 0 ? Buffer.concat(responses) : Buffer.alloc(0);\n }\n\n /** Parse a Bind message payload into portal, statement, and parameter values. */\n private _parseBind(payload: Buffer): { portal: string; statement: string; params: string[] } {\n let off = 0;\n // portal name \\0\n const portalEnd = payload.indexOf(0, off);\n const portal = portalEnd > off ? payload.toString('utf8', off, portalEnd) : '';\n off = portalEnd + 1;\n // statement name \\0\n const stmtEnd = payload.indexOf(0, off);\n const statement = stmtEnd > off ? payload.toString('utf8', off, stmtEnd) : '';\n off = stmtEnd + 1;\n // Int16 num format codes + format codes (skip)\n const numFormats = payload.readInt16BE(off); off += 2;\n off += numFormats * 2; // skip format codes\n // Int16 num params\n const numParams = payload.readInt16BE(off); off += 2;\n const params: string[] = [];\n for (let i = 0; i < numParams; i++) {\n const len = payload.readInt32BE(off); off += 4;\n if (len === -1) {\n params.push('NULL');\n } else {\n params.push(payload.toString('utf8', off, off + len));\n off += len;\n }\n }\n return { portal, statement, params };\n }\n\n /** Execute a parameterized query — substitutes $1, $2, … and runs via PGlite. */\n private async _execQueryWithParams(sql: string, params: string[]): Promise<Buffer> {\n const trimmed = sql.trim();\n const upper = trimmed.toUpperCase();\n\n if (upper === 'BEGIN') { this._txState = 'T'; return proto.commandComplete('BEGIN'); }\n if (upper === 'COMMIT') { this._txState = 'I'; return proto.commandComplete('COMMIT'); }\n if (upper === 'ROLLBACK') { this._txState = 'I'; return proto.commandComplete('ROLLBACK'); }\n\n const db = await this._pglite;\n try {\n // PGlite supports parameterized queries directly\n const result = await db.query(trimmed, params);\n const fields: Array<{ name: string }> = result.fields ?? [];\n const rows: Array<Record<string, unknown>> = result.rows ?? [];\n\n const bufs: Buffer[] = [];\n if (fields.length > 0) {\n bufs.push(proto.rowDescription(fields.map((f: { name: string }) => f.name)));\n for (const row of rows) {\n bufs.push(proto.dataRow(fields.map((f: { name: string }) => {\n const v = row[f.name];\n return v === null || v === undefined ? null : String(v);\n })));\n }\n }\n\n const tag = inferTag(trimmed, rows.length, result.affectedRows as number | undefined);\n bufs.push(proto.commandComplete(tag));\n return Buffer.concat(bufs);\n } catch (err) {\n return proto.errorResponse(err instanceof Error ? err.message : String(err));\n }\n }\n\n private async _execQuery(sql: string): Promise<Buffer> {\n const trimmed = sql.trim();\n const upper = trimmed.toUpperCase();\n\n if (upper === 'BEGIN') { this._txState = 'T'; return Buffer.concat([proto.commandComplete('BEGIN'), proto.readyForQuery('T')]); }\n if (upper === 'COMMIT') { this._txState = 'I'; return Buffer.concat([proto.commandComplete('COMMIT'), proto.readyForQuery('I')]); }\n if (upper === 'ROLLBACK') { this._txState = 'I'; return Buffer.concat([proto.commandComplete('ROLLBACK'), proto.readyForQuery('I')]); }\n\n const db = await this._pglite;\n try {\n const result = await db.query(trimmed);\n const fields: Array<{ name: string }> = result.fields ?? [];\n const rows: Array<Record<string, unknown>> = result.rows ?? [];\n\n const bufs: Buffer[] = [];\n\n if (fields.length > 0) {\n bufs.push(proto.rowDescription(fields.map((f: { name: string }) => f.name)));\n for (const row of rows) {\n bufs.push(proto.dataRow(fields.map((f: { name: string }) => {\n const v = row[f.name];\n return v === null || v === undefined ? null : String(v);\n })));\n }\n }\n\n const tag = inferTag(trimmed, rows.length, result.affectedRows as number | undefined);\n if (tag === 'BEGIN') this._txState = 'T';\n else if (tag === 'COMMIT' || tag === 'ROLLBACK') this._txState = 'I';\n\n bufs.push(proto.commandComplete(tag));\n bufs.push(proto.readyForQuery(this._txState));\n return Buffer.concat(bufs);\n } catch (err) {\n return Buffer.concat([\n proto.errorResponse(err instanceof Error ? err.message : String(err)),\n proto.readyForQuery(this._txState === 'T' ? 'E' : 'I'),\n ]);\n }\n }\n}\n\n// ── PgMock ────────────────────────────────────────────────────────────────────\n\nexport class PgMock {\n /** Shared PGlite instance (one per PgMock, lazy-initialised). */\n private _pglite: Promise<PGliteInstance>;\n /** Tracks all in-flight seed operations so ready() can await them. */\n private _seedPromise: Promise<void> = Promise.resolve();\n private _connections = new Map<number, PgConnection>();\n\n constructor() {\n this._pglite = createPGliteInstance();\n }\n\n /**\n * Resolves once PGlite is initialised AND all pending seedData() calls have\n * been mirrored into PGlite. Await this before making wire-protocol queries\n * in tests that call seedData().\n */\n async ready(): Promise<void> {\n await this._pglite;\n await this._seedPromise;\n }\n\n /**\n * Seed data directly into PGlite.\n * Creates a simple text-column table with the supplied rows.\n */\n seedData(table: string, rows: Array<Record<string, string | null>>): void {\n // Write directly to PGlite (no legacy sync store).\n this._seedPromise = this._seedPromise.then(() => this._seedPGlite(table, rows));\n }\n\n private async _seedPGlite(table: string, rows: Array<Record<string, string | null>>): Promise<void> {\n if (rows.length === 0) return;\n const db = await this._pglite;\n const cols = Object.keys(rows[0]);\n const colDefs = cols.map(c => `\"${c}\" TEXT`).join(', ');\n await db.exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${colDefs})`);\n for (const row of rows) {\n const vals = cols.map(c => row[c] === null ? 'NULL' : `'${String(row[c]).replace(/'/g, \"''\")}'`).join(', ');\n await db.exec(`INSERT INTO \"${table}\" (${cols.map(c => `\"${c}\"`).join(', ')}) VALUES (${vals})`);\n }\n }\n\n /**\n * Execute a raw SQL query against the embedded PGlite instance.\n * Returns rows as plain objects keyed by column name.\n */\n async query<T = Record<string, unknown>>(sql: string): Promise<{ rows: T[]; fields: Array<{ name: string }> }> {\n const db = await this._pglite;\n return db.query(sql) as Promise<{ rows: T[]; fields: Array<{ name: string }> }>;\n }\n\n createHandler(): TcpMockHandler {\n return async (data: Buffer, ctx: TcpMockContext): Promise<TcpHandlerResult> => {\n if (!this._connections.has(ctx.socketId)) {\n this._connections.set(ctx.socketId, new PgConnection(this._pglite));\n }\n return this._connections.get(ctx.socketId)!.processData(data);\n };\n }\n}\n\nexport { proto };\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,IAAM,QAAQ,CAAC,MAAsB;AAAE,QAAM,IAAI,OAAO,MAAM,CAAC;AAAG,IAAE,aAAa,CAAC;AAAG,SAAO;AAAG;AAC/F,IAAM,QAAQ,CAAC,MAAsB;AAAE,QAAM,IAAI,OAAO,MAAM,CAAC;AAAG,IAAE,aAAa,CAAC;AAAG,SAAO;AAAG;AAC/F,IAAM,OAAO,CAAC,MAAsB,OAAO,KAAK,IAAI,MAAM,MAAM;AAEhE,SAAS,MAAM,MAAc,SAAyB;AACpD,SAAO,OAAO,OAAO,CAAC,OAAO,KAAK,IAAI,GAAG,MAAM,QAAQ,SAAS,CAAC,GAAG,OAAO,CAAC;AAC9E;AAEO,IAAM,SAAS,MAAc,MAAM,KAAK,MAAM,CAAC,CAAC;AAChD,IAAM,cAAc,CAAC,GAAW,MAAsB,MAAM,KAAK,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;AAClG,IAAM,iBAAiB,MAAc,MAAM,KAAK,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACnF,IAAM,gBAAgB,CAAC,MAA+B,MAAM,KAAK,OAAO,KAAK,CAAC,CAAC;AAE/E,SAAS,eAAe,MAAwB;AACrD,QAAM,QAAkB,CAAC,MAAM,KAAK,MAAM,CAAC;AAC3C,aAAW,KAAK,MAAM;AAEpB,UAAM,cAAc,OAAO,MAAM,CAAC;AAClC,gBAAY,aAAa,EAAE;AAC3B,UAAM,KAAK,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,EAAE,GAAG,aAAa,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA,EACrF;AACA,SAAO,MAAM,KAAK,OAAO,OAAO,KAAK,CAAC;AACxC;AAEO,SAAS,QAAQ,MAAiC;AACvD,QAAM,QAAkB,CAAC,MAAM,KAAK,MAAM,CAAC;AAC3C,aAAW,KAAK,MAAM;AACpB,QAAI,MAAM,MAAM;AAAE,YAAM,KAAK,MAAM,EAAE,CAAC;AAAA,IAAG,OACpC;AAAE,YAAM,IAAI,OAAO,KAAK,GAAG,MAAM;AAAG,YAAM,KAAK,MAAM,EAAE,MAAM,GAAG,CAAC;AAAA,IAAG;AAAA,EAC3E;AACA,SAAO,MAAM,KAAK,OAAO,OAAO,KAAK,CAAC;AACxC;AAEO,IAAM,kBAAkB,CAAC,QAAwB,MAAM,KAAK,KAAK,GAAG,CAAC;AAErE,SAAS,cAAc,KAAa,OAAO,SAAiB;AACjE,SAAO,MAAM,KAAK,OAAO,OAAO;AAAA,IAC9B,OAAO,KAAK,GAAG;AAAA,IAAG,KAAK,OAAO;AAAA,IAC9B,OAAO,KAAK,GAAG;AAAA,IAAG,KAAK,IAAI;AAAA,IAC3B,OAAO,KAAK,GAAG;AAAA,IAAG,KAAK,GAAG;AAAA,IAC1B,OAAO,KAAK,CAAC,CAAC,CAAC;AAAA,EACjB,CAAC,CAAC;AACJ;AAEO,SAAS,kBAA0B;AACxC,SAAO,OAAO,OAAO;AAAA,IACnB,OAAO;AAAA,IACP,YAAY,kBAAkB,MAAM;AAAA,IACpC,YAAY,mBAAmB,MAAM;AAAA,IACrC,eAAe;AAAA,IACf,cAAc,GAAG;AAAA,EACnB,CAAC;AACH;AAGO,IAAM,gBAAiB,MAAc,MAAM,KAAK,OAAO,MAAM,CAAC,CAAC;AAC/D,IAAM,eAAiB,MAAc,MAAM,KAAK,OAAO,MAAM,CAAC,CAAC;AAC/D,IAAM,SAAiB,MAAc,MAAM,KAAK,OAAO,MAAM,CAAC,CAAC;AAO/D,SAAS,gBAAgB,SAAyB;AAEvD,QAAM,MAAM,QAAQ,QAAQ,CAAC;AAC7B,SAAO,MAAM,IAAI,QAAQ,SAAS,QAAQ,GAAG,GAAG,IAAI;AACtD;AAEO,SAAS,gBAAgB,MAAuE;AACrG,MAAI,KAAK,UAAU,KAAK,KAAK,YAAY,CAAC,MAAM,SAAU,QAAO,EAAE,OAAO,KAAK;AAC/E,MAAI,MAAM;AACV,QAAM,SAAiC,CAAC;AACxC,SAAO,MAAM,KAAK,SAAS,GAAG;AAC5B,UAAM,OAAO,KAAK,QAAQ,GAAG,GAAG;AAAG,QAAI,OAAO,EAAG;AACjD,UAAM,MAAM,KAAK,SAAS,QAAQ,KAAK,IAAI;AAAG,UAAM,OAAO;AAC3D,UAAM,OAAO,KAAK,QAAQ,GAAG,GAAG;AAAG,QAAI,OAAO,EAAG;AACjD,WAAO,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK,IAAI;AAAG,UAAM,OAAO;AAAA,EAC/D;AACA,SAAO,EAAE,MAAM,OAAO,QAAQ,WAAW,UAAU,OAAO,YAAY,UAAU;AAClF;AAEO,SAAS,cAAc,MAAsB;AAElD,QAAM,MAAM,KAAK,QAAQ,GAAG,CAAC;AAC7B,SAAO,KAAK,SAAS,QAAQ,GAAG,OAAO,IAAI,MAAM,KAAK,MAAM;AAC9D;;;ACrFO,IAAM,8BAAN,cAA0C,MAAM;AAAA,EACrD,YAAY,QAAgB;AAC1B,UAAM,4CAA4C,MAAM,EAAE;AAC1D,SAAK,OAAO;AAAA,EACd;AACF;AAQA,SAAS,uBAAgD;AACvD,SAAO,OAAO,sBAAsB,EAAE,KAAK,CAAC,EAAE,OAAO,MAAM,IAAI,OAAO,CAAC;AACzE;AAGA,SAAS,SAAS,KAAa,UAAkB,UAA2B;AAC1E,QAAM,OAAO,IAAI,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC,GAAG,YAAY,KAAK;AAC1D,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAU,aAAO,UAAU,QAAQ;AAAA,IACxC,KAAK;AAAU,aAAO,YAAY,YAAY,QAAQ;AAAA,IACtD,KAAK;AAAU,aAAO,UAAU,YAAY,QAAQ;AAAA,IACpD,KAAK;AAAU,aAAO,UAAU,YAAY,QAAQ;AAAA,IACpD,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAY,aAAO;AAAA,IACxB;AAAe,aAAO;AAAA,EACxB;AACF;AAIA,IAAM,eAAN,MAAmB;AAAA,EAUjB,YAAoB,SAAkC;AAAlC;AAAA,EAAmC;AAAA,EAT/C,SAA8B;AAAA,EAC9B,WAA4B;AAAA,EAC5B,OAAe,OAAO,MAAM,CAAC;AAAA;AAAA,EAG7B,cAAc,oBAAI,IAAoB;AAAA;AAAA,EAEtC,WAAW,oBAAI,IAA+C;AAAA,EAItE,MAAM,YAAY,MAA+B;AAC/C,SAAK,OAAO,KAAK,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,IAAI;AAGtE,QAAI,KAAK,WAAW,WAAW;AAE7B,UAAI,KAAK,KAAK,SAAS,EAAG,QAAO,OAAO,MAAM,CAAC;AAC/C,YAAM,SAAS,KAAK,KAAK,YAAY,CAAC;AACtC,UAAI,KAAK,KAAK,SAAS,OAAQ,QAAO,OAAO,MAAM,CAAC;AAEpD,YAAM,MAAM,KAAK,KAAK,SAAS,GAAG,MAAM;AACxC,WAAK,OAAO,KAAK,KAAK,SAAS,MAAM;AAErC,YAAM,SAAe,gBAAgB,GAAG;AACxC,UAAI,WAAW,OAAQ,QAAO,OAAO,KAAK,GAAG;AAC7C,WAAK,SAAS;AACd,aAAa,gBAAgB;AAAA,IAC/B;AAGA,UAAM,YAAsB,CAAC;AAE7B,WAAO,KAAK,KAAK,UAAU,GAAG;AAC5B,YAAM,UAAU,OAAO,aAAa,KAAK,KAAK,CAAC,CAAC;AAChD,YAAM,SAAU,KAAK,KAAK,YAAY,CAAC;AACvC,YAAM,WAAW,IAAI;AACrB,UAAI,KAAK,KAAK,SAAS,SAAU;AAEjC,YAAM,UAAU,KAAK,KAAK,SAAS,GAAG,QAAQ;AAC9C,WAAK,OAAO,KAAK,KAAK,SAAS,QAAQ;AAGvC,UAAI,YAAY,KAAK;AACnB,cAAM,MAAM,QAAQ,QAAQ,CAAC;AAC7B,cAAM,MAAM,QAAQ,SAAS,QAAQ,GAAG,OAAO,IAAI,MAAM,QAAQ,MAAM;AACvE,kBAAU,KAAK,MAAM,KAAK,WAAW,GAAG,CAAC;AACzC;AAAA,MACF;AAEA,cAAQ,SAAS;AAAA,QACf,KAAK,KAAK;AACR,gBAAM,UAAU,QAAQ,QAAQ,CAAC;AACjC,gBAAM,WAAW,UAAU,IAAI,QAAQ,SAAS,QAAQ,GAAG,OAAO,IAAI;AACtE,gBAAM,aAAa,UAAU;AAC7B,gBAAM,WAAW,QAAQ,QAAQ,GAAG,UAAU;AAC9C,gBAAM,MAAM,QAAQ,SAAS,QAAQ,YAAY,YAAY,IAAI,WAAW,QAAQ,MAAM;AAC1F,eAAK,YAAY,IAAI,UAAU,GAAG;AAClC,oBAAU,KAAW,cAAc,CAAC;AACpC;AAAA,QACF;AAAA,QACA,KAAK,KAAK;AACR,gBAAM,EAAE,QAAQ,WAAW,OAAO,IAAI,KAAK,WAAW,OAAO;AAC7D,gBAAM,WAAW,KAAK,YAAY,IAAI,SAAS,KAAK;AACpD,eAAK,SAAS,IAAI,QAAQ,EAAE,KAAK,UAAU,OAAO,CAAC;AACnD,oBAAU,KAAW,aAAa,CAAC;AACnC;AAAA,QACF;AAAA,QACA,KAAK,KAAK;AACR,oBAAU,KAAW,OAAO,CAAC;AAC7B;AAAA,QACF;AAAA,QACA,KAAK,KAAK;AACR,gBAAM,YAAY,QAAQ,QAAQ,CAAC;AACnC,gBAAM,aAAa,YAAY,IAAI,QAAQ,SAAS,QAAQ,GAAG,SAAS,IAAI;AAC5E,gBAAM,QAAQ,KAAK,SAAS,IAAI,UAAU;AAC1C,cAAI,SAAS,MAAM,KAAK;AACtB,kBAAM,IAAI,MAAM,KAAK,qBAAqB,MAAM,KAAK,MAAM,MAAM;AACjE,sBAAU,KAAK,CAAC;AAAA,UAClB;AACA;AAAA,QACF;AAAA,QACA,KAAK,KAAK;AACR,oBAAU,KAAW,cAAc,KAAK,QAAQ,CAAC;AACjD;AAAA,QACF;AAAA,QACA,KAAK,KAAK;AACR;AAAA,QACF;AAAA,QACA;AAEE;AAAA,MACJ;AAAA,IACF;AAEA,WAAO,UAAU,SAAS,IAAI,OAAO,OAAO,SAAS,IAAI,OAAO,MAAM,CAAC;AAAA,EACzE;AAAA;AAAA,EAGQ,WAAW,SAA0E;AAC3F,QAAI,MAAM;AAEV,UAAM,YAAY,QAAQ,QAAQ,GAAG,GAAG;AACxC,UAAM,SAAS,YAAY,MAAM,QAAQ,SAAS,QAAQ,KAAK,SAAS,IAAI;AAC5E,UAAM,YAAY;AAElB,UAAM,UAAU,QAAQ,QAAQ,GAAG,GAAG;AACtC,UAAM,YAAY,UAAU,MAAM,QAAQ,SAAS,QAAQ,KAAK,OAAO,IAAI;AAC3E,UAAM,UAAU;AAEhB,UAAM,aAAa,QAAQ,YAAY,GAAG;AAAG,WAAO;AACpD,WAAO,aAAa;AAEpB,UAAM,YAAY,QAAQ,YAAY,GAAG;AAAG,WAAO;AACnD,UAAM,SAAmB,CAAC;AAC1B,aAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,YAAM,MAAM,QAAQ,YAAY,GAAG;AAAG,aAAO;AAC7C,UAAI,QAAQ,IAAI;AACd,eAAO,KAAK,MAAM;AAAA,MACpB,OAAO;AACL,eAAO,KAAK,QAAQ,SAAS,QAAQ,KAAK,MAAM,GAAG,CAAC;AACpD,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,WAAW,OAAO;AAAA,EACrC;AAAA;AAAA,EAGA,MAAc,qBAAqB,KAAa,QAAmC;AACjF,UAAM,UAAU,IAAI,KAAK;AACzB,UAAM,QAAQ,QAAQ,YAAY;AAElC,QAAI,UAAU,SAAY;AAAE,WAAK,WAAW;AAAK,aAAa,gBAAgB,OAAO;AAAA,IAAG;AACxF,QAAI,UAAU,UAAY;AAAE,WAAK,WAAW;AAAK,aAAa,gBAAgB,QAAQ;AAAA,IAAG;AACzF,QAAI,UAAU,YAAY;AAAE,WAAK,WAAW;AAAK,aAAa,gBAAgB,UAAU;AAAA,IAAG;AAE3F,UAAM,KAAK,MAAM,KAAK;AACtB,QAAI;AAEF,YAAM,SAAS,MAAM,GAAG,MAAM,SAAS,MAAM;AAC7C,YAAM,SAAkC,OAAO,UAAU,CAAC;AAC1D,YAAM,OAAyC,OAAO,QAAQ,CAAC;AAE/D,YAAM,OAAiB,CAAC;AACxB,UAAI,OAAO,SAAS,GAAG;AACrB,aAAK,KAAW,eAAe,OAAO,IAAI,CAAC,MAAwB,EAAE,IAAI,CAAC,CAAC;AAC3E,mBAAW,OAAO,MAAM;AACtB,eAAK,KAAW,QAAQ,OAAO,IAAI,CAAC,MAAwB;AAC1D,kBAAM,IAAI,IAAI,EAAE,IAAI;AACpB,mBAAO,MAAM,QAAQ,MAAM,SAAY,OAAO,OAAO,CAAC;AAAA,UACxD,CAAC,CAAC,CAAC;AAAA,QACL;AAAA,MACF;AAEA,YAAM,MAAM,SAAS,SAAS,KAAK,QAAQ,OAAO,YAAkC;AACpF,WAAK,KAAW,gBAAgB,GAAG,CAAC;AACpC,aAAO,OAAO,OAAO,IAAI;AAAA,IAC3B,SAAS,KAAK;AACZ,aAAa,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC7E;AAAA,EACF;AAAA,EAEA,MAAc,WAAW,KAA8B;AACrD,UAAM,UAAU,IAAI,KAAK;AACzB,UAAM,QAAU,QAAQ,YAAY;AAEpC,QAAI,UAAU,SAAY;AAAE,WAAK,WAAW;AAAK,aAAO,OAAO,OAAO,CAAO,gBAAgB,OAAO,GAAY,cAAc,GAAG,CAAC,CAAC;AAAA,IAAG;AACtI,QAAI,UAAU,UAAY;AAAE,WAAK,WAAW;AAAK,aAAO,OAAO,OAAO,CAAO,gBAAgB,QAAQ,GAAW,cAAc,GAAG,CAAC,CAAC;AAAA,IAAG;AACtI,QAAI,UAAU,YAAY;AAAE,WAAK,WAAW;AAAK,aAAO,OAAO,OAAO,CAAO,gBAAgB,UAAU,GAAS,cAAc,GAAG,CAAC,CAAC;AAAA,IAAG;AAEtI,UAAM,KAAK,MAAM,KAAK;AACtB,QAAI;AACF,YAAM,SAAS,MAAM,GAAG,MAAM,OAAO;AACrC,YAAM,SAAkC,OAAO,UAAU,CAAC;AAC1D,YAAM,OAAyC,OAAO,QAAS,CAAC;AAEhE,YAAM,OAAiB,CAAC;AAExB,UAAI,OAAO,SAAS,GAAG;AACrB,aAAK,KAAW,eAAe,OAAO,IAAI,CAAC,MAAwB,EAAE,IAAI,CAAC,CAAC;AAC3E,mBAAW,OAAO,MAAM;AACtB,eAAK,KAAW,QAAQ,OAAO,IAAI,CAAC,MAAwB;AAC1D,kBAAM,IAAI,IAAI,EAAE,IAAI;AACpB,mBAAO,MAAM,QAAQ,MAAM,SAAY,OAAO,OAAO,CAAC;AAAA,UACxD,CAAC,CAAC,CAAC;AAAA,QACL;AAAA,MACF;AAEA,YAAM,MAAM,SAAS,SAAS,KAAK,QAAQ,OAAO,YAAkC;AACpF,UAAI,QAAQ,QAAS,MAAK,WAAW;AAAA,eAC5B,QAAQ,YAAY,QAAQ,WAAY,MAAK,WAAW;AAEjE,WAAK,KAAW,gBAAgB,GAAG,CAAC;AACpC,WAAK,KAAW,cAAc,KAAK,QAAQ,CAAC;AAC5C,aAAO,OAAO,OAAO,IAAI;AAAA,IAC3B,SAAS,KAAK;AACZ,aAAO,OAAO,OAAO;AAAA,QACb,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC9D,cAAc,KAAK,aAAa,MAAM,MAAM,GAAG;AAAA,MACvD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAIO,IAAM,SAAN,MAAa;AAAA;AAAA,EAEV;AAAA;AAAA,EAEA,eAA8B,QAAQ,QAAQ;AAAA,EAC9C,eAAe,oBAAI,IAA0B;AAAA,EAErD,cAAc;AACZ,SAAK,UAAU,qBAAqB;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,UAAM,KAAK;AACX,UAAM,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,OAAe,MAAkD;AAExE,SAAK,eAAe,KAAK,aAAa,KAAK,MAAM,KAAK,YAAY,OAAO,IAAI,CAAC;AAAA,EAChF;AAAA,EAEA,MAAc,YAAY,OAAe,MAA2D;AAClG,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,KAAK,MAAM,KAAK;AACtB,UAAM,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC;AAChC,UAAM,UAAU,KAAK,IAAI,OAAK,IAAI,CAAC,QAAQ,EAAE,KAAK,IAAI;AACtD,UAAM,GAAG,KAAK,+BAA+B,KAAK,MAAM,OAAO,GAAG;AAClE,eAAW,OAAO,MAAM;AACtB,YAAM,OAAO,KAAK,IAAI,OAAK,IAAI,CAAC,MAAM,OAAO,SAAS,IAAI,OAAO,IAAI,CAAC,CAAC,EAAE,QAAQ,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AAC1G,YAAM,GAAG,KAAK,gBAAgB,KAAK,MAAM,KAAK,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,aAAa,IAAI,GAAG;AAAA,IACjG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAmC,KAAsE;AAC7G,UAAM,KAAK,MAAM,KAAK;AACtB,WAAO,GAAG,MAAM,GAAG;AAAA,EACrB;AAAA,EAEA,gBAAgC;AAC9B,WAAO,OAAO,MAAc,QAAmD;AAC7E,UAAI,CAAC,KAAK,aAAa,IAAI,IAAI,QAAQ,GAAG;AACxC,aAAK,aAAa,IAAI,IAAI,UAAU,IAAI,aAAa,KAAK,OAAO,CAAC;AAAA,MACpE;AACA,aAAO,KAAK,aAAa,IAAI,IAAI,QAAQ,EAAG,YAAY,IAAI;AAAA,IAC9D;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const authOk: () => Buffer;
|
|
2
|
+
export declare const paramStatus: (k: string, v: string) => Buffer;
|
|
3
|
+
export declare const backendKeyData: () => Buffer;
|
|
4
|
+
export declare const readyForQuery: (s: "I" | "T" | "E") => Buffer;
|
|
5
|
+
export declare function rowDescription(cols: string[]): Buffer;
|
|
6
|
+
export declare function dataRow(vals: (string | null)[]): Buffer;
|
|
7
|
+
export declare const commandComplete: (tag: string) => Buffer;
|
|
8
|
+
export declare function errorResponse(msg: string, code?: string): Buffer;
|
|
9
|
+
export declare function startupResponse(): Buffer;
|
|
10
|
+
export declare const parseComplete: () => Buffer;
|
|
11
|
+
export declare const bindComplete: () => Buffer;
|
|
12
|
+
export declare const noData: () => Buffer;
|
|
13
|
+
/**
|
|
14
|
+
* Extract the portal name from an Execute message payload so that
|
|
15
|
+
* PgConnection can look up the prepared SQL. Returns empty string
|
|
16
|
+
* (unnamed portal) when no explicit portal name was given.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseExecuteMsg(payload: Buffer): string;
|
|
19
|
+
export declare function parseStartupMsg(data: Buffer): {
|
|
20
|
+
isSSL: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
user: string;
|
|
23
|
+
database: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function parseQueryMsg(data: Buffer): string;
|
|
26
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM,QAAO,MAA8B,CAAC;AACzD,eAAO,MAAM,WAAW,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAAuD,CAAC;AAC3G,eAAO,MAAM,cAAc,QAAO,MAAyD,CAAC;AAC5F,eAAO,MAAM,aAAa,GAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,KAAG,MAAoC,CAAC;AAExF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CASrD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,MAAM,CAOvD;AAED,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,MAA+B,CAAC;AAE9E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAU,GAAG,MAAM,CAOjE;AAED,wBAAgB,eAAe,IAAI,MAAM,CAQxC;AAGD,eAAO,MAAM,aAAa,QAAQ,MAAqC,CAAC;AACxE,eAAO,MAAM,YAAY,QAAS,MAAqC,CAAC;AACxE,eAAO,MAAM,MAAM,QAAe,MAAqC,CAAC;AAExE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAWrG;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIlD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crashlab/pg-mock",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup && tsc --build tsconfig.json --force",
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"prepack": "npm run build",
|
|
23
|
+
"build:pkg": "tsup && tsc --build tsconfig.json --force"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@electric-sql/pglite": "^0.2.0"
|
|
27
|
+
}
|
|
28
|
+
}
|