@crvouga/mockingbird-service-junction 3.6.0 → 3.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.
@@ -6,13 +6,14 @@ import {
6
6
  labAccountBehaviorFrom,
7
7
  normalizeBootControls,
8
8
  normalizeControls,
9
+ normalizeIdBase,
9
10
  ownedTransport,
10
11
  parseDuration,
11
12
  parseFaultHeader,
12
13
  pullCorpus,
13
14
  resolveAdminPrefix,
14
15
  restoreClock
15
- } from "./chunk-ZRF3DDJ4.js";
16
+ } from "./chunk-27N4VA23.js";
16
17
  import {
17
18
  defaultCorpus
18
19
  } from "./chunk-2FZJPMTR.js";
@@ -27,6 +28,19 @@ import { readFile as readFile6 } from "node:fs/promises";
27
28
  import { readFile as readFile5 } from "node:fs/promises";
28
29
  import { parseArgs } from "node:util";
29
30
 
31
+ // ../../adapters/node/dist/boot-settings.js
32
+ var checkSettings = (target, settings, label) => {
33
+ if (!target.parseSettings)
34
+ throw new Error(`${label}: ${target.name} takes no boot settings`);
35
+ try {
36
+ return target.parseSettings(settings);
37
+ } catch (error) {
38
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
39
+ cause: error
40
+ });
41
+ }
42
+ };
43
+
30
44
  // ../../adapters/node/dist/fleet.js
31
45
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
32
46
  import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
@@ -2676,6 +2690,9 @@ var fleet_config_schema_default = {
2676
2690
  adminControls: {
2677
2691
  $ref: "#/$defs/controls"
2678
2692
  },
2693
+ idBase: {
2694
+ $ref: "#/$defs/idBase"
2695
+ },
2679
2696
  namespace: {
2680
2697
  type: "string",
2681
2698
  pattern: "^[A-Za-z0-9_.-]{1,64}$"
@@ -2765,6 +2782,12 @@ var fleet_config_schema_default = {
2765
2782
  adminControls: {
2766
2783
  $ref: "#/$defs/controls"
2767
2784
  },
2785
+ idBase: {
2786
+ $ref: "#/$defs/idBase"
2787
+ },
2788
+ settings: {
2789
+ type: "object"
2790
+ },
2768
2791
  fixtures: {
2769
2792
  type: "object"
2770
2793
  },
@@ -2811,6 +2834,18 @@ var fleet_config_schema_default = {
2811
2834
  }
2812
2835
  }
2813
2836
  },
2837
+ idBase: {
2838
+ anyOf: [
2839
+ {
2840
+ type: "integer",
2841
+ minimum: 0
2842
+ },
2843
+ {
2844
+ type: "string",
2845
+ pattern: "^(boot|[0-9]+)$"
2846
+ }
2847
+ ]
2848
+ },
2814
2849
  controls: {
2815
2850
  type: "object",
2816
2851
  additionalProperties: false,
@@ -2960,6 +2995,8 @@ async function preflightFleet(config, options) {
2960
2995
  throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
2961
2996
  resolveAdminPrefix(config.adminPrefix);
2962
2997
  normalizeBootControls(config.adminControls);
2998
+ if (config.idBase !== void 0)
2999
+ normalizeIdBase(config.idBase, "idBase");
2963
3000
  const targets = /* @__PURE__ */ new Map();
2964
3001
  const ports = /* @__PURE__ */ new Set();
2965
3002
  for (const [name, entry] of Object.entries(config.services)) {
@@ -2991,6 +3028,13 @@ async function preflightFleet(config, options) {
2991
3028
  throw new Error("invalid protocol namespace selector");
2992
3029
  }
2993
3030
  }
3031
+ if (entry.idBase !== void 0)
3032
+ normalizeIdBase(entry.idBase, "idBase");
3033
+ if (entry.settings !== void 0) {
3034
+ if (protocol !== "http" || !("create" in target))
3035
+ throw new Error("settings: only HTTP services take boot settings");
3036
+ checkSettings(target, entry.settings, "settings");
3037
+ }
2994
3038
  const port = entry.port ?? target.defaultPort;
2995
3039
  if (port !== 0) {
2996
3040
  const key = `${entry.host ?? "127.0.0.1"}:${port}`;
@@ -3026,7 +3070,7 @@ async function preflightFleet(config, options) {
3026
3070
  break;
3027
3071
  }
3028
3072
  }
3029
- targets.set(name, { ...target, packageVersion: packageVersion ?? "3.6.0" });
3073
+ targets.set(name, { ...target, packageVersion: packageVersion ?? "3.7.0" });
3030
3074
  } catch (error) {
3031
3075
  throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3032
3076
  }
@@ -3346,6 +3390,7 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3346
3390
  options.onLog?.(entry2);
3347
3391
  },
3348
3392
  ...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
3393
+ ...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
3349
3394
  ...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
3350
3395
  ...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
3351
3396
  });
@@ -3364,6 +3409,8 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3364
3409
  ].some((method) => typeof created[method] !== "function"))
3365
3410
  throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
3366
3411
  const runtime = created;
3412
+ if (entry.idBase !== void 0)
3413
+ runtime.setIdBase?.(entry.idBase);
3367
3414
  const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
3368
3415
  if (options.outbound && runtime.webhooks)
3369
3416
  runtime.webhooks.setTransportPolicy(ownedTransport(options.outbound));
@@ -3962,6 +4009,7 @@ async function startFleet(config, options) {
3962
4009
  ...entry,
3963
4010
  adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
3964
4011
  ...adminKey !== void 0 ? { adminKey } : {},
4012
+ ...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
3965
4013
  // Fleet-wide controls, with the service's own keys replacing them.
3966
4014
  ...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
3967
4015
  }, options, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
@@ -4646,6 +4694,16 @@ var COMMON_SERVE_OPTIONS = {
4646
4694
  value: "<seed>",
4647
4695
  description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
4648
4696
  },
4697
+ "id-base": {
4698
+ type: "string",
4699
+ value: "<n|boot>",
4700
+ description: "Start generated ids at this base so a restart does not reissue the last boot's ids; boot derives one from the clock (env MOCKINGBIRD_ID_BASE; default 0, the same ids every boot)"
4701
+ },
4702
+ settings: {
4703
+ type: "string",
4704
+ value: "<json|file>",
4705
+ description: "Service settings to boot with, as JSON or a JSON file; every namespace starts from them (env MOCKINGBIRD_SETTINGS; only for services that declare settings)"
4706
+ },
4649
4707
  "admin-controls": {
4650
4708
  type: "string",
4651
4709
  value: "<json|file>",
@@ -4740,19 +4798,40 @@ var readJsonFile = async (path, label) => {
4740
4798
  }
4741
4799
  };
4742
4800
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4801
+ var readJsonOption = async (source, label) => {
4802
+ const trimmed = source.trim();
4803
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4804
+ try {
4805
+ return JSON.parse(trimmed);
4806
+ } catch {
4807
+ throw new Error(`${label}: not valid JSON`);
4808
+ }
4809
+ }
4810
+ return readJsonFile(source, label);
4811
+ };
4812
+ var resolveServeIdBase = (values, env = process.env) => {
4813
+ const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
4814
+ if (source === void 0 || source.trim() === "")
4815
+ return void 0;
4816
+ normalizeIdBase(source.trim(), "--id-base");
4817
+ return source.trim();
4818
+ };
4819
+ var resolveServeSettings = async (target, values, env = process.env) => {
4820
+ const flag = asString(values.settings);
4821
+ const source = flag ?? env.MOCKINGBIRD_SETTINGS;
4822
+ if (source === void 0 || source.trim() === "")
4823
+ return void 0;
4824
+ const label = flag !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
4825
+ const parsed = await readJsonOption(source, label);
4826
+ if (!isObject2(parsed))
4827
+ throw new Error(`${label}: expected a JSON object`);
4828
+ return checkSettings(target, parsed, label);
4829
+ };
4743
4830
  var resolveServeControls = async (values, env = process.env, base) => {
4744
4831
  const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
4745
4832
  let document = isObject2(base) ? { ...base } : {};
4746
4833
  if (source !== void 0 && source.trim() !== "") {
4747
- let parsed;
4748
- if (source.trim().startsWith("{")) {
4749
- try {
4750
- parsed = JSON.parse(source);
4751
- } catch {
4752
- throw new Error("--admin-controls: not valid JSON");
4753
- }
4754
- } else
4755
- parsed = await readJsonFile(source, "--admin-controls");
4834
+ const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
4756
4835
  if (!isObject2(parsed))
4757
4836
  throw new Error("--admin-controls: expected a JSON object");
4758
4837
  document = { ...document, ...parsed };
@@ -4802,8 +4881,11 @@ var start = async (target, values, config) => {
4802
4881
  adminKey: config.adminKey,
4803
4882
  seed: config.seed,
4804
4883
  onLog: formatLog(config.log),
4884
+ ...config.settings !== void 0 ? { settings: config.settings } : {},
4805
4885
  ...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
4806
4886
  });
4887
+ if (config.idBase !== void 0)
4888
+ runtime.setIdBase?.(config.idBase);
4807
4889
  const listening = await listen(runtime, { port: config.port, host: config.host });
4808
4890
  target.listening?.(listening);
4809
4891
  console.log(`${target.name} emulator listening on ${listening.url}`);
@@ -4849,6 +4931,11 @@ var serveCommand = (target) => ({
4849
4931
  const adminControls = await resolveServeControls(values, process.env, config.adminControls);
4850
4932
  if (adminControls !== void 0)
4851
4933
  config.adminControls = adminControls;
4934
+ if (values.settings !== void 0)
4935
+ throw new Error("--settings serves a single service; give each service its own `settings` in the config");
4936
+ const idBase = config.idBase ?? resolveServeIdBase(values);
4937
+ if (idBase !== void 0)
4938
+ config.idBase = idBase;
4852
4939
  const fleet = await startFleet(config, {
4853
4940
  load: (name) => loadTarget(name, target),
4854
4941
  onLog: formatLog(config.log ?? log),
@@ -4878,8 +4965,12 @@ var serveCommand = (target) => ({
4878
4965
  let listening;
4879
4966
  try {
4880
4967
  const adminControls = await resolveServeControls(values);
4968
+ const idBase = resolveServeIdBase(values);
4969
+ const settings = await resolveServeSettings(target, values);
4881
4970
  listening = await start(target, values, {
4882
4971
  ...adminControls !== void 0 ? { adminControls } : {},
4972
+ ...idBase !== void 0 ? { idBase } : {},
4973
+ ...settings !== void 0 ? { settings } : {},
4883
4974
  adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
4884
4975
  port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
4885
4976
  host: asString(values.host) ?? "127.0.0.1",
@@ -4963,8 +5054,27 @@ var serveTarget = {
4963
5054
  },
4964
5055
  geo: {
4965
5056
  type: "string",
4966
- value: "<corpus|synthetic>",
4967
- description: "Unknown ZIPs: refuse (corpus) or invent coverage (synthetic)"
5057
+ value: "<corpus|synthetic|corpus+synthetic>",
5058
+ description: "Unknown ZIPs: refuse (corpus), invent coverage (synthetic), or replay covered ZIPs and invent the rest (corpus+synthetic)"
5059
+ },
5060
+ "zip-overlays": {
5061
+ type: "string",
5062
+ value: "<file>",
5063
+ description: 'ZIP aliases and per-ZIP overrides: { "aliases": { "07105": "85004" }, "overrides": { "07105": { "phlebotomy_served": true } } }'
5064
+ },
5065
+ "catalog-overlays": {
5066
+ type: "string",
5067
+ value: "<file>",
5068
+ description: 'Lab tests cloned from catalog ones at boot: a JSON array of { "from", "id", "name", "slug", "lab", "stripAoe", "autoGenerated" }'
5069
+ },
5070
+ "marker-catalog": {
5071
+ type: "string",
5072
+ value: "<file>",
5073
+ description: "Markers GET /v3/lab_tests/markers offers on top of the catalog's: a JSON array (price, aoe, expected_results, a_la_carte_enabled)"
5074
+ },
5075
+ "add-on-source-markers": {
5076
+ type: "boolean",
5077
+ description: "Name the markers behind each add-on result line (source_markers[].marker_id)"
4968
5078
  },
4969
5079
  "lab-accounts": {
4970
5080
  type: "string",
@@ -5054,9 +5164,15 @@ var serveTarget = {
5054
5164
  corpus = await loadCorpus(asString2(values.corpus) ?? "default");
5055
5165
  }
5056
5166
  const geo = asString2(values.geo);
5057
- if (geo !== void 0 && geo !== "corpus" && geo !== "synthetic") {
5058
- throw new Error(`--geo must be corpus or synthetic (got ${geo})`);
5167
+ if (geo !== void 0 && geo !== "corpus" && geo !== "synthetic" && geo !== "corpus+synthetic") {
5168
+ throw new Error(`--geo must be corpus, synthetic or corpus+synthetic (got ${geo})`);
5059
5169
  }
5170
+ const zipFile = asString2(values["zip-overlays"]);
5171
+ const zipOverlays = zipFile === void 0 ? void 0 : await readJson(zipFile);
5172
+ const overlayFile = asString2(values["catalog-overlays"]);
5173
+ const catalogOverlays = overlayFile === void 0 ? void 0 : await readJson(overlayFile);
5174
+ const markerFile = asString2(values["marker-catalog"]);
5175
+ const markerCatalog = markerFile === void 0 ? void 0 : await readJson(markerFile);
5060
5176
  const accountsFile = asString2(values["lab-accounts"]);
5061
5177
  const labAccounts = accountsFile === void 0 ? void 0 : await readJson(accountsFile);
5062
5178
  const fixturesFile = asString2(values.fixtures);
@@ -5087,6 +5203,10 @@ var serveTarget = {
5087
5203
  return createRuntime({
5088
5204
  ...corpus ? { corpus } : {},
5089
5205
  ...geo ? { geo } : {},
5206
+ ...zipOverlays ? { zipOverlays } : {},
5207
+ ...catalogOverlays ? { catalogOverlays } : {},
5208
+ ...markerCatalog ? { markerCatalog } : {},
5209
+ ...values["add-on-source-markers"] === true ? { addOnSourceMarkers: true } : {},
5090
5210
  ...labAccounts ? { labAccounts } : {},
5091
5211
  ...fixtures ? { fixtures } : {},
5092
5212
  ...teamId !== void 0 ? { teamId } : {},
@@ -5116,7 +5236,7 @@ var serveTarget = {
5116
5236
  const info = api.corpusInfo();
5117
5237
  return [
5118
5238
  info ? `corpus ${info.label}: ${info.observations} observations, ${info.zips} ZIPs, ${info.labTests} lab tests, ${info.labAccounts} lab accounts (recorded ${info.recordedAt} from ${info.source})` : "corpus none: synthetic catalog and coverage",
5119
- `geo ${api.geoMode}${api.geoMode === "corpus" ? " (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)" : ""}`,
5239
+ `geo ${api.geoMode}${api.geoMode === "corpus" ? " (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)" : api.geoMode === "corpus+synthetic" ? " (covered ZIPs replay the recording, the rest are synthesized)" : ""}`,
5120
5240
  `team ${api.teamId}`,
5121
5241
  `lab accounts: ${api.labAccounts().length}`,
5122
5242
  `limits: ${Object.entries(api.limits).filter(([, value]) => value !== null && value !== false).map(([key, value]) => `${key}=${String(value)}`).join(", ") || "none (sandbox caps off)"}`,
@@ -5135,4 +5255,4 @@ export {
5135
5255
  createServer2 as createServer,
5136
5256
  serveTarget
5137
5257
  };
5138
- //# sourceMappingURL=chunk-WMJJYHRV.js.map
5258
+ //# sourceMappingURL=chunk-NTGGNJKR.js.map