@crvouga/mockingbird-service-postgres 3.4.1 → 3.5.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/server.js CHANGED
@@ -30533,6 +30533,9 @@ var start = async (options) => {
30533
30533
  });
30534
30534
  };
30535
30535
 
30536
+ // src/wire/fleet.ts
30537
+ import { createHash as createHash4 } from "node:crypto";
30538
+
30536
30539
  // ../core/dist/clock.js
30537
30540
  var createClock = (source = Date.now) => {
30538
30541
  let offsetMs = 0;
@@ -30574,6 +30577,310 @@ var createClock = (source = Date.now) => {
30574
30577
  };
30575
30578
  };
30576
30579
 
30580
+ // ../core/dist/controls.js
30581
+ var ControlError = class extends RangeError {
30582
+ constructor(message2) {
30583
+ super(message2);
30584
+ this.name = "ControlError";
30585
+ }
30586
+ };
30587
+ var UNITS = {
30588
+ ms: 1,
30589
+ s: 1e3,
30590
+ m: 6e4,
30591
+ h: 36e5,
30592
+ d: 864e5
30593
+ };
30594
+ var parseDuration = (value) => {
30595
+ if (typeof value === "number")
30596
+ return Number.isFinite(value) ? value : void 0;
30597
+ if (typeof value !== "string")
30598
+ return void 0;
30599
+ const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/.exec(value.trim());
30600
+ if (!match)
30601
+ return void 0;
30602
+ return Number(match[1]) * UNITS[match[2] ?? "ms"];
30603
+ };
30604
+ var parseInstant = (value) => {
30605
+ if (typeof value === "number")
30606
+ return Number.isFinite(value) ? value : void 0;
30607
+ if (typeof value !== "string")
30608
+ return void 0;
30609
+ const text = value.trim();
30610
+ if (/^-?\d+$/.test(text))
30611
+ return Number(text);
30612
+ const parsed = Date.parse(text);
30613
+ return Number.isNaN(parsed) ? void 0 : parsed;
30614
+ };
30615
+ var durationMs = (value, label, negative = false) => {
30616
+ const parsed = parseDuration(value);
30617
+ if (parsed === void 0)
30618
+ throw new ControlError(`${label}: expected milliseconds or a duration like "250ms" or "15m"`);
30619
+ if (!negative && parsed < 0)
30620
+ throw new ControlError(`${label}: must not be negative`);
30621
+ return parsed;
30622
+ };
30623
+ var instantMs = (value, label) => {
30624
+ const parsed = parseInstant(value);
30625
+ if (parsed === void 0)
30626
+ throw new ControlError(`${label}: expected epoch ms or ISO-8601`);
30627
+ return parsed;
30628
+ };
30629
+ var normalizeSeed = (seed, label = "seed") => {
30630
+ if (typeof seed === "number" && Number.isFinite(seed))
30631
+ return seed;
30632
+ if (typeof seed === "string" && seed !== "")
30633
+ return /^\d+$/.test(seed) ? Number(seed) : seed;
30634
+ throw new ControlError(`${label}: expected a number or a non-empty string`);
30635
+ };
30636
+ var SNAPSHOT_FORMAT = "mockingbird.snapshot/v1";
30637
+ var NAMESPACE_KEYS = /* @__PURE__ */ new Set(["seed", "clock", "virtualTime", "latency", "faults", "snapshot"]);
30638
+ var MOCK_KEYS = /* @__PURE__ */ new Set([...NAMESPACE_KEYS, "credentials", "namespaces"]);
30639
+ var REPORTED_KEYS = /* @__PURE__ */ new Set(["namespace", "snapshots"]);
30640
+ var REPORTED_CLOCK_KEYS = /* @__PURE__ */ new Set(["iso", "offsetMs"]);
30641
+ var KEY_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
30642
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
30643
+ var unknownKeys = (value, known, label, reported) => {
30644
+ for (const key of Object.keys(value)) {
30645
+ if (!known.has(key) && !reported?.has(key))
30646
+ throw new ControlError(`${label}: unknown control ${JSON.stringify(key)}; expected ${[...known].join(", ")}`);
30647
+ }
30648
+ };
30649
+ var latencyRule = (value, label) => {
30650
+ if (!isRecord2(value))
30651
+ return { delayMs: durationMs(value, label), jitterMs: 0 };
30652
+ unknownKeys(value, /* @__PURE__ */ new Set(["delay", "jitter", "delayMs", "jitterMs", "operations"]), label);
30653
+ return {
30654
+ delayMs: durationMs(value.delay ?? value.delayMs ?? 0, `${label}.delay`),
30655
+ jitterMs: durationMs(value.jitter ?? value.jitterMs ?? 0, `${label}.jitter`)
30656
+ };
30657
+ };
30658
+ var normalizeLatency = (value, label = "latency") => {
30659
+ const base = latencyRule(value, label);
30660
+ const operations = {};
30661
+ if (isRecord2(value) && value.operations !== void 0) {
30662
+ if (!isRecord2(value.operations))
30663
+ throw new ControlError(`${label}.operations: expected an object keyed by operationId`);
30664
+ for (const [operationId, rule] of Object.entries(value.operations)) {
30665
+ if (isRecord2(rule) && rule.operations !== void 0)
30666
+ throw new ControlError(`${label}.operations.${operationId}: cannot nest operations`);
30667
+ operations[operationId] = latencyRule(rule, `${label}.operations.${operationId}`);
30668
+ }
30669
+ }
30670
+ return { ...base, operations };
30671
+ };
30672
+ var normalizeClock = (value, label = "clock") => {
30673
+ if (!isRecord2(value))
30674
+ throw new ControlError(`${label}: expected an object`);
30675
+ unknownKeys(value, /* @__PURE__ */ new Set(["now", "set", "frozen", "freeze", "autoAdvance", "autoAdvanceMs"]), label, REPORTED_CLOCK_KEYS);
30676
+ const plan = {};
30677
+ const now = value.now ?? value.set;
30678
+ if (now !== void 0)
30679
+ plan.now = instantMs(now, `${label}.now`);
30680
+ const frozen = value.frozen ?? value.freeze;
30681
+ if (frozen !== void 0) {
30682
+ if (typeof frozen !== "boolean")
30683
+ throw new ControlError(`${label}.frozen: expected a boolean`);
30684
+ plan.frozen = frozen;
30685
+ }
30686
+ const auto = value.autoAdvance ?? value.autoAdvanceMs;
30687
+ if (auto !== void 0)
30688
+ plan.autoAdvanceMs = durationMs(auto, `${label}.autoAdvance`);
30689
+ return plan;
30690
+ };
30691
+ var assertSnapshotDocument = (value, label = "snapshot") => {
30692
+ if (!isRecord2(value))
30693
+ throw new ControlError(`${label}: expected a snapshot document`);
30694
+ if (value.format !== SNAPSHOT_FORMAT)
30695
+ throw new ControlError(`${label}.format: expected ${JSON.stringify(SNAPSHOT_FORMAT)}`);
30696
+ if (!Array.isArray(value.records))
30697
+ throw new ControlError(`${label}.records: expected a list`);
30698
+ value.records.forEach((row, index) => {
30699
+ if (!isRecord2(row) || typeof row.collection !== "string" || typeof row.id !== "string" || row.value === void 0 && typeof row.raw !== "string" || row.seq !== void 0 && !Number.isSafeInteger(row.seq))
30700
+ throw new ControlError(`${label}.records[${index}]: expected {collection, id, value, seq?}`);
30701
+ });
30702
+ if (value.sequences !== void 0) {
30703
+ if (!Array.isArray(value.sequences))
30704
+ throw new ControlError(`${label}.sequences: expected a list`);
30705
+ value.sequences.forEach((row, index) => {
30706
+ if (!isRecord2(row) || typeof row.name !== "string" || typeof row.kind !== "string" || !Number.isSafeInteger(row.value))
30707
+ throw new ControlError(`${label}.sequences[${index}]: expected {name, kind, value}`);
30708
+ });
30709
+ }
30710
+ if (value.clock !== void 0) {
30711
+ if (!isRecord2(value.clock) || typeof value.clock.now !== "number" || !Number.isFinite(value.clock.now) || typeof value.clock.frozen !== "boolean")
30712
+ throw new ControlError(`${label}.clock: expected {now, frozen}`);
30713
+ }
30714
+ return value;
30715
+ };
30716
+ var normalizeControls = (value, label = "controls") => {
30717
+ if (!isRecord2(value))
30718
+ throw new ControlError(`${label}: expected an object`);
30719
+ unknownKeys(value, NAMESPACE_KEYS, label, REPORTED_KEYS);
30720
+ const plan = {};
30721
+ if (value.seed !== void 0)
30722
+ plan.seed = normalizeSeed(value.seed, `${label}.seed`);
30723
+ if (value.clock !== void 0)
30724
+ plan.clock = normalizeClock(value.clock, `${label}.clock`);
30725
+ if (value.virtualTime !== void 0) {
30726
+ if (typeof value.virtualTime !== "boolean")
30727
+ throw new ControlError(`${label}.virtualTime: expected a boolean`);
30728
+ plan.virtualTime = value.virtualTime;
30729
+ }
30730
+ if (value.latency !== void 0)
30731
+ plan.latency = normalizeLatency(value.latency, `${label}.latency`);
30732
+ if (value.faults !== void 0) {
30733
+ if (!Array.isArray(value.faults) || !value.faults.every(isRecord2))
30734
+ throw new ControlError(`${label}.faults: expected a list of fault rules`);
30735
+ plan.faults = value.faults;
30736
+ }
30737
+ if (value.snapshot !== void 0)
30738
+ plan.snapshot = assertSnapshotDocument(value.snapshot, `${label}.snapshot`);
30739
+ return plan;
30740
+ };
30741
+ var normalizeBootControls = (value, label = "controls") => {
30742
+ if (value === void 0)
30743
+ return { shared: {}, namespaces: {}, credentials: {} };
30744
+ if (!isRecord2(value))
30745
+ throw new ControlError(`${label}: expected an object`);
30746
+ unknownKeys(value, MOCK_KEYS, label);
30747
+ const { credentials, namespaces, ...shared } = value;
30748
+ const plan = {
30749
+ shared: normalizeControls(shared, label),
30750
+ namespaces: {},
30751
+ credentials: {}
30752
+ };
30753
+ if (namespaces !== void 0) {
30754
+ if (!isRecord2(namespaces))
30755
+ throw new ControlError(`${label}.namespaces: expected an object`);
30756
+ for (const [name, each] of Object.entries(namespaces)) {
30757
+ if (!KEY_PATTERN.test(name))
30758
+ throw new ControlError(`${label}.namespaces: bad namespace ${JSON.stringify(name)}`);
30759
+ plan.namespaces[name] = normalizeControls(each, `${label}.namespaces.${name}`);
30760
+ }
30761
+ }
30762
+ if (credentials !== void 0) {
30763
+ if (!isRecord2(credentials))
30764
+ throw new ControlError(`${label}.credentials: expected an object`);
30765
+ for (const [credential, namespace] of Object.entries(credentials)) {
30766
+ if (typeof namespace !== "string" || !KEY_PATTERN.test(namespace))
30767
+ throw new ControlError(`${label}.credentials: bad namespace for a credential`);
30768
+ plan.credentials[credential] = namespace;
30769
+ }
30770
+ }
30771
+ return plan;
30772
+ };
30773
+ var CONTROL_HEADERS = {
30774
+ namespace: "x-mockingbird-namespace",
30775
+ adminKey: "x-mockingbird-admin-key",
30776
+ branch: "x-mockingbird-branch",
30777
+ at: "x-mockingbird-at",
30778
+ reset: "x-mockingbird-reset",
30779
+ restore: "x-mockingbird-restore",
30780
+ seed: "x-mockingbird-seed",
30781
+ time: "x-mockingbird-time",
30782
+ advance: "x-mockingbird-advance",
30783
+ virtualTime: "x-mockingbird-virtual-time",
30784
+ fault: "x-mockingbird-fault",
30785
+ delay: "x-mockingbird-delay",
30786
+ timeout: "x-mockingbird-timeout",
30787
+ snapshot: "x-mockingbird-snapshot"
30788
+ };
30789
+ var CONTROL_HEADER_REFERENCE = [
30790
+ {
30791
+ header: CONTROL_HEADERS.namespace,
30792
+ value: "<key>",
30793
+ scope: "select",
30794
+ guarded: false,
30795
+ summary: "Isolation key. Each key has its own records, clock, random streams, faults, latency, journal and snapshots."
30796
+ },
30797
+ {
30798
+ header: CONTROL_HEADERS.branch,
30799
+ value: "<name>",
30800
+ scope: "select",
30801
+ guarded: false,
30802
+ summary: "Read and write a named copy-on-write branch of the namespace's history."
30803
+ },
30804
+ {
30805
+ header: CONTROL_HEADERS.at,
30806
+ value: "<checkpoint>",
30807
+ scope: "select",
30808
+ guarded: false,
30809
+ summary: "Read the namespace as it was at a checkpoint. With a branch, start the branch there."
30810
+ },
30811
+ {
30812
+ header: CONTROL_HEADERS.reset,
30813
+ value: "true",
30814
+ scope: "namespace",
30815
+ guarded: true,
30816
+ summary: "Reset the namespace to its boot state before the request is handled."
30817
+ },
30818
+ {
30819
+ header: CONTROL_HEADERS.restore,
30820
+ value: "<snapshot>",
30821
+ scope: "namespace",
30822
+ guarded: true,
30823
+ summary: "Restore a snapshot before the request is handled."
30824
+ },
30825
+ {
30826
+ header: CONTROL_HEADERS.seed,
30827
+ value: "<seed>",
30828
+ scope: "namespace",
30829
+ guarded: true,
30830
+ summary: "Restart the namespace's random streams from this seed."
30831
+ },
30832
+ {
30833
+ header: CONTROL_HEADERS.time,
30834
+ value: "<epoch ms | ISO-8601>",
30835
+ scope: "namespace",
30836
+ guarded: true,
30837
+ summary: "Freeze the namespace clock at this instant."
30838
+ },
30839
+ {
30840
+ header: CONTROL_HEADERS.advance,
30841
+ value: "<duration>",
30842
+ scope: "namespace",
30843
+ guarded: true,
30844
+ summary: "Move the namespace clock by this much, forward or (negative) back."
30845
+ },
30846
+ {
30847
+ header: CONTROL_HEADERS.virtualTime,
30848
+ value: "true | false",
30849
+ scope: "namespace",
30850
+ guarded: true,
30851
+ summary: "Spend injected waits on the mock clock (true) or the wall clock (false)."
30852
+ },
30853
+ {
30854
+ header: CONTROL_HEADERS.delay,
30855
+ value: "<duration>",
30856
+ scope: "request",
30857
+ guarded: true,
30858
+ summary: "Hold this response back this long, on top of the namespace's latency."
30859
+ },
30860
+ {
30861
+ header: CONTROL_HEADERS.timeout,
30862
+ value: "<duration>",
30863
+ scope: "request",
30864
+ guarded: true,
30865
+ summary: "Never answer this request: hold it this long, then drop the connection."
30866
+ },
30867
+ {
30868
+ header: CONTROL_HEADERS.fault,
30869
+ value: '<status> | drop | <preset> | key=value; \u2026 | {"status": 503, \u2026}',
30870
+ scope: "request",
30871
+ guarded: true,
30872
+ summary: "Fault this request only. Stored fault rules are not consulted when it answers."
30873
+ },
30874
+ {
30875
+ header: CONTROL_HEADERS.snapshot,
30876
+ value: "<name>",
30877
+ scope: "namespace",
30878
+ guarded: true,
30879
+ summary: "Snapshot the namespace after the request is handled. Echoed on the response."
30880
+ }
30881
+ ];
30882
+ var GUARDED = CONTROL_HEADER_REFERENCE.filter((each) => each.guarded).map((each) => each.header);
30883
+
30577
30884
  // src/wire/probe.ts
30578
30885
  import { createHash as createHash3, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
30579
30886
  import { createConnection } from "node:net";
@@ -30678,26 +30985,38 @@ function probe(connectionString) {
30678
30985
  async function startProtocol(entry) {
30679
30986
  const namespaces = /* @__PURE__ */ new Map();
30680
30987
  const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
30988
+ if (entry.seed !== void 0 && (!String(entry.seed).trim() || !Number.isFinite(Number(entry.seed))))
30989
+ throw new Error("Postgres seed must be a finite number");
30990
+ let admission;
30991
+ const boot = normalizeBootControls(entry.adminControls);
30992
+ const create = async (name, databaseName, port = 0) => {
30993
+ if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string" || !databaseName.length)
30994
+ throw new Error("invalid Postgres namespace/database name");
30995
+ const clock = createClock();
30996
+ const control = { ...boot.shared.clock, ...boot.namespaces[name]?.clock };
30997
+ if (control.frozen) clock.freeze();
30998
+ if (control.now !== void 0) clock.set(control.now);
30999
+ const database = new Database({
31000
+ now: () => new Date(clock.now()),
31001
+ ...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
31002
+ });
31003
+ const server = await serve({
31004
+ database,
31005
+ databaseName,
31006
+ allowDatabaseSelection: true,
31007
+ host: entry.host ?? "127.0.0.1",
31008
+ port,
31009
+ ...entry.password !== void 0 ? { password: entry.password } : {},
31010
+ ...entry.user !== void 0 ? { user: entry.user } : {}
31011
+ });
31012
+ server.server.prependListener("connection", (socket) => {
31013
+ if (admission?.(name) === false) socket.destroy();
31014
+ });
31015
+ namespaces.set(name, { server, clock });
31016
+ };
30681
31017
  try {
30682
- for (const [name, databaseName] of Object.entries(selectors)) {
30683
- if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
30684
- throw new Error("invalid Postgres namespace/database name");
30685
- const clock = createClock();
30686
- const database = new Database({
30687
- now: () => new Date(clock.now()),
30688
- ...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
30689
- });
30690
- const server = await serve({
30691
- database,
30692
- databaseName,
30693
- allowDatabaseSelection: true,
30694
- host: entry.host ?? "127.0.0.1",
30695
- port: name === "default" ? entry.port ?? 0 : 0,
30696
- ...entry.password !== void 0 ? { password: entry.password } : {},
30697
- ...entry.user !== void 0 ? { user: entry.user } : {}
30698
- });
30699
- namespaces.set(name, { server, clock });
30700
- }
31018
+ for (const [name, databaseName] of Object.entries(selectors))
31019
+ await create(name, databaseName, name === "default" ? entry.port ?? 0 : 0);
30701
31020
  } catch (error) {
30702
31021
  await Promise.allSettled([...namespaces.values()].map(({ server }) => server.close()));
30703
31022
  throw error;
@@ -30719,17 +31038,143 @@ async function startProtocol(entry) {
30719
31038
  protocol: "postgres",
30720
31039
  url: publicUrl("default"),
30721
31040
  connection: get("default").server.connectionString,
30722
- connections: Object.fromEntries([...namespaces].map(([name, { server }]) => [name, server.connectionString])),
30723
- namespaces: {
30724
- mechanism: "endpoint",
30725
- endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl(name)]))
31041
+ get connections() {
31042
+ return Object.fromEntries([...namespaces].map(([name, { server }]) => [name, server.connectionString]));
31043
+ },
31044
+ get namespaces() {
31045
+ return {
31046
+ mechanism: "endpoint",
31047
+ endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl(name)]))
31048
+ };
31049
+ },
31050
+ async createNamespace(name) {
31051
+ if (namespaces.has(name)) throw new Error("namespace already exists");
31052
+ await create(name, entry.database ?? "postgres");
31053
+ origins.set(name, get(name).server.snapshotAll());
31054
+ },
31055
+ async deleteNamespace(name) {
31056
+ const { server } = get(name);
31057
+ if (server.connections) throw new Error("Postgres namespace has open sessions");
31058
+ await server.close();
31059
+ for (const database of server.databaseNames()) server.getDatabase(database).close();
31060
+ namespaces.delete(name);
31061
+ origins.delete(name);
31062
+ },
31063
+ async exportCheckpoint(namespace, handle) {
31064
+ const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
31065
+ if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
31066
+ return {
31067
+ format: "mockingbird.postgres/v1",
31068
+ inventory: Object.fromEntries(
31069
+ [...point.databases].map(([name, snapshot]) => {
31070
+ const database = snapshot.open();
31071
+ try {
31072
+ const tables = database.prepare("SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'public'").all();
31073
+ return [
31074
+ name,
31075
+ Object.fromEntries(
31076
+ tables.map((table) => {
31077
+ const identifier2 = table.table_name.replaceAll('"', '""');
31078
+ const rows = database.prepare(`SELECT * FROM public."${identifier2}"`).all();
31079
+ return [
31080
+ table.table_name,
31081
+ {
31082
+ count: rows.length,
31083
+ records: Object.fromEntries(
31084
+ rows.map((row, index) => [
31085
+ String(row.id ?? index),
31086
+ createHash4("sha256").update(
31087
+ JSON.stringify(
31088
+ row,
31089
+ (_key, value) => typeof value === "bigint" ? String(value) : value
31090
+ )
31091
+ ).digest("hex")
31092
+ ])
31093
+ )
31094
+ }
31095
+ ];
31096
+ })
31097
+ )
31098
+ ];
31099
+ } finally {
31100
+ database.close();
31101
+ }
31102
+ })
31103
+ ),
31104
+ databases: [...point.databases].map(([name, snapshot]) => ({
31105
+ name,
31106
+ bytes: Buffer.from(snapshot.encode()).toString("base64")
31107
+ })),
31108
+ clock: point.clock,
31109
+ faults: point.faults
31110
+ };
31111
+ },
31112
+ async importCheckpoint(namespace, document) {
31113
+ const doc = document;
31114
+ if (doc?.format !== "mockingbird.postgres/v1" || !Array.isArray(doc.databases) || !doc.databases.length || !Number.isFinite(doc.clock?.now) || typeof doc.clock?.frozen !== "boolean")
31115
+ throw new Error("invalid Postgres archive");
31116
+ const databases = /* @__PURE__ */ new Map();
31117
+ for (const row of doc.databases) {
31118
+ if (typeof row.name !== "string" || databases.has(row.name) || typeof row.bytes !== "string" || !/^[A-Za-z0-9+/]*={0,2}$/.test(row.bytes))
31119
+ throw new Error("invalid Postgres archive database");
31120
+ databases.set(row.name, Snapshot.decode(Buffer.from(row.bytes, "base64")));
31121
+ }
31122
+ const handle = {};
31123
+ snapshots.set(handle, { namespace, databases, clock: doc.clock, faults: doc.faults ?? {} });
31124
+ return handle;
31125
+ },
31126
+ async inspect(namespace, { offset, limit, collection }) {
31127
+ const { server } = get(namespace);
31128
+ const database = server.getDatabase(server.databaseNames()[0]);
31129
+ const tables = database.prepare("SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'public'").all();
31130
+ if (!collection) return { tables };
31131
+ const table = collection.replaceAll('"', '""');
31132
+ return {
31133
+ records: database.prepare(`SELECT * FROM public."${table}" LIMIT ${limit} OFFSET ${offset}`).all(),
31134
+ offset
31135
+ };
31136
+ },
31137
+ async initializationAccess() {
31138
+ const privateServers = [];
31139
+ const connections = {};
31140
+ try {
31141
+ for (const [name, { server }] of namespaces) {
31142
+ const databaseName = server.databaseNames()[0];
31143
+ const seedServer = await serve({
31144
+ database: server.getDatabase(databaseName),
31145
+ databaseName,
31146
+ port: 0,
31147
+ host: "127.0.0.1",
31148
+ ...entry.user ? { user: entry.user } : {},
31149
+ ...entry.password ? { password: entry.password } : {}
31150
+ });
31151
+ privateServers.push(seedServer);
31152
+ connections[name] = seedServer.connectionString;
31153
+ }
31154
+ } catch (error) {
31155
+ await Promise.allSettled(privateServers.map((s) => s.close()));
31156
+ throw error;
31157
+ }
31158
+ return {
31159
+ connections,
31160
+ close: async () => {
31161
+ await Promise.all(privateServers.map((s) => s.close()));
31162
+ }
31163
+ };
31164
+ },
31165
+ setAdmission(allow) {
31166
+ admission = allow;
30726
31167
  },
30727
31168
  async ready() {
31169
+ if (admission) return [...namespaces.values()].every(({ server }) => server.server.listening);
30728
31170
  const results = await Promise.all(
30729
31171
  [...namespaces.values()].map(
30730
31172
  ({ server }) => server.server.listening ? probe(server.connectionString) : false
30731
31173
  )
30732
31174
  );
31175
+ const deadline = Date.now() + 2e3;
31176
+ while ([...namespaces.values()].some(({ server }) => server.connections > 0) && Date.now() < deadline)
31177
+ await new Promise((resolve) => setTimeout(resolve, 1));
30733
31178
  return results.every(Boolean);
30734
31179
  },
30735
31180
  async close() {
@@ -30791,7 +31236,11 @@ async function startProtocol(entry) {
30791
31236
  for (const name of server.databaseNames()) server.getDatabase(name).now = () => new Date(clock.now());
30792
31237
  server.clearFaults();
30793
31238
  clock.reset();
31239
+ const control = { ...boot.shared.clock, ...boot.namespaces[namespace]?.clock };
31240
+ if (control.frozen) clock.freeze();
31241
+ if (control.now !== void 0) clock.set(control.now);
30794
31242
  },
31243
+ nextEvent: () => void 0,
30795
31244
  clock: (namespace) => get(namespace).clock
30796
31245
  };
30797
31246
  }