@crvouga/mockingbird-service-posthog 3.5.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.
@@ -4,21 +4,32 @@ import {
4
4
  createRuntime,
5
5
  normalizeBootControls,
6
6
  normalizeControls,
7
+ normalizeIdBase,
7
8
  ownedTransport,
8
9
  parseDuration,
9
10
  parseFaultHeader,
10
11
  resolveAdminPrefix,
11
12
  restoreClock,
12
13
  specsFromState
13
- } from "./chunk-YTVQ24KD.js";
14
+ } from "./chunk-2ZTMN4SX.js";
14
15
 
15
16
  // ../../adapters/node/dist/cli.js
16
17
  import { readFile as readFile5 } from "node:fs/promises";
17
- import { createRequire as createRequire3 } from "node:module";
18
- import { resolve as resolve3 } from "node:path";
19
- import { pathToFileURL } from "node:url";
20
18
  import { parseArgs } from "node:util";
21
19
 
20
+ // ../../adapters/node/dist/boot-settings.js
21
+ var checkSettings = (target, settings, label) => {
22
+ if (!target.parseSettings)
23
+ throw new Error(`${label}: ${target.name} takes no boot settings`);
24
+ try {
25
+ return target.parseSettings(settings);
26
+ } catch (error) {
27
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
28
+ cause: error
29
+ });
30
+ }
31
+ };
32
+
22
33
  // ../../adapters/node/dist/fleet.js
23
34
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
24
35
  import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
@@ -30,6 +41,31 @@ import { createHash as createHash3, randomBytes, randomUUID as randomUUID3 } fro
30
41
 
31
42
  // ../../adapters/node/dist/fleet-archive.js
32
43
  import { createHash } from "node:crypto";
44
+ function validateCompatibilityTags(tags) {
45
+ if (!tags || typeof tags !== "object" || Array.isArray(tags))
46
+ throw new Error("invalid archive compatibility tags");
47
+ const entries = Object.entries(tags);
48
+ if (entries.length > 32 || entries.some(([key, value]) => !/^[A-Za-z0-9_.-]{1,64}$/.test(key) || typeof value !== "string" || value.length < 1 || value.length > 256 || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)))
49
+ throw new Error("invalid archive compatibility tags");
50
+ }
51
+ function assertCompatibilityTags(tags, policy) {
52
+ if (tags !== void 0)
53
+ validateCompatibilityTags(tags);
54
+ if (!policy)
55
+ return;
56
+ validateCompatibilityTags(policy.tags);
57
+ if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
58
+ throw new Error("invalid missing compatibility policy");
59
+ if (tags === void 0 && policy.missing === "allow")
60
+ return;
61
+ if (canonical(tags) !== canonical(policy.tags))
62
+ throw new Error("application archive compatibility mismatch");
63
+ }
64
+ function assertArchiveCompatibility(input, policy) {
65
+ const archive = validateArchive(input);
66
+ assertCompatibilityTags(archive.compatibility, policy);
67
+ return archive;
68
+ }
33
69
  var canonical = (value) => {
34
70
  if (Array.isArray(value))
35
71
  return `[${value.map(canonical).join(",")}]`;
@@ -49,6 +85,8 @@ function validateArchive(value) {
49
85
  const { sha256, ...content } = doc;
50
86
  if (doc.format !== "mockingbird.fleet/v1" || typeof doc.sourceId !== "string" || typeof doc.createdAt !== "string" || !doc.namespaces || typeof doc.namespaces !== "object" || Array.isArray(doc.namespaces) || sha256 !== digest(content))
51
87
  throw new Error("unsupported or corrupt fleet archive");
88
+ if (doc.compatibility !== void 0)
89
+ validateCompatibilityTags(doc.compatibility);
52
90
  for (const [namespace, entry] of Object.entries(doc.namespaces)) {
53
91
  if (!/^[A-Za-z0-9_.-]{1,64}$/.test(namespace) || !entry || !Number.isFinite(entry.clock?.now) || typeof entry.clock?.frozen !== "boolean" || !entry.services || !Object.keys(entry.services).length)
54
92
  throw new Error("invalid archive namespace");
@@ -64,6 +102,7 @@ function inspectArchive(input) {
64
102
  format: archive.format,
65
103
  createdAt: archive.createdAt,
66
104
  sha256: archive.sha256,
105
+ ...archive.compatibility ? { compatibility: archive.compatibility } : {},
67
106
  namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name, ns]) => [
68
107
  name,
69
108
  {
@@ -706,11 +745,28 @@ var ingressSupport = {
706
745
  streaming: true,
707
746
  signing: "vendor request signing is not used; webhook signatures remain payload based"
708
747
  },
748
+ cognito: {
749
+ publicOrigin: true,
750
+ pathPrefix: true,
751
+ namespaces: "path-or-header",
752
+ streaming: true,
753
+ signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
754
+ },
755
+ medplum: {
756
+ publicOrigin: true,
757
+ pathPrefix: true,
758
+ namespaces: "path-or-header",
759
+ streaming: true,
760
+ signing: "FHIR, OAuth and storage URLs use the configured public namespace"
761
+ },
709
762
  other: {
710
763
  publicOrigin: false,
711
764
  reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
712
765
  }
713
766
  };
767
+ function supportsPublicIngress(service) {
768
+ return service === "stripe" || service === "cognito" || service === "medplum";
769
+ }
714
770
  function advertisedHttpBase(entry) {
715
771
  if (entry.publicPath !== void 0 && !entry.publicOrigin)
716
772
  throw new Error("publicPath requires publicOrigin");
@@ -1249,7 +1305,7 @@ data: ${JSON.stringify(data)}
1249
1305
  {
1250
1306
  protocol: c.protocol,
1251
1307
  coverage: c.coverage,
1252
- ingress: c.targetName === "stripe" ? ingressSupport.stripe : ingressSupport.other,
1308
+ ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
1253
1309
  target: c.targetName,
1254
1310
  outboundPolicy: c.outboundPolicy ?? "unsupported",
1255
1311
  version: 1,
@@ -2333,11 +2389,12 @@ data: ${JSON.stringify(data)}
2333
2389
  format: "mockingbird.fleet/v1",
2334
2390
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2335
2391
  sourceId: this.ctx.id,
2392
+ ...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
2336
2393
  namespaces: contents
2337
2394
  });
2338
2395
  }
2339
2396
  async restoreArchive(input, selectedNamespace) {
2340
- const archive = validateArchive(input);
2397
+ const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
2341
2398
  const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
2342
2399
  if (!entries.length)
2343
2400
  throw new Error("selected namespace absent from archive");
@@ -2622,6 +2679,9 @@ var fleet_config_schema_default = {
2622
2679
  adminControls: {
2623
2680
  $ref: "#/$defs/controls"
2624
2681
  },
2682
+ idBase: {
2683
+ $ref: "#/$defs/idBase"
2684
+ },
2625
2685
  namespace: {
2626
2686
  type: "string",
2627
2687
  pattern: "^[A-Za-z0-9_.-]{1,64}$"
@@ -2711,6 +2771,12 @@ var fleet_config_schema_default = {
2711
2771
  adminControls: {
2712
2772
  $ref: "#/$defs/controls"
2713
2773
  },
2774
+ idBase: {
2775
+ $ref: "#/$defs/idBase"
2776
+ },
2777
+ settings: {
2778
+ type: "object"
2779
+ },
2714
2780
  fixtures: {
2715
2781
  type: "object"
2716
2782
  },
@@ -2757,6 +2823,18 @@ var fleet_config_schema_default = {
2757
2823
  }
2758
2824
  }
2759
2825
  },
2826
+ idBase: {
2827
+ anyOf: [
2828
+ {
2829
+ type: "integer",
2830
+ minimum: 0
2831
+ },
2832
+ {
2833
+ type: "string",
2834
+ pattern: "^(boot|[0-9]+)$"
2835
+ }
2836
+ ]
2837
+ },
2760
2838
  controls: {
2761
2839
  type: "object",
2762
2840
  additionalProperties: false,
@@ -2906,6 +2984,8 @@ async function preflightFleet(config, options) {
2906
2984
  throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
2907
2985
  resolveAdminPrefix(config.adminPrefix);
2908
2986
  normalizeBootControls(config.adminControls);
2987
+ if (config.idBase !== void 0)
2988
+ normalizeIdBase(config.idBase, "idBase");
2909
2989
  const targets = /* @__PURE__ */ new Map();
2910
2990
  const ports = /* @__PURE__ */ new Set();
2911
2991
  for (const [name, entry] of Object.entries(config.services)) {
@@ -2921,7 +3001,7 @@ async function preflightFleet(config, options) {
2921
3001
  if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
2922
3002
  throw new Error("target must name a supported service suffix");
2923
3003
  const service = entry.target ?? name;
2924
- if (advertisedHttpBase(entry) && service !== "stripe")
3004
+ if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
2925
3005
  throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
2926
3006
  const target = await options.load(service);
2927
3007
  const protocol = "protocol" in target ? target.protocol : "http";
@@ -2937,6 +3017,13 @@ async function preflightFleet(config, options) {
2937
3017
  throw new Error("invalid protocol namespace selector");
2938
3018
  }
2939
3019
  }
3020
+ if (entry.idBase !== void 0)
3021
+ normalizeIdBase(entry.idBase, "idBase");
3022
+ if (entry.settings !== void 0) {
3023
+ if (protocol !== "http" || !("create" in target))
3024
+ throw new Error("settings: only HTTP services take boot settings");
3025
+ checkSettings(target, entry.settings, "settings");
3026
+ }
2940
3027
  const port = entry.port ?? target.defaultPort;
2941
3028
  if (port !== 0) {
2942
3029
  const key = `${entry.host ?? "127.0.0.1"}:${port}`;
@@ -2972,7 +3059,7 @@ async function preflightFleet(config, options) {
2972
3059
  break;
2973
3060
  }
2974
3061
  }
2975
- targets.set(name, { ...target, packageVersion: packageVersion ?? "3.5.0" });
3062
+ targets.set(name, { ...target, packageVersion: packageVersion ?? "3.7.0" });
2976
3063
  } catch (error) {
2977
3064
  throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
2978
3065
  }
@@ -3016,6 +3103,11 @@ async function preflightFleet(config, options) {
3016
3103
  return ordered;
3017
3104
  }
3018
3105
  function validateFleetPolicies(options) {
3106
+ if (options.archiveCompatibility) {
3107
+ validateCompatibilityTags(options.archiveCompatibility.tags);
3108
+ if (options.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options.archiveCompatibility.missing))
3109
+ throw new Error("archiveCompatibility.missing must be allow or reject");
3110
+ }
3019
3111
  if (options.journal && !options.repository)
3020
3112
  throw new Error("durable operation journal requires an archive repository");
3021
3113
  if (options.cors) {
@@ -3287,6 +3379,7 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3287
3379
  options.onLog?.(entry2);
3288
3380
  },
3289
3381
  ...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
3382
+ ...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
3290
3383
  ...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
3291
3384
  ...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
3292
3385
  });
@@ -3305,6 +3398,8 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3305
3398
  ].some((method) => typeof created[method] !== "function"))
3306
3399
  throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
3307
3400
  const runtime = created;
3401
+ if (entry.idBase !== void 0)
3402
+ runtime.setIdBase?.(entry.idBase);
3308
3403
  const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
3309
3404
  if (options.outbound && runtime.webhooks)
3310
3405
  runtime.webhooks.setTransportPolicy(ownedTransport(options.outbound));
@@ -3903,6 +3998,7 @@ async function startFleet(config, options) {
3903
3998
  ...entry,
3904
3999
  adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
3905
4000
  ...adminKey !== void 0 ? { adminKey } : {},
4001
+ ...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
3906
4002
  // Fleet-wide controls, with the service's own keys replacing them.
3907
4003
  ...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
3908
4004
  }, options, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
@@ -4008,10 +4104,44 @@ async function startFleet(config, options) {
4008
4104
  }
4009
4105
  }
4010
4106
 
4011
- // ../../adapters/node/dist/fleet-tools.js
4012
- import { readFile as readFile4 } from "node:fs/promises";
4107
+ // ../../adapters/node/dist/fleet-loader.js
4013
4108
  import { createRequire as createRequire2 } from "node:module";
4014
4109
  import { resolve as resolve2 } from "node:path";
4110
+ import { pathToFileURL } from "node:url";
4111
+ var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4112
+ function createInstalledServiceLoader(options) {
4113
+ if (!options.projectRoot)
4114
+ throw new Error("consumer projectRoot required");
4115
+ const allowed = new Set(options.services);
4116
+ for (const name of allowed)
4117
+ if (!suffix.test(name))
4118
+ throw new Error("invalid service suffix");
4119
+ const require2 = createRequire2(resolve2(options.projectRoot, "package.json"));
4120
+ const targets = new Map((options.targets ?? []).map((target) => [target.name, target]));
4121
+ return async (name) => {
4122
+ if (!suffix.test(name) || !allowed.has(name))
4123
+ throw new Error("service is not declared in loader allowlist");
4124
+ const own = targets.get(name);
4125
+ if (own)
4126
+ return own;
4127
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
4128
+ let location;
4129
+ try {
4130
+ location = require2.resolve(specifier);
4131
+ } catch {
4132
+ throw new Error(`cannot load service "${name}": install @crvouga/mockingbird-service-${name} in the consumer project; its /server export is required`);
4133
+ }
4134
+ const mod = await import(pathToFileURL(location).href);
4135
+ if (!mod.serveTarget || mod.serveTarget.name !== name || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
4136
+ throw new Error(`${specifier} exports no matching serveTarget`);
4137
+ return mod.serveTarget;
4138
+ };
4139
+ }
4140
+
4141
+ // ../../adapters/node/dist/fleet-tools.js
4142
+ import { readFile as readFile4 } from "node:fs/promises";
4143
+ import { createRequire as createRequire3 } from "node:module";
4144
+ import { resolve as resolve3 } from "node:path";
4015
4145
 
4016
4146
  // ../../adapters/node/dist/fleet-client.js
4017
4147
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -4025,6 +4155,14 @@ var FleetClientError = class extends Error {
4025
4155
  this.details = details;
4026
4156
  }
4027
4157
  };
4158
+ var FleetClientResponseError = class extends FleetClientError {
4159
+ code;
4160
+ constructor(code, status, origin) {
4161
+ super(status, { code, body: "omitted", ...origin ? { origin } : {} });
4162
+ this.code = code;
4163
+ this.name = "FleetClientResponseError";
4164
+ }
4165
+ };
4028
4166
  var FleetClient = class {
4029
4167
  base;
4030
4168
  options;
@@ -4037,7 +4175,14 @@ var FleetClient = class {
4037
4175
  if (url.username || url.password)
4038
4176
  throw new Error("fleet client URL must not contain credentials");
4039
4177
  url.searchParams.set("namespace", options.namespace ?? this.options.namespace ?? "default");
4178
+ const limit = options.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
4179
+ if (!Number.isSafeInteger(limit) || limit < 1)
4180
+ throw new Error("invalid response byte limit");
4181
+ const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
4182
+ const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
4183
+ signal.throwIfAborted();
4040
4184
  const response = await (this.options.fetch ?? fetch)(url, {
4185
+ redirect: "manual",
4041
4186
  method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
4042
4187
  headers: {
4043
4188
  "content-type": "application/json",
@@ -4045,11 +4190,71 @@ var FleetClient = class {
4045
4190
  ...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
4046
4191
  },
4047
4192
  ...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
4048
- signal: options.signal ?? AbortSignal.timeout(this.options.timeoutMs ?? 3e4)
4193
+ signal
4049
4194
  });
4050
- const data = await response.json();
4051
- if (!response.ok)
4052
- throw new FleetClientError(response.status, data);
4195
+ if (response.status >= 300 && response.status < 400) {
4196
+ await response.body?.cancel();
4197
+ throw new FleetClientResponseError("redirect", response.status, url.origin);
4198
+ }
4199
+ if (Number(response.headers.get("content-length")) > limit) {
4200
+ await response.body?.cancel();
4201
+ throw new FleetClientResponseError("response_too_large", response.status);
4202
+ }
4203
+ const reader = response.body?.getReader();
4204
+ const chunks = [];
4205
+ let size = 0;
4206
+ const abort = () => {
4207
+ void reader?.cancel(signal.reason).catch(() => {
4208
+ });
4209
+ };
4210
+ signal.addEventListener("abort", abort, { once: true });
4211
+ try {
4212
+ if (reader)
4213
+ while (true) {
4214
+ signal.throwIfAborted();
4215
+ const { done, value } = await reader.read();
4216
+ signal.throwIfAborted();
4217
+ if (done)
4218
+ break;
4219
+ size += value.byteLength;
4220
+ if (size > limit) {
4221
+ await reader.cancel();
4222
+ throw new FleetClientResponseError("response_too_large", response.status);
4223
+ }
4224
+ chunks.push(value);
4225
+ }
4226
+ } finally {
4227
+ signal.removeEventListener("abort", abort);
4228
+ if (signal.aborted)
4229
+ await reader?.cancel(signal.reason).catch(() => {
4230
+ });
4231
+ reader?.releaseLock();
4232
+ }
4233
+ const bytes = new Uint8Array(size);
4234
+ let offset = 0;
4235
+ for (const chunk of chunks) {
4236
+ bytes.set(chunk, offset);
4237
+ offset += chunk.byteLength;
4238
+ }
4239
+ const text2 = new TextDecoder().decode(bytes);
4240
+ let data;
4241
+ try {
4242
+ data = text2.length ? JSON.parse(text2) : void 0;
4243
+ } catch {
4244
+ if (response.ok)
4245
+ throw new FleetClientResponseError("invalid_json", response.status);
4246
+ }
4247
+ if (!response.ok) {
4248
+ const error = data && typeof data === "object" ? data.error : void 0;
4249
+ const detail = { body: "omitted" };
4250
+ if (error && typeof error === "object")
4251
+ for (const key of ["type", "code"]) {
4252
+ const value = error[key];
4253
+ if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
4254
+ detail[key] = value;
4255
+ }
4256
+ throw new FleetClientError(response.status, detail);
4257
+ }
4053
4258
  return data;
4054
4259
  }
4055
4260
  discover() {
@@ -4115,17 +4320,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
4115
4320
  const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
4116
4321
  if (!child || !secret)
4117
4322
  throw new Error(`missing service mapping ${entry.service}`);
4323
+ let endpoint;
4118
4324
  if (child.protocol === "http") {
4119
4325
  const template = child.namespaces.path;
4120
4326
  if (typeof template !== "string")
4121
4327
  throw new Error(`missing HTTP namespace mapping ${entry.service}`);
4122
- result[variable] = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4328
+ endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4123
4329
  } else {
4124
- const endpoint = secret.namespaces?.[namespace];
4125
- if (!endpoint)
4330
+ const selected = secret.namespaces?.[namespace];
4331
+ if (!selected)
4126
4332
  throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
4127
- result[variable] = endpoint;
4333
+ endpoint = selected;
4334
+ }
4335
+ const url = new URL(endpoint);
4336
+ const field = entry.field ?? "url";
4337
+ let value;
4338
+ switch (field) {
4339
+ case "url":
4340
+ value = endpoint;
4341
+ break;
4342
+ case "hostname":
4343
+ value = url.hostname.replace(/^\[|\]$/g, "");
4344
+ break;
4345
+ case "port":
4346
+ value = url.port;
4347
+ break;
4348
+ case "username":
4349
+ value = decodeURIComponent(url.username);
4350
+ break;
4351
+ case "password":
4352
+ value = decodeURIComponent(url.password);
4353
+ break;
4354
+ case "pathname":
4355
+ value = decodeURIComponent(url.pathname);
4356
+ break;
4357
+ case "database":
4358
+ value = decodeURIComponent(url.pathname.replace(/^\//, ""));
4359
+ break;
4360
+ default:
4361
+ throw new Error("unsupported connection field");
4128
4362
  }
4363
+ if (!value) {
4364
+ if (typeof entry.fallback !== "string")
4365
+ throw new Error(`missing connection component ${field}`);
4366
+ value = entry.fallback;
4367
+ }
4368
+ result[variable] = value;
4129
4369
  }
4130
4370
  return result;
4131
4371
  }
@@ -4149,7 +4389,7 @@ async function doctorFleet(config, options, probes = []) {
4149
4389
  const plan = [];
4150
4390
  try {
4151
4391
  const targets = await preflightFleet(config, options);
4152
- const require2 = createRequire2(resolve2(process.cwd(), "package.json"));
4392
+ const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
4153
4393
  for (const [service, target] of targets) {
4154
4394
  let version = "development";
4155
4395
  try {
@@ -4204,7 +4444,7 @@ async function doctorFleet(config, options, probes = []) {
4204
4444
  };
4205
4445
  }
4206
4446
  async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e4) {
4207
- const archive = validateArchive(input);
4447
+ const archive = assertArchiveCompatibility(input, options.archiveCompatibility);
4208
4448
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4209
4449
  const isolated = {
4210
4450
  ...config,
@@ -4216,7 +4456,7 @@ async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e
4216
4456
  };
4217
4457
  let fleet;
4218
4458
  try {
4219
- fleet = await startFleet(isolated, { load: options.load });
4459
+ fleet = await startFleet(isolated, options);
4220
4460
  const client = new FleetClient(fleet.manifest.adminBase, {
4221
4461
  ...config.adminKey ? { adminKey: config.adminKey } : {}
4222
4462
  });
@@ -4443,6 +4683,16 @@ var COMMON_SERVE_OPTIONS = {
4443
4683
  value: "<seed>",
4444
4684
  description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
4445
4685
  },
4686
+ "id-base": {
4687
+ type: "string",
4688
+ value: "<n|boot>",
4689
+ 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)"
4690
+ },
4691
+ settings: {
4692
+ type: "string",
4693
+ value: "<json|file>",
4694
+ 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)"
4695
+ },
4446
4696
  "admin-controls": {
4447
4697
  type: "string",
4448
4698
  value: "<json|file>",
@@ -4537,19 +4787,40 @@ var readJsonFile = async (path, label) => {
4537
4787
  }
4538
4788
  };
4539
4789
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4790
+ var readJsonOption = async (source, label) => {
4791
+ const trimmed = source.trim();
4792
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4793
+ try {
4794
+ return JSON.parse(trimmed);
4795
+ } catch {
4796
+ throw new Error(`${label}: not valid JSON`);
4797
+ }
4798
+ }
4799
+ return readJsonFile(source, label);
4800
+ };
4801
+ var resolveServeIdBase = (values, env = process.env) => {
4802
+ const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
4803
+ if (source === void 0 || source.trim() === "")
4804
+ return void 0;
4805
+ normalizeIdBase(source.trim(), "--id-base");
4806
+ return source.trim();
4807
+ };
4808
+ var resolveServeSettings = async (target, values, env = process.env) => {
4809
+ const flag = asString(values.settings);
4810
+ const source = flag ?? env.MOCKINGBIRD_SETTINGS;
4811
+ if (source === void 0 || source.trim() === "")
4812
+ return void 0;
4813
+ const label = flag !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
4814
+ const parsed = await readJsonOption(source, label);
4815
+ if (!isObject2(parsed))
4816
+ throw new Error(`${label}: expected a JSON object`);
4817
+ return checkSettings(target, parsed, label);
4818
+ };
4540
4819
  var resolveServeControls = async (values, env = process.env, base) => {
4541
4820
  const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
4542
4821
  let document = isObject2(base) ? { ...base } : {};
4543
4822
  if (source !== void 0 && source.trim() !== "") {
4544
- let parsed;
4545
- if (source.trim().startsWith("{")) {
4546
- try {
4547
- parsed = JSON.parse(source);
4548
- } catch {
4549
- throw new Error("--admin-controls: not valid JSON");
4550
- }
4551
- } else
4552
- parsed = await readJsonFile(source, "--admin-controls");
4823
+ const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
4553
4824
  if (!isObject2(parsed))
4554
4825
  throw new Error("--admin-controls: expected a JSON object");
4555
4826
  document = { ...document, ...parsed };
@@ -4591,26 +4862,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
4591
4862
  normalizeBootControls(document, "adminControls");
4592
4863
  return document;
4593
4864
  };
4594
- var loadTarget = async (name, own) => {
4595
- if (name === own.name)
4596
- return own;
4597
- const specifier = `@crvouga/mockingbird-service-${name}/server`;
4598
- try {
4599
- let mod;
4600
- try {
4601
- mod = await import(specifier);
4602
- } catch {
4603
- const fromProject = createRequire3(resolve3(process.cwd(), "package.json"));
4604
- mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
4605
- }
4606
- if (!mod.serveTarget)
4607
- throw new Error(`${specifier} exports no serveTarget`);
4608
- return mod.serveTarget;
4609
- } catch (error) {
4610
- const reason = error instanceof Error ? error.message : String(error);
4611
- throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
4612
- }
4613
- };
4865
+ var loadTarget = (name, own) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name], targets: [own] })(name);
4614
4866
  var start = async (target, values, config) => {
4615
4867
  const adminPrefix = resolveAdminPrefix(config.adminPrefix);
4616
4868
  const runtime = await target.create(values, {
@@ -4618,8 +4870,11 @@ var start = async (target, values, config) => {
4618
4870
  adminKey: config.adminKey,
4619
4871
  seed: config.seed,
4620
4872
  onLog: formatLog(config.log),
4873
+ ...config.settings !== void 0 ? { settings: config.settings } : {},
4621
4874
  ...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
4622
4875
  });
4876
+ if (config.idBase !== void 0)
4877
+ runtime.setIdBase?.(config.idBase);
4623
4878
  const listening = await listen(runtime, { port: config.port, host: config.host });
4624
4879
  target.listening?.(listening);
4625
4880
  console.log(`${target.name} emulator listening on ${listening.url}`);
@@ -4665,6 +4920,11 @@ var serveCommand = (target) => ({
4665
4920
  const adminControls = await resolveServeControls(values, process.env, config.adminControls);
4666
4921
  if (adminControls !== void 0)
4667
4922
  config.adminControls = adminControls;
4923
+ if (values.settings !== void 0)
4924
+ throw new Error("--settings serves a single service; give each service its own `settings` in the config");
4925
+ const idBase = config.idBase ?? resolveServeIdBase(values);
4926
+ if (idBase !== void 0)
4927
+ config.idBase = idBase;
4668
4928
  const fleet = await startFleet(config, {
4669
4929
  load: (name) => loadTarget(name, target),
4670
4930
  onLog: formatLog(config.log ?? log),
@@ -4694,8 +4954,12 @@ var serveCommand = (target) => ({
4694
4954
  let listening;
4695
4955
  try {
4696
4956
  const adminControls = await resolveServeControls(values);
4957
+ const idBase = resolveServeIdBase(values);
4958
+ const settings = await resolveServeSettings(target, values);
4697
4959
  listening = await start(target, values, {
4698
4960
  ...adminControls !== void 0 ? { adminControls } : {},
4961
+ ...idBase !== void 0 ? { idBase } : {},
4962
+ ...settings !== void 0 ? { settings } : {},
4699
4963
  adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
4700
4964
  port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
4701
4965
  host: asString(values.host) ?? "127.0.0.1",
@@ -4765,4 +5029,4 @@ export {
4765
5029
  createServer2 as createServer,
4766
5030
  serveTarget
4767
5031
  };
4768
- //# sourceMappingURL=chunk-IODRZUXV.js.map
5032
+ //# sourceMappingURL=chunk-E3VNLDYM.js.map