@crvouga/mockingbird-service-mailosaur 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.
- package/CHANGELOG.md +13 -1
- package/dist/{chunk-RTJ3O3AX.js → chunk-GRXH63CD.js} +315 -51
- package/dist/chunk-GRXH63CD.js.map +7 -0
- package/dist/{chunk-U3FYDM4L.js → chunk-PROCZMVA.js} +114 -30
- package/dist/chunk-PROCZMVA.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +28 -0
- package/dist/index.js +1 -1
- package/dist/server.d.ts +69 -0
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-RTJ3O3AX.js.map +0 -7
- package/dist/chunk-U3FYDM4L.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-mailosaur
|
|
2
2
|
|
|
3
|
-
## 3.
|
|
3
|
+
## 3.7.0 (2026-10-19)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- resolve the agent-reported issue queue in one batch ([25c52b6](https://github.com/crvouga/mockingbird/commit/25c52b668a53ddb80b968419601a20abf33f956b))
|
|
8
|
+
|
|
9
|
+
## 3.6.0 (2026-10-16)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- resolve fleet and emulator issue batch ([26d2f8c](https://github.com/crvouga/mockingbird/commit/26d2f8cee99fefed93192204bac44fb1be35c1da))
|
|
14
|
+
|
|
15
|
+
## 3.5.0 (2026-10-15)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
6
18
|
|
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
createRuntime,
|
|
4
4
|
normalizeBootControls,
|
|
5
5
|
normalizeControls,
|
|
6
|
+
normalizeIdBase,
|
|
6
7
|
ownedTransport,
|
|
7
8
|
parseDuration,
|
|
8
9
|
parseFaultHeader,
|
|
9
10
|
resolveAdminPrefix,
|
|
10
11
|
restoreClock
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PROCZMVA.js";
|
|
12
13
|
|
|
13
14
|
// src/server.ts
|
|
14
15
|
import { writeFileSync } from "node:fs";
|
|
@@ -21,11 +22,21 @@ import { createServer as createTlsServer } from "node:tls";
|
|
|
21
22
|
|
|
22
23
|
// ../../adapters/node/dist/cli.js
|
|
23
24
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
24
|
-
import { createRequire as createRequire3 } from "node:module";
|
|
25
|
-
import { resolve as resolve3 } from "node:path";
|
|
26
|
-
import { pathToFileURL } from "node:url";
|
|
27
25
|
import { parseArgs } from "node:util";
|
|
28
26
|
|
|
27
|
+
// ../../adapters/node/dist/boot-settings.js
|
|
28
|
+
var checkSettings = (target, settings, label) => {
|
|
29
|
+
if (!target.parseSettings)
|
|
30
|
+
throw new Error(`${label}: ${target.name} takes no boot settings`);
|
|
31
|
+
try {
|
|
32
|
+
return target.parseSettings(settings);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
35
|
+
cause: error
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
29
40
|
// ../../adapters/node/dist/fleet.js
|
|
30
41
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
31
42
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -37,6 +48,31 @@ import { createHash as createHash3, randomBytes, randomUUID as randomUUID3 } fro
|
|
|
37
48
|
|
|
38
49
|
// ../../adapters/node/dist/fleet-archive.js
|
|
39
50
|
import { createHash } from "node:crypto";
|
|
51
|
+
function validateCompatibilityTags(tags) {
|
|
52
|
+
if (!tags || typeof tags !== "object" || Array.isArray(tags))
|
|
53
|
+
throw new Error("invalid archive compatibility tags");
|
|
54
|
+
const entries = Object.entries(tags);
|
|
55
|
+
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)))
|
|
56
|
+
throw new Error("invalid archive compatibility tags");
|
|
57
|
+
}
|
|
58
|
+
function assertCompatibilityTags(tags, policy) {
|
|
59
|
+
if (tags !== void 0)
|
|
60
|
+
validateCompatibilityTags(tags);
|
|
61
|
+
if (!policy)
|
|
62
|
+
return;
|
|
63
|
+
validateCompatibilityTags(policy.tags);
|
|
64
|
+
if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
|
|
65
|
+
throw new Error("invalid missing compatibility policy");
|
|
66
|
+
if (tags === void 0 && policy.missing === "allow")
|
|
67
|
+
return;
|
|
68
|
+
if (canonical(tags) !== canonical(policy.tags))
|
|
69
|
+
throw new Error("application archive compatibility mismatch");
|
|
70
|
+
}
|
|
71
|
+
function assertArchiveCompatibility(input, policy) {
|
|
72
|
+
const archive = validateArchive(input);
|
|
73
|
+
assertCompatibilityTags(archive.compatibility, policy);
|
|
74
|
+
return archive;
|
|
75
|
+
}
|
|
40
76
|
var canonical = (value) => {
|
|
41
77
|
if (Array.isArray(value))
|
|
42
78
|
return `[${value.map(canonical).join(",")}]`;
|
|
@@ -56,6 +92,8 @@ function validateArchive(value) {
|
|
|
56
92
|
const { sha256, ...content } = doc;
|
|
57
93
|
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))
|
|
58
94
|
throw new Error("unsupported or corrupt fleet archive");
|
|
95
|
+
if (doc.compatibility !== void 0)
|
|
96
|
+
validateCompatibilityTags(doc.compatibility);
|
|
59
97
|
for (const [namespace, entry] of Object.entries(doc.namespaces)) {
|
|
60
98
|
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)
|
|
61
99
|
throw new Error("invalid archive namespace");
|
|
@@ -71,6 +109,7 @@ function inspectArchive(input) {
|
|
|
71
109
|
format: archive.format,
|
|
72
110
|
createdAt: archive.createdAt,
|
|
73
111
|
sha256: archive.sha256,
|
|
112
|
+
...archive.compatibility ? { compatibility: archive.compatibility } : {},
|
|
74
113
|
namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name2, ns]) => [
|
|
75
114
|
name2,
|
|
76
115
|
{
|
|
@@ -713,11 +752,28 @@ var ingressSupport = {
|
|
|
713
752
|
streaming: true,
|
|
714
753
|
signing: "vendor request signing is not used; webhook signatures remain payload based"
|
|
715
754
|
},
|
|
755
|
+
cognito: {
|
|
756
|
+
publicOrigin: true,
|
|
757
|
+
pathPrefix: true,
|
|
758
|
+
namespaces: "path-or-header",
|
|
759
|
+
streaming: true,
|
|
760
|
+
signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
|
|
761
|
+
},
|
|
762
|
+
medplum: {
|
|
763
|
+
publicOrigin: true,
|
|
764
|
+
pathPrefix: true,
|
|
765
|
+
namespaces: "path-or-header",
|
|
766
|
+
streaming: true,
|
|
767
|
+
signing: "FHIR, OAuth and storage URLs use the configured public namespace"
|
|
768
|
+
},
|
|
716
769
|
other: {
|
|
717
770
|
publicOrigin: false,
|
|
718
771
|
reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
|
|
719
772
|
}
|
|
720
773
|
};
|
|
774
|
+
function supportsPublicIngress(service) {
|
|
775
|
+
return service === "stripe" || service === "cognito" || service === "medplum";
|
|
776
|
+
}
|
|
721
777
|
function advertisedHttpBase(entry) {
|
|
722
778
|
if (entry.publicPath !== void 0 && !entry.publicOrigin)
|
|
723
779
|
throw new Error("publicPath requires publicOrigin");
|
|
@@ -1256,7 +1312,7 @@ data: ${JSON.stringify(data)}
|
|
|
1256
1312
|
{
|
|
1257
1313
|
protocol: c.protocol,
|
|
1258
1314
|
coverage: c.coverage,
|
|
1259
|
-
ingress: c.targetName
|
|
1315
|
+
ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
|
|
1260
1316
|
target: c.targetName,
|
|
1261
1317
|
outboundPolicy: c.outboundPolicy ?? "unsupported",
|
|
1262
1318
|
version: 1,
|
|
@@ -2340,11 +2396,12 @@ data: ${JSON.stringify(data)}
|
|
|
2340
2396
|
format: "mockingbird.fleet/v1",
|
|
2341
2397
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2342
2398
|
sourceId: this.ctx.id,
|
|
2399
|
+
...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
|
|
2343
2400
|
namespaces: contents
|
|
2344
2401
|
});
|
|
2345
2402
|
}
|
|
2346
2403
|
async restoreArchive(input, selectedNamespace) {
|
|
2347
|
-
const archive =
|
|
2404
|
+
const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
|
|
2348
2405
|
const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
|
|
2349
2406
|
if (!entries.length)
|
|
2350
2407
|
throw new Error("selected namespace absent from archive");
|
|
@@ -2629,6 +2686,9 @@ var fleet_config_schema_default = {
|
|
|
2629
2686
|
adminControls: {
|
|
2630
2687
|
$ref: "#/$defs/controls"
|
|
2631
2688
|
},
|
|
2689
|
+
idBase: {
|
|
2690
|
+
$ref: "#/$defs/idBase"
|
|
2691
|
+
},
|
|
2632
2692
|
namespace: {
|
|
2633
2693
|
type: "string",
|
|
2634
2694
|
pattern: "^[A-Za-z0-9_.-]{1,64}$"
|
|
@@ -2718,6 +2778,12 @@ var fleet_config_schema_default = {
|
|
|
2718
2778
|
adminControls: {
|
|
2719
2779
|
$ref: "#/$defs/controls"
|
|
2720
2780
|
},
|
|
2781
|
+
idBase: {
|
|
2782
|
+
$ref: "#/$defs/idBase"
|
|
2783
|
+
},
|
|
2784
|
+
settings: {
|
|
2785
|
+
type: "object"
|
|
2786
|
+
},
|
|
2721
2787
|
fixtures: {
|
|
2722
2788
|
type: "object"
|
|
2723
2789
|
},
|
|
@@ -2764,6 +2830,18 @@ var fleet_config_schema_default = {
|
|
|
2764
2830
|
}
|
|
2765
2831
|
}
|
|
2766
2832
|
},
|
|
2833
|
+
idBase: {
|
|
2834
|
+
anyOf: [
|
|
2835
|
+
{
|
|
2836
|
+
type: "integer",
|
|
2837
|
+
minimum: 0
|
|
2838
|
+
},
|
|
2839
|
+
{
|
|
2840
|
+
type: "string",
|
|
2841
|
+
pattern: "^(boot|[0-9]+)$"
|
|
2842
|
+
}
|
|
2843
|
+
]
|
|
2844
|
+
},
|
|
2767
2845
|
controls: {
|
|
2768
2846
|
type: "object",
|
|
2769
2847
|
additionalProperties: false,
|
|
@@ -2913,6 +2991,8 @@ async function preflightFleet(config, options) {
|
|
|
2913
2991
|
throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
|
|
2914
2992
|
resolveAdminPrefix(config.adminPrefix);
|
|
2915
2993
|
normalizeBootControls(config.adminControls);
|
|
2994
|
+
if (config.idBase !== void 0)
|
|
2995
|
+
normalizeIdBase(config.idBase, "idBase");
|
|
2916
2996
|
const targets = /* @__PURE__ */ new Map();
|
|
2917
2997
|
const ports = /* @__PURE__ */ new Set();
|
|
2918
2998
|
for (const [name2, entry] of Object.entries(config.services)) {
|
|
@@ -2928,7 +3008,7 @@ async function preflightFleet(config, options) {
|
|
|
2928
3008
|
if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
|
|
2929
3009
|
throw new Error("target must name a supported service suffix");
|
|
2930
3010
|
const service = entry.target ?? name2;
|
|
2931
|
-
if (advertisedHttpBase(entry) && service
|
|
3011
|
+
if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
|
|
2932
3012
|
throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
|
|
2933
3013
|
const target = await options.load(service);
|
|
2934
3014
|
const protocol = "protocol" in target ? target.protocol : "http";
|
|
@@ -2944,6 +3024,13 @@ async function preflightFleet(config, options) {
|
|
|
2944
3024
|
throw new Error("invalid protocol namespace selector");
|
|
2945
3025
|
}
|
|
2946
3026
|
}
|
|
3027
|
+
if (entry.idBase !== void 0)
|
|
3028
|
+
normalizeIdBase(entry.idBase, "idBase");
|
|
3029
|
+
if (entry.settings !== void 0) {
|
|
3030
|
+
if (protocol !== "http" || !("create" in target))
|
|
3031
|
+
throw new Error("settings: only HTTP services take boot settings");
|
|
3032
|
+
checkSettings(target, entry.settings, "settings");
|
|
3033
|
+
}
|
|
2947
3034
|
const port = entry.port ?? target.defaultPort;
|
|
2948
3035
|
if (port !== 0) {
|
|
2949
3036
|
const key = `${entry.host ?? "127.0.0.1"}:${port}`;
|
|
@@ -2979,7 +3066,7 @@ async function preflightFleet(config, options) {
|
|
|
2979
3066
|
break;
|
|
2980
3067
|
}
|
|
2981
3068
|
}
|
|
2982
|
-
targets.set(name2, { ...target, packageVersion: packageVersion ?? "3.
|
|
3069
|
+
targets.set(name2, { ...target, packageVersion: packageVersion ?? "3.7.0" });
|
|
2983
3070
|
} catch (error) {
|
|
2984
3071
|
throw new Error(`service ${name2}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
2985
3072
|
}
|
|
@@ -3023,6 +3110,11 @@ async function preflightFleet(config, options) {
|
|
|
3023
3110
|
return ordered;
|
|
3024
3111
|
}
|
|
3025
3112
|
function validateFleetPolicies(options) {
|
|
3113
|
+
if (options.archiveCompatibility) {
|
|
3114
|
+
validateCompatibilityTags(options.archiveCompatibility.tags);
|
|
3115
|
+
if (options.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options.archiveCompatibility.missing))
|
|
3116
|
+
throw new Error("archiveCompatibility.missing must be allow or reject");
|
|
3117
|
+
}
|
|
3026
3118
|
if (options.journal && !options.repository)
|
|
3027
3119
|
throw new Error("durable operation journal requires an archive repository");
|
|
3028
3120
|
if (options.cors) {
|
|
@@ -3294,6 +3386,7 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
|
|
|
3294
3386
|
options.onLog?.(entry2);
|
|
3295
3387
|
},
|
|
3296
3388
|
...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
|
|
3389
|
+
...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
|
|
3297
3390
|
...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
|
|
3298
3391
|
...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
|
|
3299
3392
|
});
|
|
@@ -3312,6 +3405,8 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
|
|
|
3312
3405
|
].some((method) => typeof created[method] !== "function"))
|
|
3313
3406
|
throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
|
|
3314
3407
|
const runtime = created;
|
|
3408
|
+
if (entry.idBase !== void 0)
|
|
3409
|
+
runtime.setIdBase?.(entry.idBase);
|
|
3315
3410
|
const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
|
|
3316
3411
|
if (options.outbound && runtime.webhooks)
|
|
3317
3412
|
runtime.webhooks.setTransportPolicy(ownedTransport(options.outbound));
|
|
@@ -3910,6 +4005,7 @@ async function startFleet(config, options) {
|
|
|
3910
4005
|
...entry,
|
|
3911
4006
|
adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
|
|
3912
4007
|
...adminKey !== void 0 ? { adminKey } : {},
|
|
4008
|
+
...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
|
|
3913
4009
|
// Fleet-wide controls, with the service's own keys replacing them.
|
|
3914
4010
|
...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
|
|
3915
4011
|
}, options, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
|
|
@@ -4015,10 +4111,44 @@ async function startFleet(config, options) {
|
|
|
4015
4111
|
}
|
|
4016
4112
|
}
|
|
4017
4113
|
|
|
4018
|
-
// ../../adapters/node/dist/fleet-
|
|
4019
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
4114
|
+
// ../../adapters/node/dist/fleet-loader.js
|
|
4020
4115
|
import { createRequire as createRequire2 } from "node:module";
|
|
4021
4116
|
import { resolve as resolve2 } from "node:path";
|
|
4117
|
+
import { pathToFileURL } from "node:url";
|
|
4118
|
+
var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4119
|
+
function createInstalledServiceLoader(options) {
|
|
4120
|
+
if (!options.projectRoot)
|
|
4121
|
+
throw new Error("consumer projectRoot required");
|
|
4122
|
+
const allowed = new Set(options.services);
|
|
4123
|
+
for (const name2 of allowed)
|
|
4124
|
+
if (!suffix.test(name2))
|
|
4125
|
+
throw new Error("invalid service suffix");
|
|
4126
|
+
const require2 = createRequire2(resolve2(options.projectRoot, "package.json"));
|
|
4127
|
+
const targets = new Map((options.targets ?? []).map((target) => [target.name, target]));
|
|
4128
|
+
return async (name2) => {
|
|
4129
|
+
if (!suffix.test(name2) || !allowed.has(name2))
|
|
4130
|
+
throw new Error("service is not declared in loader allowlist");
|
|
4131
|
+
const own = targets.get(name2);
|
|
4132
|
+
if (own)
|
|
4133
|
+
return own;
|
|
4134
|
+
const specifier = `@crvouga/mockingbird-service-${name2}/server`;
|
|
4135
|
+
let location;
|
|
4136
|
+
try {
|
|
4137
|
+
location = require2.resolve(specifier);
|
|
4138
|
+
} catch {
|
|
4139
|
+
throw new Error(`cannot load service "${name2}": install @crvouga/mockingbird-service-${name2} in the consumer project; its /server export is required`);
|
|
4140
|
+
}
|
|
4141
|
+
const mod = await import(pathToFileURL(location).href);
|
|
4142
|
+
if (!mod.serveTarget || mod.serveTarget.name !== name2 || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
|
|
4143
|
+
throw new Error(`${specifier} exports no matching serveTarget`);
|
|
4144
|
+
return mod.serveTarget;
|
|
4145
|
+
};
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
// ../../adapters/node/dist/fleet-tools.js
|
|
4149
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
4150
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
4151
|
+
import { resolve as resolve3 } from "node:path";
|
|
4022
4152
|
|
|
4023
4153
|
// ../../adapters/node/dist/fleet-client.js
|
|
4024
4154
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -4032,6 +4162,14 @@ var FleetClientError = class extends Error {
|
|
|
4032
4162
|
this.details = details;
|
|
4033
4163
|
}
|
|
4034
4164
|
};
|
|
4165
|
+
var FleetClientResponseError = class extends FleetClientError {
|
|
4166
|
+
code;
|
|
4167
|
+
constructor(code, status, origin) {
|
|
4168
|
+
super(status, { code, body: "omitted", ...origin ? { origin } : {} });
|
|
4169
|
+
this.code = code;
|
|
4170
|
+
this.name = "FleetClientResponseError";
|
|
4171
|
+
}
|
|
4172
|
+
};
|
|
4035
4173
|
var FleetClient = class {
|
|
4036
4174
|
base;
|
|
4037
4175
|
options;
|
|
@@ -4044,7 +4182,14 @@ var FleetClient = class {
|
|
|
4044
4182
|
if (url.username || url.password)
|
|
4045
4183
|
throw new Error("fleet client URL must not contain credentials");
|
|
4046
4184
|
url.searchParams.set("namespace", options.namespace ?? this.options.namespace ?? "default");
|
|
4185
|
+
const limit = options.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
|
|
4186
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
4187
|
+
throw new Error("invalid response byte limit");
|
|
4188
|
+
const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
|
|
4189
|
+
const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
|
|
4190
|
+
signal.throwIfAborted();
|
|
4047
4191
|
const response = await (this.options.fetch ?? fetch)(url, {
|
|
4192
|
+
redirect: "manual",
|
|
4048
4193
|
method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
|
|
4049
4194
|
headers: {
|
|
4050
4195
|
"content-type": "application/json",
|
|
@@ -4052,11 +4197,71 @@ var FleetClient = class {
|
|
|
4052
4197
|
...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
|
|
4053
4198
|
},
|
|
4054
4199
|
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
4055
|
-
signal
|
|
4200
|
+
signal
|
|
4056
4201
|
});
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
throw new
|
|
4202
|
+
if (response.status >= 300 && response.status < 400) {
|
|
4203
|
+
await response.body?.cancel();
|
|
4204
|
+
throw new FleetClientResponseError("redirect", response.status, url.origin);
|
|
4205
|
+
}
|
|
4206
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
4207
|
+
await response.body?.cancel();
|
|
4208
|
+
throw new FleetClientResponseError("response_too_large", response.status);
|
|
4209
|
+
}
|
|
4210
|
+
const reader = response.body?.getReader();
|
|
4211
|
+
const chunks = [];
|
|
4212
|
+
let size = 0;
|
|
4213
|
+
const abort = () => {
|
|
4214
|
+
void reader?.cancel(signal.reason).catch(() => {
|
|
4215
|
+
});
|
|
4216
|
+
};
|
|
4217
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
4218
|
+
try {
|
|
4219
|
+
if (reader)
|
|
4220
|
+
while (true) {
|
|
4221
|
+
signal.throwIfAborted();
|
|
4222
|
+
const { done, value } = await reader.read();
|
|
4223
|
+
signal.throwIfAborted();
|
|
4224
|
+
if (done)
|
|
4225
|
+
break;
|
|
4226
|
+
size += value.byteLength;
|
|
4227
|
+
if (size > limit) {
|
|
4228
|
+
await reader.cancel();
|
|
4229
|
+
throw new FleetClientResponseError("response_too_large", response.status);
|
|
4230
|
+
}
|
|
4231
|
+
chunks.push(value);
|
|
4232
|
+
}
|
|
4233
|
+
} finally {
|
|
4234
|
+
signal.removeEventListener("abort", abort);
|
|
4235
|
+
if (signal.aborted)
|
|
4236
|
+
await reader?.cancel(signal.reason).catch(() => {
|
|
4237
|
+
});
|
|
4238
|
+
reader?.releaseLock();
|
|
4239
|
+
}
|
|
4240
|
+
const bytes2 = new Uint8Array(size);
|
|
4241
|
+
let offset = 0;
|
|
4242
|
+
for (const chunk of chunks) {
|
|
4243
|
+
bytes2.set(chunk, offset);
|
|
4244
|
+
offset += chunk.byteLength;
|
|
4245
|
+
}
|
|
4246
|
+
const text2 = new TextDecoder().decode(bytes2);
|
|
4247
|
+
let data;
|
|
4248
|
+
try {
|
|
4249
|
+
data = text2.length ? JSON.parse(text2) : void 0;
|
|
4250
|
+
} catch {
|
|
4251
|
+
if (response.ok)
|
|
4252
|
+
throw new FleetClientResponseError("invalid_json", response.status);
|
|
4253
|
+
}
|
|
4254
|
+
if (!response.ok) {
|
|
4255
|
+
const error = data && typeof data === "object" ? data.error : void 0;
|
|
4256
|
+
const detail = { body: "omitted" };
|
|
4257
|
+
if (error && typeof error === "object")
|
|
4258
|
+
for (const key of ["type", "code"]) {
|
|
4259
|
+
const value = error[key];
|
|
4260
|
+
if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
|
|
4261
|
+
detail[key] = value;
|
|
4262
|
+
}
|
|
4263
|
+
throw new FleetClientError(response.status, detail);
|
|
4264
|
+
}
|
|
4060
4265
|
return data;
|
|
4061
4266
|
}
|
|
4062
4267
|
discover() {
|
|
@@ -4122,17 +4327,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
|
|
|
4122
4327
|
const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
|
|
4123
4328
|
if (!child || !secret)
|
|
4124
4329
|
throw new Error(`missing service mapping ${entry.service}`);
|
|
4330
|
+
let endpoint;
|
|
4125
4331
|
if (child.protocol === "http") {
|
|
4126
4332
|
const template = child.namespaces.path;
|
|
4127
4333
|
if (typeof template !== "string")
|
|
4128
4334
|
throw new Error(`missing HTTP namespace mapping ${entry.service}`);
|
|
4129
|
-
|
|
4335
|
+
endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
|
|
4130
4336
|
} else {
|
|
4131
|
-
const
|
|
4132
|
-
if (!
|
|
4337
|
+
const selected = secret.namespaces?.[namespace];
|
|
4338
|
+
if (!selected)
|
|
4133
4339
|
throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
|
|
4134
|
-
|
|
4340
|
+
endpoint = selected;
|
|
4341
|
+
}
|
|
4342
|
+
const url = new URL(endpoint);
|
|
4343
|
+
const field = entry.field ?? "url";
|
|
4344
|
+
let value;
|
|
4345
|
+
switch (field) {
|
|
4346
|
+
case "url":
|
|
4347
|
+
value = endpoint;
|
|
4348
|
+
break;
|
|
4349
|
+
case "hostname":
|
|
4350
|
+
value = url.hostname.replace(/^\[|\]$/g, "");
|
|
4351
|
+
break;
|
|
4352
|
+
case "port":
|
|
4353
|
+
value = url.port;
|
|
4354
|
+
break;
|
|
4355
|
+
case "username":
|
|
4356
|
+
value = decodeURIComponent(url.username);
|
|
4357
|
+
break;
|
|
4358
|
+
case "password":
|
|
4359
|
+
value = decodeURIComponent(url.password);
|
|
4360
|
+
break;
|
|
4361
|
+
case "pathname":
|
|
4362
|
+
value = decodeURIComponent(url.pathname);
|
|
4363
|
+
break;
|
|
4364
|
+
case "database":
|
|
4365
|
+
value = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
4366
|
+
break;
|
|
4367
|
+
default:
|
|
4368
|
+
throw new Error("unsupported connection field");
|
|
4135
4369
|
}
|
|
4370
|
+
if (!value) {
|
|
4371
|
+
if (typeof entry.fallback !== "string")
|
|
4372
|
+
throw new Error(`missing connection component ${field}`);
|
|
4373
|
+
value = entry.fallback;
|
|
4374
|
+
}
|
|
4375
|
+
result[variable] = value;
|
|
4136
4376
|
}
|
|
4137
4377
|
return result;
|
|
4138
4378
|
}
|
|
@@ -4156,7 +4396,7 @@ async function doctorFleet(config, options, probes = []) {
|
|
|
4156
4396
|
const plan = [];
|
|
4157
4397
|
try {
|
|
4158
4398
|
const targets = await preflightFleet(config, options);
|
|
4159
|
-
const require2 =
|
|
4399
|
+
const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
|
|
4160
4400
|
for (const [service, target] of targets) {
|
|
4161
4401
|
let version = "development";
|
|
4162
4402
|
try {
|
|
@@ -4211,7 +4451,7 @@ async function doctorFleet(config, options, probes = []) {
|
|
|
4211
4451
|
};
|
|
4212
4452
|
}
|
|
4213
4453
|
async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e4) {
|
|
4214
|
-
const archive =
|
|
4454
|
+
const archive = assertArchiveCompatibility(input, options.archiveCompatibility);
|
|
4215
4455
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4216
4456
|
const isolated = {
|
|
4217
4457
|
...config,
|
|
@@ -4223,7 +4463,7 @@ async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e
|
|
|
4223
4463
|
};
|
|
4224
4464
|
let fleet;
|
|
4225
4465
|
try {
|
|
4226
|
-
fleet = await startFleet(isolated,
|
|
4466
|
+
fleet = await startFleet(isolated, options);
|
|
4227
4467
|
const client = new FleetClient(fleet.manifest.adminBase, {
|
|
4228
4468
|
...config.adminKey ? { adminKey: config.adminKey } : {}
|
|
4229
4469
|
});
|
|
@@ -4450,6 +4690,16 @@ var COMMON_SERVE_OPTIONS = {
|
|
|
4450
4690
|
value: "<seed>",
|
|
4451
4691
|
description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
|
|
4452
4692
|
},
|
|
4693
|
+
"id-base": {
|
|
4694
|
+
type: "string",
|
|
4695
|
+
value: "<n|boot>",
|
|
4696
|
+
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)"
|
|
4697
|
+
},
|
|
4698
|
+
settings: {
|
|
4699
|
+
type: "string",
|
|
4700
|
+
value: "<json|file>",
|
|
4701
|
+
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)"
|
|
4702
|
+
},
|
|
4453
4703
|
"admin-controls": {
|
|
4454
4704
|
type: "string",
|
|
4455
4705
|
value: "<json|file>",
|
|
@@ -4544,19 +4794,40 @@ var readJsonFile = async (path, label) => {
|
|
|
4544
4794
|
}
|
|
4545
4795
|
};
|
|
4546
4796
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4797
|
+
var readJsonOption = async (source, label) => {
|
|
4798
|
+
const trimmed = source.trim();
|
|
4799
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
4800
|
+
try {
|
|
4801
|
+
return JSON.parse(trimmed);
|
|
4802
|
+
} catch {
|
|
4803
|
+
throw new Error(`${label}: not valid JSON`);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
return readJsonFile(source, label);
|
|
4807
|
+
};
|
|
4808
|
+
var resolveServeIdBase = (values, env = process.env) => {
|
|
4809
|
+
const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
|
|
4810
|
+
if (source === void 0 || source.trim() === "")
|
|
4811
|
+
return void 0;
|
|
4812
|
+
normalizeIdBase(source.trim(), "--id-base");
|
|
4813
|
+
return source.trim();
|
|
4814
|
+
};
|
|
4815
|
+
var resolveServeSettings = async (target, values, env = process.env) => {
|
|
4816
|
+
const flag = asString(values.settings);
|
|
4817
|
+
const source = flag ?? env.MOCKINGBIRD_SETTINGS;
|
|
4818
|
+
if (source === void 0 || source.trim() === "")
|
|
4819
|
+
return void 0;
|
|
4820
|
+
const label = flag !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
|
|
4821
|
+
const parsed = await readJsonOption(source, label);
|
|
4822
|
+
if (!isObject2(parsed))
|
|
4823
|
+
throw new Error(`${label}: expected a JSON object`);
|
|
4824
|
+
return checkSettings(target, parsed, label);
|
|
4825
|
+
};
|
|
4547
4826
|
var resolveServeControls = async (values, env = process.env, base) => {
|
|
4548
4827
|
const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
|
|
4549
4828
|
let document = isObject2(base) ? { ...base } : {};
|
|
4550
4829
|
if (source !== void 0 && source.trim() !== "") {
|
|
4551
|
-
|
|
4552
|
-
if (source.trim().startsWith("{")) {
|
|
4553
|
-
try {
|
|
4554
|
-
parsed = JSON.parse(source);
|
|
4555
|
-
} catch {
|
|
4556
|
-
throw new Error("--admin-controls: not valid JSON");
|
|
4557
|
-
}
|
|
4558
|
-
} else
|
|
4559
|
-
parsed = await readJsonFile(source, "--admin-controls");
|
|
4830
|
+
const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
|
|
4560
4831
|
if (!isObject2(parsed))
|
|
4561
4832
|
throw new Error("--admin-controls: expected a JSON object");
|
|
4562
4833
|
document = { ...document, ...parsed };
|
|
@@ -4598,26 +4869,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
|
|
|
4598
4869
|
normalizeBootControls(document, "adminControls");
|
|
4599
4870
|
return document;
|
|
4600
4871
|
};
|
|
4601
|
-
var loadTarget =
|
|
4602
|
-
if (name2 === own.name)
|
|
4603
|
-
return own;
|
|
4604
|
-
const specifier = `@crvouga/mockingbird-service-${name2}/server`;
|
|
4605
|
-
try {
|
|
4606
|
-
let mod;
|
|
4607
|
-
try {
|
|
4608
|
-
mod = await import(specifier);
|
|
4609
|
-
} catch {
|
|
4610
|
-
const fromProject = createRequire3(resolve3(process.cwd(), "package.json"));
|
|
4611
|
-
mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
|
|
4612
|
-
}
|
|
4613
|
-
if (!mod.serveTarget)
|
|
4614
|
-
throw new Error(`${specifier} exports no serveTarget`);
|
|
4615
|
-
return mod.serveTarget;
|
|
4616
|
-
} catch (error) {
|
|
4617
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
4618
|
-
throw new Error(`cannot load service "${name2}": ${reason}. Install @crvouga/mockingbird-service-${name2}.`);
|
|
4619
|
-
}
|
|
4620
|
-
};
|
|
4872
|
+
var loadTarget = (name2, own) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name2], targets: [own] })(name2);
|
|
4621
4873
|
var start = async (target, values, config) => {
|
|
4622
4874
|
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
4623
4875
|
const runtime = await target.create(values, {
|
|
@@ -4625,8 +4877,11 @@ var start = async (target, values, config) => {
|
|
|
4625
4877
|
adminKey: config.adminKey,
|
|
4626
4878
|
seed: config.seed,
|
|
4627
4879
|
onLog: formatLog(config.log),
|
|
4880
|
+
...config.settings !== void 0 ? { settings: config.settings } : {},
|
|
4628
4881
|
...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
|
|
4629
4882
|
});
|
|
4883
|
+
if (config.idBase !== void 0)
|
|
4884
|
+
runtime.setIdBase?.(config.idBase);
|
|
4630
4885
|
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
4631
4886
|
target.listening?.(listening);
|
|
4632
4887
|
console.log(`${target.name} emulator listening on ${listening.url}`);
|
|
@@ -4672,6 +4927,11 @@ var serveCommand = (target) => ({
|
|
|
4672
4927
|
const adminControls = await resolveServeControls(values, process.env, config.adminControls);
|
|
4673
4928
|
if (adminControls !== void 0)
|
|
4674
4929
|
config.adminControls = adminControls;
|
|
4930
|
+
if (values.settings !== void 0)
|
|
4931
|
+
throw new Error("--settings serves a single service; give each service its own `settings` in the config");
|
|
4932
|
+
const idBase = config.idBase ?? resolveServeIdBase(values);
|
|
4933
|
+
if (idBase !== void 0)
|
|
4934
|
+
config.idBase = idBase;
|
|
4675
4935
|
const fleet = await startFleet(config, {
|
|
4676
4936
|
load: (name2) => loadTarget(name2, target),
|
|
4677
4937
|
onLog: formatLog(config.log ?? log),
|
|
@@ -4701,8 +4961,12 @@ var serveCommand = (target) => ({
|
|
|
4701
4961
|
let listening;
|
|
4702
4962
|
try {
|
|
4703
4963
|
const adminControls = await resolveServeControls(values);
|
|
4964
|
+
const idBase = resolveServeIdBase(values);
|
|
4965
|
+
const settings = await resolveServeSettings(target, values);
|
|
4704
4966
|
listening = await start(target, values, {
|
|
4705
4967
|
...adminControls !== void 0 ? { adminControls } : {},
|
|
4968
|
+
...idBase !== void 0 ? { idBase } : {},
|
|
4969
|
+
...settings !== void 0 ? { settings } : {},
|
|
4706
4970
|
adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
|
|
4707
4971
|
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
4708
4972
|
host: asString(values.host) ?? "127.0.0.1",
|
|
@@ -4976,4 +5240,4 @@ export {
|
|
|
4976
5240
|
createServer2 as createServer,
|
|
4977
5241
|
serveTarget
|
|
4978
5242
|
};
|
|
4979
|
-
//# sourceMappingURL=chunk-
|
|
5243
|
+
//# sourceMappingURL=chunk-GRXH63CD.js.map
|