@crvouga/mockingbird-service-postgres 1.7.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 +19 -1
- package/DISCOVERY.md +48 -0
- package/dist/admin.js +565 -222
- package/dist/admin.js.map +3 -3
- package/dist/wire/cli.js +49 -24
- package/dist/wire/cli.js.map +3 -3
- package/dist/wire/fleet.d.ts +1 -0
- package/package.json +19 -2
package/dist/wire/cli.js
CHANGED
|
@@ -7,10 +7,6 @@ import { resolve } from "node:path";
|
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
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
10
|
// ../core/dist/clock.js
|
|
15
11
|
var createClock = (source = Date.now) => {
|
|
16
12
|
let offsetMs = 0;
|
|
@@ -48,6 +44,13 @@ var createClock = (source = Date.now) => {
|
|
|
48
44
|
};
|
|
49
45
|
|
|
50
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
|
+
};
|
|
51
54
|
var UNITS = {
|
|
52
55
|
ms: 1,
|
|
53
56
|
s: 1e3,
|
|
@@ -227,6 +230,10 @@ var Timeline = class {
|
|
|
227
230
|
}
|
|
228
231
|
};
|
|
229
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
|
+
|
|
230
237
|
// ../../adapters/node/dist/serve.js
|
|
231
238
|
import { createServer } from "node:http";
|
|
232
239
|
var serve = async (api, options2 = {}) => {
|
|
@@ -371,10 +378,12 @@ var removeOwned = async (path, id) => {
|
|
|
371
378
|
};
|
|
372
379
|
var httpChild = async (target, entry, options2) => {
|
|
373
380
|
const runtime = await target.create(entry.options ?? {}, {
|
|
381
|
+
adminPrefix: resolveAdminPrefix(entry.adminPrefix),
|
|
374
382
|
adminKey: entry.adminKey,
|
|
375
383
|
seed: entry.seed,
|
|
376
384
|
onLog: options2.onLog
|
|
377
385
|
});
|
|
386
|
+
const adminPrefix = resolveAdminPrefix(entry.adminPrefix);
|
|
378
387
|
runtime.isolateNamespaces();
|
|
379
388
|
const locked = /* @__PURE__ */ new Set();
|
|
380
389
|
const active = /* @__PURE__ */ new Map();
|
|
@@ -408,12 +417,12 @@ var httpChild = async (target, entry, options2) => {
|
|
|
408
417
|
return {
|
|
409
418
|
protocol: "http",
|
|
410
419
|
url: server2.url,
|
|
411
|
-
healthUrl: `${server2.url}/health`,
|
|
412
|
-
adminUrl: `${server2.url}
|
|
413
|
-
namespaces: { header: "x-mockingbird-namespace", path:
|
|
420
|
+
healthUrl: `${server2.url}${adminPrefix}/health`,
|
|
421
|
+
adminUrl: `${server2.url}${adminPrefix}`,
|
|
422
|
+
namespaces: { header: "x-mockingbird-namespace", path: `${adminPrefix}/ns/{name}` },
|
|
414
423
|
ready: async () => {
|
|
415
424
|
try {
|
|
416
|
-
return (await fetch(`${server2.url}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
|
|
425
|
+
return (await fetch(`${server2.url}${adminPrefix}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
|
|
417
426
|
} catch {
|
|
418
427
|
return false;
|
|
419
428
|
}
|
|
@@ -552,7 +561,9 @@ async function startFleet(config, options2) {
|
|
|
552
561
|
const api = {
|
|
553
562
|
async fetch(request) {
|
|
554
563
|
const url = new URL(request.url);
|
|
555
|
-
|
|
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") {
|
|
556
567
|
const states = await Promise.all([...children].map(async ([name, child]) => {
|
|
557
568
|
let protocolReady = false;
|
|
558
569
|
try {
|
|
@@ -576,18 +587,18 @@ async function startFleet(config, options2) {
|
|
|
576
587
|
services: Object.fromEntries(states)
|
|
577
588
|
});
|
|
578
589
|
}
|
|
579
|
-
if (!
|
|
590
|
+
if (!path.startsWith("/fleet/"))
|
|
580
591
|
return json(404, { error: "not found" });
|
|
581
592
|
const adminKey = config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
582
593
|
if (adminKey !== void 0 && request.headers.get("x-mockingbird-admin-key") !== adminKey)
|
|
583
594
|
return json(401, { error: "admin key required" });
|
|
584
|
-
const route = /^\/
|
|
595
|
+
const route = /^\/fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(path);
|
|
585
596
|
const namespace = route ? decodeURIComponent(route[1]) : url.searchParams.get("namespace") ?? request.headers.get("x-mockingbird-namespace") ?? config.namespace ?? "default";
|
|
586
597
|
if (!NS.test(namespace))
|
|
587
598
|
return json(400, { error: "invalid namespace" });
|
|
588
|
-
if (request.method === "GET" &&
|
|
599
|
+
if (request.method === "GET" && path === "/fleet/metrics")
|
|
589
600
|
return json(200, { namespace, services: diagnostics(namespace) });
|
|
590
|
-
if (request.method === "GET" &&
|
|
601
|
+
if (request.method === "GET" && path === "/fleet/clock")
|
|
591
602
|
return json(200, {
|
|
592
603
|
namespace,
|
|
593
604
|
...clock(namespace).state(),
|
|
@@ -611,12 +622,12 @@ async function startFleet(config, options2) {
|
|
|
611
622
|
} catch (error) {
|
|
612
623
|
return json(400, { error: error instanceof Error ? error.message : "bad selection" });
|
|
613
624
|
}
|
|
614
|
-
if (
|
|
625
|
+
if (path === "/fleet/wait-until-idle") {
|
|
615
626
|
const services = diagnostics(namespace);
|
|
616
627
|
const idle = Object.values(services).every((d) => pending(d) === 0);
|
|
617
628
|
return json(idle ? 200 : 409, { status: idle ? "idle" : "busy", namespace, services });
|
|
618
629
|
}
|
|
619
|
-
if (
|
|
630
|
+
if (path === "/fleet/clock") {
|
|
620
631
|
const current = clock(namespace);
|
|
621
632
|
let target = current.now();
|
|
622
633
|
if (body.set !== void 0) {
|
|
@@ -692,6 +703,7 @@ async function startFleet(config, options2) {
|
|
|
692
703
|
}
|
|
693
704
|
};
|
|
694
705
|
try {
|
|
706
|
+
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
695
707
|
if (!isObject(config.services) || Object.keys(config.services).length === 0)
|
|
696
708
|
throw new Error("config must contain at least one service");
|
|
697
709
|
for (const [name, entry] of Object.entries(config.services)) {
|
|
@@ -702,7 +714,11 @@ async function startFleet(config, options2) {
|
|
|
702
714
|
if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
|
|
703
715
|
throw new Error("configured protocol does not match target");
|
|
704
716
|
const adminKey = entry.adminKey ?? config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
705
|
-
const child = "start" in target ? await target.start(entry) : await httpChild(target, {
|
|
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);
|
|
706
722
|
children.set(name, child);
|
|
707
723
|
if (!await child.ready())
|
|
708
724
|
throw new Error("protocol readiness failed");
|
|
@@ -720,15 +736,15 @@ async function startFleet(config, options2) {
|
|
|
720
736
|
pid: process.pid,
|
|
721
737
|
id,
|
|
722
738
|
startedAt,
|
|
723
|
-
adminBase: `${supervisor.url}/
|
|
724
|
-
healthUrl: `${supervisor.url}/health`,
|
|
739
|
+
adminBase: `${supervisor.url}${adminPrefix}/fleet`,
|
|
740
|
+
healthUrl: `${supervisor.url}${adminPrefix}/health`,
|
|
725
741
|
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
726
742
|
name,
|
|
727
743
|
{
|
|
728
744
|
protocol: child.protocol,
|
|
729
745
|
url: publicUrl(child.url),
|
|
730
|
-
healthUrl: child.healthUrl ?? `${supervisor?.url}/health`,
|
|
731
|
-
adminUrl: child.adminUrl ?? `${supervisor?.url}/
|
|
746
|
+
healthUrl: child.healthUrl ?? `${supervisor?.url}${adminPrefix}/health`,
|
|
747
|
+
adminUrl: child.adminUrl ?? `${supervisor?.url}${adminPrefix}/fleet`,
|
|
732
748
|
namespaces: child.namespaces,
|
|
733
749
|
processReady: true,
|
|
734
750
|
protocolReady: true
|
|
@@ -824,6 +840,11 @@ ${commandHelp(spec, name, command2)}`);
|
|
|
824
840
|
var COMMON_SERVE_OPTIONS = {
|
|
825
841
|
port: { type: "string", value: "<port>", description: "Port to listen on" },
|
|
826
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
|
+
},
|
|
827
848
|
"admin-key": {
|
|
828
849
|
type: "string",
|
|
829
850
|
value: "<key>",
|
|
@@ -897,7 +918,9 @@ var loadTarget = async (name, own) => {
|
|
|
897
918
|
}
|
|
898
919
|
};
|
|
899
920
|
var start = async (target, values, config) => {
|
|
921
|
+
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
900
922
|
const runtime = await target.create(values, {
|
|
923
|
+
adminPrefix,
|
|
901
924
|
adminKey: config.adminKey,
|
|
902
925
|
seed: config.seed,
|
|
903
926
|
onLog: formatLog(config.log)
|
|
@@ -905,11 +928,11 @@ var start = async (target, values, config) => {
|
|
|
905
928
|
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
906
929
|
target.listening?.(listening);
|
|
907
930
|
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}
|
|
910
|
-
console.log(`${target.name} admin ui: ${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`);
|
|
911
934
|
for (const line of target.banner?.(runtime) ?? [])
|
|
912
|
-
console.log(`${target.name} ${line}`);
|
|
935
|
+
console.log(`${target.name} ${line.replaceAll("/__admin", adminPrefix)}`);
|
|
913
936
|
return listening;
|
|
914
937
|
};
|
|
915
938
|
var untilSignal = async (servers) => new Promise((resolve2) => {
|
|
@@ -936,6 +959,7 @@ var serveCommand = (target) => ({
|
|
|
936
959
|
if (configPath !== void 0) {
|
|
937
960
|
try {
|
|
938
961
|
const config = JSON.parse(await readFile2(configPath, "utf8"));
|
|
962
|
+
config.adminPrefix = resolveAdminPrefix(config.adminPrefix ?? asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX);
|
|
939
963
|
const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
940
964
|
if (adminKey2 !== void 0)
|
|
941
965
|
config.adminKey = adminKey2;
|
|
@@ -968,6 +992,7 @@ var serveCommand = (target) => ({
|
|
|
968
992
|
let listening;
|
|
969
993
|
try {
|
|
970
994
|
listening = await start(target, values, {
|
|
995
|
+
adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
|
|
971
996
|
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
972
997
|
host: asString(values.host) ?? "127.0.0.1",
|
|
973
998
|
...adminKey !== void 0 ? { adminKey } : {},
|