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