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