@crvouga/mockingbird-service-postgres 1.7.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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}/__admin`,
413
- namespaces: { header: "x-mockingbird-namespace", path: "/ns/{name}" },
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
- if (url.pathname === "/health" && request.method === "GET") {
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 (!url.pathname.startsWith("/__fleet/"))
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 = /^\/__fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(url.pathname);
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" && url.pathname === "/__fleet/metrics")
599
+ if (request.method === "GET" && path === "/fleet/metrics")
589
600
  return json(200, { namespace, services: diagnostics(namespace) });
590
- if (request.method === "GET" && url.pathname === "/__fleet/clock")
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 (url.pathname === "/__fleet/wait-until-idle") {
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 (url.pathname === "/__fleet/clock") {
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, { ...entry, ...adminKey !== void 0 ? { adminKey } : {} }, options2);
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}/__fleet`,
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}/__fleet`,
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}/__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`);
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 } : {},