@crvouga/mockingbird-service-slack 3.4.0 → 3.6.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog — @crvouga/mockingbird-service-slack
2
2
 
3
+ ## 3.6.0 (2026-10-17)
4
+
5
+ ### Features
6
+
7
+ - resolve fleet and emulator issue batch ([26d2f8c](https://github.com/crvouga/mockingbird/commit/26d2f8cee99fefed93192204bac44fb1be35c1da))
8
+
9
+ ## 3.5.0 (2026-10-15)
10
+
11
+ ### Features
12
+
13
+ - resolve SQL, routing and writer gaps ([73f97d0](https://github.com/crvouga/mockingbird/commit/73f97d0d2379f44b9131180498f4fab76648fed9))
14
+
3
15
  ## 3.4.0 (2026-10-15)
4
16
 
5
17
  ### Features
@@ -3756,7 +3756,7 @@ var stateAdminRoutes = (routes) => {
3756
3756
  };
3757
3757
 
3758
3758
  // ../core/dist/version.js
3759
- var PACKAGE_VERSION = true ? "3.4.0" : UNRELEASED_VERSION;
3759
+ var PACKAGE_VERSION = true ? "3.6.0" : UNRELEASED_VERSION;
3760
3760
 
3761
3761
  // ../core/dist/webhooks.js
3762
3762
  var json5 = (status, body) => new Response(JSON.stringify(body), {
@@ -17,9 +17,6 @@ import {
17
17
 
18
18
  // ../../adapters/node/dist/cli.js
19
19
  import { readFile as readFile5 } from "node:fs/promises";
20
- import { createRequire as createRequire3 } from "node:module";
21
- import { resolve as resolve3 } from "node:path";
22
- import { pathToFileURL } from "node:url";
23
20
  import { parseArgs } from "node:util";
24
21
 
25
22
  // ../../adapters/node/dist/fleet.js
@@ -33,6 +30,31 @@ import { createHash as createHash3, randomBytes, randomUUID as randomUUID3 } fro
33
30
 
34
31
  // ../../adapters/node/dist/fleet-archive.js
35
32
  import { createHash } from "node:crypto";
33
+ function validateCompatibilityTags(tags) {
34
+ if (!tags || typeof tags !== "object" || Array.isArray(tags))
35
+ throw new Error("invalid archive compatibility tags");
36
+ const entries = Object.entries(tags);
37
+ 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)))
38
+ throw new Error("invalid archive compatibility tags");
39
+ }
40
+ function assertCompatibilityTags(tags, policy) {
41
+ if (tags !== void 0)
42
+ validateCompatibilityTags(tags);
43
+ if (!policy)
44
+ return;
45
+ validateCompatibilityTags(policy.tags);
46
+ if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
47
+ throw new Error("invalid missing compatibility policy");
48
+ if (tags === void 0 && policy.missing === "allow")
49
+ return;
50
+ if (canonical(tags) !== canonical(policy.tags))
51
+ throw new Error("application archive compatibility mismatch");
52
+ }
53
+ function assertArchiveCompatibility(input, policy) {
54
+ const archive = validateArchive(input);
55
+ assertCompatibilityTags(archive.compatibility, policy);
56
+ return archive;
57
+ }
36
58
  var canonical = (value) => {
37
59
  if (Array.isArray(value))
38
60
  return `[${value.map(canonical).join(",")}]`;
@@ -52,6 +74,8 @@ function validateArchive(value) {
52
74
  const { sha256, ...content } = doc;
53
75
  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))
54
76
  throw new Error("unsupported or corrupt fleet archive");
77
+ if (doc.compatibility !== void 0)
78
+ validateCompatibilityTags(doc.compatibility);
55
79
  for (const [namespace, entry] of Object.entries(doc.namespaces)) {
56
80
  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)
57
81
  throw new Error("invalid archive namespace");
@@ -67,6 +91,7 @@ function inspectArchive(input) {
67
91
  format: archive.format,
68
92
  createdAt: archive.createdAt,
69
93
  sha256: archive.sha256,
94
+ ...archive.compatibility ? { compatibility: archive.compatibility } : {},
70
95
  namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name, ns]) => [
71
96
  name,
72
97
  {
@@ -709,11 +734,28 @@ var ingressSupport = {
709
734
  streaming: true,
710
735
  signing: "vendor request signing is not used; webhook signatures remain payload based"
711
736
  },
737
+ cognito: {
738
+ publicOrigin: true,
739
+ pathPrefix: true,
740
+ namespaces: "path-or-header",
741
+ streaming: true,
742
+ signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
743
+ },
744
+ medplum: {
745
+ publicOrigin: true,
746
+ pathPrefix: true,
747
+ namespaces: "path-or-header",
748
+ streaming: true,
749
+ signing: "FHIR, OAuth and storage URLs use the configured public namespace"
750
+ },
712
751
  other: {
713
752
  publicOrigin: false,
714
753
  reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
715
754
  }
716
755
  };
756
+ function supportsPublicIngress(service) {
757
+ return service === "stripe" || service === "cognito" || service === "medplum";
758
+ }
717
759
  function advertisedHttpBase(entry) {
718
760
  if (entry.publicPath !== void 0 && !entry.publicOrigin)
719
761
  throw new Error("publicPath requires publicOrigin");
@@ -1252,7 +1294,7 @@ data: ${JSON.stringify(data)}
1252
1294
  {
1253
1295
  protocol: c.protocol,
1254
1296
  coverage: c.coverage,
1255
- ingress: c.targetName === "stripe" ? ingressSupport.stripe : ingressSupport.other,
1297
+ ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
1256
1298
  target: c.targetName,
1257
1299
  outboundPolicy: c.outboundPolicy ?? "unsupported",
1258
1300
  version: 1,
@@ -2336,11 +2378,12 @@ data: ${JSON.stringify(data)}
2336
2378
  format: "mockingbird.fleet/v1",
2337
2379
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2338
2380
  sourceId: this.ctx.id,
2381
+ ...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
2339
2382
  namespaces: contents
2340
2383
  });
2341
2384
  }
2342
2385
  async restoreArchive(input, selectedNamespace) {
2343
- const archive = validateArchive(input);
2386
+ const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
2344
2387
  const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
2345
2388
  if (!entries.length)
2346
2389
  throw new Error("selected namespace absent from archive");
@@ -2924,7 +2967,7 @@ async function preflightFleet(config, options) {
2924
2967
  if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
2925
2968
  throw new Error("target must name a supported service suffix");
2926
2969
  const service = entry.target ?? name;
2927
- if (advertisedHttpBase(entry) && service !== "stripe")
2970
+ if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
2928
2971
  throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
2929
2972
  const target = await options.load(service);
2930
2973
  const protocol = "protocol" in target ? target.protocol : "http";
@@ -2975,7 +3018,7 @@ async function preflightFleet(config, options) {
2975
3018
  break;
2976
3019
  }
2977
3020
  }
2978
- targets.set(name, { ...target, packageVersion: packageVersion ?? "3.4.0" });
3021
+ targets.set(name, { ...target, packageVersion: packageVersion ?? "3.6.0" });
2979
3022
  } catch (error) {
2980
3023
  throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
2981
3024
  }
@@ -3019,6 +3062,11 @@ async function preflightFleet(config, options) {
3019
3062
  return ordered;
3020
3063
  }
3021
3064
  function validateFleetPolicies(options) {
3065
+ if (options.archiveCompatibility) {
3066
+ validateCompatibilityTags(options.archiveCompatibility.tags);
3067
+ if (options.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options.archiveCompatibility.missing))
3068
+ throw new Error("archiveCompatibility.missing must be allow or reject");
3069
+ }
3022
3070
  if (options.journal && !options.repository)
3023
3071
  throw new Error("durable operation journal requires an archive repository");
3024
3072
  if (options.cors) {
@@ -4011,10 +4059,44 @@ async function startFleet(config, options) {
4011
4059
  }
4012
4060
  }
4013
4061
 
4014
- // ../../adapters/node/dist/fleet-tools.js
4015
- import { readFile as readFile4 } from "node:fs/promises";
4062
+ // ../../adapters/node/dist/fleet-loader.js
4016
4063
  import { createRequire as createRequire2 } from "node:module";
4017
4064
  import { resolve as resolve2 } from "node:path";
4065
+ import { pathToFileURL } from "node:url";
4066
+ var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4067
+ function createInstalledServiceLoader(options) {
4068
+ if (!options.projectRoot)
4069
+ throw new Error("consumer projectRoot required");
4070
+ const allowed = new Set(options.services);
4071
+ for (const name of allowed)
4072
+ if (!suffix.test(name))
4073
+ throw new Error("invalid service suffix");
4074
+ const require2 = createRequire2(resolve2(options.projectRoot, "package.json"));
4075
+ const targets = new Map((options.targets ?? []).map((target) => [target.name, target]));
4076
+ return async (name) => {
4077
+ if (!suffix.test(name) || !allowed.has(name))
4078
+ throw new Error("service is not declared in loader allowlist");
4079
+ const own = targets.get(name);
4080
+ if (own)
4081
+ return own;
4082
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
4083
+ let location;
4084
+ try {
4085
+ location = require2.resolve(specifier);
4086
+ } catch {
4087
+ throw new Error(`cannot load service "${name}": install @crvouga/mockingbird-service-${name} in the consumer project; its /server export is required`);
4088
+ }
4089
+ const mod = await import(pathToFileURL(location).href);
4090
+ if (!mod.serveTarget || mod.serveTarget.name !== name || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
4091
+ throw new Error(`${specifier} exports no matching serveTarget`);
4092
+ return mod.serveTarget;
4093
+ };
4094
+ }
4095
+
4096
+ // ../../adapters/node/dist/fleet-tools.js
4097
+ import { readFile as readFile4 } from "node:fs/promises";
4098
+ import { createRequire as createRequire3 } from "node:module";
4099
+ import { resolve as resolve3 } from "node:path";
4018
4100
 
4019
4101
  // ../../adapters/node/dist/fleet-client.js
4020
4102
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -4028,6 +4110,14 @@ var FleetClientError = class extends Error {
4028
4110
  this.details = details;
4029
4111
  }
4030
4112
  };
4113
+ var FleetClientResponseError = class extends FleetClientError {
4114
+ code;
4115
+ constructor(code, status, origin) {
4116
+ super(status, { code, body: "omitted", ...origin ? { origin } : {} });
4117
+ this.code = code;
4118
+ this.name = "FleetClientResponseError";
4119
+ }
4120
+ };
4031
4121
  var FleetClient = class {
4032
4122
  base;
4033
4123
  options;
@@ -4040,7 +4130,14 @@ var FleetClient = class {
4040
4130
  if (url.username || url.password)
4041
4131
  throw new Error("fleet client URL must not contain credentials");
4042
4132
  url.searchParams.set("namespace", options.namespace ?? this.options.namespace ?? "default");
4133
+ const limit = options.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
4134
+ if (!Number.isSafeInteger(limit) || limit < 1)
4135
+ throw new Error("invalid response byte limit");
4136
+ const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
4137
+ const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
4138
+ signal.throwIfAborted();
4043
4139
  const response = await (this.options.fetch ?? fetch)(url, {
4140
+ redirect: "manual",
4044
4141
  method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
4045
4142
  headers: {
4046
4143
  "content-type": "application/json",
@@ -4048,11 +4145,71 @@ var FleetClient = class {
4048
4145
  ...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
4049
4146
  },
4050
4147
  ...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
4051
- signal: options.signal ?? AbortSignal.timeout(this.options.timeoutMs ?? 3e4)
4148
+ signal
4052
4149
  });
4053
- const data = await response.json();
4054
- if (!response.ok)
4055
- throw new FleetClientError(response.status, data);
4150
+ if (response.status >= 300 && response.status < 400) {
4151
+ await response.body?.cancel();
4152
+ throw new FleetClientResponseError("redirect", response.status, url.origin);
4153
+ }
4154
+ if (Number(response.headers.get("content-length")) > limit) {
4155
+ await response.body?.cancel();
4156
+ throw new FleetClientResponseError("response_too_large", response.status);
4157
+ }
4158
+ const reader = response.body?.getReader();
4159
+ const chunks = [];
4160
+ let size = 0;
4161
+ const abort = () => {
4162
+ void reader?.cancel(signal.reason).catch(() => {
4163
+ });
4164
+ };
4165
+ signal.addEventListener("abort", abort, { once: true });
4166
+ try {
4167
+ if (reader)
4168
+ while (true) {
4169
+ signal.throwIfAborted();
4170
+ const { done, value } = await reader.read();
4171
+ signal.throwIfAborted();
4172
+ if (done)
4173
+ break;
4174
+ size += value.byteLength;
4175
+ if (size > limit) {
4176
+ await reader.cancel();
4177
+ throw new FleetClientResponseError("response_too_large", response.status);
4178
+ }
4179
+ chunks.push(value);
4180
+ }
4181
+ } finally {
4182
+ signal.removeEventListener("abort", abort);
4183
+ if (signal.aborted)
4184
+ await reader?.cancel(signal.reason).catch(() => {
4185
+ });
4186
+ reader?.releaseLock();
4187
+ }
4188
+ const bytes = new Uint8Array(size);
4189
+ let offset = 0;
4190
+ for (const chunk of chunks) {
4191
+ bytes.set(chunk, offset);
4192
+ offset += chunk.byteLength;
4193
+ }
4194
+ const text2 = new TextDecoder().decode(bytes);
4195
+ let data;
4196
+ try {
4197
+ data = text2.length ? JSON.parse(text2) : void 0;
4198
+ } catch {
4199
+ if (response.ok)
4200
+ throw new FleetClientResponseError("invalid_json", response.status);
4201
+ }
4202
+ if (!response.ok) {
4203
+ const error = data && typeof data === "object" ? data.error : void 0;
4204
+ const detail = { body: "omitted" };
4205
+ if (error && typeof error === "object")
4206
+ for (const key of ["type", "code"]) {
4207
+ const value = error[key];
4208
+ if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
4209
+ detail[key] = value;
4210
+ }
4211
+ throw new FleetClientError(response.status, detail);
4212
+ }
4056
4213
  return data;
4057
4214
  }
4058
4215
  discover() {
@@ -4118,17 +4275,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
4118
4275
  const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
4119
4276
  if (!child || !secret)
4120
4277
  throw new Error(`missing service mapping ${entry.service}`);
4278
+ let endpoint;
4121
4279
  if (child.protocol === "http") {
4122
4280
  const template = child.namespaces.path;
4123
4281
  if (typeof template !== "string")
4124
4282
  throw new Error(`missing HTTP namespace mapping ${entry.service}`);
4125
- result[variable] = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4283
+ endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4126
4284
  } else {
4127
- const endpoint = secret.namespaces?.[namespace];
4128
- if (!endpoint)
4285
+ const selected = secret.namespaces?.[namespace];
4286
+ if (!selected)
4129
4287
  throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
4130
- result[variable] = endpoint;
4288
+ endpoint = selected;
4131
4289
  }
4290
+ const url = new URL(endpoint);
4291
+ const field = entry.field ?? "url";
4292
+ let value;
4293
+ switch (field) {
4294
+ case "url":
4295
+ value = endpoint;
4296
+ break;
4297
+ case "hostname":
4298
+ value = url.hostname.replace(/^\[|\]$/g, "");
4299
+ break;
4300
+ case "port":
4301
+ value = url.port;
4302
+ break;
4303
+ case "username":
4304
+ value = decodeURIComponent(url.username);
4305
+ break;
4306
+ case "password":
4307
+ value = decodeURIComponent(url.password);
4308
+ break;
4309
+ case "pathname":
4310
+ value = decodeURIComponent(url.pathname);
4311
+ break;
4312
+ case "database":
4313
+ value = decodeURIComponent(url.pathname.replace(/^\//, ""));
4314
+ break;
4315
+ default:
4316
+ throw new Error("unsupported connection field");
4317
+ }
4318
+ if (!value) {
4319
+ if (typeof entry.fallback !== "string")
4320
+ throw new Error(`missing connection component ${field}`);
4321
+ value = entry.fallback;
4322
+ }
4323
+ result[variable] = value;
4132
4324
  }
4133
4325
  return result;
4134
4326
  }
@@ -4152,7 +4344,7 @@ async function doctorFleet(config, options, probes = []) {
4152
4344
  const plan = [];
4153
4345
  try {
4154
4346
  const targets = await preflightFleet(config, options);
4155
- const require2 = createRequire2(resolve2(process.cwd(), "package.json"));
4347
+ const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
4156
4348
  for (const [service, target] of targets) {
4157
4349
  let version = "development";
4158
4350
  try {
@@ -4207,7 +4399,7 @@ async function doctorFleet(config, options, probes = []) {
4207
4399
  };
4208
4400
  }
4209
4401
  async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e4) {
4210
- const archive = validateArchive(input);
4402
+ const archive = assertArchiveCompatibility(input, options.archiveCompatibility);
4211
4403
  const startedAt2 = (/* @__PURE__ */ new Date()).toISOString();
4212
4404
  const isolated = {
4213
4405
  ...config,
@@ -4219,7 +4411,7 @@ async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e
4219
4411
  };
4220
4412
  let fleet;
4221
4413
  try {
4222
- fleet = await startFleet(isolated, { load: options.load });
4414
+ fleet = await startFleet(isolated, options);
4223
4415
  const client = new FleetClient(fleet.manifest.adminBase, {
4224
4416
  ...config.adminKey ? { adminKey: config.adminKey } : {}
4225
4417
  });
@@ -4594,26 +4786,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
4594
4786
  normalizeBootControls(document, "adminControls");
4595
4787
  return document;
4596
4788
  };
4597
- var loadTarget = async (name, own) => {
4598
- if (name === own.name)
4599
- return own;
4600
- const specifier = `@crvouga/mockingbird-service-${name}/server`;
4601
- try {
4602
- let mod;
4603
- try {
4604
- mod = await import(specifier);
4605
- } catch {
4606
- const fromProject = createRequire3(resolve3(process.cwd(), "package.json"));
4607
- mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
4608
- }
4609
- if (!mod.serveTarget)
4610
- throw new Error(`${specifier} exports no serveTarget`);
4611
- return mod.serveTarget;
4612
- } catch (error) {
4613
- const reason = error instanceof Error ? error.message : String(error);
4614
- throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
4615
- }
4616
- };
4789
+ var loadTarget = (name, own) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name], targets: [own] })(name);
4617
4790
  var start = async (target, values, config) => {
4618
4791
  const adminPrefix = resolveAdminPrefix(config.adminPrefix);
4619
4792
  const runtime = await target.create(values, {
@@ -5110,4 +5283,4 @@ export {
5110
5283
  createServer2 as createServer,
5111
5284
  serveTarget
5112
5285
  };
5113
- //# sourceMappingURL=chunk-DHAINBRN.js.map
5286
+ //# sourceMappingURL=chunk-TMQCTVOT.js.map