@crvouga/mockingbird-service-oauth 3.6.0 → 3.8.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/README.md +15 -2
- package/dist/{chunk-OYEO5VHR.js → chunk-AAKIJOPV.js} +315 -51
- package/dist/chunk-AAKIJOPV.js.map +7 -0
- package/dist/{chunk-ANDSQ2HK.js → chunk-VAXKCT3L.js} +143 -26
- package/dist/chunk-VAXKCT3L.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +59 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +99 -0
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-ANDSQ2HK.js.map +0 -7
- package/dist/chunk-OYEO5VHR.js.map +0 -7
|
@@ -5,23 +5,34 @@ import {
|
|
|
5
5
|
createRuntime,
|
|
6
6
|
normalizeBootControls,
|
|
7
7
|
normalizeControls,
|
|
8
|
+
normalizeIdBase,
|
|
8
9
|
ownedTransport,
|
|
9
10
|
parseDuration,
|
|
10
11
|
parseFaultHeader,
|
|
11
12
|
resolveAdminPrefix,
|
|
12
13
|
restoreClock
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-VAXKCT3L.js";
|
|
14
15
|
|
|
15
16
|
// src/server.ts
|
|
16
17
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
17
18
|
|
|
18
19
|
// ../../adapters/node/dist/cli.js
|
|
19
20
|
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
21
|
import { parseArgs } from "node:util";
|
|
24
22
|
|
|
23
|
+
// ../../adapters/node/dist/boot-settings.js
|
|
24
|
+
var checkSettings = (target, settings, label) => {
|
|
25
|
+
if (!target.parseSettings)
|
|
26
|
+
throw new Error(`${label}: ${target.name} takes no boot settings`);
|
|
27
|
+
try {
|
|
28
|
+
return target.parseSettings(settings);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
31
|
+
cause: error
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
25
36
|
// ../../adapters/node/dist/fleet.js
|
|
26
37
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
27
38
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -33,6 +44,31 @@ import { createHash as createHash3, randomBytes, randomUUID as randomUUID3 } fro
|
|
|
33
44
|
|
|
34
45
|
// ../../adapters/node/dist/fleet-archive.js
|
|
35
46
|
import { createHash } from "node:crypto";
|
|
47
|
+
function validateCompatibilityTags(tags) {
|
|
48
|
+
if (!tags || typeof tags !== "object" || Array.isArray(tags))
|
|
49
|
+
throw new Error("invalid archive compatibility tags");
|
|
50
|
+
const entries = Object.entries(tags);
|
|
51
|
+
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)))
|
|
52
|
+
throw new Error("invalid archive compatibility tags");
|
|
53
|
+
}
|
|
54
|
+
function assertCompatibilityTags(tags, policy) {
|
|
55
|
+
if (tags !== void 0)
|
|
56
|
+
validateCompatibilityTags(tags);
|
|
57
|
+
if (!policy)
|
|
58
|
+
return;
|
|
59
|
+
validateCompatibilityTags(policy.tags);
|
|
60
|
+
if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
|
|
61
|
+
throw new Error("invalid missing compatibility policy");
|
|
62
|
+
if (tags === void 0 && policy.missing === "allow")
|
|
63
|
+
return;
|
|
64
|
+
if (canonical(tags) !== canonical(policy.tags))
|
|
65
|
+
throw new Error("application archive compatibility mismatch");
|
|
66
|
+
}
|
|
67
|
+
function assertArchiveCompatibility(input, policy) {
|
|
68
|
+
const archive = validateArchive(input);
|
|
69
|
+
assertCompatibilityTags(archive.compatibility, policy);
|
|
70
|
+
return archive;
|
|
71
|
+
}
|
|
36
72
|
var canonical = (value) => {
|
|
37
73
|
if (Array.isArray(value))
|
|
38
74
|
return `[${value.map(canonical).join(",")}]`;
|
|
@@ -52,6 +88,8 @@ function validateArchive(value) {
|
|
|
52
88
|
const { sha256, ...content } = doc;
|
|
53
89
|
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
90
|
throw new Error("unsupported or corrupt fleet archive");
|
|
91
|
+
if (doc.compatibility !== void 0)
|
|
92
|
+
validateCompatibilityTags(doc.compatibility);
|
|
55
93
|
for (const [namespace, entry] of Object.entries(doc.namespaces)) {
|
|
56
94
|
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
95
|
throw new Error("invalid archive namespace");
|
|
@@ -67,6 +105,7 @@ function inspectArchive(input) {
|
|
|
67
105
|
format: archive.format,
|
|
68
106
|
createdAt: archive.createdAt,
|
|
69
107
|
sha256: archive.sha256,
|
|
108
|
+
...archive.compatibility ? { compatibility: archive.compatibility } : {},
|
|
70
109
|
namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name, ns]) => [
|
|
71
110
|
name,
|
|
72
111
|
{
|
|
@@ -709,11 +748,28 @@ var ingressSupport = {
|
|
|
709
748
|
streaming: true,
|
|
710
749
|
signing: "vendor request signing is not used; webhook signatures remain payload based"
|
|
711
750
|
},
|
|
751
|
+
cognito: {
|
|
752
|
+
publicOrigin: true,
|
|
753
|
+
pathPrefix: true,
|
|
754
|
+
namespaces: "path-or-header",
|
|
755
|
+
streaming: true,
|
|
756
|
+
signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
|
|
757
|
+
},
|
|
758
|
+
medplum: {
|
|
759
|
+
publicOrigin: true,
|
|
760
|
+
pathPrefix: true,
|
|
761
|
+
namespaces: "path-or-header",
|
|
762
|
+
streaming: true,
|
|
763
|
+
signing: "FHIR, OAuth and storage URLs use the configured public namespace"
|
|
764
|
+
},
|
|
712
765
|
other: {
|
|
713
766
|
publicOrigin: false,
|
|
714
767
|
reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
|
|
715
768
|
}
|
|
716
769
|
};
|
|
770
|
+
function supportsPublicIngress(service) {
|
|
771
|
+
return service === "stripe" || service === "cognito" || service === "medplum";
|
|
772
|
+
}
|
|
717
773
|
function advertisedHttpBase(entry) {
|
|
718
774
|
if (entry.publicPath !== void 0 && !entry.publicOrigin)
|
|
719
775
|
throw new Error("publicPath requires publicOrigin");
|
|
@@ -1252,7 +1308,7 @@ data: ${JSON.stringify(data)}
|
|
|
1252
1308
|
{
|
|
1253
1309
|
protocol: c.protocol,
|
|
1254
1310
|
coverage: c.coverage,
|
|
1255
|
-
ingress: c.targetName
|
|
1311
|
+
ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
|
|
1256
1312
|
target: c.targetName,
|
|
1257
1313
|
outboundPolicy: c.outboundPolicy ?? "unsupported",
|
|
1258
1314
|
version: 1,
|
|
@@ -2336,11 +2392,12 @@ data: ${JSON.stringify(data)}
|
|
|
2336
2392
|
format: "mockingbird.fleet/v1",
|
|
2337
2393
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2338
2394
|
sourceId: this.ctx.id,
|
|
2395
|
+
...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
|
|
2339
2396
|
namespaces: contents
|
|
2340
2397
|
});
|
|
2341
2398
|
}
|
|
2342
2399
|
async restoreArchive(input, selectedNamespace) {
|
|
2343
|
-
const archive =
|
|
2400
|
+
const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
|
|
2344
2401
|
const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
|
|
2345
2402
|
if (!entries.length)
|
|
2346
2403
|
throw new Error("selected namespace absent from archive");
|
|
@@ -2625,6 +2682,9 @@ var fleet_config_schema_default = {
|
|
|
2625
2682
|
adminControls: {
|
|
2626
2683
|
$ref: "#/$defs/controls"
|
|
2627
2684
|
},
|
|
2685
|
+
idBase: {
|
|
2686
|
+
$ref: "#/$defs/idBase"
|
|
2687
|
+
},
|
|
2628
2688
|
namespace: {
|
|
2629
2689
|
type: "string",
|
|
2630
2690
|
pattern: "^[A-Za-z0-9_.-]{1,64}$"
|
|
@@ -2714,6 +2774,12 @@ var fleet_config_schema_default = {
|
|
|
2714
2774
|
adminControls: {
|
|
2715
2775
|
$ref: "#/$defs/controls"
|
|
2716
2776
|
},
|
|
2777
|
+
idBase: {
|
|
2778
|
+
$ref: "#/$defs/idBase"
|
|
2779
|
+
},
|
|
2780
|
+
settings: {
|
|
2781
|
+
type: "object"
|
|
2782
|
+
},
|
|
2717
2783
|
fixtures: {
|
|
2718
2784
|
type: "object"
|
|
2719
2785
|
},
|
|
@@ -2760,6 +2826,18 @@ var fleet_config_schema_default = {
|
|
|
2760
2826
|
}
|
|
2761
2827
|
}
|
|
2762
2828
|
},
|
|
2829
|
+
idBase: {
|
|
2830
|
+
anyOf: [
|
|
2831
|
+
{
|
|
2832
|
+
type: "integer",
|
|
2833
|
+
minimum: 0
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
type: "string",
|
|
2837
|
+
pattern: "^(boot|[0-9]+)$"
|
|
2838
|
+
}
|
|
2839
|
+
]
|
|
2840
|
+
},
|
|
2763
2841
|
controls: {
|
|
2764
2842
|
type: "object",
|
|
2765
2843
|
additionalProperties: false,
|
|
@@ -2909,6 +2987,8 @@ async function preflightFleet(config, options) {
|
|
|
2909
2987
|
throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
|
|
2910
2988
|
resolveAdminPrefix(config.adminPrefix);
|
|
2911
2989
|
normalizeBootControls(config.adminControls);
|
|
2990
|
+
if (config.idBase !== void 0)
|
|
2991
|
+
normalizeIdBase(config.idBase, "idBase");
|
|
2912
2992
|
const targets = /* @__PURE__ */ new Map();
|
|
2913
2993
|
const ports = /* @__PURE__ */ new Set();
|
|
2914
2994
|
for (const [name, entry] of Object.entries(config.services)) {
|
|
@@ -2924,7 +3004,7 @@ async function preflightFleet(config, options) {
|
|
|
2924
3004
|
if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
|
|
2925
3005
|
throw new Error("target must name a supported service suffix");
|
|
2926
3006
|
const service = entry.target ?? name;
|
|
2927
|
-
if (advertisedHttpBase(entry) && service
|
|
3007
|
+
if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
|
|
2928
3008
|
throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
|
|
2929
3009
|
const target = await options.load(service);
|
|
2930
3010
|
const protocol = "protocol" in target ? target.protocol : "http";
|
|
@@ -2940,6 +3020,13 @@ async function preflightFleet(config, options) {
|
|
|
2940
3020
|
throw new Error("invalid protocol namespace selector");
|
|
2941
3021
|
}
|
|
2942
3022
|
}
|
|
3023
|
+
if (entry.idBase !== void 0)
|
|
3024
|
+
normalizeIdBase(entry.idBase, "idBase");
|
|
3025
|
+
if (entry.settings !== void 0) {
|
|
3026
|
+
if (protocol !== "http" || !("create" in target))
|
|
3027
|
+
throw new Error("settings: only HTTP services take boot settings");
|
|
3028
|
+
checkSettings(target, entry.settings, "settings");
|
|
3029
|
+
}
|
|
2943
3030
|
const port = entry.port ?? target.defaultPort;
|
|
2944
3031
|
if (port !== 0) {
|
|
2945
3032
|
const key = `${entry.host ?? "127.0.0.1"}:${port}`;
|
|
@@ -2975,7 +3062,7 @@ async function preflightFleet(config, options) {
|
|
|
2975
3062
|
break;
|
|
2976
3063
|
}
|
|
2977
3064
|
}
|
|
2978
|
-
targets.set(name, { ...target, packageVersion: packageVersion ?? "3.
|
|
3065
|
+
targets.set(name, { ...target, packageVersion: packageVersion ?? "3.8.0" });
|
|
2979
3066
|
} catch (error) {
|
|
2980
3067
|
throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
2981
3068
|
}
|
|
@@ -3019,6 +3106,11 @@ async function preflightFleet(config, options) {
|
|
|
3019
3106
|
return ordered;
|
|
3020
3107
|
}
|
|
3021
3108
|
function validateFleetPolicies(options) {
|
|
3109
|
+
if (options.archiveCompatibility) {
|
|
3110
|
+
validateCompatibilityTags(options.archiveCompatibility.tags);
|
|
3111
|
+
if (options.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options.archiveCompatibility.missing))
|
|
3112
|
+
throw new Error("archiveCompatibility.missing must be allow or reject");
|
|
3113
|
+
}
|
|
3022
3114
|
if (options.journal && !options.repository)
|
|
3023
3115
|
throw new Error("durable operation journal requires an archive repository");
|
|
3024
3116
|
if (options.cors) {
|
|
@@ -3290,6 +3382,7 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
|
|
|
3290
3382
|
options.onLog?.(entry2);
|
|
3291
3383
|
},
|
|
3292
3384
|
...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
|
|
3385
|
+
...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
|
|
3293
3386
|
...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
|
|
3294
3387
|
...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
|
|
3295
3388
|
});
|
|
@@ -3308,6 +3401,8 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
|
|
|
3308
3401
|
].some((method) => typeof created[method] !== "function"))
|
|
3309
3402
|
throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
|
|
3310
3403
|
const runtime = created;
|
|
3404
|
+
if (entry.idBase !== void 0)
|
|
3405
|
+
runtime.setIdBase?.(entry.idBase);
|
|
3311
3406
|
const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
|
|
3312
3407
|
if (options.outbound && runtime.webhooks)
|
|
3313
3408
|
runtime.webhooks.setTransportPolicy(ownedTransport(options.outbound));
|
|
@@ -3906,6 +4001,7 @@ async function startFleet(config, options) {
|
|
|
3906
4001
|
...entry,
|
|
3907
4002
|
adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
|
|
3908
4003
|
...adminKey !== void 0 ? { adminKey } : {},
|
|
4004
|
+
...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
|
|
3909
4005
|
// Fleet-wide controls, with the service's own keys replacing them.
|
|
3910
4006
|
...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
|
|
3911
4007
|
}, options, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
|
|
@@ -4011,10 +4107,44 @@ async function startFleet(config, options) {
|
|
|
4011
4107
|
}
|
|
4012
4108
|
}
|
|
4013
4109
|
|
|
4014
|
-
// ../../adapters/node/dist/fleet-
|
|
4015
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
4110
|
+
// ../../adapters/node/dist/fleet-loader.js
|
|
4016
4111
|
import { createRequire as createRequire2 } from "node:module";
|
|
4017
4112
|
import { resolve as resolve2 } from "node:path";
|
|
4113
|
+
import { pathToFileURL } from "node:url";
|
|
4114
|
+
var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4115
|
+
function createInstalledServiceLoader(options) {
|
|
4116
|
+
if (!options.projectRoot)
|
|
4117
|
+
throw new Error("consumer projectRoot required");
|
|
4118
|
+
const allowed = new Set(options.services);
|
|
4119
|
+
for (const name of allowed)
|
|
4120
|
+
if (!suffix.test(name))
|
|
4121
|
+
throw new Error("invalid service suffix");
|
|
4122
|
+
const require2 = createRequire2(resolve2(options.projectRoot, "package.json"));
|
|
4123
|
+
const targets = new Map((options.targets ?? []).map((target) => [target.name, target]));
|
|
4124
|
+
return async (name) => {
|
|
4125
|
+
if (!suffix.test(name) || !allowed.has(name))
|
|
4126
|
+
throw new Error("service is not declared in loader allowlist");
|
|
4127
|
+
const own = targets.get(name);
|
|
4128
|
+
if (own)
|
|
4129
|
+
return own;
|
|
4130
|
+
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
4131
|
+
let location;
|
|
4132
|
+
try {
|
|
4133
|
+
location = require2.resolve(specifier);
|
|
4134
|
+
} catch {
|
|
4135
|
+
throw new Error(`cannot load service "${name}": install @crvouga/mockingbird-service-${name} in the consumer project; its /server export is required`);
|
|
4136
|
+
}
|
|
4137
|
+
const mod = await import(pathToFileURL(location).href);
|
|
4138
|
+
if (!mod.serveTarget || mod.serveTarget.name !== name || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
|
|
4139
|
+
throw new Error(`${specifier} exports no matching serveTarget`);
|
|
4140
|
+
return mod.serveTarget;
|
|
4141
|
+
};
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
// ../../adapters/node/dist/fleet-tools.js
|
|
4145
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
4146
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
4147
|
+
import { resolve as resolve3 } from "node:path";
|
|
4018
4148
|
|
|
4019
4149
|
// ../../adapters/node/dist/fleet-client.js
|
|
4020
4150
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -4028,6 +4158,14 @@ var FleetClientError = class extends Error {
|
|
|
4028
4158
|
this.details = details;
|
|
4029
4159
|
}
|
|
4030
4160
|
};
|
|
4161
|
+
var FleetClientResponseError = class extends FleetClientError {
|
|
4162
|
+
code;
|
|
4163
|
+
constructor(code, status, origin) {
|
|
4164
|
+
super(status, { code, body: "omitted", ...origin ? { origin } : {} });
|
|
4165
|
+
this.code = code;
|
|
4166
|
+
this.name = "FleetClientResponseError";
|
|
4167
|
+
}
|
|
4168
|
+
};
|
|
4031
4169
|
var FleetClient = class {
|
|
4032
4170
|
base;
|
|
4033
4171
|
options;
|
|
@@ -4040,7 +4178,14 @@ var FleetClient = class {
|
|
|
4040
4178
|
if (url.username || url.password)
|
|
4041
4179
|
throw new Error("fleet client URL must not contain credentials");
|
|
4042
4180
|
url.searchParams.set("namespace", options.namespace ?? this.options.namespace ?? "default");
|
|
4181
|
+
const limit = options.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
|
|
4182
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
4183
|
+
throw new Error("invalid response byte limit");
|
|
4184
|
+
const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
|
|
4185
|
+
const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
|
|
4186
|
+
signal.throwIfAborted();
|
|
4043
4187
|
const response = await (this.options.fetch ?? fetch)(url, {
|
|
4188
|
+
redirect: "manual",
|
|
4044
4189
|
method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
|
|
4045
4190
|
headers: {
|
|
4046
4191
|
"content-type": "application/json",
|
|
@@ -4048,11 +4193,71 @@ var FleetClient = class {
|
|
|
4048
4193
|
...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
|
|
4049
4194
|
},
|
|
4050
4195
|
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
4051
|
-
signal
|
|
4196
|
+
signal
|
|
4052
4197
|
});
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
throw new
|
|
4198
|
+
if (response.status >= 300 && response.status < 400) {
|
|
4199
|
+
await response.body?.cancel();
|
|
4200
|
+
throw new FleetClientResponseError("redirect", response.status, url.origin);
|
|
4201
|
+
}
|
|
4202
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
4203
|
+
await response.body?.cancel();
|
|
4204
|
+
throw new FleetClientResponseError("response_too_large", response.status);
|
|
4205
|
+
}
|
|
4206
|
+
const reader = response.body?.getReader();
|
|
4207
|
+
const chunks = [];
|
|
4208
|
+
let size = 0;
|
|
4209
|
+
const abort = () => {
|
|
4210
|
+
void reader?.cancel(signal.reason).catch(() => {
|
|
4211
|
+
});
|
|
4212
|
+
};
|
|
4213
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
4214
|
+
try {
|
|
4215
|
+
if (reader)
|
|
4216
|
+
while (true) {
|
|
4217
|
+
signal.throwIfAborted();
|
|
4218
|
+
const { done, value } = await reader.read();
|
|
4219
|
+
signal.throwIfAborted();
|
|
4220
|
+
if (done)
|
|
4221
|
+
break;
|
|
4222
|
+
size += value.byteLength;
|
|
4223
|
+
if (size > limit) {
|
|
4224
|
+
await reader.cancel();
|
|
4225
|
+
throw new FleetClientResponseError("response_too_large", response.status);
|
|
4226
|
+
}
|
|
4227
|
+
chunks.push(value);
|
|
4228
|
+
}
|
|
4229
|
+
} finally {
|
|
4230
|
+
signal.removeEventListener("abort", abort);
|
|
4231
|
+
if (signal.aborted)
|
|
4232
|
+
await reader?.cancel(signal.reason).catch(() => {
|
|
4233
|
+
});
|
|
4234
|
+
reader?.releaseLock();
|
|
4235
|
+
}
|
|
4236
|
+
const bytes = new Uint8Array(size);
|
|
4237
|
+
let offset = 0;
|
|
4238
|
+
for (const chunk of chunks) {
|
|
4239
|
+
bytes.set(chunk, offset);
|
|
4240
|
+
offset += chunk.byteLength;
|
|
4241
|
+
}
|
|
4242
|
+
const text = new TextDecoder().decode(bytes);
|
|
4243
|
+
let data;
|
|
4244
|
+
try {
|
|
4245
|
+
data = text.length ? JSON.parse(text) : void 0;
|
|
4246
|
+
} catch {
|
|
4247
|
+
if (response.ok)
|
|
4248
|
+
throw new FleetClientResponseError("invalid_json", response.status);
|
|
4249
|
+
}
|
|
4250
|
+
if (!response.ok) {
|
|
4251
|
+
const error = data && typeof data === "object" ? data.error : void 0;
|
|
4252
|
+
const detail = { body: "omitted" };
|
|
4253
|
+
if (error && typeof error === "object")
|
|
4254
|
+
for (const key of ["type", "code"]) {
|
|
4255
|
+
const value = error[key];
|
|
4256
|
+
if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
|
|
4257
|
+
detail[key] = value;
|
|
4258
|
+
}
|
|
4259
|
+
throw new FleetClientError(response.status, detail);
|
|
4260
|
+
}
|
|
4056
4261
|
return data;
|
|
4057
4262
|
}
|
|
4058
4263
|
discover() {
|
|
@@ -4118,17 +4323,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
|
|
|
4118
4323
|
const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
|
|
4119
4324
|
if (!child || !secret)
|
|
4120
4325
|
throw new Error(`missing service mapping ${entry.service}`);
|
|
4326
|
+
let endpoint;
|
|
4121
4327
|
if (child.protocol === "http") {
|
|
4122
4328
|
const template = child.namespaces.path;
|
|
4123
4329
|
if (typeof template !== "string")
|
|
4124
4330
|
throw new Error(`missing HTTP namespace mapping ${entry.service}`);
|
|
4125
|
-
|
|
4331
|
+
endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
|
|
4126
4332
|
} else {
|
|
4127
|
-
const
|
|
4128
|
-
if (!
|
|
4333
|
+
const selected = secret.namespaces?.[namespace];
|
|
4334
|
+
if (!selected)
|
|
4129
4335
|
throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
|
|
4130
|
-
|
|
4336
|
+
endpoint = selected;
|
|
4337
|
+
}
|
|
4338
|
+
const url = new URL(endpoint);
|
|
4339
|
+
const field = entry.field ?? "url";
|
|
4340
|
+
let value;
|
|
4341
|
+
switch (field) {
|
|
4342
|
+
case "url":
|
|
4343
|
+
value = endpoint;
|
|
4344
|
+
break;
|
|
4345
|
+
case "hostname":
|
|
4346
|
+
value = url.hostname.replace(/^\[|\]$/g, "");
|
|
4347
|
+
break;
|
|
4348
|
+
case "port":
|
|
4349
|
+
value = url.port;
|
|
4350
|
+
break;
|
|
4351
|
+
case "username":
|
|
4352
|
+
value = decodeURIComponent(url.username);
|
|
4353
|
+
break;
|
|
4354
|
+
case "password":
|
|
4355
|
+
value = decodeURIComponent(url.password);
|
|
4356
|
+
break;
|
|
4357
|
+
case "pathname":
|
|
4358
|
+
value = decodeURIComponent(url.pathname);
|
|
4359
|
+
break;
|
|
4360
|
+
case "database":
|
|
4361
|
+
value = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
4362
|
+
break;
|
|
4363
|
+
default:
|
|
4364
|
+
throw new Error("unsupported connection field");
|
|
4131
4365
|
}
|
|
4366
|
+
if (!value) {
|
|
4367
|
+
if (typeof entry.fallback !== "string")
|
|
4368
|
+
throw new Error(`missing connection component ${field}`);
|
|
4369
|
+
value = entry.fallback;
|
|
4370
|
+
}
|
|
4371
|
+
result[variable] = value;
|
|
4132
4372
|
}
|
|
4133
4373
|
return result;
|
|
4134
4374
|
}
|
|
@@ -4152,7 +4392,7 @@ async function doctorFleet(config, options, probes = []) {
|
|
|
4152
4392
|
const plan = [];
|
|
4153
4393
|
try {
|
|
4154
4394
|
const targets = await preflightFleet(config, options);
|
|
4155
|
-
const require2 =
|
|
4395
|
+
const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
|
|
4156
4396
|
for (const [service, target] of targets) {
|
|
4157
4397
|
let version = "development";
|
|
4158
4398
|
try {
|
|
@@ -4207,7 +4447,7 @@ async function doctorFleet(config, options, probes = []) {
|
|
|
4207
4447
|
};
|
|
4208
4448
|
}
|
|
4209
4449
|
async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e4) {
|
|
4210
|
-
const archive =
|
|
4450
|
+
const archive = assertArchiveCompatibility(input, options.archiveCompatibility);
|
|
4211
4451
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4212
4452
|
const isolated = {
|
|
4213
4453
|
...config,
|
|
@@ -4219,7 +4459,7 @@ async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e
|
|
|
4219
4459
|
};
|
|
4220
4460
|
let fleet;
|
|
4221
4461
|
try {
|
|
4222
|
-
fleet = await startFleet(isolated,
|
|
4462
|
+
fleet = await startFleet(isolated, options);
|
|
4223
4463
|
const client = new FleetClient(fleet.manifest.adminBase, {
|
|
4224
4464
|
...config.adminKey ? { adminKey: config.adminKey } : {}
|
|
4225
4465
|
});
|
|
@@ -4446,6 +4686,16 @@ var COMMON_SERVE_OPTIONS = {
|
|
|
4446
4686
|
value: "<seed>",
|
|
4447
4687
|
description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
|
|
4448
4688
|
},
|
|
4689
|
+
"id-base": {
|
|
4690
|
+
type: "string",
|
|
4691
|
+
value: "<n|boot>",
|
|
4692
|
+
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)"
|
|
4693
|
+
},
|
|
4694
|
+
settings: {
|
|
4695
|
+
type: "string",
|
|
4696
|
+
value: "<json|file>",
|
|
4697
|
+
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)"
|
|
4698
|
+
},
|
|
4449
4699
|
"admin-controls": {
|
|
4450
4700
|
type: "string",
|
|
4451
4701
|
value: "<json|file>",
|
|
@@ -4540,19 +4790,40 @@ var readJsonFile = async (path, label) => {
|
|
|
4540
4790
|
}
|
|
4541
4791
|
};
|
|
4542
4792
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4793
|
+
var readJsonOption = async (source, label) => {
|
|
4794
|
+
const trimmed = source.trim();
|
|
4795
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
4796
|
+
try {
|
|
4797
|
+
return JSON.parse(trimmed);
|
|
4798
|
+
} catch {
|
|
4799
|
+
throw new Error(`${label}: not valid JSON`);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
return readJsonFile(source, label);
|
|
4803
|
+
};
|
|
4804
|
+
var resolveServeIdBase = (values, env = process.env) => {
|
|
4805
|
+
const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
|
|
4806
|
+
if (source === void 0 || source.trim() === "")
|
|
4807
|
+
return void 0;
|
|
4808
|
+
normalizeIdBase(source.trim(), "--id-base");
|
|
4809
|
+
return source.trim();
|
|
4810
|
+
};
|
|
4811
|
+
var resolveServeSettings = async (target, values, env = process.env) => {
|
|
4812
|
+
const flag = asString(values.settings);
|
|
4813
|
+
const source = flag ?? env.MOCKINGBIRD_SETTINGS;
|
|
4814
|
+
if (source === void 0 || source.trim() === "")
|
|
4815
|
+
return void 0;
|
|
4816
|
+
const label = flag !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
|
|
4817
|
+
const parsed = await readJsonOption(source, label);
|
|
4818
|
+
if (!isObject2(parsed))
|
|
4819
|
+
throw new Error(`${label}: expected a JSON object`);
|
|
4820
|
+
return checkSettings(target, parsed, label);
|
|
4821
|
+
};
|
|
4543
4822
|
var resolveServeControls = async (values, env = process.env, base) => {
|
|
4544
4823
|
const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
|
|
4545
4824
|
let document = isObject2(base) ? { ...base } : {};
|
|
4546
4825
|
if (source !== void 0 && source.trim() !== "") {
|
|
4547
|
-
|
|
4548
|
-
if (source.trim().startsWith("{")) {
|
|
4549
|
-
try {
|
|
4550
|
-
parsed = JSON.parse(source);
|
|
4551
|
-
} catch {
|
|
4552
|
-
throw new Error("--admin-controls: not valid JSON");
|
|
4553
|
-
}
|
|
4554
|
-
} else
|
|
4555
|
-
parsed = await readJsonFile(source, "--admin-controls");
|
|
4826
|
+
const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
|
|
4556
4827
|
if (!isObject2(parsed))
|
|
4557
4828
|
throw new Error("--admin-controls: expected a JSON object");
|
|
4558
4829
|
document = { ...document, ...parsed };
|
|
@@ -4594,26 +4865,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
|
|
|
4594
4865
|
normalizeBootControls(document, "adminControls");
|
|
4595
4866
|
return document;
|
|
4596
4867
|
};
|
|
4597
|
-
var loadTarget =
|
|
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
|
-
};
|
|
4868
|
+
var loadTarget = (name, own) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name], targets: [own] })(name);
|
|
4617
4869
|
var start = async (target, values, config) => {
|
|
4618
4870
|
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
4619
4871
|
const runtime = await target.create(values, {
|
|
@@ -4621,8 +4873,11 @@ var start = async (target, values, config) => {
|
|
|
4621
4873
|
adminKey: config.adminKey,
|
|
4622
4874
|
seed: config.seed,
|
|
4623
4875
|
onLog: formatLog(config.log),
|
|
4876
|
+
...config.settings !== void 0 ? { settings: config.settings } : {},
|
|
4624
4877
|
...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
|
|
4625
4878
|
});
|
|
4879
|
+
if (config.idBase !== void 0)
|
|
4880
|
+
runtime.setIdBase?.(config.idBase);
|
|
4626
4881
|
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
4627
4882
|
target.listening?.(listening);
|
|
4628
4883
|
console.log(`${target.name} emulator listening on ${listening.url}`);
|
|
@@ -4668,6 +4923,11 @@ var serveCommand = (target) => ({
|
|
|
4668
4923
|
const adminControls = await resolveServeControls(values, process.env, config.adminControls);
|
|
4669
4924
|
if (adminControls !== void 0)
|
|
4670
4925
|
config.adminControls = adminControls;
|
|
4926
|
+
if (values.settings !== void 0)
|
|
4927
|
+
throw new Error("--settings serves a single service; give each service its own `settings` in the config");
|
|
4928
|
+
const idBase = config.idBase ?? resolveServeIdBase(values);
|
|
4929
|
+
if (idBase !== void 0)
|
|
4930
|
+
config.idBase = idBase;
|
|
4671
4931
|
const fleet = await startFleet(config, {
|
|
4672
4932
|
load: (name) => loadTarget(name, target),
|
|
4673
4933
|
onLog: formatLog(config.log ?? log),
|
|
@@ -4697,8 +4957,12 @@ var serveCommand = (target) => ({
|
|
|
4697
4957
|
let listening;
|
|
4698
4958
|
try {
|
|
4699
4959
|
const adminControls = await resolveServeControls(values);
|
|
4960
|
+
const idBase = resolveServeIdBase(values);
|
|
4961
|
+
const settings = await resolveServeSettings(target, values);
|
|
4700
4962
|
listening = await start(target, values, {
|
|
4701
4963
|
...adminControls !== void 0 ? { adminControls } : {},
|
|
4964
|
+
...idBase !== void 0 ? { idBase } : {},
|
|
4965
|
+
...settings !== void 0 ? { settings } : {},
|
|
4702
4966
|
adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
|
|
4703
4967
|
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
4704
4968
|
host: asString(values.host) ?? "127.0.0.1",
|
|
@@ -4789,4 +5053,4 @@ export {
|
|
|
4789
5053
|
createMultiServer,
|
|
4790
5054
|
serveTarget
|
|
4791
5055
|
};
|
|
4792
|
-
//# sourceMappingURL=chunk-
|
|
5056
|
+
//# sourceMappingURL=chunk-AAKIJOPV.js.map
|