@crvouga/mockingbird-service-postgres 1.6.0 → 2.0.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/CHANGELOG.md +35 -0
- package/COMPATIBILITY.md +4 -0
- package/DISCOVERY.md +48 -0
- package/README.md +7 -0
- package/dist/admin.js +629 -235
- package/dist/admin.js.map +3 -3
- package/dist/server.d.ts +9 -0
- package/dist/server.js +25808 -0
- package/dist/server.js.map +7 -0
- package/dist/wire/cli.js +1215 -107
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/cluster.d.ts +3 -0
- package/dist/wire/fleet.d.ts +81 -0
- package/dist/wire/index.d.ts +6 -0
- package/dist/wire/index.js +23 -3
- package/dist/wire/index.js.map +2 -2
- package/dist/wire/probe.d.ts +2 -0
- package/package.json +31 -3
package/dist/wire/cli.js
CHANGED
|
@@ -1,7 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import {
|
|
3
|
+
// ../../adapters/node/dist/cli.js
|
|
4
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
|
|
10
|
+
// ../core/dist/clock.js
|
|
11
|
+
var createClock = (source = Date.now) => {
|
|
12
|
+
let offsetMs = 0;
|
|
13
|
+
let frozenAt;
|
|
14
|
+
const now = () => frozenAt ?? source() + offsetMs;
|
|
15
|
+
return {
|
|
16
|
+
now,
|
|
17
|
+
set: (epochMs) => {
|
|
18
|
+
if (frozenAt !== void 0)
|
|
19
|
+
frozenAt = epochMs;
|
|
20
|
+
else
|
|
21
|
+
offsetMs = epochMs - source();
|
|
22
|
+
},
|
|
23
|
+
advance: (deltaMs) => {
|
|
24
|
+
if (frozenAt !== void 0)
|
|
25
|
+
frozenAt += deltaMs;
|
|
26
|
+
else
|
|
27
|
+
offsetMs += deltaMs;
|
|
28
|
+
},
|
|
29
|
+
freeze: () => {
|
|
30
|
+
frozenAt = now();
|
|
31
|
+
},
|
|
32
|
+
unfreeze: () => {
|
|
33
|
+
if (frozenAt === void 0)
|
|
34
|
+
return;
|
|
35
|
+
offsetMs = frozenAt - source();
|
|
36
|
+
frozenAt = void 0;
|
|
37
|
+
},
|
|
38
|
+
reset: () => {
|
|
39
|
+
offsetMs = 0;
|
|
40
|
+
frozenAt = void 0;
|
|
41
|
+
},
|
|
42
|
+
state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ../core/dist/control.js
|
|
47
|
+
var ADMIN_PREFIX = "/__admin";
|
|
48
|
+
var resolveAdminPrefix = (prefix = ADMIN_PREFIX) => {
|
|
49
|
+
if (!/^\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/.test(prefix)) {
|
|
50
|
+
throw new Error("adminPrefix must be an absolute non-root path of literal segments, without a trailing slash");
|
|
51
|
+
}
|
|
52
|
+
return prefix;
|
|
53
|
+
};
|
|
54
|
+
var UNITS = {
|
|
55
|
+
ms: 1,
|
|
56
|
+
s: 1e3,
|
|
57
|
+
m: 6e4,
|
|
58
|
+
h: 36e5,
|
|
59
|
+
d: 864e5
|
|
60
|
+
};
|
|
61
|
+
var parseDuration = (value) => {
|
|
62
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
63
|
+
return value;
|
|
64
|
+
if (typeof value !== "string")
|
|
65
|
+
return void 0;
|
|
66
|
+
const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
|
|
67
|
+
if (!match)
|
|
68
|
+
return void 0;
|
|
69
|
+
return Number(match[1]) * UNITS[match[2]];
|
|
70
|
+
};
|
|
5
71
|
|
|
6
72
|
// ../../core/dist/timeline.js
|
|
7
73
|
var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
@@ -164,6 +230,786 @@ var Timeline = class {
|
|
|
164
230
|
}
|
|
165
231
|
};
|
|
166
232
|
|
|
233
|
+
// ../../adapters/node/dist/fleet.js
|
|
234
|
+
import { randomUUID } from "node:crypto";
|
|
235
|
+
import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
236
|
+
|
|
237
|
+
// ../../adapters/node/dist/serve.js
|
|
238
|
+
import { createServer } from "node:http";
|
|
239
|
+
var serve = async (api, options2 = {}) => {
|
|
240
|
+
const server2 = createServer(async (req, res) => {
|
|
241
|
+
const chunks = [];
|
|
242
|
+
for await (const chunk of req) {
|
|
243
|
+
chunks.push(Buffer.from(chunk));
|
|
244
|
+
}
|
|
245
|
+
const body = Buffer.concat(chunks);
|
|
246
|
+
const address = server2.address();
|
|
247
|
+
const port = typeof address === "object" && address !== null ? address.port : void 0;
|
|
248
|
+
const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
|
|
249
|
+
const raw = req.url ?? "/";
|
|
250
|
+
const url = new URL(raw.replace(/^\/+/, "/"), base);
|
|
251
|
+
const method = req.method ?? "GET";
|
|
252
|
+
const init = { method, headers: req.headers };
|
|
253
|
+
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
|
254
|
+
init.body = body;
|
|
255
|
+
}
|
|
256
|
+
const aborted = new AbortController();
|
|
257
|
+
res.once("close", () => {
|
|
258
|
+
if (!res.writableFinished)
|
|
259
|
+
aborted.abort();
|
|
260
|
+
});
|
|
261
|
+
init.signal = aborted.signal;
|
|
262
|
+
const request = new Request(url, init);
|
|
263
|
+
let response;
|
|
264
|
+
try {
|
|
265
|
+
response = await api.fetch(request);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error.code === "MOCKINGBIRD_DROP") {
|
|
268
|
+
req.socket.destroy();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
272
|
+
res.end(JSON.stringify({
|
|
273
|
+
error: {
|
|
274
|
+
type: "mockingbird_internal",
|
|
275
|
+
message: error instanceof Error ? error.message : String(error)
|
|
276
|
+
}
|
|
277
|
+
}));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const headers = Object.fromEntries(response.headers);
|
|
281
|
+
const cookies = response.headers.getSetCookie();
|
|
282
|
+
if (cookies.length > 0)
|
|
283
|
+
headers["set-cookie"] = cookies;
|
|
284
|
+
if (!response.body) {
|
|
285
|
+
res.writeHead(response.status, headers);
|
|
286
|
+
res.end();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
res.writeHead(response.status, headers);
|
|
290
|
+
res.flushHeaders();
|
|
291
|
+
const reader = response.body.getReader();
|
|
292
|
+
try {
|
|
293
|
+
for (; ; ) {
|
|
294
|
+
const { done, value } = await reader.read();
|
|
295
|
+
if (done)
|
|
296
|
+
break;
|
|
297
|
+
if (!res.write(value))
|
|
298
|
+
await new Promise((resolve2) => res.once("drain", resolve2));
|
|
299
|
+
}
|
|
300
|
+
res.end();
|
|
301
|
+
} catch {
|
|
302
|
+
res.destroy();
|
|
303
|
+
} finally {
|
|
304
|
+
reader.releaseLock();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
await new Promise((resolve2, reject) => {
|
|
308
|
+
server2.once("error", reject);
|
|
309
|
+
server2.listen(options2.port ?? 0, options2.host, resolve2);
|
|
310
|
+
});
|
|
311
|
+
return server2;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// ../../adapters/node/dist/listen.js
|
|
315
|
+
var listen = async (api, options2 = {}) => {
|
|
316
|
+
const host = options2.host ?? "127.0.0.1";
|
|
317
|
+
const server2 = await serve(api, { port: options2.port ?? 0, host });
|
|
318
|
+
const address = server2.address();
|
|
319
|
+
const port = typeof address === "object" && address !== null ? address.port : options2.port ?? 0;
|
|
320
|
+
const shown = host.includes(":") ? `[${host}]` : host;
|
|
321
|
+
return {
|
|
322
|
+
url: `http://${shown}:${port}`,
|
|
323
|
+
port,
|
|
324
|
+
host,
|
|
325
|
+
server: server2,
|
|
326
|
+
close: () => new Promise((resolve2, reject) => {
|
|
327
|
+
server2.close((error) => {
|
|
328
|
+
if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
|
|
329
|
+
reject(error);
|
|
330
|
+
else
|
|
331
|
+
resolve2();
|
|
332
|
+
});
|
|
333
|
+
server2.closeAllConnections?.();
|
|
334
|
+
})
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// ../../adapters/node/dist/fleet.js
|
|
339
|
+
var EMPTY = {
|
|
340
|
+
activeRequests: 0,
|
|
341
|
+
activeConnections: 0,
|
|
342
|
+
blockedCommands: 0,
|
|
343
|
+
pendingJobs: 0,
|
|
344
|
+
pendingWebhooks: 0,
|
|
345
|
+
unmatchedRequests: 0
|
|
346
|
+
};
|
|
347
|
+
var NS = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
348
|
+
var json = (status, value) => Response.json(value, { status });
|
|
349
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
350
|
+
var pending = (d) => d.activeRequests + d.blockedCommands + d.pendingJobs + d.pendingWebhooks;
|
|
351
|
+
var publicUrl = (raw) => {
|
|
352
|
+
const url = new URL(raw);
|
|
353
|
+
url.password = "";
|
|
354
|
+
return url.href;
|
|
355
|
+
};
|
|
356
|
+
var atomicWrite = async (path, value) => {
|
|
357
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
358
|
+
try {
|
|
359
|
+
await writeFile(temp, `${JSON.stringify(value, null, 2)}
|
|
360
|
+
`, { mode: 384, flag: "wx" });
|
|
361
|
+
await chmod(temp, 384);
|
|
362
|
+
await rename(temp, path);
|
|
363
|
+
} finally {
|
|
364
|
+
await rm(temp, { force: true });
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
var removeOwned = async (path, id) => {
|
|
368
|
+
if (!path)
|
|
369
|
+
return;
|
|
370
|
+
try {
|
|
371
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
372
|
+
if (isObject(value) && value.id === id)
|
|
373
|
+
await rm(path, { force: true });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (error.code !== "ENOENT")
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
var httpChild = async (target, entry, options2) => {
|
|
380
|
+
const runtime = await target.create(entry.options ?? {}, {
|
|
381
|
+
adminPrefix: resolveAdminPrefix(entry.adminPrefix),
|
|
382
|
+
adminKey: entry.adminKey,
|
|
383
|
+
seed: entry.seed,
|
|
384
|
+
onLog: options2.onLog
|
|
385
|
+
});
|
|
386
|
+
const adminPrefix = resolveAdminPrefix(entry.adminPrefix);
|
|
387
|
+
runtime.isolateNamespaces();
|
|
388
|
+
const locked = /* @__PURE__ */ new Set();
|
|
389
|
+
const active = /* @__PURE__ */ new Map();
|
|
390
|
+
const api = {
|
|
391
|
+
async fetch(request) {
|
|
392
|
+
const namespace = runtime.namespaceOf(request);
|
|
393
|
+
if (locked.has(namespace))
|
|
394
|
+
return json(409, { error: "fleet control in progress", namespace });
|
|
395
|
+
active.set(namespace, (active.get(namespace) ?? 0) + 1);
|
|
396
|
+
try {
|
|
397
|
+
return await runtime.fetch(request);
|
|
398
|
+
} finally {
|
|
399
|
+
active.set(namespace, (active.get(namespace) ?? 1) - 1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
let server2;
|
|
404
|
+
try {
|
|
405
|
+
server2 = await listen(api, {
|
|
406
|
+
port: entry.port ?? target.defaultPort,
|
|
407
|
+
host: entry.host ?? "127.0.0.1"
|
|
408
|
+
});
|
|
409
|
+
target.listening?.(server2);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
runtime.stop?.();
|
|
412
|
+
await server2?.close();
|
|
413
|
+
runtime.webhooks?.clear();
|
|
414
|
+
await runtime.webhooks?.idle();
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
protocol: "http",
|
|
419
|
+
url: server2.url,
|
|
420
|
+
healthUrl: `${server2.url}${adminPrefix}/health`,
|
|
421
|
+
adminUrl: `${server2.url}${adminPrefix}`,
|
|
422
|
+
namespaces: { header: "x-mockingbird-namespace", path: `${adminPrefix}/ns/{name}` },
|
|
423
|
+
ready: async () => {
|
|
424
|
+
try {
|
|
425
|
+
return (await fetch(`${server2.url}${adminPrefix}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
|
|
426
|
+
} catch {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
async close() {
|
|
431
|
+
runtime.stop?.();
|
|
432
|
+
runtime.webhooks?.clear();
|
|
433
|
+
await server2.close();
|
|
434
|
+
await runtime.webhooks?.idle();
|
|
435
|
+
},
|
|
436
|
+
lock(namespace) {
|
|
437
|
+
if (locked.has(namespace))
|
|
438
|
+
throw new Error("namespace already locked");
|
|
439
|
+
locked.add(namespace);
|
|
440
|
+
return () => locked.delete(namespace);
|
|
441
|
+
},
|
|
442
|
+
diagnostics(namespace) {
|
|
443
|
+
return {
|
|
444
|
+
...EMPTY,
|
|
445
|
+
activeRequests: active.get(namespace) ?? 0,
|
|
446
|
+
pendingWebhooks: runtime.webhooks?.pending(namespace) ?? 0,
|
|
447
|
+
unmatchedRequests: runtime.metrics.report(namespace).unmatched.reduce((n, r) => n + r.count, 0)
|
|
448
|
+
};
|
|
449
|
+
},
|
|
450
|
+
async checkpoint(namespace) {
|
|
451
|
+
return runtime.fleetSnapshot(namespace);
|
|
452
|
+
},
|
|
453
|
+
async restore(namespace, checkpoint) {
|
|
454
|
+
runtime.fleetRestore(checkpoint, namespace);
|
|
455
|
+
},
|
|
456
|
+
reset: (namespace) => runtime.reset(namespace),
|
|
457
|
+
clock: (namespace) => runtime.namespaceClock(namespace),
|
|
458
|
+
async settle(namespace) {
|
|
459
|
+
const instance = runtime.instance(namespace);
|
|
460
|
+
instance.tick?.();
|
|
461
|
+
await runtime.webhooks?.idle();
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
async function startFleet(config, options2) {
|
|
466
|
+
const id = randomUUID();
|
|
467
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
468
|
+
const children = /* @__PURE__ */ new Map();
|
|
469
|
+
const clocks = /* @__PURE__ */ new Map();
|
|
470
|
+
const checkpoints = /* @__PURE__ */ new Map();
|
|
471
|
+
let supervisor;
|
|
472
|
+
let stopped = false;
|
|
473
|
+
let controlling = false;
|
|
474
|
+
const close = async () => {
|
|
475
|
+
if (stopped)
|
|
476
|
+
return;
|
|
477
|
+
stopped = true;
|
|
478
|
+
await Promise.allSettled([...children.values()].map((child) => child.close()));
|
|
479
|
+
await supervisor?.close();
|
|
480
|
+
await removeOwned(options2.readyFile, id);
|
|
481
|
+
await removeOwned(options2.connectionsFile, id);
|
|
482
|
+
};
|
|
483
|
+
if (options2.readyFile)
|
|
484
|
+
await rm(options2.readyFile, { force: true });
|
|
485
|
+
if (options2.connectionsFile)
|
|
486
|
+
await rm(options2.connectionsFile, { force: true });
|
|
487
|
+
const clock = (namespace) => {
|
|
488
|
+
let value = clocks.get(namespace);
|
|
489
|
+
if (!value) {
|
|
490
|
+
value = createClock();
|
|
491
|
+
clocks.set(namespace, value);
|
|
492
|
+
}
|
|
493
|
+
return value;
|
|
494
|
+
};
|
|
495
|
+
const select = (body) => {
|
|
496
|
+
if (body.services === void 0)
|
|
497
|
+
return children;
|
|
498
|
+
if (!Array.isArray(body.services) || body.services.length === 0)
|
|
499
|
+
throw new Error("services must be a nonempty list");
|
|
500
|
+
const selected = /* @__PURE__ */ new Map();
|
|
501
|
+
for (const name of body.services) {
|
|
502
|
+
if (typeof name !== "string" || !children.has(name))
|
|
503
|
+
throw new Error("unknown service in selection");
|
|
504
|
+
selected.set(name, children.get(name));
|
|
505
|
+
}
|
|
506
|
+
return selected;
|
|
507
|
+
};
|
|
508
|
+
const diagnostics = (namespace) => Object.fromEntries([...children].map(([name, child]) => [name, child.diagnostics(namespace)]));
|
|
509
|
+
const transaction = async (namespace, selected, run) => {
|
|
510
|
+
if (controlling)
|
|
511
|
+
return json(409, { error: "another fleet operation is in progress" });
|
|
512
|
+
controlling = true;
|
|
513
|
+
const release = [];
|
|
514
|
+
const before = /* @__PURE__ */ new Map();
|
|
515
|
+
const results = {};
|
|
516
|
+
let failure;
|
|
517
|
+
try {
|
|
518
|
+
for (const [name, child] of selected) {
|
|
519
|
+
failure = name;
|
|
520
|
+
release.push(child.lock(namespace));
|
|
521
|
+
}
|
|
522
|
+
const busy = Object.fromEntries([...selected].filter(([, child]) => {
|
|
523
|
+
const state = child.diagnostics(namespace);
|
|
524
|
+
return state.activeRequests + state.blockedCommands + state.pendingWebhooks + (state.activeJobs ?? 0) > 0;
|
|
525
|
+
}).map(([name, child]) => [name, child.diagnostics(namespace)]));
|
|
526
|
+
if (Object.keys(busy).length)
|
|
527
|
+
return json(409, { error: "namespace is not quiescent", services: busy });
|
|
528
|
+
for (const [name, child] of selected) {
|
|
529
|
+
failure = name;
|
|
530
|
+
before.set(name, await child.checkpoint(namespace));
|
|
531
|
+
}
|
|
532
|
+
for (const [name, child] of selected) {
|
|
533
|
+
failure = name;
|
|
534
|
+
results[name] = { status: "ok", result: await run(name, child) };
|
|
535
|
+
}
|
|
536
|
+
return json(200, { status: "ok", namespace, services: results });
|
|
537
|
+
} catch {
|
|
538
|
+
const rollback = {};
|
|
539
|
+
for (const [name, point] of before) {
|
|
540
|
+
try {
|
|
541
|
+
await selected.get(name)?.restore(namespace, point);
|
|
542
|
+
rollback[name] = "restored";
|
|
543
|
+
} catch {
|
|
544
|
+
rollback[name] = "failed";
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return json(500, {
|
|
548
|
+
status: "failed",
|
|
549
|
+
namespace,
|
|
550
|
+
failedService: failure,
|
|
551
|
+
services: results,
|
|
552
|
+
rollback,
|
|
553
|
+
rolledBack: before.size > 0 && Object.values(rollback).every((value) => value === "restored")
|
|
554
|
+
});
|
|
555
|
+
} finally {
|
|
556
|
+
for (const unlock of release.reverse())
|
|
557
|
+
unlock();
|
|
558
|
+
controlling = false;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const api = {
|
|
562
|
+
async fetch(request) {
|
|
563
|
+
const url = new URL(request.url);
|
|
564
|
+
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
565
|
+
const path = url.pathname.startsWith(`${adminPrefix}/`) ? url.pathname.slice(adminPrefix.length) : "";
|
|
566
|
+
if (path === "/health" && request.method === "GET") {
|
|
567
|
+
const states = await Promise.all([...children].map(async ([name, child]) => {
|
|
568
|
+
let protocolReady = false;
|
|
569
|
+
try {
|
|
570
|
+
protocolReady = await child.ready();
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
return [
|
|
574
|
+
name,
|
|
575
|
+
{
|
|
576
|
+
processReady: !stopped,
|
|
577
|
+
protocolReady,
|
|
578
|
+
status: protocolReady ? "ready" : "failed",
|
|
579
|
+
url: publicUrl(child.url)
|
|
580
|
+
}
|
|
581
|
+
];
|
|
582
|
+
}));
|
|
583
|
+
const ready = !stopped && states.every(([, state]) => state.protocolReady);
|
|
584
|
+
return json(ready ? 200 : 503, {
|
|
585
|
+
status: ready ? "ready" : "failed",
|
|
586
|
+
id,
|
|
587
|
+
services: Object.fromEntries(states)
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (!path.startsWith("/fleet/"))
|
|
591
|
+
return json(404, { error: "not found" });
|
|
592
|
+
const adminKey = config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
593
|
+
if (adminKey !== void 0 && request.headers.get("x-mockingbird-admin-key") !== adminKey)
|
|
594
|
+
return json(401, { error: "admin key required" });
|
|
595
|
+
const route = /^\/fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(path);
|
|
596
|
+
const namespace = route ? decodeURIComponent(route[1]) : url.searchParams.get("namespace") ?? request.headers.get("x-mockingbird-namespace") ?? config.namespace ?? "default";
|
|
597
|
+
if (!NS.test(namespace))
|
|
598
|
+
return json(400, { error: "invalid namespace" });
|
|
599
|
+
if (request.method === "GET" && path === "/fleet/metrics")
|
|
600
|
+
return json(200, { namespace, services: diagnostics(namespace) });
|
|
601
|
+
if (request.method === "GET" && path === "/fleet/clock")
|
|
602
|
+
return json(200, {
|
|
603
|
+
namespace,
|
|
604
|
+
...clock(namespace).state(),
|
|
605
|
+
services: Object.fromEntries([...children].map(([name, child]) => [name, child.clock(namespace).state()]))
|
|
606
|
+
});
|
|
607
|
+
if (request.method !== "POST")
|
|
608
|
+
return json(405, { error: "method not allowed" });
|
|
609
|
+
let body;
|
|
610
|
+
try {
|
|
611
|
+
const text = await request.text();
|
|
612
|
+
const value = text ? JSON.parse(text) : {};
|
|
613
|
+
if (!isObject(value))
|
|
614
|
+
throw new Error("expected object");
|
|
615
|
+
body = value;
|
|
616
|
+
} catch {
|
|
617
|
+
return json(400, { error: "expected JSON object" });
|
|
618
|
+
}
|
|
619
|
+
let selected;
|
|
620
|
+
try {
|
|
621
|
+
selected = select(body);
|
|
622
|
+
} catch (error) {
|
|
623
|
+
return json(400, { error: error instanceof Error ? error.message : "bad selection" });
|
|
624
|
+
}
|
|
625
|
+
if (path === "/fleet/wait-until-idle") {
|
|
626
|
+
const services = diagnostics(namespace);
|
|
627
|
+
const idle = Object.values(services).every((d) => pending(d) === 0);
|
|
628
|
+
return json(idle ? 200 : 409, { status: idle ? "idle" : "busy", namespace, services });
|
|
629
|
+
}
|
|
630
|
+
if (path === "/fleet/clock") {
|
|
631
|
+
const current = clock(namespace);
|
|
632
|
+
let target = current.now();
|
|
633
|
+
if (body.set !== void 0) {
|
|
634
|
+
target = typeof body.set === "number" ? body.set : typeof body.set === "string" ? Date.parse(body.set) : NaN;
|
|
635
|
+
if (!Number.isFinite(target))
|
|
636
|
+
return json(400, { error: "set must be epoch ms or ISO-8601" });
|
|
637
|
+
}
|
|
638
|
+
if (body.advance !== void 0) {
|
|
639
|
+
const delta = parseDuration(body.advance);
|
|
640
|
+
if (delta === void 0)
|
|
641
|
+
return json(400, { error: "invalid advance" });
|
|
642
|
+
target += delta;
|
|
643
|
+
}
|
|
644
|
+
const frozen = body.freeze === void 0 ? current.state().frozen : body.freeze;
|
|
645
|
+
if (typeof frozen !== "boolean")
|
|
646
|
+
return json(400, { error: "freeze must be boolean" });
|
|
647
|
+
const response2 = await transaction(namespace, selected, async (_name, child) => {
|
|
648
|
+
const childClock = child.clock(namespace);
|
|
649
|
+
childClock.freeze();
|
|
650
|
+
childClock.set(target);
|
|
651
|
+
if (!frozen)
|
|
652
|
+
childClock.unfreeze();
|
|
653
|
+
await child.settle?.(namespace);
|
|
654
|
+
return childClock.state();
|
|
655
|
+
});
|
|
656
|
+
if (response2.ok) {
|
|
657
|
+
current.freeze();
|
|
658
|
+
current.set(target);
|
|
659
|
+
if (!frozen)
|
|
660
|
+
current.unfreeze();
|
|
661
|
+
}
|
|
662
|
+
return response2;
|
|
663
|
+
}
|
|
664
|
+
if (!route)
|
|
665
|
+
return json(404, { error: "not found" });
|
|
666
|
+
if (route[2] === "reset") {
|
|
667
|
+
if (route[3])
|
|
668
|
+
return json(404, { error: "not found" });
|
|
669
|
+
const response2 = await transaction(namespace, selected, async (_name, child) => child.reset(namespace));
|
|
670
|
+
if (response2.ok && selected.size === children.size)
|
|
671
|
+
clock(namespace).reset();
|
|
672
|
+
return response2;
|
|
673
|
+
}
|
|
674
|
+
if (route[3]) {
|
|
675
|
+
const point = checkpoints.get(route[3]);
|
|
676
|
+
if (!point || point.namespace !== namespace)
|
|
677
|
+
return json(404, { error: "no snapshot in this namespace" });
|
|
678
|
+
const restored = new Map([...point.values].map(([name]) => [name, children.get(name)]));
|
|
679
|
+
const response2 = await transaction(namespace, restored, async (name, child) => child.restore(namespace, point.values.get(name)));
|
|
680
|
+
if (response2.ok) {
|
|
681
|
+
const current = clock(namespace);
|
|
682
|
+
current.freeze();
|
|
683
|
+
current.set(point.clock.now);
|
|
684
|
+
if (!point.clock.frozen)
|
|
685
|
+
current.unfreeze();
|
|
686
|
+
}
|
|
687
|
+
return response2;
|
|
688
|
+
}
|
|
689
|
+
const snapshotId = randomUUID();
|
|
690
|
+
const values = /* @__PURE__ */ new Map();
|
|
691
|
+
const response = await transaction(namespace, selected, async (name, child) => {
|
|
692
|
+
values.set(name, await child.checkpoint(namespace));
|
|
693
|
+
return { checkpoint: `${snapshotId}:${name}` };
|
|
694
|
+
});
|
|
695
|
+
if (!response.ok)
|
|
696
|
+
return response;
|
|
697
|
+
checkpoints.set(snapshotId, { namespace, values, clock: clock(namespace).state() });
|
|
698
|
+
return json(201, {
|
|
699
|
+
...await response.json(),
|
|
700
|
+
id: snapshotId,
|
|
701
|
+
label: body.label
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
try {
|
|
706
|
+
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
707
|
+
if (!isObject(config.services) || Object.keys(config.services).length === 0)
|
|
708
|
+
throw new Error("config must contain at least one service");
|
|
709
|
+
for (const [name, entry] of Object.entries(config.services)) {
|
|
710
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
|
|
711
|
+
throw new Error("invalid service name");
|
|
712
|
+
try {
|
|
713
|
+
const target = await options2.load(name);
|
|
714
|
+
if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
|
|
715
|
+
throw new Error("configured protocol does not match target");
|
|
716
|
+
const adminKey = entry.adminKey ?? config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
717
|
+
const child = "start" in target ? await target.start(entry) : await httpChild(target, {
|
|
718
|
+
...entry,
|
|
719
|
+
adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
|
|
720
|
+
...adminKey !== void 0 ? { adminKey } : {}
|
|
721
|
+
}, options2);
|
|
722
|
+
children.set(name, child);
|
|
723
|
+
if (!await child.ready())
|
|
724
|
+
throw new Error("protocol readiness failed");
|
|
725
|
+
} catch {
|
|
726
|
+
throw new Error(`service ${name} failed to start; all started listeners were closed`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
supervisor = await listen(api, {
|
|
730
|
+
port: config.control?.port ?? 0,
|
|
731
|
+
host: config.control?.host ?? "127.0.0.1"
|
|
732
|
+
});
|
|
733
|
+
const manifest = {
|
|
734
|
+
version: 1,
|
|
735
|
+
state: "ready",
|
|
736
|
+
pid: process.pid,
|
|
737
|
+
id,
|
|
738
|
+
startedAt,
|
|
739
|
+
adminBase: `${supervisor.url}${adminPrefix}/fleet`,
|
|
740
|
+
healthUrl: `${supervisor.url}${adminPrefix}/health`,
|
|
741
|
+
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
742
|
+
name,
|
|
743
|
+
{
|
|
744
|
+
protocol: child.protocol,
|
|
745
|
+
url: publicUrl(child.url),
|
|
746
|
+
healthUrl: child.healthUrl ?? `${supervisor?.url}${adminPrefix}/health`,
|
|
747
|
+
adminUrl: child.adminUrl ?? `${supervisor?.url}${adminPrefix}/fleet`,
|
|
748
|
+
namespaces: child.namespaces,
|
|
749
|
+
processReady: true,
|
|
750
|
+
protocolReady: true
|
|
751
|
+
}
|
|
752
|
+
]))
|
|
753
|
+
};
|
|
754
|
+
if (options2.connectionsFile)
|
|
755
|
+
await atomicWrite(options2.connectionsFile, {
|
|
756
|
+
version: 1,
|
|
757
|
+
id,
|
|
758
|
+
pid: process.pid,
|
|
759
|
+
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
760
|
+
name,
|
|
761
|
+
{ url: child.connection ?? child.url, namespaces: child.connections }
|
|
762
|
+
]))
|
|
763
|
+
});
|
|
764
|
+
if (options2.readyFile)
|
|
765
|
+
await atomicWrite(options2.readyFile, manifest);
|
|
766
|
+
options2.onReady?.(manifest);
|
|
767
|
+
return { manifest, children, fetch: api.fetch, close };
|
|
768
|
+
} catch (error) {
|
|
769
|
+
await close();
|
|
770
|
+
throw error;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ../../adapters/node/dist/cli.js
|
|
775
|
+
var optionHelp = (options2) => Object.entries(options2).map(([name, option]) => {
|
|
776
|
+
const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
|
|
777
|
+
const fallback2 = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
|
|
778
|
+
return ` ${flag.padEnd(30)} ${option.description}${fallback2}`;
|
|
779
|
+
});
|
|
780
|
+
var help = (spec) => [
|
|
781
|
+
`${spec.bin} \u2014 ${spec.description}`,
|
|
782
|
+
"",
|
|
783
|
+
"Usage:",
|
|
784
|
+
` ${spec.bin} <command> [options]`,
|
|
785
|
+
"",
|
|
786
|
+
"Commands:",
|
|
787
|
+
...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
|
|
788
|
+
"",
|
|
789
|
+
`Run \`${spec.bin} <command> --help\` for a command's options.`
|
|
790
|
+
].join("\n");
|
|
791
|
+
var commandHelp = (spec, name, command2) => [
|
|
792
|
+
`${spec.bin} ${name} \u2014 ${command2.summary}`,
|
|
793
|
+
"",
|
|
794
|
+
"Usage:",
|
|
795
|
+
` ${command2.usage ?? `${spec.bin} ${name} [options]`}`,
|
|
796
|
+
...command2.options ? ["", "Options:", ...optionHelp(command2.options)] : []
|
|
797
|
+
].join("\n");
|
|
798
|
+
var runCli = async (spec, argv) => {
|
|
799
|
+
const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
|
|
800
|
+
const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
|
|
801
|
+
const name = words === 2 ? pair : argv[0];
|
|
802
|
+
const rest = argv.slice(words);
|
|
803
|
+
if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
|
|
804
|
+
console.log(help(spec));
|
|
805
|
+
return 0;
|
|
806
|
+
}
|
|
807
|
+
const command2 = spec.commands[name];
|
|
808
|
+
if (!command2) {
|
|
809
|
+
console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
|
|
810
|
+
|
|
811
|
+
${help(spec)}`);
|
|
812
|
+
return 2;
|
|
813
|
+
}
|
|
814
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
815
|
+
console.log(commandHelp(spec, name, command2));
|
|
816
|
+
return 0;
|
|
817
|
+
}
|
|
818
|
+
let parsed;
|
|
819
|
+
try {
|
|
820
|
+
parsed = parseArgs({
|
|
821
|
+
args: rest,
|
|
822
|
+
allowPositionals: true,
|
|
823
|
+
strict: true,
|
|
824
|
+
options: Object.fromEntries(Object.entries(command2.options ?? {}).map(([key, option]) => [
|
|
825
|
+
key,
|
|
826
|
+
{
|
|
827
|
+
type: option.type,
|
|
828
|
+
...option.default !== void 0 ? { default: option.default } : {}
|
|
829
|
+
}
|
|
830
|
+
]))
|
|
831
|
+
});
|
|
832
|
+
} catch (error) {
|
|
833
|
+
console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
834
|
+
|
|
835
|
+
${commandHelp(spec, name, command2)}`);
|
|
836
|
+
return 2;
|
|
837
|
+
}
|
|
838
|
+
return command2.run(parsed.values, parsed.positionals);
|
|
839
|
+
};
|
|
840
|
+
var COMMON_SERVE_OPTIONS = {
|
|
841
|
+
port: { type: "string", value: "<port>", description: "Port to listen on" },
|
|
842
|
+
host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
|
|
843
|
+
"admin-prefix": {
|
|
844
|
+
type: "string",
|
|
845
|
+
value: "<path>",
|
|
846
|
+
description: "Internal API prefix (default /__admin; env MOCKINGBIRD_ADMIN_PREFIX)"
|
|
847
|
+
},
|
|
848
|
+
"admin-key": {
|
|
849
|
+
type: "string",
|
|
850
|
+
value: "<key>",
|
|
851
|
+
description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
|
|
852
|
+
},
|
|
853
|
+
seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
|
|
854
|
+
log: {
|
|
855
|
+
type: "string",
|
|
856
|
+
value: "<pretty|json|off>",
|
|
857
|
+
description: "Request log format",
|
|
858
|
+
default: "pretty"
|
|
859
|
+
},
|
|
860
|
+
"log-requests": {
|
|
861
|
+
type: "boolean",
|
|
862
|
+
description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
|
|
863
|
+
},
|
|
864
|
+
config: {
|
|
865
|
+
type: "string",
|
|
866
|
+
value: "<file>",
|
|
867
|
+
description: "Serve every service in a mockingbird.json config instead"
|
|
868
|
+
},
|
|
869
|
+
"ready-file": {
|
|
870
|
+
type: "string",
|
|
871
|
+
description: "Atomically publish fleet endpoint JSON",
|
|
872
|
+
value: "<file>"
|
|
873
|
+
},
|
|
874
|
+
"connections-file": {
|
|
875
|
+
type: "string",
|
|
876
|
+
description: "Write private connection URLs with mode 0600",
|
|
877
|
+
value: "<file>"
|
|
878
|
+
},
|
|
879
|
+
"ready-json": {
|
|
880
|
+
type: "boolean",
|
|
881
|
+
description: "Emit one public JSON record after fleet readiness"
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
var formatLog = (format) => {
|
|
885
|
+
if (format === "off")
|
|
886
|
+
return void 0;
|
|
887
|
+
if (format === "json")
|
|
888
|
+
return (entry) => console.log(JSON.stringify(entry));
|
|
889
|
+
return (entry) => {
|
|
890
|
+
const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
|
|
891
|
+
const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
|
|
892
|
+
const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
|
|
893
|
+
const adopted = entry.adopted ? " adopted" : "";
|
|
894
|
+
const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
|
|
895
|
+
const issues = entry.issues?.length ? ` issues=${entry.issues.slice(0, 3).map((issue) => `${issue.path || "$"}: ${issue.message}`).join("; ")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : ""}` : "";
|
|
896
|
+
console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
|
|
897
|
+
};
|
|
898
|
+
};
|
|
899
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
900
|
+
var loadTarget = async (name, own) => {
|
|
901
|
+
if (name === own.name)
|
|
902
|
+
return own;
|
|
903
|
+
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
904
|
+
try {
|
|
905
|
+
let mod;
|
|
906
|
+
try {
|
|
907
|
+
mod = await import(specifier);
|
|
908
|
+
} catch {
|
|
909
|
+
const fromProject = createRequire(resolve(process.cwd(), "package.json"));
|
|
910
|
+
mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
|
|
911
|
+
}
|
|
912
|
+
if (!mod.serveTarget)
|
|
913
|
+
throw new Error(`${specifier} exports no serveTarget`);
|
|
914
|
+
return mod.serveTarget;
|
|
915
|
+
} catch (error) {
|
|
916
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
917
|
+
throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
var start = async (target, values, config) => {
|
|
921
|
+
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
922
|
+
const runtime = await target.create(values, {
|
|
923
|
+
adminPrefix,
|
|
924
|
+
adminKey: config.adminKey,
|
|
925
|
+
seed: config.seed,
|
|
926
|
+
onLog: formatLog(config.log)
|
|
927
|
+
});
|
|
928
|
+
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
929
|
+
target.listening?.(listening);
|
|
930
|
+
console.log(`${target.name} mock listening on ${listening.url}`);
|
|
931
|
+
console.log(`${target.name} health: GET ${listening.url}${adminPrefix}/health`);
|
|
932
|
+
console.log(`${target.name} admin: ${listening.url}${adminPrefix} (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
|
|
933
|
+
console.log(`${target.name} admin ui: ${listening.url}${adminPrefix}/ui`);
|
|
934
|
+
for (const line of target.banner?.(runtime) ?? [])
|
|
935
|
+
console.log(`${target.name} ${line.replaceAll("/__admin", adminPrefix)}`);
|
|
936
|
+
return listening;
|
|
937
|
+
};
|
|
938
|
+
var untilSignal = async (servers) => new Promise((resolve2) => {
|
|
939
|
+
const stop2 = async () => {
|
|
940
|
+
;
|
|
941
|
+
process.removeListener("SIGINT", stop2);
|
|
942
|
+
process.removeListener("SIGTERM", stop2);
|
|
943
|
+
await Promise.allSettled(servers.map((s) => s.close()));
|
|
944
|
+
resolve2(0);
|
|
945
|
+
};
|
|
946
|
+
process.once("SIGINT", stop2);
|
|
947
|
+
process.once("SIGTERM", stop2);
|
|
948
|
+
});
|
|
949
|
+
var serveCommand = (target) => ({
|
|
950
|
+
summary: `Serve the ${target.name} mock or a configured fleet`,
|
|
951
|
+
options: { ...COMMON_SERVE_OPTIONS, ..."options" in target ? target.options : {} },
|
|
952
|
+
async run(values) {
|
|
953
|
+
const log2 = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
|
|
954
|
+
if (!["pretty", "json", "off"].includes(log2)) {
|
|
955
|
+
console.error(`--log must be pretty, json or off (got ${log2})`);
|
|
956
|
+
return 2;
|
|
957
|
+
}
|
|
958
|
+
const configPath = asString(values.config);
|
|
959
|
+
if (configPath !== void 0) {
|
|
960
|
+
try {
|
|
961
|
+
const config = JSON.parse(await readFile2(configPath, "utf8"));
|
|
962
|
+
config.adminPrefix = resolveAdminPrefix(config.adminPrefix ?? asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX);
|
|
963
|
+
const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
964
|
+
if (adminKey2 !== void 0)
|
|
965
|
+
config.adminKey = adminKey2;
|
|
966
|
+
const fleet = await startFleet(config, {
|
|
967
|
+
load: (name) => loadTarget(name, target),
|
|
968
|
+
onLog: formatLog(config.log ?? log2),
|
|
969
|
+
...asString(values["ready-file"]) ? { readyFile: asString(values["ready-file"]) } : {},
|
|
970
|
+
...asString(values["connections-file"]) ? { connectionsFile: asString(values["connections-file"]) } : {},
|
|
971
|
+
onReady: (manifest) => {
|
|
972
|
+
if (values["ready-json"])
|
|
973
|
+
console.log(JSON.stringify(manifest));
|
|
974
|
+
else
|
|
975
|
+
for (const [name, endpoint] of Object.entries(manifest.services))
|
|
976
|
+
console.log(`${name} mock listening on ${endpoint.url}`);
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
return untilSignal([fleet]);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
982
|
+
return 1;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if ("start" in target) {
|
|
986
|
+
console.error("Use --config to supervise protocol services with this command");
|
|
987
|
+
return 2;
|
|
988
|
+
}
|
|
989
|
+
const port = asString(values.port);
|
|
990
|
+
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
991
|
+
const seed = asString(values.seed);
|
|
992
|
+
let listening;
|
|
993
|
+
try {
|
|
994
|
+
listening = await start(target, values, {
|
|
995
|
+
adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
|
|
996
|
+
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
997
|
+
host: asString(values.host) ?? "127.0.0.1",
|
|
998
|
+
...adminKey !== void 0 ? { adminKey } : {},
|
|
999
|
+
...seed !== void 0 ? { seed } : {},
|
|
1000
|
+
log: log2
|
|
1001
|
+
});
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1004
|
+
return 1;
|
|
1005
|
+
}
|
|
1006
|
+
return untilSignal([listening]);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// src/wire/index.ts
|
|
1011
|
+
import { createServer as createServer2 } from "node:net";
|
|
1012
|
+
|
|
167
1013
|
// src/errors/error.ts
|
|
168
1014
|
var CATEGORY_TO_SQLSTATE = {
|
|
169
1015
|
syntax: "42601",
|
|
@@ -2344,11 +3190,11 @@ var JsonParser = class {
|
|
|
2344
3190
|
}
|
|
2345
3191
|
}
|
|
2346
3192
|
parseNumber() {
|
|
2347
|
-
const
|
|
3193
|
+
const start2 = this.pos;
|
|
2348
3194
|
const re = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
2349
3195
|
re.lastIndex = this.pos;
|
|
2350
3196
|
const m = re.exec(this.text);
|
|
2351
|
-
if (!m || m.index !==
|
|
3197
|
+
if (!m || m.index !== start2) this.fail("bad number");
|
|
2352
3198
|
this.pos = re.lastIndex;
|
|
2353
3199
|
return jsonbNum(parseNumeric(m[0]));
|
|
2354
3200
|
}
|
|
@@ -4700,18 +5546,18 @@ function isoWeek(days) {
|
|
|
4700
5546
|
return jan4 - (isoDw - 1);
|
|
4701
5547
|
};
|
|
4702
5548
|
let year = y;
|
|
4703
|
-
let
|
|
4704
|
-
if (days <
|
|
5549
|
+
let start2 = week1Monday(y);
|
|
5550
|
+
if (days < start2) {
|
|
4705
5551
|
year = y - 1;
|
|
4706
|
-
|
|
5552
|
+
start2 = week1Monday(year);
|
|
4707
5553
|
} else {
|
|
4708
5554
|
const nextStart = week1Monday(y + 1);
|
|
4709
5555
|
if (days >= nextStart) {
|
|
4710
5556
|
year = y + 1;
|
|
4711
|
-
|
|
5557
|
+
start2 = nextStart;
|
|
4712
5558
|
}
|
|
4713
5559
|
}
|
|
4714
|
-
return { week: Math.floor((days -
|
|
5560
|
+
return { week: Math.floor((days - start2) / 7) + 1, year };
|
|
4715
5561
|
}
|
|
4716
5562
|
function dateTruncDays(field, days) {
|
|
4717
5563
|
const { y, m } = daysToCivil(days);
|
|
@@ -5281,9 +6127,9 @@ function getArrayFunctions() {
|
|
|
5281
6127
|
}
|
|
5282
6128
|
const elemT = arrayElemType(a.t);
|
|
5283
6129
|
const target = castTo(ctx, args2[1], elemT, {});
|
|
5284
|
-
const
|
|
6130
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
5285
6131
|
const lb = lbsOf(arr)[0] ?? 1;
|
|
5286
|
-
for (let i = Math.max(
|
|
6132
|
+
for (let i = Math.max(start2 - lb, 0); i < arr.items.length; i++) {
|
|
5287
6133
|
const item = arr.items[i];
|
|
5288
6134
|
const eq = item === null ? target.v === null : target.v !== null && datumEquals(elemT, item, target.v, ctx);
|
|
5289
6135
|
if (eq) return tv("int4", i + lb);
|
|
@@ -5763,12 +6609,12 @@ function parseByFormat(input, fmt) {
|
|
|
5763
6609
|
};
|
|
5764
6610
|
const readNum = (maxLen) => {
|
|
5765
6611
|
skipInputSeparators();
|
|
5766
|
-
const
|
|
5767
|
-
while (i < input.length && /[0-9]/.test(input[i]) && i -
|
|
5768
|
-
if (i ===
|
|
6612
|
+
const start2 = i;
|
|
6613
|
+
while (i < input.length && /[0-9]/.test(input[i]) && i - start2 < maxLen) i++;
|
|
6614
|
+
if (i === start2) {
|
|
5769
6615
|
throw pgError("invalid_datetime", `invalid value for field in source string "${input}"`, "22007");
|
|
5770
6616
|
}
|
|
5771
|
-
return Number(input.slice(
|
|
6617
|
+
return Number(input.slice(start2, i));
|
|
5772
6618
|
};
|
|
5773
6619
|
const readMonthName = () => {
|
|
5774
6620
|
skipInputSeparators();
|
|
@@ -7603,15 +8449,15 @@ var decoderUtf8 = new TextDecoder("utf-8", { fatal: false });
|
|
|
7603
8449
|
function chars(s) {
|
|
7604
8450
|
return [...s];
|
|
7605
8451
|
}
|
|
7606
|
-
function substrImpl(s,
|
|
8452
|
+
function substrImpl(s, start2, len) {
|
|
7607
8453
|
const cs = chars(s);
|
|
7608
8454
|
if (len === void 0) {
|
|
7609
|
-
const from2 = Math.max(
|
|
8455
|
+
const from2 = Math.max(start2, 1);
|
|
7610
8456
|
return cs.slice(from2 - 1).join("");
|
|
7611
8457
|
}
|
|
7612
8458
|
if (len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
|
|
7613
|
-
const end =
|
|
7614
|
-
const from = Math.max(
|
|
8459
|
+
const end = start2 + len;
|
|
8460
|
+
const from = Math.max(start2, 1);
|
|
7615
8461
|
const to = Math.max(end, 1);
|
|
7616
8462
|
if (to <= from) return "";
|
|
7617
8463
|
return cs.slice(from - 1, to - 1).join("");
|
|
@@ -7675,14 +8521,14 @@ function getStringFunctions() {
|
|
|
7675
8521
|
const a = args2[0];
|
|
7676
8522
|
if (a.t === "bytea") {
|
|
7677
8523
|
const bytes = a.v;
|
|
7678
|
-
const
|
|
8524
|
+
const start3 = argInt(ctx, args2[1]);
|
|
7679
8525
|
const len2 = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
|
|
7680
|
-
return tv("bytea", byteaSubstr(bytes,
|
|
8526
|
+
return tv("bytea", byteaSubstr(bytes, start3, len2));
|
|
7681
8527
|
}
|
|
7682
8528
|
const s = argText(ctx, a);
|
|
7683
|
-
const
|
|
8529
|
+
const start2 = argInt(ctx, args2[1]);
|
|
7684
8530
|
const len = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
|
|
7685
|
-
return tv("text", substrImpl(s,
|
|
8531
|
+
return tv("text", substrImpl(s, start2, len));
|
|
7686
8532
|
})
|
|
7687
8533
|
);
|
|
7688
8534
|
m.set(
|
|
@@ -7978,11 +8824,11 @@ function getStringFunctions() {
|
|
|
7978
8824
|
strict("int4", (ctx, args2) => {
|
|
7979
8825
|
const s = argText(ctx, args2[0]);
|
|
7980
8826
|
const pattern = argText(ctx, args2[1]);
|
|
7981
|
-
const
|
|
8827
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
7982
8828
|
const flags = args2.length > 3 ? argText(ctx, args2[3]) : "";
|
|
7983
|
-
if (
|
|
8829
|
+
if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
|
|
7984
8830
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
7985
|
-
const sub = chars(s).slice(
|
|
8831
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
7986
8832
|
let count = 0;
|
|
7987
8833
|
for (const _ of sub.matchAll(js)) count++;
|
|
7988
8834
|
return tv("int4", count);
|
|
@@ -8003,11 +8849,11 @@ function getStringFunctions() {
|
|
|
8003
8849
|
strict("text", (ctx, args2) => {
|
|
8004
8850
|
const s = argText(ctx, args2[0]);
|
|
8005
8851
|
const pattern = argText(ctx, args2[1]);
|
|
8006
|
-
const
|
|
8852
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
8007
8853
|
const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
|
|
8008
8854
|
const flags = args2.length > 4 ? argText(ctx, args2[4]) : "";
|
|
8009
8855
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
8010
|
-
const sub = chars(s).slice(
|
|
8856
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
8011
8857
|
let i = 0;
|
|
8012
8858
|
for (const match of sub.matchAll(js)) {
|
|
8013
8859
|
i++;
|
|
@@ -8021,21 +8867,21 @@ function getStringFunctions() {
|
|
|
8021
8867
|
strict("int4", (ctx, args2) => {
|
|
8022
8868
|
const s = argText(ctx, args2[0]);
|
|
8023
8869
|
const pattern = argText(ctx, args2[1]);
|
|
8024
|
-
const
|
|
8870
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
8025
8871
|
const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
|
|
8026
8872
|
const endoption = args2.length > 4 ? argInt(ctx, args2[4]) : 0;
|
|
8027
8873
|
const flags = args2.length > 5 ? argText(ctx, args2[5]) : "";
|
|
8028
|
-
if (
|
|
8874
|
+
if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
|
|
8029
8875
|
if (endoption !== 0 && endoption !== 1) {
|
|
8030
8876
|
throw pgError("invalid_parameter_value", `invalid value for parameter "endoption": ${endoption}`);
|
|
8031
8877
|
}
|
|
8032
8878
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
8033
|
-
const sub = chars(s).slice(
|
|
8879
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
8034
8880
|
let i = 0;
|
|
8035
8881
|
for (const match of sub.matchAll(js)) {
|
|
8036
8882
|
i++;
|
|
8037
8883
|
if (i === nth) {
|
|
8038
|
-
const base =
|
|
8884
|
+
const base = start2 - 1 + chars(sub.slice(0, match.index)).length;
|
|
8039
8885
|
return tv("int4", endoption === 0 ? base + 1 : base + chars(match[0]).length + 1);
|
|
8040
8886
|
}
|
|
8041
8887
|
}
|
|
@@ -8149,10 +8995,10 @@ function getStringFunctions() {
|
|
|
8149
8995
|
);
|
|
8150
8996
|
return m;
|
|
8151
8997
|
}
|
|
8152
|
-
function byteaSubstr(bytes,
|
|
8998
|
+
function byteaSubstr(bytes, start2, len) {
|
|
8153
8999
|
if (len !== void 0 && len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
|
|
8154
|
-
const from = Math.max(
|
|
8155
|
-
const to = len === void 0 ? bytes.length : Math.max(
|
|
9000
|
+
const from = Math.max(start2, 1) - 1;
|
|
9001
|
+
const to = len === void 0 ? bytes.length : Math.max(start2 + len - 1, 0);
|
|
8156
9002
|
return bytes.slice(from, to);
|
|
8157
9003
|
}
|
|
8158
9004
|
function pad(s, len, fill, left) {
|
|
@@ -10714,7 +11560,7 @@ function evalOverlaps(ctx, scope, args2) {
|
|
|
10714
11560
|
};
|
|
10715
11561
|
const [s1r, e1r] = pair(args2[0]);
|
|
10716
11562
|
const [s2r, e2r] = pair(args2[1]);
|
|
10717
|
-
const resolveEnd = (
|
|
11563
|
+
const resolveEnd = (start2, end) => end.t === "interval" ? evalBinary(ctx, "+", start2, end) : end;
|
|
10718
11564
|
const e1 = resolveEnd(s1r, e1r);
|
|
10719
11565
|
const e2 = resolveEnd(s2r, e2r);
|
|
10720
11566
|
const target = [s1r, e1, s2r, e2].map((x) => x.t).find((t) => t !== "unknown") ?? "timestamp";
|
|
@@ -11973,7 +12819,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11973
12819
|
}
|
|
11974
12820
|
if (a.t === "timestamp" || a.t === "timestamptz" || b.t === "timestamp" || b.t === "timestamptz" || a.t === "date") {
|
|
11975
12821
|
const t2 = a.t === "timestamptz" || b.t === "timestamptz" ? "timestamptz" : "timestamp";
|
|
11976
|
-
const
|
|
12822
|
+
const start3 = castTo(ctx, a, t2, { explicit: true }).v;
|
|
11977
12823
|
const stop3 = castTo(ctx, b, t2, { explicit: true }).v;
|
|
11978
12824
|
const stepArg = args2[2];
|
|
11979
12825
|
if (stepArg === void 0 || !isInterval(stepArg.v)) {
|
|
@@ -11988,7 +12834,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11988
12834
|
if (step2.months !== 0) {
|
|
11989
12835
|
let i = 0;
|
|
11990
12836
|
for (; ; ) {
|
|
11991
|
-
const { days, tod } = splitTs(
|
|
12837
|
+
const { days, tod } = splitTs(start3);
|
|
11992
12838
|
const c = daysToCivil(days);
|
|
11993
12839
|
const totalMonths = c.y * 12 + (c.m - 1) + step2.months * i;
|
|
11994
12840
|
const y = Math.floor(totalMonths / 12);
|
|
@@ -12005,19 +12851,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
12005
12851
|
const stepMicros = intervalTotalMicros(step2);
|
|
12006
12852
|
if (stepMicros === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12007
12853
|
if (stepMicros > 0n) {
|
|
12008
|
-
for (let cur =
|
|
12854
|
+
for (let cur = start3; cur <= stop3; cur += stepMicros) rows2.push([cur]);
|
|
12009
12855
|
} else {
|
|
12010
|
-
for (let cur =
|
|
12856
|
+
for (let cur = start3; cur >= stop3; cur += stepMicros) rows2.push([cur]);
|
|
12011
12857
|
}
|
|
12012
12858
|
return { columns: [{ name: alias, type: t2 }], rows: rows2 };
|
|
12013
12859
|
}
|
|
12014
12860
|
if (a.t === "numeric" || b.t === "numeric" || args2[2]?.t === "numeric") {
|
|
12015
|
-
const
|
|
12861
|
+
const start3 = castTo(ctx, a, "numeric", { explicit: true }).v;
|
|
12016
12862
|
const stop3 = castTo(ctx, b, "numeric", { explicit: true }).v;
|
|
12017
12863
|
const step2 = args2[2] ? castTo(ctx, args2[2], "numeric", { explicit: true }).v : castTo(ctx, tv("int4", 1), "numeric", { explicit: true }).v;
|
|
12018
12864
|
if (numericSign(step2) === 0) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12019
12865
|
const rows2 = [];
|
|
12020
|
-
let cur =
|
|
12866
|
+
let cur = start3;
|
|
12021
12867
|
const ascending = numericSign(step2) > 0;
|
|
12022
12868
|
while (ascending ? numericCmp(cur, stop3) <= 0 : numericCmp(cur, stop3) >= 0) {
|
|
12023
12869
|
rows2.push([cur]);
|
|
@@ -12029,19 +12875,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
12029
12875
|
}
|
|
12030
12876
|
const useBig = a.t === "int8" || b.t === "int8" || args2[2]?.t === "int8";
|
|
12031
12877
|
const t = useBig ? "int8" : "int4";
|
|
12032
|
-
const
|
|
12878
|
+
const start2 = castTo(ctx, a, "int8", {}).v;
|
|
12033
12879
|
const stop2 = castTo(ctx, b, "int8", {}).v;
|
|
12034
12880
|
const step = args2[2] ? castTo(ctx, args2[2], "int8", {}).v : 1n;
|
|
12035
12881
|
if (step === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12036
12882
|
const rows = [];
|
|
12037
12883
|
if (step > 0n) {
|
|
12038
|
-
for (let cur =
|
|
12884
|
+
for (let cur = start2; cur <= stop2; cur += step) {
|
|
12039
12885
|
rows.push([useBig ? cur : Number(cur)]);
|
|
12040
12886
|
if (rows.length > 1e7)
|
|
12041
12887
|
throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
|
|
12042
12888
|
}
|
|
12043
12889
|
} else {
|
|
12044
|
-
for (let cur =
|
|
12890
|
+
for (let cur = start2; cur >= stop2; cur += step) {
|
|
12045
12891
|
rows.push([useBig ? cur : Number(cur)]);
|
|
12046
12892
|
if (rows.length > 1e7)
|
|
12047
12893
|
throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
|
|
@@ -12471,10 +13317,10 @@ function tokenize(sql) {
|
|
|
12471
13317
|
const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(sql.slice(i));
|
|
12472
13318
|
if (m) {
|
|
12473
13319
|
const open = m[0];
|
|
12474
|
-
const
|
|
12475
|
-
const end = sql.indexOf(open,
|
|
13320
|
+
const start2 = i + open.length;
|
|
13321
|
+
const end = sql.indexOf(open, start2);
|
|
12476
13322
|
if (end === -1) syntaxError("unterminated dollar-quoted string");
|
|
12477
|
-
push("string", sql.slice(
|
|
13323
|
+
push("string", sql.slice(start2, end), i);
|
|
12478
13324
|
i = end + open.length;
|
|
12479
13325
|
continue;
|
|
12480
13326
|
}
|
|
@@ -12541,14 +13387,14 @@ function tokenize(sql) {
|
|
|
12541
13387
|
continue;
|
|
12542
13388
|
}
|
|
12543
13389
|
if (/\d/.test(c) || c === "." && /\d/.test(sql[i + 1] ?? "")) {
|
|
12544
|
-
const
|
|
13390
|
+
const start2 = i;
|
|
12545
13391
|
const radix = /^0[xX][0-9a-fA-F_]+|^0[oO][0-7_]+|^0[bB][01_]+/.exec(sql.slice(i));
|
|
12546
13392
|
if (radix) {
|
|
12547
13393
|
const raw2 = radix[0];
|
|
12548
13394
|
i += raw2.length;
|
|
12549
13395
|
const clean = raw2.replaceAll("_", "");
|
|
12550
13396
|
const v = BigInt(clean);
|
|
12551
|
-
push("number", v.toString(),
|
|
13397
|
+
push("number", v.toString(), start2);
|
|
12552
13398
|
continue;
|
|
12553
13399
|
}
|
|
12554
13400
|
let j = i;
|
|
@@ -12570,7 +13416,7 @@ function tokenize(sql) {
|
|
|
12570
13416
|
if (j < n && IDENT_START.test(sql[j])) {
|
|
12571
13417
|
syntaxError(`trailing junk after numeric literal at or near "${sql.slice(i, j + 1)}"`);
|
|
12572
13418
|
}
|
|
12573
|
-
push("number", raw,
|
|
13419
|
+
push("number", raw, start2);
|
|
12574
13420
|
i = j;
|
|
12575
13421
|
continue;
|
|
12576
13422
|
}
|
|
@@ -12628,9 +13474,9 @@ function tokenize(sql) {
|
|
|
12628
13474
|
push("eof", "", i);
|
|
12629
13475
|
return tokens;
|
|
12630
13476
|
}
|
|
12631
|
-
function scanStandardString(sql,
|
|
13477
|
+
function scanStandardString(sql, start2) {
|
|
12632
13478
|
let out = "";
|
|
12633
|
-
let i =
|
|
13479
|
+
let i = start2 + 1;
|
|
12634
13480
|
const n = sql.length;
|
|
12635
13481
|
for (; ; ) {
|
|
12636
13482
|
if (i >= n) syntaxError("unterminated quoted string");
|
|
@@ -12658,9 +13504,9 @@ function scanStandardString(sql, start) {
|
|
|
12658
13504
|
i++;
|
|
12659
13505
|
}
|
|
12660
13506
|
}
|
|
12661
|
-
function scanEscapeString(sql,
|
|
13507
|
+
function scanEscapeString(sql, start2) {
|
|
12662
13508
|
let out = "";
|
|
12663
|
-
let i =
|
|
13509
|
+
let i = start2 + 1;
|
|
12664
13510
|
const n = sql.length;
|
|
12665
13511
|
for (; ; ) {
|
|
12666
13512
|
if (i >= n) syntaxError("unterminated quoted string");
|
|
@@ -16400,14 +17246,14 @@ var Parser = class {
|
|
|
16400
17246
|
let frame = null;
|
|
16401
17247
|
if (this.atKw("range") || this.atKw("rows") || this.atKw("groups")) {
|
|
16402
17248
|
const mode = this.next().value;
|
|
16403
|
-
let
|
|
17249
|
+
let start2;
|
|
16404
17250
|
let end = null;
|
|
16405
17251
|
if (this.eatKw("between")) {
|
|
16406
|
-
|
|
17252
|
+
start2 = this.parseFrameBound();
|
|
16407
17253
|
this.expectKw("and");
|
|
16408
17254
|
end = this.parseFrameBound();
|
|
16409
17255
|
} else {
|
|
16410
|
-
|
|
17256
|
+
start2 = this.parseFrameBound();
|
|
16411
17257
|
}
|
|
16412
17258
|
let exclusion = null;
|
|
16413
17259
|
if (this.eatKw("exclude")) {
|
|
@@ -16424,7 +17270,7 @@ var Parser = class {
|
|
|
16424
17270
|
exclusion = "no_others";
|
|
16425
17271
|
}
|
|
16426
17272
|
}
|
|
16427
|
-
frame = { mode, start, end, exclusion };
|
|
17273
|
+
frame = { mode, start: start2, end, exclusion };
|
|
16428
17274
|
}
|
|
16429
17275
|
return { name, partitionBy, orderBy, frame };
|
|
16430
17276
|
}
|
|
@@ -16670,7 +17516,7 @@ var PlParser = class {
|
|
|
16670
17516
|
return { kind: "assign", target, expr: this.parseExprUntilSemi() };
|
|
16671
17517
|
}
|
|
16672
17518
|
parseSqlTextUntilSemi() {
|
|
16673
|
-
const
|
|
17519
|
+
const start2 = this.peek().pos;
|
|
16674
17520
|
let depth = 0;
|
|
16675
17521
|
for (; ; ) {
|
|
16676
17522
|
const t = this.peek();
|
|
@@ -16678,7 +17524,7 @@ var PlParser = class {
|
|
|
16678
17524
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16679
17525
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16680
17526
|
if (t.type === "punct" && t.value === ";" && depth === 0) {
|
|
16681
|
-
const text = this.src.slice(
|
|
17527
|
+
const text = this.src.slice(start2, t.pos).trim();
|
|
16682
17528
|
this.pos++;
|
|
16683
17529
|
return text;
|
|
16684
17530
|
}
|
|
@@ -16686,7 +17532,7 @@ var PlParser = class {
|
|
|
16686
17532
|
}
|
|
16687
17533
|
}
|
|
16688
17534
|
parseExprUntilSemi() {
|
|
16689
|
-
const
|
|
17535
|
+
const start2 = this.peek().pos;
|
|
16690
17536
|
let depth = 0;
|
|
16691
17537
|
for (; ; ) {
|
|
16692
17538
|
const t = this.peek();
|
|
@@ -16694,7 +17540,7 @@ var PlParser = class {
|
|
|
16694
17540
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16695
17541
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16696
17542
|
if (t.type === "punct" && t.value === ";" && depth === 0) {
|
|
16697
|
-
const text = this.src.slice(
|
|
17543
|
+
const text = this.src.slice(start2, t.pos);
|
|
16698
17544
|
this.pos++;
|
|
16699
17545
|
return parseSqlExpr(text);
|
|
16700
17546
|
}
|
|
@@ -16702,7 +17548,7 @@ var PlParser = class {
|
|
|
16702
17548
|
}
|
|
16703
17549
|
}
|
|
16704
17550
|
parseExprUntilKw(kw) {
|
|
16705
|
-
const
|
|
17551
|
+
const start2 = this.peek().pos;
|
|
16706
17552
|
let depth = 0;
|
|
16707
17553
|
let caseDepth = 0;
|
|
16708
17554
|
for (; ; ) {
|
|
@@ -16713,7 +17559,7 @@ var PlParser = class {
|
|
|
16713
17559
|
if (t.type === "ident" && t.value === "case" && depth === 0) caseDepth++;
|
|
16714
17560
|
if (t.type === "ident" && t.value === "end" && depth === 0 && caseDepth > 0) caseDepth--;
|
|
16715
17561
|
if (t.type === "ident" && t.value === kw && depth === 0 && caseDepth === 0) {
|
|
16716
|
-
const text = this.src.slice(
|
|
17562
|
+
const text = this.src.slice(start2, t.pos);
|
|
16717
17563
|
this.pos++;
|
|
16718
17564
|
return parseSqlExpr(text);
|
|
16719
17565
|
}
|
|
@@ -16721,7 +17567,7 @@ var PlParser = class {
|
|
|
16721
17567
|
}
|
|
16722
17568
|
}
|
|
16723
17569
|
parseQueryUntilKw(kw) {
|
|
16724
|
-
const
|
|
17570
|
+
const start2 = this.peek().pos;
|
|
16725
17571
|
let depth = 0;
|
|
16726
17572
|
for (; ; ) {
|
|
16727
17573
|
const t = this.peek();
|
|
@@ -16729,7 +17575,7 @@ var PlParser = class {
|
|
|
16729
17575
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16730
17576
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16731
17577
|
if (t.type === "ident" && t.value === kw && depth === 0) {
|
|
16732
|
-
const text = this.src.slice(
|
|
17578
|
+
const text = this.src.slice(start2, t.pos).trim();
|
|
16733
17579
|
this.pos++;
|
|
16734
17580
|
const stmts = parse(text);
|
|
16735
17581
|
const q = stmts[0];
|
|
@@ -18272,7 +19118,7 @@ function buildFrom(env, items, where = null) {
|
|
|
18272
19118
|
}
|
|
18273
19119
|
}
|
|
18274
19120
|
if (cols === null) {
|
|
18275
|
-
const
|
|
19121
|
+
const probe2 = materializeItem(
|
|
18276
19122
|
env,
|
|
18277
19123
|
item,
|
|
18278
19124
|
new RowScope(
|
|
@@ -18282,8 +19128,8 @@ function buildFrom(env, items, where = null) {
|
|
|
18282
19128
|
accVars
|
|
18283
19129
|
)
|
|
18284
19130
|
);
|
|
18285
|
-
cols = [...accRel.columns, ...
|
|
18286
|
-
vars = /* @__PURE__ */ new Set([...accVars, ...
|
|
19131
|
+
cols = [...accRel.columns, ...probe2.rel.columns];
|
|
19132
|
+
vars = /* @__PURE__ */ new Set([...accVars, ...probe2.rangeVars]);
|
|
18287
19133
|
}
|
|
18288
19134
|
acc = { rel: { columns: cols, rows }, rangeVars: vars };
|
|
18289
19135
|
} else {
|
|
@@ -19043,13 +19889,13 @@ function expandTargets(core, columns) {
|
|
|
19043
19889
|
return items;
|
|
19044
19890
|
}
|
|
19045
19891
|
function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
|
|
19046
|
-
const
|
|
19892
|
+
const probe2 = new RowScope(
|
|
19047
19893
|
columns,
|
|
19048
19894
|
columns.map(() => null),
|
|
19049
19895
|
env.outer,
|
|
19050
19896
|
rangeVars
|
|
19051
19897
|
);
|
|
19052
|
-
checkBoolExprType(env.ctx, makeEvalScope(env,
|
|
19898
|
+
checkBoolExprType(env.ctx, makeEvalScope(env, probe2, extras), e, kind);
|
|
19053
19899
|
}
|
|
19054
19900
|
function executeCore(env0, core, orderBy, opts) {
|
|
19055
19901
|
const env = env0;
|
|
@@ -19139,8 +19985,8 @@ function executeCore(env0, core, orderBy, opts) {
|
|
|
19139
19985
|
const groupValues = groupDefs.map((d, di) => {
|
|
19140
19986
|
if (!activeSet[di]) {
|
|
19141
19987
|
if (rows.length > 0) {
|
|
19142
|
-
const
|
|
19143
|
-
return tv(
|
|
19988
|
+
const probe2 = evalScalar(env, repScope, d.expr);
|
|
19989
|
+
return tv(probe2.t, null);
|
|
19144
19990
|
}
|
|
19145
19991
|
return tv(UNKNOWN, null);
|
|
19146
19992
|
}
|
|
@@ -20293,8 +21139,8 @@ function executeTransaction(env, stmt) {
|
|
|
20293
21139
|
var defaultSettings = null;
|
|
20294
21140
|
function getDefaultSettings() {
|
|
20295
21141
|
if (!defaultSettings) {
|
|
20296
|
-
const
|
|
20297
|
-
defaultSettings = new Map(
|
|
21142
|
+
const probe2 = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
|
|
21143
|
+
defaultSettings = new Map(probe2.settings);
|
|
20298
21144
|
}
|
|
20299
21145
|
return defaultSettings;
|
|
20300
21146
|
}
|
|
@@ -20561,7 +21407,7 @@ function parseCsvLine(line, delim, quote) {
|
|
|
20561
21407
|
}
|
|
20562
21408
|
function splitCsvRecords(data, quote) {
|
|
20563
21409
|
const records = [];
|
|
20564
|
-
let
|
|
21410
|
+
let start2 = 0;
|
|
20565
21411
|
let quoted = false;
|
|
20566
21412
|
for (let i = 0; i < data.length; i++) {
|
|
20567
21413
|
const char = data[i];
|
|
@@ -20571,13 +21417,13 @@ function splitCsvRecords(data, quote) {
|
|
|
20571
21417
|
continue;
|
|
20572
21418
|
}
|
|
20573
21419
|
if (char === "\n" && !quoted) {
|
|
20574
|
-
const end = i >
|
|
20575
|
-
records.push(data.slice(
|
|
20576
|
-
|
|
21420
|
+
const end = i > start2 && data[i - 1] === "\r" ? i - 1 : i;
|
|
21421
|
+
records.push(data.slice(start2, end));
|
|
21422
|
+
start2 = i + 1;
|
|
20577
21423
|
}
|
|
20578
21424
|
}
|
|
20579
21425
|
if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
20580
|
-
if (
|
|
21426
|
+
if (start2 < data.length) records.push(data.slice(start2));
|
|
20581
21427
|
return records;
|
|
20582
21428
|
}
|
|
20583
21429
|
function executeCopyFromData(env, stmt, data) {
|
|
@@ -23735,6 +24581,16 @@ var Cluster = class {
|
|
|
23735
24581
|
databaseNames() {
|
|
23736
24582
|
return [...this.databases.keys()].sort();
|
|
23737
24583
|
}
|
|
24584
|
+
snapshotAll() {
|
|
24585
|
+
return new Map([...this.databases].map(([name, db]) => [name, db.snapshot()]));
|
|
24586
|
+
}
|
|
24587
|
+
restoreAll(points) {
|
|
24588
|
+
if (this.sessions.size > 0) throw new Error("cannot restore a catalog with active sessions");
|
|
24589
|
+
if (!points.has(this.defaultDatabaseName)) throw new Error("snapshot lacks default database");
|
|
24590
|
+
for (const db of this.databases.values()) db.close();
|
|
24591
|
+
this.databases.clear();
|
|
24592
|
+
for (const [name, point] of points) this.installDatabase(name, point.open());
|
|
24593
|
+
}
|
|
23738
24594
|
getDatabase(name) {
|
|
23739
24595
|
return this.databases.get(name);
|
|
23740
24596
|
}
|
|
@@ -23747,7 +24603,7 @@ var Cluster = class {
|
|
|
23747
24603
|
if (this.databases.has(name)) {
|
|
23748
24604
|
throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
|
|
23749
24605
|
}
|
|
23750
|
-
const db = template === void 0 ? new Database() : this.requireTemplate(template).snapshot().open();
|
|
24606
|
+
const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
|
|
23751
24607
|
this.installDatabase(name, db);
|
|
23752
24608
|
}
|
|
23753
24609
|
dropDatabase(name, requester) {
|
|
@@ -23811,8 +24667,8 @@ var Cluster = class {
|
|
|
23811
24667
|
if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
|
|
23812
24668
|
return Promise.reject(deadlock());
|
|
23813
24669
|
}
|
|
23814
|
-
return new Promise((
|
|
23815
|
-
const waiter = { session, resolve, reject };
|
|
24670
|
+
return new Promise((resolve2, reject) => {
|
|
24671
|
+
const waiter = { session, resolve: resolve2, reject };
|
|
23816
24672
|
session.waitingForTurn = waiter;
|
|
23817
24673
|
this.turnQueue.push(waiter);
|
|
23818
24674
|
});
|
|
@@ -23843,8 +24699,8 @@ var Cluster = class {
|
|
|
23843
24699
|
if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
|
|
23844
24700
|
return Promise.reject(deadlock());
|
|
23845
24701
|
}
|
|
23846
|
-
return new Promise((
|
|
23847
|
-
const waiter = { session, resolve, reject };
|
|
24702
|
+
return new Promise((resolve2, reject) => {
|
|
24703
|
+
const waiter = { session, resolve: resolve2, reject };
|
|
23848
24704
|
session.waitingForLock = key;
|
|
23849
24705
|
const queue = this.lockQueues.get(key) ?? [];
|
|
23850
24706
|
queue.push(waiter);
|
|
@@ -24103,9 +24959,9 @@ var backend = {
|
|
|
24103
24959
|
no: () => new Uint8Array([78])
|
|
24104
24960
|
};
|
|
24105
24961
|
var ByteReader = class {
|
|
24106
|
-
constructor(bytes,
|
|
24962
|
+
constructor(bytes, start2 = 0) {
|
|
24107
24963
|
this.bytes = bytes;
|
|
24108
|
-
this.pos =
|
|
24964
|
+
this.pos = start2;
|
|
24109
24965
|
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
24110
24966
|
}
|
|
24111
24967
|
bytes;
|
|
@@ -24265,11 +25121,11 @@ var ScramServer = class {
|
|
|
24265
25121
|
// src/wire/split.ts
|
|
24266
25122
|
var splitStatements = (script) => {
|
|
24267
25123
|
const out = [];
|
|
24268
|
-
let
|
|
25124
|
+
let start2 = 0;
|
|
24269
25125
|
let i = 0;
|
|
24270
25126
|
const n = script.length;
|
|
24271
25127
|
const push = (end) => {
|
|
24272
|
-
const text = script.slice(
|
|
25128
|
+
const text = script.slice(start2, end).trim();
|
|
24273
25129
|
if (text.length > 0) out.push(text);
|
|
24274
25130
|
};
|
|
24275
25131
|
while (i < n) {
|
|
@@ -24325,7 +25181,7 @@ var splitStatements = (script) => {
|
|
|
24325
25181
|
}
|
|
24326
25182
|
if (c === ";") {
|
|
24327
25183
|
push(i);
|
|
24328
|
-
|
|
25184
|
+
start2 = i + 1;
|
|
24329
25185
|
}
|
|
24330
25186
|
i++;
|
|
24331
25187
|
}
|
|
@@ -24786,8 +25642,8 @@ var Connection = class {
|
|
|
24786
25642
|
"25P02"
|
|
24787
25643
|
);
|
|
24788
25644
|
}
|
|
24789
|
-
const
|
|
24790
|
-
if (
|
|
25645
|
+
const listen2 = LISTEN.exec(text);
|
|
25646
|
+
if (listen2) return this.listen(listen2[2]);
|
|
24791
25647
|
const unlisten = UNLISTEN.exec(text);
|
|
24792
25648
|
if (unlisten) return this.unlisten(unlisten[1].replaceAll('"', ""));
|
|
24793
25649
|
const notify = NOTIFY.exec(text);
|
|
@@ -24804,7 +25660,7 @@ var Connection = class {
|
|
|
24804
25660
|
if (faults.delayStatementMs !== void 0) {
|
|
24805
25661
|
const ms = faults.delayStatementMs;
|
|
24806
25662
|
faults.delayStatementMs = void 0;
|
|
24807
|
-
await new Promise((
|
|
25663
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
24808
25664
|
}
|
|
24809
25665
|
if (faults.dropConnection && this.inTransaction) {
|
|
24810
25666
|
faults.dropConnection = void 0;
|
|
@@ -24828,7 +25684,7 @@ var Connection = class {
|
|
|
24828
25684
|
}
|
|
24829
25685
|
}
|
|
24830
25686
|
waitForLock(key) {
|
|
24831
|
-
return new Promise((
|
|
25687
|
+
return new Promise((resolve2, reject) => {
|
|
24832
25688
|
this.lockWaiter = (error) => {
|
|
24833
25689
|
this.lockWaiter = null;
|
|
24834
25690
|
this.waitingForLock = null;
|
|
@@ -24837,7 +25693,7 @@ var Connection = class {
|
|
|
24837
25693
|
this.cluster.waitLock(this, key).then(
|
|
24838
25694
|
() => {
|
|
24839
25695
|
this.lockWaiter = null;
|
|
24840
|
-
|
|
25696
|
+
resolve2();
|
|
24841
25697
|
},
|
|
24842
25698
|
(error) => {
|
|
24843
25699
|
this.lockWaiter = null;
|
|
@@ -25029,10 +25885,10 @@ var Connection = class {
|
|
|
25029
25885
|
this.cluster.dropDatabase(name, this);
|
|
25030
25886
|
return command("DROP DATABASE");
|
|
25031
25887
|
}
|
|
25032
|
-
const
|
|
25033
|
-
if (
|
|
25034
|
-
const from = identifier(
|
|
25035
|
-
const to = identifier(
|
|
25888
|
+
const rename2 = RENAME_DATABASE.exec(text);
|
|
25889
|
+
if (rename2) {
|
|
25890
|
+
const from = identifier(rename2[1], rename2[2]);
|
|
25891
|
+
const to = identifier(rename2[3], rename2[4]);
|
|
25036
25892
|
this.cluster.renameDatabase(from, to, this);
|
|
25037
25893
|
return command("ALTER DATABASE");
|
|
25038
25894
|
}
|
|
@@ -25455,7 +26311,7 @@ var normalize = (input) => {
|
|
|
25455
26311
|
};
|
|
25456
26312
|
};
|
|
25457
26313
|
var isSnapshot = (value) => typeof value === "object" && value !== null && "open" in value && typeof value.open === "function";
|
|
25458
|
-
var
|
|
26314
|
+
var serve2 = (input = {}) => {
|
|
25459
26315
|
const options2 = normalize(input);
|
|
25460
26316
|
const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
|
|
25461
26317
|
const initialName = options2.databaseName ?? "postgres";
|
|
@@ -25464,20 +26320,20 @@ var serve = (input = {}) => {
|
|
|
25464
26320
|
const connections = /* @__PURE__ */ new Set();
|
|
25465
26321
|
const host = options2.host ?? "127.0.0.1";
|
|
25466
26322
|
const user = options2.user || "postgres";
|
|
25467
|
-
const server2 =
|
|
26323
|
+
const server2 = createServer2((socket) => {
|
|
25468
26324
|
socket.setNoDelay(true);
|
|
25469
26325
|
const connection = new Connection(socket, cluster, {
|
|
25470
26326
|
...options2.password !== void 0 ? { password: options2.password } : {},
|
|
25471
26327
|
serverVersion: options2.serverVersion ?? "18.3",
|
|
25472
26328
|
parameters: options2.parameters ?? {},
|
|
25473
26329
|
faults,
|
|
25474
|
-
...options2.databaseName !== void 0 ? { database: options2.databaseName } : {},
|
|
26330
|
+
...options2.databaseName !== void 0 && !options2.allowDatabaseSelection ? { database: options2.databaseName } : {},
|
|
25475
26331
|
...options2.onLog ? { onLog: options2.onLog } : {}
|
|
25476
26332
|
});
|
|
25477
26333
|
connections.add(connection);
|
|
25478
26334
|
socket.on("close", () => connections.delete(connection));
|
|
25479
26335
|
});
|
|
25480
|
-
return new Promise((
|
|
26336
|
+
return new Promise((resolve2, reject) => {
|
|
25481
26337
|
server2.once("error", reject);
|
|
25482
26338
|
server2.listen(options2.port ?? 0, host, () => {
|
|
25483
26339
|
server2.removeListener("error", reject);
|
|
@@ -25485,11 +26341,13 @@ var serve = (input = {}) => {
|
|
|
25485
26341
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
25486
26342
|
const name = initialName;
|
|
25487
26343
|
const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
|
|
25488
|
-
|
|
26344
|
+
resolve2({
|
|
25489
26345
|
port,
|
|
25490
26346
|
host,
|
|
25491
26347
|
connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
|
|
25492
|
-
database
|
|
26348
|
+
get database() {
|
|
26349
|
+
return cluster.db;
|
|
26350
|
+
},
|
|
25493
26351
|
databaseNames() {
|
|
25494
26352
|
return cluster.databaseNames();
|
|
25495
26353
|
},
|
|
@@ -25503,6 +26361,14 @@ var serve = (input = {}) => {
|
|
|
25503
26361
|
fault(next) {
|
|
25504
26362
|
Object.assign(faults, next);
|
|
25505
26363
|
},
|
|
26364
|
+
clearFaults() {
|
|
26365
|
+
for (const key of Object.keys(faults)) delete faults[key];
|
|
26366
|
+
},
|
|
26367
|
+
faultState() {
|
|
26368
|
+
return structuredClone(faults);
|
|
26369
|
+
},
|
|
26370
|
+
snapshotAll: () => cluster.snapshotAll(),
|
|
26371
|
+
restoreAll: (points) => cluster.restoreAll(points),
|
|
25506
26372
|
snapshot(databaseName = initialName) {
|
|
25507
26373
|
return cluster.requireDatabase(databaseName).snapshot();
|
|
25508
26374
|
},
|
|
@@ -25518,9 +26384,251 @@ var serve = (input = {}) => {
|
|
|
25518
26384
|
});
|
|
25519
26385
|
};
|
|
25520
26386
|
|
|
26387
|
+
// src/wire/probe.ts
|
|
26388
|
+
import { createHash as createHash2, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
|
|
26389
|
+
import { createConnection } from "node:net";
|
|
26390
|
+
function probe(connectionString) {
|
|
26391
|
+
const url = new URL(connectionString);
|
|
26392
|
+
return new Promise((resolve2) => {
|
|
26393
|
+
const socket = createConnection({ host: url.hostname, port: Number(url.port) });
|
|
26394
|
+
let buffer = Buffer.alloc(0);
|
|
26395
|
+
let result = false;
|
|
26396
|
+
let querying = false;
|
|
26397
|
+
let row = false;
|
|
26398
|
+
const nonce = randomBytes2(18).toString("base64");
|
|
26399
|
+
const first = `n=,r=${nonce}`;
|
|
26400
|
+
const finish = (ok) => {
|
|
26401
|
+
result = ok;
|
|
26402
|
+
socket.destroy();
|
|
26403
|
+
};
|
|
26404
|
+
const timeout = setTimeout(() => finish(false), 2e3);
|
|
26405
|
+
const send = (type, body) => {
|
|
26406
|
+
const header = Buffer.alloc(5);
|
|
26407
|
+
header.write(type);
|
|
26408
|
+
header.writeInt32BE(body.length + 4, 1);
|
|
26409
|
+
socket.write(Buffer.concat([header, body]));
|
|
26410
|
+
};
|
|
26411
|
+
socket.once("close", () => {
|
|
26412
|
+
clearTimeout(timeout);
|
|
26413
|
+
resolve2(result);
|
|
26414
|
+
});
|
|
26415
|
+
socket.on("error", () => finish(false));
|
|
26416
|
+
socket.once("connect", () => {
|
|
26417
|
+
const parameters = Buffer.from(
|
|
26418
|
+
`user\0${decodeURIComponent(url.username)}\0database\0${decodeURIComponent(url.pathname.slice(1))}\0\0`
|
|
26419
|
+
);
|
|
26420
|
+
const header = Buffer.alloc(8);
|
|
26421
|
+
header.writeInt32BE(parameters.length + 8);
|
|
26422
|
+
header.writeInt32BE(196608, 4);
|
|
26423
|
+
socket.write(Buffer.concat([header, parameters]));
|
|
26424
|
+
});
|
|
26425
|
+
socket.on("data", (data) => {
|
|
26426
|
+
buffer = Buffer.concat([buffer, data]);
|
|
26427
|
+
while (buffer.length >= 5) {
|
|
26428
|
+
const length = buffer.readInt32BE(1);
|
|
26429
|
+
if (length < 4 || length > 1e6) {
|
|
26430
|
+
finish(false);
|
|
26431
|
+
return;
|
|
26432
|
+
}
|
|
26433
|
+
if (buffer.length < length + 1) return;
|
|
26434
|
+
const type = String.fromCharCode(buffer[0]);
|
|
26435
|
+
const body = buffer.subarray(5, length + 1);
|
|
26436
|
+
buffer = buffer.subarray(length + 1);
|
|
26437
|
+
if (type === "E") {
|
|
26438
|
+
finish(false);
|
|
26439
|
+
return;
|
|
26440
|
+
}
|
|
26441
|
+
if (type === "R") {
|
|
26442
|
+
const auth = body.readInt32BE();
|
|
26443
|
+
if (auth === 10) {
|
|
26444
|
+
const initial = Buffer.from(`n,,${first}`);
|
|
26445
|
+
const size = Buffer.alloc(4);
|
|
26446
|
+
size.writeInt32BE(initial.length);
|
|
26447
|
+
send("p", Buffer.concat([Buffer.from("SCRAM-SHA-256\0"), size, initial]));
|
|
26448
|
+
} else if (auth === 11) {
|
|
26449
|
+
const serverFirst = body.subarray(4).toString();
|
|
26450
|
+
const fields = Object.fromEntries(serverFirst.split(",").map((field) => [field[0], field.slice(2)]));
|
|
26451
|
+
if (!fields.r?.startsWith(nonce) || !fields.s || fields.i !== "4096") {
|
|
26452
|
+
finish(false);
|
|
26453
|
+
return;
|
|
26454
|
+
}
|
|
26455
|
+
const salted = pbkdf2Sync2(
|
|
26456
|
+
decodeURIComponent(url.password),
|
|
26457
|
+
Buffer.from(fields.s, "base64"),
|
|
26458
|
+
4096,
|
|
26459
|
+
32,
|
|
26460
|
+
"sha256"
|
|
26461
|
+
);
|
|
26462
|
+
const key = createHmac2("sha256", salted).update("Client Key").digest();
|
|
26463
|
+
const stored = createHash2("sha256").update(key).digest();
|
|
26464
|
+
const final = `c=biws,r=${fields.r}`;
|
|
26465
|
+
const signature = createHmac2("sha256", stored).update(`${first},${serverFirst},${final}`).digest();
|
|
26466
|
+
const proof = Buffer.from(key.map((value, index) => value ^ signature[index]));
|
|
26467
|
+
send("p", Buffer.from(`${final},p=${proof.toString("base64")}`));
|
|
26468
|
+
} else if (auth !== 0 && auth !== 12) {
|
|
26469
|
+
finish(false);
|
|
26470
|
+
return;
|
|
26471
|
+
}
|
|
26472
|
+
}
|
|
26473
|
+
if (type === "D") row = body.readInt16BE() === 1 && body.subarray(6).toString() === "1";
|
|
26474
|
+
if (type === "Z") {
|
|
26475
|
+
if (querying) {
|
|
26476
|
+
finish(row);
|
|
26477
|
+
return;
|
|
26478
|
+
}
|
|
26479
|
+
querying = true;
|
|
26480
|
+
send("Q", Buffer.from("SELECT 1\0"));
|
|
26481
|
+
}
|
|
26482
|
+
}
|
|
26483
|
+
});
|
|
26484
|
+
});
|
|
26485
|
+
}
|
|
26486
|
+
|
|
26487
|
+
// src/wire/fleet.ts
|
|
26488
|
+
async function startProtocol(entry) {
|
|
26489
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
26490
|
+
const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
|
|
26491
|
+
try {
|
|
26492
|
+
for (const [name, databaseName] of Object.entries(selectors)) {
|
|
26493
|
+
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
|
|
26494
|
+
throw new Error("invalid Postgres namespace/database name");
|
|
26495
|
+
const clock = createClock();
|
|
26496
|
+
const database = new Database({
|
|
26497
|
+
now: () => new Date(clock.now()),
|
|
26498
|
+
...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
|
|
26499
|
+
});
|
|
26500
|
+
const server2 = await serve2({
|
|
26501
|
+
database,
|
|
26502
|
+
databaseName,
|
|
26503
|
+
allowDatabaseSelection: true,
|
|
26504
|
+
host: entry.host ?? "127.0.0.1",
|
|
26505
|
+
port: name === "default" ? entry.port ?? 0 : 0,
|
|
26506
|
+
...entry.password !== void 0 ? { password: entry.password } : {},
|
|
26507
|
+
...entry.user !== void 0 ? { user: entry.user } : {}
|
|
26508
|
+
});
|
|
26509
|
+
namespaces.set(name, { server: server2, clock });
|
|
26510
|
+
}
|
|
26511
|
+
} catch (error) {
|
|
26512
|
+
await Promise.allSettled([...namespaces.values()].map(({ server: server2 }) => server2.close()));
|
|
26513
|
+
throw error;
|
|
26514
|
+
}
|
|
26515
|
+
const get = (name) => {
|
|
26516
|
+
const found = namespaces.get(name);
|
|
26517
|
+
if (!found) throw new Error(`Postgres namespace ${name} is not configured`);
|
|
26518
|
+
return found;
|
|
26519
|
+
};
|
|
26520
|
+
const publicUrl2 = (name) => {
|
|
26521
|
+
const url = new URL(get(name).server.connectionString);
|
|
26522
|
+
url.password = "";
|
|
26523
|
+
return url.href;
|
|
26524
|
+
};
|
|
26525
|
+
const locked = /* @__PURE__ */ new Set();
|
|
26526
|
+
const snapshots = /* @__PURE__ */ new WeakMap();
|
|
26527
|
+
const origins = new Map([...namespaces].map(([name, { server: server2 }]) => [name, server2.snapshotAll()]));
|
|
26528
|
+
return {
|
|
26529
|
+
protocol: "postgres",
|
|
26530
|
+
url: publicUrl2("default"),
|
|
26531
|
+
connection: get("default").server.connectionString,
|
|
26532
|
+
connections: Object.fromEntries([...namespaces].map(([name, { server: server2 }]) => [name, server2.connectionString])),
|
|
26533
|
+
namespaces: {
|
|
26534
|
+
mechanism: "endpoint",
|
|
26535
|
+
endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl2(name)]))
|
|
26536
|
+
},
|
|
26537
|
+
async ready() {
|
|
26538
|
+
const results = await Promise.all(
|
|
26539
|
+
[...namespaces.values()].map(
|
|
26540
|
+
({ server: server2 }) => server2.server.listening ? probe(server2.connectionString) : false
|
|
26541
|
+
)
|
|
26542
|
+
);
|
|
26543
|
+
return results.every(Boolean);
|
|
26544
|
+
},
|
|
26545
|
+
async close() {
|
|
26546
|
+
await Promise.all(
|
|
26547
|
+
[...namespaces.values()].map(async ({ server: server2 }) => {
|
|
26548
|
+
await server2.close();
|
|
26549
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).close();
|
|
26550
|
+
})
|
|
26551
|
+
);
|
|
26552
|
+
},
|
|
26553
|
+
lock(namespace) {
|
|
26554
|
+
const { server: server2 } = get(namespace);
|
|
26555
|
+
if (locked.has(namespace)) throw new Error("Postgres namespace already locked");
|
|
26556
|
+
locked.add(namespace);
|
|
26557
|
+
const reject = (socket) => socket.destroy();
|
|
26558
|
+
server2.server.prependListener("connection", reject);
|
|
26559
|
+
return () => {
|
|
26560
|
+
locked.delete(namespace);
|
|
26561
|
+
server2.server.off("connection", reject);
|
|
26562
|
+
};
|
|
26563
|
+
},
|
|
26564
|
+
diagnostics(namespace) {
|
|
26565
|
+
const { server: server2 } = get(namespace);
|
|
26566
|
+
return {
|
|
26567
|
+
activeRequests: server2.connections,
|
|
26568
|
+
activeConnections: server2.connections,
|
|
26569
|
+
blockedCommands: 0,
|
|
26570
|
+
pendingJobs: 0,
|
|
26571
|
+
pendingWebhooks: 0,
|
|
26572
|
+
unmatchedRequests: 0
|
|
26573
|
+
};
|
|
26574
|
+
},
|
|
26575
|
+
async checkpoint(namespace) {
|
|
26576
|
+
const { server: server2, clock } = get(namespace);
|
|
26577
|
+
const handle = {};
|
|
26578
|
+
snapshots.set(handle, {
|
|
26579
|
+
namespace,
|
|
26580
|
+
databases: server2.snapshotAll(),
|
|
26581
|
+
clock: clock.state(),
|
|
26582
|
+
faults: server2.faultState()
|
|
26583
|
+
});
|
|
26584
|
+
return handle;
|
|
26585
|
+
},
|
|
26586
|
+
async restore(namespace, handle) {
|
|
26587
|
+
const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
|
|
26588
|
+
if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
|
|
26589
|
+
const { server: server2, clock } = get(namespace);
|
|
26590
|
+
server2.restoreAll(point.databases);
|
|
26591
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
|
|
26592
|
+
server2.clearFaults();
|
|
26593
|
+
server2.fault(point.faults);
|
|
26594
|
+
clock.freeze();
|
|
26595
|
+
clock.set(point.clock.now);
|
|
26596
|
+
if (!point.clock.frozen) clock.unfreeze();
|
|
26597
|
+
},
|
|
26598
|
+
async reset(namespace) {
|
|
26599
|
+
const { server: server2, clock } = get(namespace);
|
|
26600
|
+
server2.restoreAll(origins.get(namespace));
|
|
26601
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
|
|
26602
|
+
server2.clearFaults();
|
|
26603
|
+
clock.reset();
|
|
26604
|
+
},
|
|
26605
|
+
clock: (namespace) => get(namespace).clock
|
|
26606
|
+
};
|
|
26607
|
+
}
|
|
26608
|
+
|
|
26609
|
+
// src/server.ts
|
|
26610
|
+
var serveTarget = {
|
|
26611
|
+
name: "postgres",
|
|
26612
|
+
protocol: "postgres",
|
|
26613
|
+
defaultPort: 0,
|
|
26614
|
+
start: startProtocol
|
|
26615
|
+
};
|
|
26616
|
+
|
|
25521
26617
|
// src/wire/cli.ts
|
|
25522
26618
|
var usage = `mockingbird-postgres serve [postgres://URI] [--port <n>] [--host <h>] [--password <p>] [--server-version <v>] [--log]`;
|
|
25523
26619
|
var args = process.argv.slice(2);
|
|
26620
|
+
if (args[0] === "serve" && args.includes("--config")) {
|
|
26621
|
+
process.exit(
|
|
26622
|
+
await runCli(
|
|
26623
|
+
{
|
|
26624
|
+
bin: "mockingbird-postgres",
|
|
26625
|
+
description: "Postgres and fleet server",
|
|
26626
|
+
commands: { serve: serveCommand(serveTarget) }
|
|
26627
|
+
},
|
|
26628
|
+
args
|
|
26629
|
+
)
|
|
26630
|
+
);
|
|
26631
|
+
}
|
|
25524
26632
|
if (args[0] !== "serve") {
|
|
25525
26633
|
console.error(usage);
|
|
25526
26634
|
process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 2);
|
|
@@ -25569,7 +26677,7 @@ if (log) {
|
|
|
25569
26677
|
`[pid ${entry.pid}] ${entry.status} ${entry.durationMs.toFixed(1)}ms ${entry.sql.replace(/\s+/g, " ").slice(0, 200)}`
|
|
25570
26678
|
);
|
|
25571
26679
|
}
|
|
25572
|
-
var server = await
|
|
26680
|
+
var server = await serve2(options);
|
|
25573
26681
|
console.log(`mockingbird-postgres listening on ${server.connectionString}`);
|
|
25574
26682
|
var stop = async () => {
|
|
25575
26683
|
await server.close();
|