@crvouga/mockingbird-service-postgres 1.6.0 → 1.7.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 +17 -0
- package/COMPATIBILITY.md +4 -0
- package/README.md +7 -0
- package/dist/admin.js +64 -13
- package/dist/admin.js.map +2 -2
- 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 +1190 -107
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/cluster.d.ts +3 -0
- package/dist/wire/fleet.d.ts +80 -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 +13 -2
package/dist/wire/cli.js
CHANGED
|
@@ -1,7 +1,70 @@
|
|
|
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
|
+
// ../../adapters/node/dist/fleet.js
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
|
|
14
|
+
// ../core/dist/clock.js
|
|
15
|
+
var createClock = (source = Date.now) => {
|
|
16
|
+
let offsetMs = 0;
|
|
17
|
+
let frozenAt;
|
|
18
|
+
const now = () => frozenAt ?? source() + offsetMs;
|
|
19
|
+
return {
|
|
20
|
+
now,
|
|
21
|
+
set: (epochMs) => {
|
|
22
|
+
if (frozenAt !== void 0)
|
|
23
|
+
frozenAt = epochMs;
|
|
24
|
+
else
|
|
25
|
+
offsetMs = epochMs - source();
|
|
26
|
+
},
|
|
27
|
+
advance: (deltaMs) => {
|
|
28
|
+
if (frozenAt !== void 0)
|
|
29
|
+
frozenAt += deltaMs;
|
|
30
|
+
else
|
|
31
|
+
offsetMs += deltaMs;
|
|
32
|
+
},
|
|
33
|
+
freeze: () => {
|
|
34
|
+
frozenAt = now();
|
|
35
|
+
},
|
|
36
|
+
unfreeze: () => {
|
|
37
|
+
if (frozenAt === void 0)
|
|
38
|
+
return;
|
|
39
|
+
offsetMs = frozenAt - source();
|
|
40
|
+
frozenAt = void 0;
|
|
41
|
+
},
|
|
42
|
+
reset: () => {
|
|
43
|
+
offsetMs = 0;
|
|
44
|
+
frozenAt = void 0;
|
|
45
|
+
},
|
|
46
|
+
state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ../core/dist/control.js
|
|
51
|
+
var UNITS = {
|
|
52
|
+
ms: 1,
|
|
53
|
+
s: 1e3,
|
|
54
|
+
m: 6e4,
|
|
55
|
+
h: 36e5,
|
|
56
|
+
d: 864e5
|
|
57
|
+
};
|
|
58
|
+
var parseDuration = (value) => {
|
|
59
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
60
|
+
return value;
|
|
61
|
+
if (typeof value !== "string")
|
|
62
|
+
return void 0;
|
|
63
|
+
const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
|
|
64
|
+
if (!match)
|
|
65
|
+
return void 0;
|
|
66
|
+
return Number(match[1]) * UNITS[match[2]];
|
|
67
|
+
};
|
|
5
68
|
|
|
6
69
|
// ../../core/dist/timeline.js
|
|
7
70
|
var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
@@ -164,6 +227,764 @@ var Timeline = class {
|
|
|
164
227
|
}
|
|
165
228
|
};
|
|
166
229
|
|
|
230
|
+
// ../../adapters/node/dist/serve.js
|
|
231
|
+
import { createServer } from "node:http";
|
|
232
|
+
var serve = async (api, options2 = {}) => {
|
|
233
|
+
const server2 = createServer(async (req, res) => {
|
|
234
|
+
const chunks = [];
|
|
235
|
+
for await (const chunk of req) {
|
|
236
|
+
chunks.push(Buffer.from(chunk));
|
|
237
|
+
}
|
|
238
|
+
const body = Buffer.concat(chunks);
|
|
239
|
+
const address = server2.address();
|
|
240
|
+
const port = typeof address === "object" && address !== null ? address.port : void 0;
|
|
241
|
+
const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
|
|
242
|
+
const raw = req.url ?? "/";
|
|
243
|
+
const url = new URL(raw.replace(/^\/+/, "/"), base);
|
|
244
|
+
const method = req.method ?? "GET";
|
|
245
|
+
const init = { method, headers: req.headers };
|
|
246
|
+
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
|
247
|
+
init.body = body;
|
|
248
|
+
}
|
|
249
|
+
const aborted = new AbortController();
|
|
250
|
+
res.once("close", () => {
|
|
251
|
+
if (!res.writableFinished)
|
|
252
|
+
aborted.abort();
|
|
253
|
+
});
|
|
254
|
+
init.signal = aborted.signal;
|
|
255
|
+
const request = new Request(url, init);
|
|
256
|
+
let response;
|
|
257
|
+
try {
|
|
258
|
+
response = await api.fetch(request);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error.code === "MOCKINGBIRD_DROP") {
|
|
261
|
+
req.socket.destroy();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
265
|
+
res.end(JSON.stringify({
|
|
266
|
+
error: {
|
|
267
|
+
type: "mockingbird_internal",
|
|
268
|
+
message: error instanceof Error ? error.message : String(error)
|
|
269
|
+
}
|
|
270
|
+
}));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const headers = Object.fromEntries(response.headers);
|
|
274
|
+
const cookies = response.headers.getSetCookie();
|
|
275
|
+
if (cookies.length > 0)
|
|
276
|
+
headers["set-cookie"] = cookies;
|
|
277
|
+
if (!response.body) {
|
|
278
|
+
res.writeHead(response.status, headers);
|
|
279
|
+
res.end();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
res.writeHead(response.status, headers);
|
|
283
|
+
res.flushHeaders();
|
|
284
|
+
const reader = response.body.getReader();
|
|
285
|
+
try {
|
|
286
|
+
for (; ; ) {
|
|
287
|
+
const { done, value } = await reader.read();
|
|
288
|
+
if (done)
|
|
289
|
+
break;
|
|
290
|
+
if (!res.write(value))
|
|
291
|
+
await new Promise((resolve2) => res.once("drain", resolve2));
|
|
292
|
+
}
|
|
293
|
+
res.end();
|
|
294
|
+
} catch {
|
|
295
|
+
res.destroy();
|
|
296
|
+
} finally {
|
|
297
|
+
reader.releaseLock();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
await new Promise((resolve2, reject) => {
|
|
301
|
+
server2.once("error", reject);
|
|
302
|
+
server2.listen(options2.port ?? 0, options2.host, resolve2);
|
|
303
|
+
});
|
|
304
|
+
return server2;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// ../../adapters/node/dist/listen.js
|
|
308
|
+
var listen = async (api, options2 = {}) => {
|
|
309
|
+
const host = options2.host ?? "127.0.0.1";
|
|
310
|
+
const server2 = await serve(api, { port: options2.port ?? 0, host });
|
|
311
|
+
const address = server2.address();
|
|
312
|
+
const port = typeof address === "object" && address !== null ? address.port : options2.port ?? 0;
|
|
313
|
+
const shown = host.includes(":") ? `[${host}]` : host;
|
|
314
|
+
return {
|
|
315
|
+
url: `http://${shown}:${port}`,
|
|
316
|
+
port,
|
|
317
|
+
host,
|
|
318
|
+
server: server2,
|
|
319
|
+
close: () => new Promise((resolve2, reject) => {
|
|
320
|
+
server2.close((error) => {
|
|
321
|
+
if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
|
|
322
|
+
reject(error);
|
|
323
|
+
else
|
|
324
|
+
resolve2();
|
|
325
|
+
});
|
|
326
|
+
server2.closeAllConnections?.();
|
|
327
|
+
})
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// ../../adapters/node/dist/fleet.js
|
|
332
|
+
var EMPTY = {
|
|
333
|
+
activeRequests: 0,
|
|
334
|
+
activeConnections: 0,
|
|
335
|
+
blockedCommands: 0,
|
|
336
|
+
pendingJobs: 0,
|
|
337
|
+
pendingWebhooks: 0,
|
|
338
|
+
unmatchedRequests: 0
|
|
339
|
+
};
|
|
340
|
+
var NS = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
341
|
+
var json = (status, value) => Response.json(value, { status });
|
|
342
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
343
|
+
var pending = (d) => d.activeRequests + d.blockedCommands + d.pendingJobs + d.pendingWebhooks;
|
|
344
|
+
var publicUrl = (raw) => {
|
|
345
|
+
const url = new URL(raw);
|
|
346
|
+
url.password = "";
|
|
347
|
+
return url.href;
|
|
348
|
+
};
|
|
349
|
+
var atomicWrite = async (path, value) => {
|
|
350
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
351
|
+
try {
|
|
352
|
+
await writeFile(temp, `${JSON.stringify(value, null, 2)}
|
|
353
|
+
`, { mode: 384, flag: "wx" });
|
|
354
|
+
await chmod(temp, 384);
|
|
355
|
+
await rename(temp, path);
|
|
356
|
+
} finally {
|
|
357
|
+
await rm(temp, { force: true });
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var removeOwned = async (path, id) => {
|
|
361
|
+
if (!path)
|
|
362
|
+
return;
|
|
363
|
+
try {
|
|
364
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
365
|
+
if (isObject(value) && value.id === id)
|
|
366
|
+
await rm(path, { force: true });
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error.code !== "ENOENT")
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
var httpChild = async (target, entry, options2) => {
|
|
373
|
+
const runtime = await target.create(entry.options ?? {}, {
|
|
374
|
+
adminKey: entry.adminKey,
|
|
375
|
+
seed: entry.seed,
|
|
376
|
+
onLog: options2.onLog
|
|
377
|
+
});
|
|
378
|
+
runtime.isolateNamespaces();
|
|
379
|
+
const locked = /* @__PURE__ */ new Set();
|
|
380
|
+
const active = /* @__PURE__ */ new Map();
|
|
381
|
+
const api = {
|
|
382
|
+
async fetch(request) {
|
|
383
|
+
const namespace = runtime.namespaceOf(request);
|
|
384
|
+
if (locked.has(namespace))
|
|
385
|
+
return json(409, { error: "fleet control in progress", namespace });
|
|
386
|
+
active.set(namespace, (active.get(namespace) ?? 0) + 1);
|
|
387
|
+
try {
|
|
388
|
+
return await runtime.fetch(request);
|
|
389
|
+
} finally {
|
|
390
|
+
active.set(namespace, (active.get(namespace) ?? 1) - 1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
let server2;
|
|
395
|
+
try {
|
|
396
|
+
server2 = await listen(api, {
|
|
397
|
+
port: entry.port ?? target.defaultPort,
|
|
398
|
+
host: entry.host ?? "127.0.0.1"
|
|
399
|
+
});
|
|
400
|
+
target.listening?.(server2);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
runtime.stop?.();
|
|
403
|
+
await server2?.close();
|
|
404
|
+
runtime.webhooks?.clear();
|
|
405
|
+
await runtime.webhooks?.idle();
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
protocol: "http",
|
|
410
|
+
url: server2.url,
|
|
411
|
+
healthUrl: `${server2.url}/health`,
|
|
412
|
+
adminUrl: `${server2.url}/__admin`,
|
|
413
|
+
namespaces: { header: "x-mockingbird-namespace", path: "/ns/{name}" },
|
|
414
|
+
ready: async () => {
|
|
415
|
+
try {
|
|
416
|
+
return (await fetch(`${server2.url}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
async close() {
|
|
422
|
+
runtime.stop?.();
|
|
423
|
+
runtime.webhooks?.clear();
|
|
424
|
+
await server2.close();
|
|
425
|
+
await runtime.webhooks?.idle();
|
|
426
|
+
},
|
|
427
|
+
lock(namespace) {
|
|
428
|
+
if (locked.has(namespace))
|
|
429
|
+
throw new Error("namespace already locked");
|
|
430
|
+
locked.add(namespace);
|
|
431
|
+
return () => locked.delete(namespace);
|
|
432
|
+
},
|
|
433
|
+
diagnostics(namespace) {
|
|
434
|
+
return {
|
|
435
|
+
...EMPTY,
|
|
436
|
+
activeRequests: active.get(namespace) ?? 0,
|
|
437
|
+
pendingWebhooks: runtime.webhooks?.pending(namespace) ?? 0,
|
|
438
|
+
unmatchedRequests: runtime.metrics.report(namespace).unmatched.reduce((n, r) => n + r.count, 0)
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
async checkpoint(namespace) {
|
|
442
|
+
return runtime.fleetSnapshot(namespace);
|
|
443
|
+
},
|
|
444
|
+
async restore(namespace, checkpoint) {
|
|
445
|
+
runtime.fleetRestore(checkpoint, namespace);
|
|
446
|
+
},
|
|
447
|
+
reset: (namespace) => runtime.reset(namespace),
|
|
448
|
+
clock: (namespace) => runtime.namespaceClock(namespace),
|
|
449
|
+
async settle(namespace) {
|
|
450
|
+
const instance = runtime.instance(namespace);
|
|
451
|
+
instance.tick?.();
|
|
452
|
+
await runtime.webhooks?.idle();
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
async function startFleet(config, options2) {
|
|
457
|
+
const id = randomUUID();
|
|
458
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
459
|
+
const children = /* @__PURE__ */ new Map();
|
|
460
|
+
const clocks = /* @__PURE__ */ new Map();
|
|
461
|
+
const checkpoints = /* @__PURE__ */ new Map();
|
|
462
|
+
let supervisor;
|
|
463
|
+
let stopped = false;
|
|
464
|
+
let controlling = false;
|
|
465
|
+
const close = async () => {
|
|
466
|
+
if (stopped)
|
|
467
|
+
return;
|
|
468
|
+
stopped = true;
|
|
469
|
+
await Promise.allSettled([...children.values()].map((child) => child.close()));
|
|
470
|
+
await supervisor?.close();
|
|
471
|
+
await removeOwned(options2.readyFile, id);
|
|
472
|
+
await removeOwned(options2.connectionsFile, id);
|
|
473
|
+
};
|
|
474
|
+
if (options2.readyFile)
|
|
475
|
+
await rm(options2.readyFile, { force: true });
|
|
476
|
+
if (options2.connectionsFile)
|
|
477
|
+
await rm(options2.connectionsFile, { force: true });
|
|
478
|
+
const clock = (namespace) => {
|
|
479
|
+
let value = clocks.get(namespace);
|
|
480
|
+
if (!value) {
|
|
481
|
+
value = createClock();
|
|
482
|
+
clocks.set(namespace, value);
|
|
483
|
+
}
|
|
484
|
+
return value;
|
|
485
|
+
};
|
|
486
|
+
const select = (body) => {
|
|
487
|
+
if (body.services === void 0)
|
|
488
|
+
return children;
|
|
489
|
+
if (!Array.isArray(body.services) || body.services.length === 0)
|
|
490
|
+
throw new Error("services must be a nonempty list");
|
|
491
|
+
const selected = /* @__PURE__ */ new Map();
|
|
492
|
+
for (const name of body.services) {
|
|
493
|
+
if (typeof name !== "string" || !children.has(name))
|
|
494
|
+
throw new Error("unknown service in selection");
|
|
495
|
+
selected.set(name, children.get(name));
|
|
496
|
+
}
|
|
497
|
+
return selected;
|
|
498
|
+
};
|
|
499
|
+
const diagnostics = (namespace) => Object.fromEntries([...children].map(([name, child]) => [name, child.diagnostics(namespace)]));
|
|
500
|
+
const transaction = async (namespace, selected, run) => {
|
|
501
|
+
if (controlling)
|
|
502
|
+
return json(409, { error: "another fleet operation is in progress" });
|
|
503
|
+
controlling = true;
|
|
504
|
+
const release = [];
|
|
505
|
+
const before = /* @__PURE__ */ new Map();
|
|
506
|
+
const results = {};
|
|
507
|
+
let failure;
|
|
508
|
+
try {
|
|
509
|
+
for (const [name, child] of selected) {
|
|
510
|
+
failure = name;
|
|
511
|
+
release.push(child.lock(namespace));
|
|
512
|
+
}
|
|
513
|
+
const busy = Object.fromEntries([...selected].filter(([, child]) => {
|
|
514
|
+
const state = child.diagnostics(namespace);
|
|
515
|
+
return state.activeRequests + state.blockedCommands + state.pendingWebhooks + (state.activeJobs ?? 0) > 0;
|
|
516
|
+
}).map(([name, child]) => [name, child.diagnostics(namespace)]));
|
|
517
|
+
if (Object.keys(busy).length)
|
|
518
|
+
return json(409, { error: "namespace is not quiescent", services: busy });
|
|
519
|
+
for (const [name, child] of selected) {
|
|
520
|
+
failure = name;
|
|
521
|
+
before.set(name, await child.checkpoint(namespace));
|
|
522
|
+
}
|
|
523
|
+
for (const [name, child] of selected) {
|
|
524
|
+
failure = name;
|
|
525
|
+
results[name] = { status: "ok", result: await run(name, child) };
|
|
526
|
+
}
|
|
527
|
+
return json(200, { status: "ok", namespace, services: results });
|
|
528
|
+
} catch {
|
|
529
|
+
const rollback = {};
|
|
530
|
+
for (const [name, point] of before) {
|
|
531
|
+
try {
|
|
532
|
+
await selected.get(name)?.restore(namespace, point);
|
|
533
|
+
rollback[name] = "restored";
|
|
534
|
+
} catch {
|
|
535
|
+
rollback[name] = "failed";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return json(500, {
|
|
539
|
+
status: "failed",
|
|
540
|
+
namespace,
|
|
541
|
+
failedService: failure,
|
|
542
|
+
services: results,
|
|
543
|
+
rollback,
|
|
544
|
+
rolledBack: before.size > 0 && Object.values(rollback).every((value) => value === "restored")
|
|
545
|
+
});
|
|
546
|
+
} finally {
|
|
547
|
+
for (const unlock of release.reverse())
|
|
548
|
+
unlock();
|
|
549
|
+
controlling = false;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
const api = {
|
|
553
|
+
async fetch(request) {
|
|
554
|
+
const url = new URL(request.url);
|
|
555
|
+
if (url.pathname === "/health" && request.method === "GET") {
|
|
556
|
+
const states = await Promise.all([...children].map(async ([name, child]) => {
|
|
557
|
+
let protocolReady = false;
|
|
558
|
+
try {
|
|
559
|
+
protocolReady = await child.ready();
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
return [
|
|
563
|
+
name,
|
|
564
|
+
{
|
|
565
|
+
processReady: !stopped,
|
|
566
|
+
protocolReady,
|
|
567
|
+
status: protocolReady ? "ready" : "failed",
|
|
568
|
+
url: publicUrl(child.url)
|
|
569
|
+
}
|
|
570
|
+
];
|
|
571
|
+
}));
|
|
572
|
+
const ready = !stopped && states.every(([, state]) => state.protocolReady);
|
|
573
|
+
return json(ready ? 200 : 503, {
|
|
574
|
+
status: ready ? "ready" : "failed",
|
|
575
|
+
id,
|
|
576
|
+
services: Object.fromEntries(states)
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (!url.pathname.startsWith("/__fleet/"))
|
|
580
|
+
return json(404, { error: "not found" });
|
|
581
|
+
const adminKey = config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
582
|
+
if (adminKey !== void 0 && request.headers.get("x-mockingbird-admin-key") !== adminKey)
|
|
583
|
+
return json(401, { error: "admin key required" });
|
|
584
|
+
const route = /^\/__fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(url.pathname);
|
|
585
|
+
const namespace = route ? decodeURIComponent(route[1]) : url.searchParams.get("namespace") ?? request.headers.get("x-mockingbird-namespace") ?? config.namespace ?? "default";
|
|
586
|
+
if (!NS.test(namespace))
|
|
587
|
+
return json(400, { error: "invalid namespace" });
|
|
588
|
+
if (request.method === "GET" && url.pathname === "/__fleet/metrics")
|
|
589
|
+
return json(200, { namespace, services: diagnostics(namespace) });
|
|
590
|
+
if (request.method === "GET" && url.pathname === "/__fleet/clock")
|
|
591
|
+
return json(200, {
|
|
592
|
+
namespace,
|
|
593
|
+
...clock(namespace).state(),
|
|
594
|
+
services: Object.fromEntries([...children].map(([name, child]) => [name, child.clock(namespace).state()]))
|
|
595
|
+
});
|
|
596
|
+
if (request.method !== "POST")
|
|
597
|
+
return json(405, { error: "method not allowed" });
|
|
598
|
+
let body;
|
|
599
|
+
try {
|
|
600
|
+
const text = await request.text();
|
|
601
|
+
const value = text ? JSON.parse(text) : {};
|
|
602
|
+
if (!isObject(value))
|
|
603
|
+
throw new Error("expected object");
|
|
604
|
+
body = value;
|
|
605
|
+
} catch {
|
|
606
|
+
return json(400, { error: "expected JSON object" });
|
|
607
|
+
}
|
|
608
|
+
let selected;
|
|
609
|
+
try {
|
|
610
|
+
selected = select(body);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
return json(400, { error: error instanceof Error ? error.message : "bad selection" });
|
|
613
|
+
}
|
|
614
|
+
if (url.pathname === "/__fleet/wait-until-idle") {
|
|
615
|
+
const services = diagnostics(namespace);
|
|
616
|
+
const idle = Object.values(services).every((d) => pending(d) === 0);
|
|
617
|
+
return json(idle ? 200 : 409, { status: idle ? "idle" : "busy", namespace, services });
|
|
618
|
+
}
|
|
619
|
+
if (url.pathname === "/__fleet/clock") {
|
|
620
|
+
const current = clock(namespace);
|
|
621
|
+
let target = current.now();
|
|
622
|
+
if (body.set !== void 0) {
|
|
623
|
+
target = typeof body.set === "number" ? body.set : typeof body.set === "string" ? Date.parse(body.set) : NaN;
|
|
624
|
+
if (!Number.isFinite(target))
|
|
625
|
+
return json(400, { error: "set must be epoch ms or ISO-8601" });
|
|
626
|
+
}
|
|
627
|
+
if (body.advance !== void 0) {
|
|
628
|
+
const delta = parseDuration(body.advance);
|
|
629
|
+
if (delta === void 0)
|
|
630
|
+
return json(400, { error: "invalid advance" });
|
|
631
|
+
target += delta;
|
|
632
|
+
}
|
|
633
|
+
const frozen = body.freeze === void 0 ? current.state().frozen : body.freeze;
|
|
634
|
+
if (typeof frozen !== "boolean")
|
|
635
|
+
return json(400, { error: "freeze must be boolean" });
|
|
636
|
+
const response2 = await transaction(namespace, selected, async (_name, child) => {
|
|
637
|
+
const childClock = child.clock(namespace);
|
|
638
|
+
childClock.freeze();
|
|
639
|
+
childClock.set(target);
|
|
640
|
+
if (!frozen)
|
|
641
|
+
childClock.unfreeze();
|
|
642
|
+
await child.settle?.(namespace);
|
|
643
|
+
return childClock.state();
|
|
644
|
+
});
|
|
645
|
+
if (response2.ok) {
|
|
646
|
+
current.freeze();
|
|
647
|
+
current.set(target);
|
|
648
|
+
if (!frozen)
|
|
649
|
+
current.unfreeze();
|
|
650
|
+
}
|
|
651
|
+
return response2;
|
|
652
|
+
}
|
|
653
|
+
if (!route)
|
|
654
|
+
return json(404, { error: "not found" });
|
|
655
|
+
if (route[2] === "reset") {
|
|
656
|
+
if (route[3])
|
|
657
|
+
return json(404, { error: "not found" });
|
|
658
|
+
const response2 = await transaction(namespace, selected, async (_name, child) => child.reset(namespace));
|
|
659
|
+
if (response2.ok && selected.size === children.size)
|
|
660
|
+
clock(namespace).reset();
|
|
661
|
+
return response2;
|
|
662
|
+
}
|
|
663
|
+
if (route[3]) {
|
|
664
|
+
const point = checkpoints.get(route[3]);
|
|
665
|
+
if (!point || point.namespace !== namespace)
|
|
666
|
+
return json(404, { error: "no snapshot in this namespace" });
|
|
667
|
+
const restored = new Map([...point.values].map(([name]) => [name, children.get(name)]));
|
|
668
|
+
const response2 = await transaction(namespace, restored, async (name, child) => child.restore(namespace, point.values.get(name)));
|
|
669
|
+
if (response2.ok) {
|
|
670
|
+
const current = clock(namespace);
|
|
671
|
+
current.freeze();
|
|
672
|
+
current.set(point.clock.now);
|
|
673
|
+
if (!point.clock.frozen)
|
|
674
|
+
current.unfreeze();
|
|
675
|
+
}
|
|
676
|
+
return response2;
|
|
677
|
+
}
|
|
678
|
+
const snapshotId = randomUUID();
|
|
679
|
+
const values = /* @__PURE__ */ new Map();
|
|
680
|
+
const response = await transaction(namespace, selected, async (name, child) => {
|
|
681
|
+
values.set(name, await child.checkpoint(namespace));
|
|
682
|
+
return { checkpoint: `${snapshotId}:${name}` };
|
|
683
|
+
});
|
|
684
|
+
if (!response.ok)
|
|
685
|
+
return response;
|
|
686
|
+
checkpoints.set(snapshotId, { namespace, values, clock: clock(namespace).state() });
|
|
687
|
+
return json(201, {
|
|
688
|
+
...await response.json(),
|
|
689
|
+
id: snapshotId,
|
|
690
|
+
label: body.label
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
try {
|
|
695
|
+
if (!isObject(config.services) || Object.keys(config.services).length === 0)
|
|
696
|
+
throw new Error("config must contain at least one service");
|
|
697
|
+
for (const [name, entry] of Object.entries(config.services)) {
|
|
698
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
|
|
699
|
+
throw new Error("invalid service name");
|
|
700
|
+
try {
|
|
701
|
+
const target = await options2.load(name);
|
|
702
|
+
if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
|
|
703
|
+
throw new Error("configured protocol does not match target");
|
|
704
|
+
const adminKey = entry.adminKey ?? config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
705
|
+
const child = "start" in target ? await target.start(entry) : await httpChild(target, { ...entry, ...adminKey !== void 0 ? { adminKey } : {} }, options2);
|
|
706
|
+
children.set(name, child);
|
|
707
|
+
if (!await child.ready())
|
|
708
|
+
throw new Error("protocol readiness failed");
|
|
709
|
+
} catch {
|
|
710
|
+
throw new Error(`service ${name} failed to start; all started listeners were closed`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
supervisor = await listen(api, {
|
|
714
|
+
port: config.control?.port ?? 0,
|
|
715
|
+
host: config.control?.host ?? "127.0.0.1"
|
|
716
|
+
});
|
|
717
|
+
const manifest = {
|
|
718
|
+
version: 1,
|
|
719
|
+
state: "ready",
|
|
720
|
+
pid: process.pid,
|
|
721
|
+
id,
|
|
722
|
+
startedAt,
|
|
723
|
+
adminBase: `${supervisor.url}/__fleet`,
|
|
724
|
+
healthUrl: `${supervisor.url}/health`,
|
|
725
|
+
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
726
|
+
name,
|
|
727
|
+
{
|
|
728
|
+
protocol: child.protocol,
|
|
729
|
+
url: publicUrl(child.url),
|
|
730
|
+
healthUrl: child.healthUrl ?? `${supervisor?.url}/health`,
|
|
731
|
+
adminUrl: child.adminUrl ?? `${supervisor?.url}/__fleet`,
|
|
732
|
+
namespaces: child.namespaces,
|
|
733
|
+
processReady: true,
|
|
734
|
+
protocolReady: true
|
|
735
|
+
}
|
|
736
|
+
]))
|
|
737
|
+
};
|
|
738
|
+
if (options2.connectionsFile)
|
|
739
|
+
await atomicWrite(options2.connectionsFile, {
|
|
740
|
+
version: 1,
|
|
741
|
+
id,
|
|
742
|
+
pid: process.pid,
|
|
743
|
+
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
744
|
+
name,
|
|
745
|
+
{ url: child.connection ?? child.url, namespaces: child.connections }
|
|
746
|
+
]))
|
|
747
|
+
});
|
|
748
|
+
if (options2.readyFile)
|
|
749
|
+
await atomicWrite(options2.readyFile, manifest);
|
|
750
|
+
options2.onReady?.(manifest);
|
|
751
|
+
return { manifest, children, fetch: api.fetch, close };
|
|
752
|
+
} catch (error) {
|
|
753
|
+
await close();
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// ../../adapters/node/dist/cli.js
|
|
759
|
+
var optionHelp = (options2) => Object.entries(options2).map(([name, option]) => {
|
|
760
|
+
const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
|
|
761
|
+
const fallback2 = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
|
|
762
|
+
return ` ${flag.padEnd(30)} ${option.description}${fallback2}`;
|
|
763
|
+
});
|
|
764
|
+
var help = (spec) => [
|
|
765
|
+
`${spec.bin} \u2014 ${spec.description}`,
|
|
766
|
+
"",
|
|
767
|
+
"Usage:",
|
|
768
|
+
` ${spec.bin} <command> [options]`,
|
|
769
|
+
"",
|
|
770
|
+
"Commands:",
|
|
771
|
+
...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
|
|
772
|
+
"",
|
|
773
|
+
`Run \`${spec.bin} <command> --help\` for a command's options.`
|
|
774
|
+
].join("\n");
|
|
775
|
+
var commandHelp = (spec, name, command2) => [
|
|
776
|
+
`${spec.bin} ${name} \u2014 ${command2.summary}`,
|
|
777
|
+
"",
|
|
778
|
+
"Usage:",
|
|
779
|
+
` ${command2.usage ?? `${spec.bin} ${name} [options]`}`,
|
|
780
|
+
...command2.options ? ["", "Options:", ...optionHelp(command2.options)] : []
|
|
781
|
+
].join("\n");
|
|
782
|
+
var runCli = async (spec, argv) => {
|
|
783
|
+
const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
|
|
784
|
+
const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
|
|
785
|
+
const name = words === 2 ? pair : argv[0];
|
|
786
|
+
const rest = argv.slice(words);
|
|
787
|
+
if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
|
|
788
|
+
console.log(help(spec));
|
|
789
|
+
return 0;
|
|
790
|
+
}
|
|
791
|
+
const command2 = spec.commands[name];
|
|
792
|
+
if (!command2) {
|
|
793
|
+
console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
|
|
794
|
+
|
|
795
|
+
${help(spec)}`);
|
|
796
|
+
return 2;
|
|
797
|
+
}
|
|
798
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
799
|
+
console.log(commandHelp(spec, name, command2));
|
|
800
|
+
return 0;
|
|
801
|
+
}
|
|
802
|
+
let parsed;
|
|
803
|
+
try {
|
|
804
|
+
parsed = parseArgs({
|
|
805
|
+
args: rest,
|
|
806
|
+
allowPositionals: true,
|
|
807
|
+
strict: true,
|
|
808
|
+
options: Object.fromEntries(Object.entries(command2.options ?? {}).map(([key, option]) => [
|
|
809
|
+
key,
|
|
810
|
+
{
|
|
811
|
+
type: option.type,
|
|
812
|
+
...option.default !== void 0 ? { default: option.default } : {}
|
|
813
|
+
}
|
|
814
|
+
]))
|
|
815
|
+
});
|
|
816
|
+
} catch (error) {
|
|
817
|
+
console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
818
|
+
|
|
819
|
+
${commandHelp(spec, name, command2)}`);
|
|
820
|
+
return 2;
|
|
821
|
+
}
|
|
822
|
+
return command2.run(parsed.values, parsed.positionals);
|
|
823
|
+
};
|
|
824
|
+
var COMMON_SERVE_OPTIONS = {
|
|
825
|
+
port: { type: "string", value: "<port>", description: "Port to listen on" },
|
|
826
|
+
host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
|
|
827
|
+
"admin-key": {
|
|
828
|
+
type: "string",
|
|
829
|
+
value: "<key>",
|
|
830
|
+
description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
|
|
831
|
+
},
|
|
832
|
+
seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
|
|
833
|
+
log: {
|
|
834
|
+
type: "string",
|
|
835
|
+
value: "<pretty|json|off>",
|
|
836
|
+
description: "Request log format",
|
|
837
|
+
default: "pretty"
|
|
838
|
+
},
|
|
839
|
+
"log-requests": {
|
|
840
|
+
type: "boolean",
|
|
841
|
+
description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
|
|
842
|
+
},
|
|
843
|
+
config: {
|
|
844
|
+
type: "string",
|
|
845
|
+
value: "<file>",
|
|
846
|
+
description: "Serve every service in a mockingbird.json config instead"
|
|
847
|
+
},
|
|
848
|
+
"ready-file": {
|
|
849
|
+
type: "string",
|
|
850
|
+
description: "Atomically publish fleet endpoint JSON",
|
|
851
|
+
value: "<file>"
|
|
852
|
+
},
|
|
853
|
+
"connections-file": {
|
|
854
|
+
type: "string",
|
|
855
|
+
description: "Write private connection URLs with mode 0600",
|
|
856
|
+
value: "<file>"
|
|
857
|
+
},
|
|
858
|
+
"ready-json": {
|
|
859
|
+
type: "boolean",
|
|
860
|
+
description: "Emit one public JSON record after fleet readiness"
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
var formatLog = (format) => {
|
|
864
|
+
if (format === "off")
|
|
865
|
+
return void 0;
|
|
866
|
+
if (format === "json")
|
|
867
|
+
return (entry) => console.log(JSON.stringify(entry));
|
|
868
|
+
return (entry) => {
|
|
869
|
+
const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
|
|
870
|
+
const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
|
|
871
|
+
const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
|
|
872
|
+
const adopted = entry.adopted ? " adopted" : "";
|
|
873
|
+
const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
|
|
874
|
+
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})` : ""}` : "";
|
|
875
|
+
console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
|
|
876
|
+
};
|
|
877
|
+
};
|
|
878
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
879
|
+
var loadTarget = async (name, own) => {
|
|
880
|
+
if (name === own.name)
|
|
881
|
+
return own;
|
|
882
|
+
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
883
|
+
try {
|
|
884
|
+
let mod;
|
|
885
|
+
try {
|
|
886
|
+
mod = await import(specifier);
|
|
887
|
+
} catch {
|
|
888
|
+
const fromProject = createRequire(resolve(process.cwd(), "package.json"));
|
|
889
|
+
mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
|
|
890
|
+
}
|
|
891
|
+
if (!mod.serveTarget)
|
|
892
|
+
throw new Error(`${specifier} exports no serveTarget`);
|
|
893
|
+
return mod.serveTarget;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
896
|
+
throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
var start = async (target, values, config) => {
|
|
900
|
+
const runtime = await target.create(values, {
|
|
901
|
+
adminKey: config.adminKey,
|
|
902
|
+
seed: config.seed,
|
|
903
|
+
onLog: formatLog(config.log)
|
|
904
|
+
});
|
|
905
|
+
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
906
|
+
target.listening?.(listening);
|
|
907
|
+
console.log(`${target.name} mock listening on ${listening.url}`);
|
|
908
|
+
console.log(`${target.name} health: GET ${listening.url}/health`);
|
|
909
|
+
console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
|
|
910
|
+
console.log(`${target.name} admin ui: ${listening.url}/__admin/ui`);
|
|
911
|
+
for (const line of target.banner?.(runtime) ?? [])
|
|
912
|
+
console.log(`${target.name} ${line}`);
|
|
913
|
+
return listening;
|
|
914
|
+
};
|
|
915
|
+
var untilSignal = async (servers) => new Promise((resolve2) => {
|
|
916
|
+
const stop2 = async () => {
|
|
917
|
+
;
|
|
918
|
+
process.removeListener("SIGINT", stop2);
|
|
919
|
+
process.removeListener("SIGTERM", stop2);
|
|
920
|
+
await Promise.allSettled(servers.map((s) => s.close()));
|
|
921
|
+
resolve2(0);
|
|
922
|
+
};
|
|
923
|
+
process.once("SIGINT", stop2);
|
|
924
|
+
process.once("SIGTERM", stop2);
|
|
925
|
+
});
|
|
926
|
+
var serveCommand = (target) => ({
|
|
927
|
+
summary: `Serve the ${target.name} mock or a configured fleet`,
|
|
928
|
+
options: { ...COMMON_SERVE_OPTIONS, ..."options" in target ? target.options : {} },
|
|
929
|
+
async run(values) {
|
|
930
|
+
const log2 = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
|
|
931
|
+
if (!["pretty", "json", "off"].includes(log2)) {
|
|
932
|
+
console.error(`--log must be pretty, json or off (got ${log2})`);
|
|
933
|
+
return 2;
|
|
934
|
+
}
|
|
935
|
+
const configPath = asString(values.config);
|
|
936
|
+
if (configPath !== void 0) {
|
|
937
|
+
try {
|
|
938
|
+
const config = JSON.parse(await readFile2(configPath, "utf8"));
|
|
939
|
+
const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
940
|
+
if (adminKey2 !== void 0)
|
|
941
|
+
config.adminKey = adminKey2;
|
|
942
|
+
const fleet = await startFleet(config, {
|
|
943
|
+
load: (name) => loadTarget(name, target),
|
|
944
|
+
onLog: formatLog(config.log ?? log2),
|
|
945
|
+
...asString(values["ready-file"]) ? { readyFile: asString(values["ready-file"]) } : {},
|
|
946
|
+
...asString(values["connections-file"]) ? { connectionsFile: asString(values["connections-file"]) } : {},
|
|
947
|
+
onReady: (manifest) => {
|
|
948
|
+
if (values["ready-json"])
|
|
949
|
+
console.log(JSON.stringify(manifest));
|
|
950
|
+
else
|
|
951
|
+
for (const [name, endpoint] of Object.entries(manifest.services))
|
|
952
|
+
console.log(`${name} mock listening on ${endpoint.url}`);
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
return untilSignal([fleet]);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
958
|
+
return 1;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if ("start" in target) {
|
|
962
|
+
console.error("Use --config to supervise protocol services with this command");
|
|
963
|
+
return 2;
|
|
964
|
+
}
|
|
965
|
+
const port = asString(values.port);
|
|
966
|
+
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
967
|
+
const seed = asString(values.seed);
|
|
968
|
+
let listening;
|
|
969
|
+
try {
|
|
970
|
+
listening = await start(target, values, {
|
|
971
|
+
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
972
|
+
host: asString(values.host) ?? "127.0.0.1",
|
|
973
|
+
...adminKey !== void 0 ? { adminKey } : {},
|
|
974
|
+
...seed !== void 0 ? { seed } : {},
|
|
975
|
+
log: log2
|
|
976
|
+
});
|
|
977
|
+
} catch (error) {
|
|
978
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
979
|
+
return 1;
|
|
980
|
+
}
|
|
981
|
+
return untilSignal([listening]);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// src/wire/index.ts
|
|
986
|
+
import { createServer as createServer2 } from "node:net";
|
|
987
|
+
|
|
167
988
|
// src/errors/error.ts
|
|
168
989
|
var CATEGORY_TO_SQLSTATE = {
|
|
169
990
|
syntax: "42601",
|
|
@@ -2344,11 +3165,11 @@ var JsonParser = class {
|
|
|
2344
3165
|
}
|
|
2345
3166
|
}
|
|
2346
3167
|
parseNumber() {
|
|
2347
|
-
const
|
|
3168
|
+
const start2 = this.pos;
|
|
2348
3169
|
const re = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
2349
3170
|
re.lastIndex = this.pos;
|
|
2350
3171
|
const m = re.exec(this.text);
|
|
2351
|
-
if (!m || m.index !==
|
|
3172
|
+
if (!m || m.index !== start2) this.fail("bad number");
|
|
2352
3173
|
this.pos = re.lastIndex;
|
|
2353
3174
|
return jsonbNum(parseNumeric(m[0]));
|
|
2354
3175
|
}
|
|
@@ -4700,18 +5521,18 @@ function isoWeek(days) {
|
|
|
4700
5521
|
return jan4 - (isoDw - 1);
|
|
4701
5522
|
};
|
|
4702
5523
|
let year = y;
|
|
4703
|
-
let
|
|
4704
|
-
if (days <
|
|
5524
|
+
let start2 = week1Monday(y);
|
|
5525
|
+
if (days < start2) {
|
|
4705
5526
|
year = y - 1;
|
|
4706
|
-
|
|
5527
|
+
start2 = week1Monday(year);
|
|
4707
5528
|
} else {
|
|
4708
5529
|
const nextStart = week1Monday(y + 1);
|
|
4709
5530
|
if (days >= nextStart) {
|
|
4710
5531
|
year = y + 1;
|
|
4711
|
-
|
|
5532
|
+
start2 = nextStart;
|
|
4712
5533
|
}
|
|
4713
5534
|
}
|
|
4714
|
-
return { week: Math.floor((days -
|
|
5535
|
+
return { week: Math.floor((days - start2) / 7) + 1, year };
|
|
4715
5536
|
}
|
|
4716
5537
|
function dateTruncDays(field, days) {
|
|
4717
5538
|
const { y, m } = daysToCivil(days);
|
|
@@ -5281,9 +6102,9 @@ function getArrayFunctions() {
|
|
|
5281
6102
|
}
|
|
5282
6103
|
const elemT = arrayElemType(a.t);
|
|
5283
6104
|
const target = castTo(ctx, args2[1], elemT, {});
|
|
5284
|
-
const
|
|
6105
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
5285
6106
|
const lb = lbsOf(arr)[0] ?? 1;
|
|
5286
|
-
for (let i = Math.max(
|
|
6107
|
+
for (let i = Math.max(start2 - lb, 0); i < arr.items.length; i++) {
|
|
5287
6108
|
const item = arr.items[i];
|
|
5288
6109
|
const eq = item === null ? target.v === null : target.v !== null && datumEquals(elemT, item, target.v, ctx);
|
|
5289
6110
|
if (eq) return tv("int4", i + lb);
|
|
@@ -5763,12 +6584,12 @@ function parseByFormat(input, fmt) {
|
|
|
5763
6584
|
};
|
|
5764
6585
|
const readNum = (maxLen) => {
|
|
5765
6586
|
skipInputSeparators();
|
|
5766
|
-
const
|
|
5767
|
-
while (i < input.length && /[0-9]/.test(input[i]) && i -
|
|
5768
|
-
if (i ===
|
|
6587
|
+
const start2 = i;
|
|
6588
|
+
while (i < input.length && /[0-9]/.test(input[i]) && i - start2 < maxLen) i++;
|
|
6589
|
+
if (i === start2) {
|
|
5769
6590
|
throw pgError("invalid_datetime", `invalid value for field in source string "${input}"`, "22007");
|
|
5770
6591
|
}
|
|
5771
|
-
return Number(input.slice(
|
|
6592
|
+
return Number(input.slice(start2, i));
|
|
5772
6593
|
};
|
|
5773
6594
|
const readMonthName = () => {
|
|
5774
6595
|
skipInputSeparators();
|
|
@@ -7603,15 +8424,15 @@ var decoderUtf8 = new TextDecoder("utf-8", { fatal: false });
|
|
|
7603
8424
|
function chars(s) {
|
|
7604
8425
|
return [...s];
|
|
7605
8426
|
}
|
|
7606
|
-
function substrImpl(s,
|
|
8427
|
+
function substrImpl(s, start2, len) {
|
|
7607
8428
|
const cs = chars(s);
|
|
7608
8429
|
if (len === void 0) {
|
|
7609
|
-
const from2 = Math.max(
|
|
8430
|
+
const from2 = Math.max(start2, 1);
|
|
7610
8431
|
return cs.slice(from2 - 1).join("");
|
|
7611
8432
|
}
|
|
7612
8433
|
if (len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
|
|
7613
|
-
const end =
|
|
7614
|
-
const from = Math.max(
|
|
8434
|
+
const end = start2 + len;
|
|
8435
|
+
const from = Math.max(start2, 1);
|
|
7615
8436
|
const to = Math.max(end, 1);
|
|
7616
8437
|
if (to <= from) return "";
|
|
7617
8438
|
return cs.slice(from - 1, to - 1).join("");
|
|
@@ -7675,14 +8496,14 @@ function getStringFunctions() {
|
|
|
7675
8496
|
const a = args2[0];
|
|
7676
8497
|
if (a.t === "bytea") {
|
|
7677
8498
|
const bytes = a.v;
|
|
7678
|
-
const
|
|
8499
|
+
const start3 = argInt(ctx, args2[1]);
|
|
7679
8500
|
const len2 = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
|
|
7680
|
-
return tv("bytea", byteaSubstr(bytes,
|
|
8501
|
+
return tv("bytea", byteaSubstr(bytes, start3, len2));
|
|
7681
8502
|
}
|
|
7682
8503
|
const s = argText(ctx, a);
|
|
7683
|
-
const
|
|
8504
|
+
const start2 = argInt(ctx, args2[1]);
|
|
7684
8505
|
const len = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
|
|
7685
|
-
return tv("text", substrImpl(s,
|
|
8506
|
+
return tv("text", substrImpl(s, start2, len));
|
|
7686
8507
|
})
|
|
7687
8508
|
);
|
|
7688
8509
|
m.set(
|
|
@@ -7978,11 +8799,11 @@ function getStringFunctions() {
|
|
|
7978
8799
|
strict("int4", (ctx, args2) => {
|
|
7979
8800
|
const s = argText(ctx, args2[0]);
|
|
7980
8801
|
const pattern = argText(ctx, args2[1]);
|
|
7981
|
-
const
|
|
8802
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
7982
8803
|
const flags = args2.length > 3 ? argText(ctx, args2[3]) : "";
|
|
7983
|
-
if (
|
|
8804
|
+
if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
|
|
7984
8805
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
7985
|
-
const sub = chars(s).slice(
|
|
8806
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
7986
8807
|
let count = 0;
|
|
7987
8808
|
for (const _ of sub.matchAll(js)) count++;
|
|
7988
8809
|
return tv("int4", count);
|
|
@@ -8003,11 +8824,11 @@ function getStringFunctions() {
|
|
|
8003
8824
|
strict("text", (ctx, args2) => {
|
|
8004
8825
|
const s = argText(ctx, args2[0]);
|
|
8005
8826
|
const pattern = argText(ctx, args2[1]);
|
|
8006
|
-
const
|
|
8827
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
8007
8828
|
const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
|
|
8008
8829
|
const flags = args2.length > 4 ? argText(ctx, args2[4]) : "";
|
|
8009
8830
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
8010
|
-
const sub = chars(s).slice(
|
|
8831
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
8011
8832
|
let i = 0;
|
|
8012
8833
|
for (const match of sub.matchAll(js)) {
|
|
8013
8834
|
i++;
|
|
@@ -8021,21 +8842,21 @@ function getStringFunctions() {
|
|
|
8021
8842
|
strict("int4", (ctx, args2) => {
|
|
8022
8843
|
const s = argText(ctx, args2[0]);
|
|
8023
8844
|
const pattern = argText(ctx, args2[1]);
|
|
8024
|
-
const
|
|
8845
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
8025
8846
|
const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
|
|
8026
8847
|
const endoption = args2.length > 4 ? argInt(ctx, args2[4]) : 0;
|
|
8027
8848
|
const flags = args2.length > 5 ? argText(ctx, args2[5]) : "";
|
|
8028
|
-
if (
|
|
8849
|
+
if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
|
|
8029
8850
|
if (endoption !== 0 && endoption !== 1) {
|
|
8030
8851
|
throw pgError("invalid_parameter_value", `invalid value for parameter "endoption": ${endoption}`);
|
|
8031
8852
|
}
|
|
8032
8853
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
8033
|
-
const sub = chars(s).slice(
|
|
8854
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
8034
8855
|
let i = 0;
|
|
8035
8856
|
for (const match of sub.matchAll(js)) {
|
|
8036
8857
|
i++;
|
|
8037
8858
|
if (i === nth) {
|
|
8038
|
-
const base =
|
|
8859
|
+
const base = start2 - 1 + chars(sub.slice(0, match.index)).length;
|
|
8039
8860
|
return tv("int4", endoption === 0 ? base + 1 : base + chars(match[0]).length + 1);
|
|
8040
8861
|
}
|
|
8041
8862
|
}
|
|
@@ -8149,10 +8970,10 @@ function getStringFunctions() {
|
|
|
8149
8970
|
);
|
|
8150
8971
|
return m;
|
|
8151
8972
|
}
|
|
8152
|
-
function byteaSubstr(bytes,
|
|
8973
|
+
function byteaSubstr(bytes, start2, len) {
|
|
8153
8974
|
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(
|
|
8975
|
+
const from = Math.max(start2, 1) - 1;
|
|
8976
|
+
const to = len === void 0 ? bytes.length : Math.max(start2 + len - 1, 0);
|
|
8156
8977
|
return bytes.slice(from, to);
|
|
8157
8978
|
}
|
|
8158
8979
|
function pad(s, len, fill, left) {
|
|
@@ -10714,7 +11535,7 @@ function evalOverlaps(ctx, scope, args2) {
|
|
|
10714
11535
|
};
|
|
10715
11536
|
const [s1r, e1r] = pair(args2[0]);
|
|
10716
11537
|
const [s2r, e2r] = pair(args2[1]);
|
|
10717
|
-
const resolveEnd = (
|
|
11538
|
+
const resolveEnd = (start2, end) => end.t === "interval" ? evalBinary(ctx, "+", start2, end) : end;
|
|
10718
11539
|
const e1 = resolveEnd(s1r, e1r);
|
|
10719
11540
|
const e2 = resolveEnd(s2r, e2r);
|
|
10720
11541
|
const target = [s1r, e1, s2r, e2].map((x) => x.t).find((t) => t !== "unknown") ?? "timestamp";
|
|
@@ -11973,7 +12794,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11973
12794
|
}
|
|
11974
12795
|
if (a.t === "timestamp" || a.t === "timestamptz" || b.t === "timestamp" || b.t === "timestamptz" || a.t === "date") {
|
|
11975
12796
|
const t2 = a.t === "timestamptz" || b.t === "timestamptz" ? "timestamptz" : "timestamp";
|
|
11976
|
-
const
|
|
12797
|
+
const start3 = castTo(ctx, a, t2, { explicit: true }).v;
|
|
11977
12798
|
const stop3 = castTo(ctx, b, t2, { explicit: true }).v;
|
|
11978
12799
|
const stepArg = args2[2];
|
|
11979
12800
|
if (stepArg === void 0 || !isInterval(stepArg.v)) {
|
|
@@ -11988,7 +12809,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11988
12809
|
if (step2.months !== 0) {
|
|
11989
12810
|
let i = 0;
|
|
11990
12811
|
for (; ; ) {
|
|
11991
|
-
const { days, tod } = splitTs(
|
|
12812
|
+
const { days, tod } = splitTs(start3);
|
|
11992
12813
|
const c = daysToCivil(days);
|
|
11993
12814
|
const totalMonths = c.y * 12 + (c.m - 1) + step2.months * i;
|
|
11994
12815
|
const y = Math.floor(totalMonths / 12);
|
|
@@ -12005,19 +12826,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
12005
12826
|
const stepMicros = intervalTotalMicros(step2);
|
|
12006
12827
|
if (stepMicros === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12007
12828
|
if (stepMicros > 0n) {
|
|
12008
|
-
for (let cur =
|
|
12829
|
+
for (let cur = start3; cur <= stop3; cur += stepMicros) rows2.push([cur]);
|
|
12009
12830
|
} else {
|
|
12010
|
-
for (let cur =
|
|
12831
|
+
for (let cur = start3; cur >= stop3; cur += stepMicros) rows2.push([cur]);
|
|
12011
12832
|
}
|
|
12012
12833
|
return { columns: [{ name: alias, type: t2 }], rows: rows2 };
|
|
12013
12834
|
}
|
|
12014
12835
|
if (a.t === "numeric" || b.t === "numeric" || args2[2]?.t === "numeric") {
|
|
12015
|
-
const
|
|
12836
|
+
const start3 = castTo(ctx, a, "numeric", { explicit: true }).v;
|
|
12016
12837
|
const stop3 = castTo(ctx, b, "numeric", { explicit: true }).v;
|
|
12017
12838
|
const step2 = args2[2] ? castTo(ctx, args2[2], "numeric", { explicit: true }).v : castTo(ctx, tv("int4", 1), "numeric", { explicit: true }).v;
|
|
12018
12839
|
if (numericSign(step2) === 0) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12019
12840
|
const rows2 = [];
|
|
12020
|
-
let cur =
|
|
12841
|
+
let cur = start3;
|
|
12021
12842
|
const ascending = numericSign(step2) > 0;
|
|
12022
12843
|
while (ascending ? numericCmp(cur, stop3) <= 0 : numericCmp(cur, stop3) >= 0) {
|
|
12023
12844
|
rows2.push([cur]);
|
|
@@ -12029,19 +12850,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
12029
12850
|
}
|
|
12030
12851
|
const useBig = a.t === "int8" || b.t === "int8" || args2[2]?.t === "int8";
|
|
12031
12852
|
const t = useBig ? "int8" : "int4";
|
|
12032
|
-
const
|
|
12853
|
+
const start2 = castTo(ctx, a, "int8", {}).v;
|
|
12033
12854
|
const stop2 = castTo(ctx, b, "int8", {}).v;
|
|
12034
12855
|
const step = args2[2] ? castTo(ctx, args2[2], "int8", {}).v : 1n;
|
|
12035
12856
|
if (step === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12036
12857
|
const rows = [];
|
|
12037
12858
|
if (step > 0n) {
|
|
12038
|
-
for (let cur =
|
|
12859
|
+
for (let cur = start2; cur <= stop2; cur += step) {
|
|
12039
12860
|
rows.push([useBig ? cur : Number(cur)]);
|
|
12040
12861
|
if (rows.length > 1e7)
|
|
12041
12862
|
throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
|
|
12042
12863
|
}
|
|
12043
12864
|
} else {
|
|
12044
|
-
for (let cur =
|
|
12865
|
+
for (let cur = start2; cur >= stop2; cur += step) {
|
|
12045
12866
|
rows.push([useBig ? cur : Number(cur)]);
|
|
12046
12867
|
if (rows.length > 1e7)
|
|
12047
12868
|
throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
|
|
@@ -12471,10 +13292,10 @@ function tokenize(sql) {
|
|
|
12471
13292
|
const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(sql.slice(i));
|
|
12472
13293
|
if (m) {
|
|
12473
13294
|
const open = m[0];
|
|
12474
|
-
const
|
|
12475
|
-
const end = sql.indexOf(open,
|
|
13295
|
+
const start2 = i + open.length;
|
|
13296
|
+
const end = sql.indexOf(open, start2);
|
|
12476
13297
|
if (end === -1) syntaxError("unterminated dollar-quoted string");
|
|
12477
|
-
push("string", sql.slice(
|
|
13298
|
+
push("string", sql.slice(start2, end), i);
|
|
12478
13299
|
i = end + open.length;
|
|
12479
13300
|
continue;
|
|
12480
13301
|
}
|
|
@@ -12541,14 +13362,14 @@ function tokenize(sql) {
|
|
|
12541
13362
|
continue;
|
|
12542
13363
|
}
|
|
12543
13364
|
if (/\d/.test(c) || c === "." && /\d/.test(sql[i + 1] ?? "")) {
|
|
12544
|
-
const
|
|
13365
|
+
const start2 = i;
|
|
12545
13366
|
const radix = /^0[xX][0-9a-fA-F_]+|^0[oO][0-7_]+|^0[bB][01_]+/.exec(sql.slice(i));
|
|
12546
13367
|
if (radix) {
|
|
12547
13368
|
const raw2 = radix[0];
|
|
12548
13369
|
i += raw2.length;
|
|
12549
13370
|
const clean = raw2.replaceAll("_", "");
|
|
12550
13371
|
const v = BigInt(clean);
|
|
12551
|
-
push("number", v.toString(),
|
|
13372
|
+
push("number", v.toString(), start2);
|
|
12552
13373
|
continue;
|
|
12553
13374
|
}
|
|
12554
13375
|
let j = i;
|
|
@@ -12570,7 +13391,7 @@ function tokenize(sql) {
|
|
|
12570
13391
|
if (j < n && IDENT_START.test(sql[j])) {
|
|
12571
13392
|
syntaxError(`trailing junk after numeric literal at or near "${sql.slice(i, j + 1)}"`);
|
|
12572
13393
|
}
|
|
12573
|
-
push("number", raw,
|
|
13394
|
+
push("number", raw, start2);
|
|
12574
13395
|
i = j;
|
|
12575
13396
|
continue;
|
|
12576
13397
|
}
|
|
@@ -12628,9 +13449,9 @@ function tokenize(sql) {
|
|
|
12628
13449
|
push("eof", "", i);
|
|
12629
13450
|
return tokens;
|
|
12630
13451
|
}
|
|
12631
|
-
function scanStandardString(sql,
|
|
13452
|
+
function scanStandardString(sql, start2) {
|
|
12632
13453
|
let out = "";
|
|
12633
|
-
let i =
|
|
13454
|
+
let i = start2 + 1;
|
|
12634
13455
|
const n = sql.length;
|
|
12635
13456
|
for (; ; ) {
|
|
12636
13457
|
if (i >= n) syntaxError("unterminated quoted string");
|
|
@@ -12658,9 +13479,9 @@ function scanStandardString(sql, start) {
|
|
|
12658
13479
|
i++;
|
|
12659
13480
|
}
|
|
12660
13481
|
}
|
|
12661
|
-
function scanEscapeString(sql,
|
|
13482
|
+
function scanEscapeString(sql, start2) {
|
|
12662
13483
|
let out = "";
|
|
12663
|
-
let i =
|
|
13484
|
+
let i = start2 + 1;
|
|
12664
13485
|
const n = sql.length;
|
|
12665
13486
|
for (; ; ) {
|
|
12666
13487
|
if (i >= n) syntaxError("unterminated quoted string");
|
|
@@ -16400,14 +17221,14 @@ var Parser = class {
|
|
|
16400
17221
|
let frame = null;
|
|
16401
17222
|
if (this.atKw("range") || this.atKw("rows") || this.atKw("groups")) {
|
|
16402
17223
|
const mode = this.next().value;
|
|
16403
|
-
let
|
|
17224
|
+
let start2;
|
|
16404
17225
|
let end = null;
|
|
16405
17226
|
if (this.eatKw("between")) {
|
|
16406
|
-
|
|
17227
|
+
start2 = this.parseFrameBound();
|
|
16407
17228
|
this.expectKw("and");
|
|
16408
17229
|
end = this.parseFrameBound();
|
|
16409
17230
|
} else {
|
|
16410
|
-
|
|
17231
|
+
start2 = this.parseFrameBound();
|
|
16411
17232
|
}
|
|
16412
17233
|
let exclusion = null;
|
|
16413
17234
|
if (this.eatKw("exclude")) {
|
|
@@ -16424,7 +17245,7 @@ var Parser = class {
|
|
|
16424
17245
|
exclusion = "no_others";
|
|
16425
17246
|
}
|
|
16426
17247
|
}
|
|
16427
|
-
frame = { mode, start, end, exclusion };
|
|
17248
|
+
frame = { mode, start: start2, end, exclusion };
|
|
16428
17249
|
}
|
|
16429
17250
|
return { name, partitionBy, orderBy, frame };
|
|
16430
17251
|
}
|
|
@@ -16670,7 +17491,7 @@ var PlParser = class {
|
|
|
16670
17491
|
return { kind: "assign", target, expr: this.parseExprUntilSemi() };
|
|
16671
17492
|
}
|
|
16672
17493
|
parseSqlTextUntilSemi() {
|
|
16673
|
-
const
|
|
17494
|
+
const start2 = this.peek().pos;
|
|
16674
17495
|
let depth = 0;
|
|
16675
17496
|
for (; ; ) {
|
|
16676
17497
|
const t = this.peek();
|
|
@@ -16678,7 +17499,7 @@ var PlParser = class {
|
|
|
16678
17499
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16679
17500
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16680
17501
|
if (t.type === "punct" && t.value === ";" && depth === 0) {
|
|
16681
|
-
const text = this.src.slice(
|
|
17502
|
+
const text = this.src.slice(start2, t.pos).trim();
|
|
16682
17503
|
this.pos++;
|
|
16683
17504
|
return text;
|
|
16684
17505
|
}
|
|
@@ -16686,7 +17507,7 @@ var PlParser = class {
|
|
|
16686
17507
|
}
|
|
16687
17508
|
}
|
|
16688
17509
|
parseExprUntilSemi() {
|
|
16689
|
-
const
|
|
17510
|
+
const start2 = this.peek().pos;
|
|
16690
17511
|
let depth = 0;
|
|
16691
17512
|
for (; ; ) {
|
|
16692
17513
|
const t = this.peek();
|
|
@@ -16694,7 +17515,7 @@ var PlParser = class {
|
|
|
16694
17515
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16695
17516
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16696
17517
|
if (t.type === "punct" && t.value === ";" && depth === 0) {
|
|
16697
|
-
const text = this.src.slice(
|
|
17518
|
+
const text = this.src.slice(start2, t.pos);
|
|
16698
17519
|
this.pos++;
|
|
16699
17520
|
return parseSqlExpr(text);
|
|
16700
17521
|
}
|
|
@@ -16702,7 +17523,7 @@ var PlParser = class {
|
|
|
16702
17523
|
}
|
|
16703
17524
|
}
|
|
16704
17525
|
parseExprUntilKw(kw) {
|
|
16705
|
-
const
|
|
17526
|
+
const start2 = this.peek().pos;
|
|
16706
17527
|
let depth = 0;
|
|
16707
17528
|
let caseDepth = 0;
|
|
16708
17529
|
for (; ; ) {
|
|
@@ -16713,7 +17534,7 @@ var PlParser = class {
|
|
|
16713
17534
|
if (t.type === "ident" && t.value === "case" && depth === 0) caseDepth++;
|
|
16714
17535
|
if (t.type === "ident" && t.value === "end" && depth === 0 && caseDepth > 0) caseDepth--;
|
|
16715
17536
|
if (t.type === "ident" && t.value === kw && depth === 0 && caseDepth === 0) {
|
|
16716
|
-
const text = this.src.slice(
|
|
17537
|
+
const text = this.src.slice(start2, t.pos);
|
|
16717
17538
|
this.pos++;
|
|
16718
17539
|
return parseSqlExpr(text);
|
|
16719
17540
|
}
|
|
@@ -16721,7 +17542,7 @@ var PlParser = class {
|
|
|
16721
17542
|
}
|
|
16722
17543
|
}
|
|
16723
17544
|
parseQueryUntilKw(kw) {
|
|
16724
|
-
const
|
|
17545
|
+
const start2 = this.peek().pos;
|
|
16725
17546
|
let depth = 0;
|
|
16726
17547
|
for (; ; ) {
|
|
16727
17548
|
const t = this.peek();
|
|
@@ -16729,7 +17550,7 @@ var PlParser = class {
|
|
|
16729
17550
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16730
17551
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16731
17552
|
if (t.type === "ident" && t.value === kw && depth === 0) {
|
|
16732
|
-
const text = this.src.slice(
|
|
17553
|
+
const text = this.src.slice(start2, t.pos).trim();
|
|
16733
17554
|
this.pos++;
|
|
16734
17555
|
const stmts = parse(text);
|
|
16735
17556
|
const q = stmts[0];
|
|
@@ -18272,7 +19093,7 @@ function buildFrom(env, items, where = null) {
|
|
|
18272
19093
|
}
|
|
18273
19094
|
}
|
|
18274
19095
|
if (cols === null) {
|
|
18275
|
-
const
|
|
19096
|
+
const probe2 = materializeItem(
|
|
18276
19097
|
env,
|
|
18277
19098
|
item,
|
|
18278
19099
|
new RowScope(
|
|
@@ -18282,8 +19103,8 @@ function buildFrom(env, items, where = null) {
|
|
|
18282
19103
|
accVars
|
|
18283
19104
|
)
|
|
18284
19105
|
);
|
|
18285
|
-
cols = [...accRel.columns, ...
|
|
18286
|
-
vars = /* @__PURE__ */ new Set([...accVars, ...
|
|
19106
|
+
cols = [...accRel.columns, ...probe2.rel.columns];
|
|
19107
|
+
vars = /* @__PURE__ */ new Set([...accVars, ...probe2.rangeVars]);
|
|
18287
19108
|
}
|
|
18288
19109
|
acc = { rel: { columns: cols, rows }, rangeVars: vars };
|
|
18289
19110
|
} else {
|
|
@@ -19043,13 +19864,13 @@ function expandTargets(core, columns) {
|
|
|
19043
19864
|
return items;
|
|
19044
19865
|
}
|
|
19045
19866
|
function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
|
|
19046
|
-
const
|
|
19867
|
+
const probe2 = new RowScope(
|
|
19047
19868
|
columns,
|
|
19048
19869
|
columns.map(() => null),
|
|
19049
19870
|
env.outer,
|
|
19050
19871
|
rangeVars
|
|
19051
19872
|
);
|
|
19052
|
-
checkBoolExprType(env.ctx, makeEvalScope(env,
|
|
19873
|
+
checkBoolExprType(env.ctx, makeEvalScope(env, probe2, extras), e, kind);
|
|
19053
19874
|
}
|
|
19054
19875
|
function executeCore(env0, core, orderBy, opts) {
|
|
19055
19876
|
const env = env0;
|
|
@@ -19139,8 +19960,8 @@ function executeCore(env0, core, orderBy, opts) {
|
|
|
19139
19960
|
const groupValues = groupDefs.map((d, di) => {
|
|
19140
19961
|
if (!activeSet[di]) {
|
|
19141
19962
|
if (rows.length > 0) {
|
|
19142
|
-
const
|
|
19143
|
-
return tv(
|
|
19963
|
+
const probe2 = evalScalar(env, repScope, d.expr);
|
|
19964
|
+
return tv(probe2.t, null);
|
|
19144
19965
|
}
|
|
19145
19966
|
return tv(UNKNOWN, null);
|
|
19146
19967
|
}
|
|
@@ -20293,8 +21114,8 @@ function executeTransaction(env, stmt) {
|
|
|
20293
21114
|
var defaultSettings = null;
|
|
20294
21115
|
function getDefaultSettings() {
|
|
20295
21116
|
if (!defaultSettings) {
|
|
20296
|
-
const
|
|
20297
|
-
defaultSettings = new Map(
|
|
21117
|
+
const probe2 = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
|
|
21118
|
+
defaultSettings = new Map(probe2.settings);
|
|
20298
21119
|
}
|
|
20299
21120
|
return defaultSettings;
|
|
20300
21121
|
}
|
|
@@ -20561,7 +21382,7 @@ function parseCsvLine(line, delim, quote) {
|
|
|
20561
21382
|
}
|
|
20562
21383
|
function splitCsvRecords(data, quote) {
|
|
20563
21384
|
const records = [];
|
|
20564
|
-
let
|
|
21385
|
+
let start2 = 0;
|
|
20565
21386
|
let quoted = false;
|
|
20566
21387
|
for (let i = 0; i < data.length; i++) {
|
|
20567
21388
|
const char = data[i];
|
|
@@ -20571,13 +21392,13 @@ function splitCsvRecords(data, quote) {
|
|
|
20571
21392
|
continue;
|
|
20572
21393
|
}
|
|
20573
21394
|
if (char === "\n" && !quoted) {
|
|
20574
|
-
const end = i >
|
|
20575
|
-
records.push(data.slice(
|
|
20576
|
-
|
|
21395
|
+
const end = i > start2 && data[i - 1] === "\r" ? i - 1 : i;
|
|
21396
|
+
records.push(data.slice(start2, end));
|
|
21397
|
+
start2 = i + 1;
|
|
20577
21398
|
}
|
|
20578
21399
|
}
|
|
20579
21400
|
if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
20580
|
-
if (
|
|
21401
|
+
if (start2 < data.length) records.push(data.slice(start2));
|
|
20581
21402
|
return records;
|
|
20582
21403
|
}
|
|
20583
21404
|
function executeCopyFromData(env, stmt, data) {
|
|
@@ -23735,6 +24556,16 @@ var Cluster = class {
|
|
|
23735
24556
|
databaseNames() {
|
|
23736
24557
|
return [...this.databases.keys()].sort();
|
|
23737
24558
|
}
|
|
24559
|
+
snapshotAll() {
|
|
24560
|
+
return new Map([...this.databases].map(([name, db]) => [name, db.snapshot()]));
|
|
24561
|
+
}
|
|
24562
|
+
restoreAll(points) {
|
|
24563
|
+
if (this.sessions.size > 0) throw new Error("cannot restore a catalog with active sessions");
|
|
24564
|
+
if (!points.has(this.defaultDatabaseName)) throw new Error("snapshot lacks default database");
|
|
24565
|
+
for (const db of this.databases.values()) db.close();
|
|
24566
|
+
this.databases.clear();
|
|
24567
|
+
for (const [name, point] of points) this.installDatabase(name, point.open());
|
|
24568
|
+
}
|
|
23738
24569
|
getDatabase(name) {
|
|
23739
24570
|
return this.databases.get(name);
|
|
23740
24571
|
}
|
|
@@ -23747,7 +24578,7 @@ var Cluster = class {
|
|
|
23747
24578
|
if (this.databases.has(name)) {
|
|
23748
24579
|
throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
|
|
23749
24580
|
}
|
|
23750
|
-
const db = template === void 0 ? new Database() : this.requireTemplate(template).snapshot().open();
|
|
24581
|
+
const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
|
|
23751
24582
|
this.installDatabase(name, db);
|
|
23752
24583
|
}
|
|
23753
24584
|
dropDatabase(name, requester) {
|
|
@@ -23811,8 +24642,8 @@ var Cluster = class {
|
|
|
23811
24642
|
if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
|
|
23812
24643
|
return Promise.reject(deadlock());
|
|
23813
24644
|
}
|
|
23814
|
-
return new Promise((
|
|
23815
|
-
const waiter = { session, resolve, reject };
|
|
24645
|
+
return new Promise((resolve2, reject) => {
|
|
24646
|
+
const waiter = { session, resolve: resolve2, reject };
|
|
23816
24647
|
session.waitingForTurn = waiter;
|
|
23817
24648
|
this.turnQueue.push(waiter);
|
|
23818
24649
|
});
|
|
@@ -23843,8 +24674,8 @@ var Cluster = class {
|
|
|
23843
24674
|
if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
|
|
23844
24675
|
return Promise.reject(deadlock());
|
|
23845
24676
|
}
|
|
23846
|
-
return new Promise((
|
|
23847
|
-
const waiter = { session, resolve, reject };
|
|
24677
|
+
return new Promise((resolve2, reject) => {
|
|
24678
|
+
const waiter = { session, resolve: resolve2, reject };
|
|
23848
24679
|
session.waitingForLock = key;
|
|
23849
24680
|
const queue = this.lockQueues.get(key) ?? [];
|
|
23850
24681
|
queue.push(waiter);
|
|
@@ -24103,9 +24934,9 @@ var backend = {
|
|
|
24103
24934
|
no: () => new Uint8Array([78])
|
|
24104
24935
|
};
|
|
24105
24936
|
var ByteReader = class {
|
|
24106
|
-
constructor(bytes,
|
|
24937
|
+
constructor(bytes, start2 = 0) {
|
|
24107
24938
|
this.bytes = bytes;
|
|
24108
|
-
this.pos =
|
|
24939
|
+
this.pos = start2;
|
|
24109
24940
|
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
24110
24941
|
}
|
|
24111
24942
|
bytes;
|
|
@@ -24265,11 +25096,11 @@ var ScramServer = class {
|
|
|
24265
25096
|
// src/wire/split.ts
|
|
24266
25097
|
var splitStatements = (script) => {
|
|
24267
25098
|
const out = [];
|
|
24268
|
-
let
|
|
25099
|
+
let start2 = 0;
|
|
24269
25100
|
let i = 0;
|
|
24270
25101
|
const n = script.length;
|
|
24271
25102
|
const push = (end) => {
|
|
24272
|
-
const text = script.slice(
|
|
25103
|
+
const text = script.slice(start2, end).trim();
|
|
24273
25104
|
if (text.length > 0) out.push(text);
|
|
24274
25105
|
};
|
|
24275
25106
|
while (i < n) {
|
|
@@ -24325,7 +25156,7 @@ var splitStatements = (script) => {
|
|
|
24325
25156
|
}
|
|
24326
25157
|
if (c === ";") {
|
|
24327
25158
|
push(i);
|
|
24328
|
-
|
|
25159
|
+
start2 = i + 1;
|
|
24329
25160
|
}
|
|
24330
25161
|
i++;
|
|
24331
25162
|
}
|
|
@@ -24786,8 +25617,8 @@ var Connection = class {
|
|
|
24786
25617
|
"25P02"
|
|
24787
25618
|
);
|
|
24788
25619
|
}
|
|
24789
|
-
const
|
|
24790
|
-
if (
|
|
25620
|
+
const listen2 = LISTEN.exec(text);
|
|
25621
|
+
if (listen2) return this.listen(listen2[2]);
|
|
24791
25622
|
const unlisten = UNLISTEN.exec(text);
|
|
24792
25623
|
if (unlisten) return this.unlisten(unlisten[1].replaceAll('"', ""));
|
|
24793
25624
|
const notify = NOTIFY.exec(text);
|
|
@@ -24804,7 +25635,7 @@ var Connection = class {
|
|
|
24804
25635
|
if (faults.delayStatementMs !== void 0) {
|
|
24805
25636
|
const ms = faults.delayStatementMs;
|
|
24806
25637
|
faults.delayStatementMs = void 0;
|
|
24807
|
-
await new Promise((
|
|
25638
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
24808
25639
|
}
|
|
24809
25640
|
if (faults.dropConnection && this.inTransaction) {
|
|
24810
25641
|
faults.dropConnection = void 0;
|
|
@@ -24828,7 +25659,7 @@ var Connection = class {
|
|
|
24828
25659
|
}
|
|
24829
25660
|
}
|
|
24830
25661
|
waitForLock(key) {
|
|
24831
|
-
return new Promise((
|
|
25662
|
+
return new Promise((resolve2, reject) => {
|
|
24832
25663
|
this.lockWaiter = (error) => {
|
|
24833
25664
|
this.lockWaiter = null;
|
|
24834
25665
|
this.waitingForLock = null;
|
|
@@ -24837,7 +25668,7 @@ var Connection = class {
|
|
|
24837
25668
|
this.cluster.waitLock(this, key).then(
|
|
24838
25669
|
() => {
|
|
24839
25670
|
this.lockWaiter = null;
|
|
24840
|
-
|
|
25671
|
+
resolve2();
|
|
24841
25672
|
},
|
|
24842
25673
|
(error) => {
|
|
24843
25674
|
this.lockWaiter = null;
|
|
@@ -25029,10 +25860,10 @@ var Connection = class {
|
|
|
25029
25860
|
this.cluster.dropDatabase(name, this);
|
|
25030
25861
|
return command("DROP DATABASE");
|
|
25031
25862
|
}
|
|
25032
|
-
const
|
|
25033
|
-
if (
|
|
25034
|
-
const from = identifier(
|
|
25035
|
-
const to = identifier(
|
|
25863
|
+
const rename2 = RENAME_DATABASE.exec(text);
|
|
25864
|
+
if (rename2) {
|
|
25865
|
+
const from = identifier(rename2[1], rename2[2]);
|
|
25866
|
+
const to = identifier(rename2[3], rename2[4]);
|
|
25036
25867
|
this.cluster.renameDatabase(from, to, this);
|
|
25037
25868
|
return command("ALTER DATABASE");
|
|
25038
25869
|
}
|
|
@@ -25455,7 +26286,7 @@ var normalize = (input) => {
|
|
|
25455
26286
|
};
|
|
25456
26287
|
};
|
|
25457
26288
|
var isSnapshot = (value) => typeof value === "object" && value !== null && "open" in value && typeof value.open === "function";
|
|
25458
|
-
var
|
|
26289
|
+
var serve2 = (input = {}) => {
|
|
25459
26290
|
const options2 = normalize(input);
|
|
25460
26291
|
const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
|
|
25461
26292
|
const initialName = options2.databaseName ?? "postgres";
|
|
@@ -25464,20 +26295,20 @@ var serve = (input = {}) => {
|
|
|
25464
26295
|
const connections = /* @__PURE__ */ new Set();
|
|
25465
26296
|
const host = options2.host ?? "127.0.0.1";
|
|
25466
26297
|
const user = options2.user || "postgres";
|
|
25467
|
-
const server2 =
|
|
26298
|
+
const server2 = createServer2((socket) => {
|
|
25468
26299
|
socket.setNoDelay(true);
|
|
25469
26300
|
const connection = new Connection(socket, cluster, {
|
|
25470
26301
|
...options2.password !== void 0 ? { password: options2.password } : {},
|
|
25471
26302
|
serverVersion: options2.serverVersion ?? "18.3",
|
|
25472
26303
|
parameters: options2.parameters ?? {},
|
|
25473
26304
|
faults,
|
|
25474
|
-
...options2.databaseName !== void 0 ? { database: options2.databaseName } : {},
|
|
26305
|
+
...options2.databaseName !== void 0 && !options2.allowDatabaseSelection ? { database: options2.databaseName } : {},
|
|
25475
26306
|
...options2.onLog ? { onLog: options2.onLog } : {}
|
|
25476
26307
|
});
|
|
25477
26308
|
connections.add(connection);
|
|
25478
26309
|
socket.on("close", () => connections.delete(connection));
|
|
25479
26310
|
});
|
|
25480
|
-
return new Promise((
|
|
26311
|
+
return new Promise((resolve2, reject) => {
|
|
25481
26312
|
server2.once("error", reject);
|
|
25482
26313
|
server2.listen(options2.port ?? 0, host, () => {
|
|
25483
26314
|
server2.removeListener("error", reject);
|
|
@@ -25485,11 +26316,13 @@ var serve = (input = {}) => {
|
|
|
25485
26316
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
25486
26317
|
const name = initialName;
|
|
25487
26318
|
const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
|
|
25488
|
-
|
|
26319
|
+
resolve2({
|
|
25489
26320
|
port,
|
|
25490
26321
|
host,
|
|
25491
26322
|
connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
|
|
25492
|
-
database
|
|
26323
|
+
get database() {
|
|
26324
|
+
return cluster.db;
|
|
26325
|
+
},
|
|
25493
26326
|
databaseNames() {
|
|
25494
26327
|
return cluster.databaseNames();
|
|
25495
26328
|
},
|
|
@@ -25503,6 +26336,14 @@ var serve = (input = {}) => {
|
|
|
25503
26336
|
fault(next) {
|
|
25504
26337
|
Object.assign(faults, next);
|
|
25505
26338
|
},
|
|
26339
|
+
clearFaults() {
|
|
26340
|
+
for (const key of Object.keys(faults)) delete faults[key];
|
|
26341
|
+
},
|
|
26342
|
+
faultState() {
|
|
26343
|
+
return structuredClone(faults);
|
|
26344
|
+
},
|
|
26345
|
+
snapshotAll: () => cluster.snapshotAll(),
|
|
26346
|
+
restoreAll: (points) => cluster.restoreAll(points),
|
|
25506
26347
|
snapshot(databaseName = initialName) {
|
|
25507
26348
|
return cluster.requireDatabase(databaseName).snapshot();
|
|
25508
26349
|
},
|
|
@@ -25518,9 +26359,251 @@ var serve = (input = {}) => {
|
|
|
25518
26359
|
});
|
|
25519
26360
|
};
|
|
25520
26361
|
|
|
26362
|
+
// src/wire/probe.ts
|
|
26363
|
+
import { createHash as createHash2, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
|
|
26364
|
+
import { createConnection } from "node:net";
|
|
26365
|
+
function probe(connectionString) {
|
|
26366
|
+
const url = new URL(connectionString);
|
|
26367
|
+
return new Promise((resolve2) => {
|
|
26368
|
+
const socket = createConnection({ host: url.hostname, port: Number(url.port) });
|
|
26369
|
+
let buffer = Buffer.alloc(0);
|
|
26370
|
+
let result = false;
|
|
26371
|
+
let querying = false;
|
|
26372
|
+
let row = false;
|
|
26373
|
+
const nonce = randomBytes2(18).toString("base64");
|
|
26374
|
+
const first = `n=,r=${nonce}`;
|
|
26375
|
+
const finish = (ok) => {
|
|
26376
|
+
result = ok;
|
|
26377
|
+
socket.destroy();
|
|
26378
|
+
};
|
|
26379
|
+
const timeout = setTimeout(() => finish(false), 2e3);
|
|
26380
|
+
const send = (type, body) => {
|
|
26381
|
+
const header = Buffer.alloc(5);
|
|
26382
|
+
header.write(type);
|
|
26383
|
+
header.writeInt32BE(body.length + 4, 1);
|
|
26384
|
+
socket.write(Buffer.concat([header, body]));
|
|
26385
|
+
};
|
|
26386
|
+
socket.once("close", () => {
|
|
26387
|
+
clearTimeout(timeout);
|
|
26388
|
+
resolve2(result);
|
|
26389
|
+
});
|
|
26390
|
+
socket.on("error", () => finish(false));
|
|
26391
|
+
socket.once("connect", () => {
|
|
26392
|
+
const parameters = Buffer.from(
|
|
26393
|
+
`user\0${decodeURIComponent(url.username)}\0database\0${decodeURIComponent(url.pathname.slice(1))}\0\0`
|
|
26394
|
+
);
|
|
26395
|
+
const header = Buffer.alloc(8);
|
|
26396
|
+
header.writeInt32BE(parameters.length + 8);
|
|
26397
|
+
header.writeInt32BE(196608, 4);
|
|
26398
|
+
socket.write(Buffer.concat([header, parameters]));
|
|
26399
|
+
});
|
|
26400
|
+
socket.on("data", (data) => {
|
|
26401
|
+
buffer = Buffer.concat([buffer, data]);
|
|
26402
|
+
while (buffer.length >= 5) {
|
|
26403
|
+
const length = buffer.readInt32BE(1);
|
|
26404
|
+
if (length < 4 || length > 1e6) {
|
|
26405
|
+
finish(false);
|
|
26406
|
+
return;
|
|
26407
|
+
}
|
|
26408
|
+
if (buffer.length < length + 1) return;
|
|
26409
|
+
const type = String.fromCharCode(buffer[0]);
|
|
26410
|
+
const body = buffer.subarray(5, length + 1);
|
|
26411
|
+
buffer = buffer.subarray(length + 1);
|
|
26412
|
+
if (type === "E") {
|
|
26413
|
+
finish(false);
|
|
26414
|
+
return;
|
|
26415
|
+
}
|
|
26416
|
+
if (type === "R") {
|
|
26417
|
+
const auth = body.readInt32BE();
|
|
26418
|
+
if (auth === 10) {
|
|
26419
|
+
const initial = Buffer.from(`n,,${first}`);
|
|
26420
|
+
const size = Buffer.alloc(4);
|
|
26421
|
+
size.writeInt32BE(initial.length);
|
|
26422
|
+
send("p", Buffer.concat([Buffer.from("SCRAM-SHA-256\0"), size, initial]));
|
|
26423
|
+
} else if (auth === 11) {
|
|
26424
|
+
const serverFirst = body.subarray(4).toString();
|
|
26425
|
+
const fields = Object.fromEntries(serverFirst.split(",").map((field) => [field[0], field.slice(2)]));
|
|
26426
|
+
if (!fields.r?.startsWith(nonce) || !fields.s || fields.i !== "4096") {
|
|
26427
|
+
finish(false);
|
|
26428
|
+
return;
|
|
26429
|
+
}
|
|
26430
|
+
const salted = pbkdf2Sync2(
|
|
26431
|
+
decodeURIComponent(url.password),
|
|
26432
|
+
Buffer.from(fields.s, "base64"),
|
|
26433
|
+
4096,
|
|
26434
|
+
32,
|
|
26435
|
+
"sha256"
|
|
26436
|
+
);
|
|
26437
|
+
const key = createHmac2("sha256", salted).update("Client Key").digest();
|
|
26438
|
+
const stored = createHash2("sha256").update(key).digest();
|
|
26439
|
+
const final = `c=biws,r=${fields.r}`;
|
|
26440
|
+
const signature = createHmac2("sha256", stored).update(`${first},${serverFirst},${final}`).digest();
|
|
26441
|
+
const proof = Buffer.from(key.map((value, index) => value ^ signature[index]));
|
|
26442
|
+
send("p", Buffer.from(`${final},p=${proof.toString("base64")}`));
|
|
26443
|
+
} else if (auth !== 0 && auth !== 12) {
|
|
26444
|
+
finish(false);
|
|
26445
|
+
return;
|
|
26446
|
+
}
|
|
26447
|
+
}
|
|
26448
|
+
if (type === "D") row = body.readInt16BE() === 1 && body.subarray(6).toString() === "1";
|
|
26449
|
+
if (type === "Z") {
|
|
26450
|
+
if (querying) {
|
|
26451
|
+
finish(row);
|
|
26452
|
+
return;
|
|
26453
|
+
}
|
|
26454
|
+
querying = true;
|
|
26455
|
+
send("Q", Buffer.from("SELECT 1\0"));
|
|
26456
|
+
}
|
|
26457
|
+
}
|
|
26458
|
+
});
|
|
26459
|
+
});
|
|
26460
|
+
}
|
|
26461
|
+
|
|
26462
|
+
// src/wire/fleet.ts
|
|
26463
|
+
async function startProtocol(entry) {
|
|
26464
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
26465
|
+
const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
|
|
26466
|
+
try {
|
|
26467
|
+
for (const [name, databaseName] of Object.entries(selectors)) {
|
|
26468
|
+
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
|
|
26469
|
+
throw new Error("invalid Postgres namespace/database name");
|
|
26470
|
+
const clock = createClock();
|
|
26471
|
+
const database = new Database({
|
|
26472
|
+
now: () => new Date(clock.now()),
|
|
26473
|
+
...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
|
|
26474
|
+
});
|
|
26475
|
+
const server2 = await serve2({
|
|
26476
|
+
database,
|
|
26477
|
+
databaseName,
|
|
26478
|
+
allowDatabaseSelection: true,
|
|
26479
|
+
host: entry.host ?? "127.0.0.1",
|
|
26480
|
+
port: name === "default" ? entry.port ?? 0 : 0,
|
|
26481
|
+
...entry.password !== void 0 ? { password: entry.password } : {},
|
|
26482
|
+
...entry.user !== void 0 ? { user: entry.user } : {}
|
|
26483
|
+
});
|
|
26484
|
+
namespaces.set(name, { server: server2, clock });
|
|
26485
|
+
}
|
|
26486
|
+
} catch (error) {
|
|
26487
|
+
await Promise.allSettled([...namespaces.values()].map(({ server: server2 }) => server2.close()));
|
|
26488
|
+
throw error;
|
|
26489
|
+
}
|
|
26490
|
+
const get = (name) => {
|
|
26491
|
+
const found = namespaces.get(name);
|
|
26492
|
+
if (!found) throw new Error(`Postgres namespace ${name} is not configured`);
|
|
26493
|
+
return found;
|
|
26494
|
+
};
|
|
26495
|
+
const publicUrl2 = (name) => {
|
|
26496
|
+
const url = new URL(get(name).server.connectionString);
|
|
26497
|
+
url.password = "";
|
|
26498
|
+
return url.href;
|
|
26499
|
+
};
|
|
26500
|
+
const locked = /* @__PURE__ */ new Set();
|
|
26501
|
+
const snapshots = /* @__PURE__ */ new WeakMap();
|
|
26502
|
+
const origins = new Map([...namespaces].map(([name, { server: server2 }]) => [name, server2.snapshotAll()]));
|
|
26503
|
+
return {
|
|
26504
|
+
protocol: "postgres",
|
|
26505
|
+
url: publicUrl2("default"),
|
|
26506
|
+
connection: get("default").server.connectionString,
|
|
26507
|
+
connections: Object.fromEntries([...namespaces].map(([name, { server: server2 }]) => [name, server2.connectionString])),
|
|
26508
|
+
namespaces: {
|
|
26509
|
+
mechanism: "endpoint",
|
|
26510
|
+
endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl2(name)]))
|
|
26511
|
+
},
|
|
26512
|
+
async ready() {
|
|
26513
|
+
const results = await Promise.all(
|
|
26514
|
+
[...namespaces.values()].map(
|
|
26515
|
+
({ server: server2 }) => server2.server.listening ? probe(server2.connectionString) : false
|
|
26516
|
+
)
|
|
26517
|
+
);
|
|
26518
|
+
return results.every(Boolean);
|
|
26519
|
+
},
|
|
26520
|
+
async close() {
|
|
26521
|
+
await Promise.all(
|
|
26522
|
+
[...namespaces.values()].map(async ({ server: server2 }) => {
|
|
26523
|
+
await server2.close();
|
|
26524
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).close();
|
|
26525
|
+
})
|
|
26526
|
+
);
|
|
26527
|
+
},
|
|
26528
|
+
lock(namespace) {
|
|
26529
|
+
const { server: server2 } = get(namespace);
|
|
26530
|
+
if (locked.has(namespace)) throw new Error("Postgres namespace already locked");
|
|
26531
|
+
locked.add(namespace);
|
|
26532
|
+
const reject = (socket) => socket.destroy();
|
|
26533
|
+
server2.server.prependListener("connection", reject);
|
|
26534
|
+
return () => {
|
|
26535
|
+
locked.delete(namespace);
|
|
26536
|
+
server2.server.off("connection", reject);
|
|
26537
|
+
};
|
|
26538
|
+
},
|
|
26539
|
+
diagnostics(namespace) {
|
|
26540
|
+
const { server: server2 } = get(namespace);
|
|
26541
|
+
return {
|
|
26542
|
+
activeRequests: server2.connections,
|
|
26543
|
+
activeConnections: server2.connections,
|
|
26544
|
+
blockedCommands: 0,
|
|
26545
|
+
pendingJobs: 0,
|
|
26546
|
+
pendingWebhooks: 0,
|
|
26547
|
+
unmatchedRequests: 0
|
|
26548
|
+
};
|
|
26549
|
+
},
|
|
26550
|
+
async checkpoint(namespace) {
|
|
26551
|
+
const { server: server2, clock } = get(namespace);
|
|
26552
|
+
const handle = {};
|
|
26553
|
+
snapshots.set(handle, {
|
|
26554
|
+
namespace,
|
|
26555
|
+
databases: server2.snapshotAll(),
|
|
26556
|
+
clock: clock.state(),
|
|
26557
|
+
faults: server2.faultState()
|
|
26558
|
+
});
|
|
26559
|
+
return handle;
|
|
26560
|
+
},
|
|
26561
|
+
async restore(namespace, handle) {
|
|
26562
|
+
const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
|
|
26563
|
+
if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
|
|
26564
|
+
const { server: server2, clock } = get(namespace);
|
|
26565
|
+
server2.restoreAll(point.databases);
|
|
26566
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
|
|
26567
|
+
server2.clearFaults();
|
|
26568
|
+
server2.fault(point.faults);
|
|
26569
|
+
clock.freeze();
|
|
26570
|
+
clock.set(point.clock.now);
|
|
26571
|
+
if (!point.clock.frozen) clock.unfreeze();
|
|
26572
|
+
},
|
|
26573
|
+
async reset(namespace) {
|
|
26574
|
+
const { server: server2, clock } = get(namespace);
|
|
26575
|
+
server2.restoreAll(origins.get(namespace));
|
|
26576
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
|
|
26577
|
+
server2.clearFaults();
|
|
26578
|
+
clock.reset();
|
|
26579
|
+
},
|
|
26580
|
+
clock: (namespace) => get(namespace).clock
|
|
26581
|
+
};
|
|
26582
|
+
}
|
|
26583
|
+
|
|
26584
|
+
// src/server.ts
|
|
26585
|
+
var serveTarget = {
|
|
26586
|
+
name: "postgres",
|
|
26587
|
+
protocol: "postgres",
|
|
26588
|
+
defaultPort: 0,
|
|
26589
|
+
start: startProtocol
|
|
26590
|
+
};
|
|
26591
|
+
|
|
25521
26592
|
// src/wire/cli.ts
|
|
25522
26593
|
var usage = `mockingbird-postgres serve [postgres://URI] [--port <n>] [--host <h>] [--password <p>] [--server-version <v>] [--log]`;
|
|
25523
26594
|
var args = process.argv.slice(2);
|
|
26595
|
+
if (args[0] === "serve" && args.includes("--config")) {
|
|
26596
|
+
process.exit(
|
|
26597
|
+
await runCli(
|
|
26598
|
+
{
|
|
26599
|
+
bin: "mockingbird-postgres",
|
|
26600
|
+
description: "Postgres and fleet server",
|
|
26601
|
+
commands: { serve: serveCommand(serveTarget) }
|
|
26602
|
+
},
|
|
26603
|
+
args
|
|
26604
|
+
)
|
|
26605
|
+
);
|
|
26606
|
+
}
|
|
25524
26607
|
if (args[0] !== "serve") {
|
|
25525
26608
|
console.error(usage);
|
|
25526
26609
|
process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 2);
|
|
@@ -25569,7 +26652,7 @@ if (log) {
|
|
|
25569
26652
|
`[pid ${entry.pid}] ${entry.status} ${entry.durationMs.toFixed(1)}ms ${entry.sql.replace(/\s+/g, " ").slice(0, 200)}`
|
|
25570
26653
|
);
|
|
25571
26654
|
}
|
|
25572
|
-
var server = await
|
|
26655
|
+
var server = await serve2(options);
|
|
25573
26656
|
console.log(`mockingbird-postgres listening on ${server.connectionString}`);
|
|
25574
26657
|
var stop = async () => {
|
|
25575
26658
|
await server.close();
|