@crvouga/mockingbird-service-stripe 3.4.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-VV2ZADSQ.js → chunk-53CRCMWH.js} +2 -2
- package/dist/{chunk-XEIHQZPI.js → chunk-Q3J6JUZM.js} +256 -46
- package/dist/chunk-Q3J6JUZM.js.map +7 -0
- package/dist/chunk-Z5GZOENC.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/fleet.d.ts +65 -7
- package/dist/fleet.js +9 -1
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-XEIHQZPI.js.map +0 -7
- /package/dist/{chunk-VV2ZADSQ.js.map → chunk-53CRCMWH.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-stripe
|
|
2
2
|
|
|
3
|
+
## 3.6.0 (2026-10-17)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- resolve fleet and emulator issue batch ([26d2f8c](https://github.com/crvouga/mockingbird/commit/26d2f8cee99fefed93192204bac44fb1be35c1da))
|
|
8
|
+
|
|
9
|
+
## 3.5.0 (2026-10-15)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- resolve SQL, routing and writer gaps ([73f97d0](https://github.com/crvouga/mockingbird/commit/73f97d0d2379f44b9131180498f4fab76648fed9))
|
|
14
|
+
|
|
3
15
|
## 3.4.0 (2026-10-15)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-EUF2MBTV.js";
|
|
5
5
|
import {
|
|
6
6
|
listen
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Q3J6JUZM.js";
|
|
8
8
|
|
|
9
9
|
// src/server.ts
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
@@ -87,4 +87,4 @@ export {
|
|
|
87
87
|
createServer,
|
|
88
88
|
serveTarget
|
|
89
89
|
};
|
|
90
|
-
//# sourceMappingURL=chunk-
|
|
90
|
+
//# sourceMappingURL=chunk-53CRCMWH.js.map
|
|
@@ -12,6 +12,38 @@ import {
|
|
|
12
12
|
|
|
13
13
|
// ../../adapters/node/dist/fleet-archive.js
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
15
|
+
function validateCompatibilityTags(tags) {
|
|
16
|
+
if (!tags || typeof tags !== "object" || Array.isArray(tags))
|
|
17
|
+
throw new Error("invalid archive compatibility tags");
|
|
18
|
+
const entries = Object.entries(tags);
|
|
19
|
+
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)))
|
|
20
|
+
throw new Error("invalid archive compatibility tags");
|
|
21
|
+
}
|
|
22
|
+
function assertCompatibilityTags(tags, policy) {
|
|
23
|
+
if (tags !== void 0)
|
|
24
|
+
validateCompatibilityTags(tags);
|
|
25
|
+
if (!policy)
|
|
26
|
+
return;
|
|
27
|
+
validateCompatibilityTags(policy.tags);
|
|
28
|
+
if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
|
|
29
|
+
throw new Error("invalid missing compatibility policy");
|
|
30
|
+
if (tags === void 0 && policy.missing === "allow")
|
|
31
|
+
return;
|
|
32
|
+
if (canonical(tags) !== canonical(policy.tags))
|
|
33
|
+
throw new Error("application archive compatibility mismatch");
|
|
34
|
+
}
|
|
35
|
+
function assertArchiveCompatibility(input, policy) {
|
|
36
|
+
const archive = validateArchive(input);
|
|
37
|
+
assertCompatibilityTags(archive.compatibility, policy);
|
|
38
|
+
return archive;
|
|
39
|
+
}
|
|
40
|
+
async function migrateApplicationArchive(input, migration) {
|
|
41
|
+
const source = assertArchiveCompatibility(input, migration.from);
|
|
42
|
+
validateCompatibilityTags(migration.to);
|
|
43
|
+
const transformed = await migration.transform(structuredClone(source));
|
|
44
|
+
const { sha256: _sha, ...content } = transformed;
|
|
45
|
+
return validateArchive(sealArchive({ ...content, compatibility: structuredClone(migration.to) }));
|
|
46
|
+
}
|
|
15
47
|
var canonical = (value) => {
|
|
16
48
|
if (Array.isArray(value))
|
|
17
49
|
return `[${value.map(canonical).join(",")}]`;
|
|
@@ -31,6 +63,8 @@ function validateArchive(value) {
|
|
|
31
63
|
const { sha256, ...content } = doc;
|
|
32
64
|
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))
|
|
33
65
|
throw new Error("unsupported or corrupt fleet archive");
|
|
66
|
+
if (doc.compatibility !== void 0)
|
|
67
|
+
validateCompatibilityTags(doc.compatibility);
|
|
34
68
|
for (const [namespace, entry] of Object.entries(doc.namespaces)) {
|
|
35
69
|
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)
|
|
36
70
|
throw new Error("invalid archive namespace");
|
|
@@ -46,6 +80,7 @@ function inspectArchive(input) {
|
|
|
46
80
|
format: archive.format,
|
|
47
81
|
createdAt: archive.createdAt,
|
|
48
82
|
sha256: archive.sha256,
|
|
83
|
+
...archive.compatibility ? { compatibility: archive.compatibility } : {},
|
|
49
84
|
namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name, ns]) => [
|
|
50
85
|
name,
|
|
51
86
|
{
|
|
@@ -746,9 +781,9 @@ var RepositoryWriter = class {
|
|
|
746
781
|
if (!owner || !Number.isSafeInteger(ttlMs) || ttlMs < 1)
|
|
747
782
|
throw new Error("owner and positive lease duration required");
|
|
748
783
|
const current = await this.control.read();
|
|
749
|
-
if (current && current.value.lease.expiresAt > this.now())
|
|
784
|
+
if (current && !current.value.released && current.value.lease.expiresAt > this.now())
|
|
750
785
|
throw new Error("repository writer already owned");
|
|
751
|
-
if (current && !takeoverExpired)
|
|
786
|
+
if (current && !current.value.released && !takeoverExpired)
|
|
752
787
|
throw new Error("expired writer requires explicit takeover policy");
|
|
753
788
|
const lease = {
|
|
754
789
|
owner,
|
|
@@ -764,8 +799,26 @@ var RepositoryWriter = class {
|
|
|
764
799
|
return lease;
|
|
765
800
|
}
|
|
766
801
|
valid(state, lease) {
|
|
767
|
-
if (!state || state.lease.id !== lease.id || state.lease.generation !== lease.generation || state.lease.owner !== lease.owner || state.lease.expiresAt <= this.now())
|
|
802
|
+
if (!state || state.released || state.lease.id !== lease.id || state.lease.generation !== lease.generation || state.lease.owner !== lease.owner || state.lease.expiresAt <= this.now())
|
|
803
|
+
throw new Error("stale or expired repository writer");
|
|
804
|
+
}
|
|
805
|
+
/** Release after draining workloads and awaiting the final verified publish.
|
|
806
|
+
* A failed/uncertain storage operation throws; retry with the same lease to
|
|
807
|
+
* discover whether it completed. Never clears the verified recovery pointer.
|
|
808
|
+
*/
|
|
809
|
+
async release(lease) {
|
|
810
|
+
const current = await this.control.read();
|
|
811
|
+
if (!current)
|
|
768
812
|
throw new Error("stale or expired repository writer");
|
|
813
|
+
if (current.value.released && current.value.lease.id === lease.id && current.value.lease.generation === lease.generation && current.value.lease.owner === lease.owner)
|
|
814
|
+
return "already-released";
|
|
815
|
+
this.valid(current.value, lease);
|
|
816
|
+
if (!await this.control.compareAndSwap(current.revision, {
|
|
817
|
+
...current.value,
|
|
818
|
+
released: true
|
|
819
|
+
}))
|
|
820
|
+
throw new Error("writer release conflict");
|
|
821
|
+
return "released";
|
|
769
822
|
}
|
|
770
823
|
async renew(lease, ttlMs) {
|
|
771
824
|
if (!Number.isSafeInteger(ttlMs) || ttlMs < 1)
|
|
@@ -1262,11 +1315,28 @@ var ingressSupport = {
|
|
|
1262
1315
|
streaming: true,
|
|
1263
1316
|
signing: "vendor request signing is not used; webhook signatures remain payload based"
|
|
1264
1317
|
},
|
|
1318
|
+
cognito: {
|
|
1319
|
+
publicOrigin: true,
|
|
1320
|
+
pathPrefix: true,
|
|
1321
|
+
namespaces: "path-or-header",
|
|
1322
|
+
streaming: true,
|
|
1323
|
+
signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
|
|
1324
|
+
},
|
|
1325
|
+
medplum: {
|
|
1326
|
+
publicOrigin: true,
|
|
1327
|
+
pathPrefix: true,
|
|
1328
|
+
namespaces: "path-or-header",
|
|
1329
|
+
streaming: true,
|
|
1330
|
+
signing: "FHIR, OAuth and storage URLs use the configured public namespace"
|
|
1331
|
+
},
|
|
1265
1332
|
other: {
|
|
1266
1333
|
publicOrigin: false,
|
|
1267
1334
|
reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
|
|
1268
1335
|
}
|
|
1269
1336
|
};
|
|
1337
|
+
function supportsPublicIngress(service) {
|
|
1338
|
+
return service === "stripe" || service === "cognito" || service === "medplum";
|
|
1339
|
+
}
|
|
1270
1340
|
function advertisedHttpBase(entry) {
|
|
1271
1341
|
if (entry.publicPath !== void 0 && !entry.publicOrigin)
|
|
1272
1342
|
throw new Error("publicPath requires publicOrigin");
|
|
@@ -1285,7 +1355,7 @@ function createFleetIngress(config, manifest, transport = fetch) {
|
|
|
1285
1355
|
const base = advertisedHttpBase(entry);
|
|
1286
1356
|
if (!base)
|
|
1287
1357
|
return [];
|
|
1288
|
-
if ((entry.target ?? service)
|
|
1358
|
+
if (!supportsPublicIngress(entry.target ?? service))
|
|
1289
1359
|
throw new Error(`public ingress unsupported for ${entry.target ?? service}; host/path-sensitive signatures require a direct endpoint`);
|
|
1290
1360
|
const privateEndpoint = manifest.services[service];
|
|
1291
1361
|
if (!privateEndpoint || entry.protocol && entry.protocol !== "http")
|
|
@@ -1309,6 +1379,8 @@ function createFleetIngress(config, manifest, transport = fetch) {
|
|
|
1309
1379
|
const route = routes.find((r) => url.origin === r.origin && (url.pathname === r.prefix || url.pathname.startsWith(`${r.prefix}/`)));
|
|
1310
1380
|
if (!route)
|
|
1311
1381
|
return new Response("public ingress route not found", { status: 404 });
|
|
1382
|
+
if ((route.entry.target ?? route.service) === "cognito" && request.headers.get("authorization")?.startsWith("AWS4-HMAC-SHA256"))
|
|
1383
|
+
return new Response("SigV4 requests require a direct endpoint", { status: 403 });
|
|
1312
1384
|
const path = url.pathname.slice(route.prefix.length) || "/";
|
|
1313
1385
|
const adminPrefix = route.entry.adminPrefix ?? config.adminPrefix ?? "/__admin";
|
|
1314
1386
|
if ((path === adminPrefix || path.startsWith(`${adminPrefix}/`)) && !path.startsWith(`${adminPrefix}/ns/`))
|
|
@@ -2329,7 +2401,7 @@ data: ${JSON.stringify(data)}
|
|
|
2329
2401
|
{
|
|
2330
2402
|
protocol: c.protocol,
|
|
2331
2403
|
coverage: c.coverage,
|
|
2332
|
-
ingress: c.targetName
|
|
2404
|
+
ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
|
|
2333
2405
|
target: c.targetName,
|
|
2334
2406
|
outboundPolicy: c.outboundPolicy ?? "unsupported",
|
|
2335
2407
|
version: 1,
|
|
@@ -3413,11 +3485,12 @@ data: ${JSON.stringify(data)}
|
|
|
3413
3485
|
format: "mockingbird.fleet/v1",
|
|
3414
3486
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3415
3487
|
sourceId: this.ctx.id,
|
|
3488
|
+
...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
|
|
3416
3489
|
namespaces: contents
|
|
3417
3490
|
});
|
|
3418
3491
|
}
|
|
3419
3492
|
async restoreArchive(input, selectedNamespace) {
|
|
3420
|
-
const archive =
|
|
3493
|
+
const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
|
|
3421
3494
|
const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
|
|
3422
3495
|
if (!entries.length)
|
|
3423
3496
|
throw new Error("selected namespace absent from archive");
|
|
@@ -3701,7 +3774,7 @@ async function preflightFleet(config, options) {
|
|
|
3701
3774
|
if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
|
|
3702
3775
|
throw new Error("target must name a supported service suffix");
|
|
3703
3776
|
const service = entry.target ?? name;
|
|
3704
|
-
if (advertisedHttpBase(entry) && service
|
|
3777
|
+
if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
|
|
3705
3778
|
throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
|
|
3706
3779
|
const target = await options.load(service);
|
|
3707
3780
|
const protocol = "protocol" in target ? target.protocol : "http";
|
|
@@ -3752,7 +3825,7 @@ async function preflightFleet(config, options) {
|
|
|
3752
3825
|
break;
|
|
3753
3826
|
}
|
|
3754
3827
|
}
|
|
3755
|
-
targets.set(name, { ...target, packageVersion: packageVersion ?? "3.
|
|
3828
|
+
targets.set(name, { ...target, packageVersion: packageVersion ?? "3.6.0" });
|
|
3756
3829
|
} catch (error) {
|
|
3757
3830
|
throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
3758
3831
|
}
|
|
@@ -3796,6 +3869,11 @@ async function preflightFleet(config, options) {
|
|
|
3796
3869
|
return ordered;
|
|
3797
3870
|
}
|
|
3798
3871
|
function validateFleetPolicies(options) {
|
|
3872
|
+
if (options.archiveCompatibility) {
|
|
3873
|
+
validateCompatibilityTags(options.archiveCompatibility.tags);
|
|
3874
|
+
if (options.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options.archiveCompatibility.missing))
|
|
3875
|
+
throw new Error("archiveCompatibility.missing must be allow or reject");
|
|
3876
|
+
}
|
|
3799
3877
|
if (options.journal && !options.repository)
|
|
3800
3878
|
throw new Error("durable operation journal requires an archive repository");
|
|
3801
3879
|
if (options.cors) {
|
|
@@ -4627,6 +4705,40 @@ async function startFleet(config, options) {
|
|
|
4627
4705
|
}
|
|
4628
4706
|
}
|
|
4629
4707
|
|
|
4708
|
+
// ../../adapters/node/dist/fleet-loader.js
|
|
4709
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
4710
|
+
import { resolve as resolve2 } from "node:path";
|
|
4711
|
+
import { pathToFileURL } from "node:url";
|
|
4712
|
+
var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4713
|
+
function createInstalledServiceLoader(options) {
|
|
4714
|
+
if (!options.projectRoot)
|
|
4715
|
+
throw new Error("consumer projectRoot required");
|
|
4716
|
+
const allowed = new Set(options.services);
|
|
4717
|
+
for (const name of allowed)
|
|
4718
|
+
if (!suffix.test(name))
|
|
4719
|
+
throw new Error("invalid service suffix");
|
|
4720
|
+
const require2 = createRequire2(resolve2(options.projectRoot, "package.json"));
|
|
4721
|
+
const targets = new Map((options.targets ?? []).map((target) => [target.name, target]));
|
|
4722
|
+
return async (name) => {
|
|
4723
|
+
if (!suffix.test(name) || !allowed.has(name))
|
|
4724
|
+
throw new Error("service is not declared in loader allowlist");
|
|
4725
|
+
const own = targets.get(name);
|
|
4726
|
+
if (own)
|
|
4727
|
+
return own;
|
|
4728
|
+
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
4729
|
+
let location;
|
|
4730
|
+
try {
|
|
4731
|
+
location = require2.resolve(specifier);
|
|
4732
|
+
} catch {
|
|
4733
|
+
throw new Error(`cannot load service "${name}": install @crvouga/mockingbird-service-${name} in the consumer project; its /server export is required`);
|
|
4734
|
+
}
|
|
4735
|
+
const mod = await import(pathToFileURL(location).href);
|
|
4736
|
+
if (!mod.serveTarget || mod.serveTarget.name !== name || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
|
|
4737
|
+
throw new Error(`${specifier} exports no matching serveTarget`);
|
|
4738
|
+
return mod.serveTarget;
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4630
4742
|
// ../../adapters/node/dist/fleet-client.js
|
|
4631
4743
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
4632
4744
|
import { chmod as chmod2, readFile as readFile5, rename as rename4, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
|
|
@@ -4639,6 +4751,14 @@ var FleetClientError = class extends Error {
|
|
|
4639
4751
|
this.details = details;
|
|
4640
4752
|
}
|
|
4641
4753
|
};
|
|
4754
|
+
var FleetClientResponseError = class extends FleetClientError {
|
|
4755
|
+
code;
|
|
4756
|
+
constructor(code, status, origin) {
|
|
4757
|
+
super(status, { code, body: "omitted", ...origin ? { origin } : {} });
|
|
4758
|
+
this.code = code;
|
|
4759
|
+
this.name = "FleetClientResponseError";
|
|
4760
|
+
}
|
|
4761
|
+
};
|
|
4642
4762
|
var FleetClient = class {
|
|
4643
4763
|
base;
|
|
4644
4764
|
options;
|
|
@@ -4651,7 +4771,14 @@ var FleetClient = class {
|
|
|
4651
4771
|
if (url.username || url.password)
|
|
4652
4772
|
throw new Error("fleet client URL must not contain credentials");
|
|
4653
4773
|
url.searchParams.set("namespace", options.namespace ?? this.options.namespace ?? "default");
|
|
4774
|
+
const limit = options.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
|
|
4775
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
4776
|
+
throw new Error("invalid response byte limit");
|
|
4777
|
+
const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
|
|
4778
|
+
const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
|
|
4779
|
+
signal.throwIfAborted();
|
|
4654
4780
|
const response = await (this.options.fetch ?? fetch)(url, {
|
|
4781
|
+
redirect: "manual",
|
|
4655
4782
|
method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
|
|
4656
4783
|
headers: {
|
|
4657
4784
|
"content-type": "application/json",
|
|
@@ -4659,11 +4786,71 @@ var FleetClient = class {
|
|
|
4659
4786
|
...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
|
|
4660
4787
|
},
|
|
4661
4788
|
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
4662
|
-
signal
|
|
4789
|
+
signal
|
|
4663
4790
|
});
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
throw new
|
|
4791
|
+
if (response.status >= 300 && response.status < 400) {
|
|
4792
|
+
await response.body?.cancel();
|
|
4793
|
+
throw new FleetClientResponseError("redirect", response.status, url.origin);
|
|
4794
|
+
}
|
|
4795
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
4796
|
+
await response.body?.cancel();
|
|
4797
|
+
throw new FleetClientResponseError("response_too_large", response.status);
|
|
4798
|
+
}
|
|
4799
|
+
const reader = response.body?.getReader();
|
|
4800
|
+
const chunks = [];
|
|
4801
|
+
let size = 0;
|
|
4802
|
+
const abort = () => {
|
|
4803
|
+
void reader?.cancel(signal.reason).catch(() => {
|
|
4804
|
+
});
|
|
4805
|
+
};
|
|
4806
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
4807
|
+
try {
|
|
4808
|
+
if (reader)
|
|
4809
|
+
while (true) {
|
|
4810
|
+
signal.throwIfAborted();
|
|
4811
|
+
const { done, value } = await reader.read();
|
|
4812
|
+
signal.throwIfAborted();
|
|
4813
|
+
if (done)
|
|
4814
|
+
break;
|
|
4815
|
+
size += value.byteLength;
|
|
4816
|
+
if (size > limit) {
|
|
4817
|
+
await reader.cancel();
|
|
4818
|
+
throw new FleetClientResponseError("response_too_large", response.status);
|
|
4819
|
+
}
|
|
4820
|
+
chunks.push(value);
|
|
4821
|
+
}
|
|
4822
|
+
} finally {
|
|
4823
|
+
signal.removeEventListener("abort", abort);
|
|
4824
|
+
if (signal.aborted)
|
|
4825
|
+
await reader?.cancel(signal.reason).catch(() => {
|
|
4826
|
+
});
|
|
4827
|
+
reader?.releaseLock();
|
|
4828
|
+
}
|
|
4829
|
+
const bytes2 = new Uint8Array(size);
|
|
4830
|
+
let offset = 0;
|
|
4831
|
+
for (const chunk of chunks) {
|
|
4832
|
+
bytes2.set(chunk, offset);
|
|
4833
|
+
offset += chunk.byteLength;
|
|
4834
|
+
}
|
|
4835
|
+
const text = new TextDecoder().decode(bytes2);
|
|
4836
|
+
let data;
|
|
4837
|
+
try {
|
|
4838
|
+
data = text.length ? JSON.parse(text) : void 0;
|
|
4839
|
+
} catch {
|
|
4840
|
+
if (response.ok)
|
|
4841
|
+
throw new FleetClientResponseError("invalid_json", response.status);
|
|
4842
|
+
}
|
|
4843
|
+
if (!response.ok) {
|
|
4844
|
+
const error = data && typeof data === "object" ? data.error : void 0;
|
|
4845
|
+
const detail = { body: "omitted" };
|
|
4846
|
+
if (error && typeof error === "object")
|
|
4847
|
+
for (const key of ["type", "code"]) {
|
|
4848
|
+
const value = error[key];
|
|
4849
|
+
if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
|
|
4850
|
+
detail[key] = value;
|
|
4851
|
+
}
|
|
4852
|
+
throw new FleetClientError(response.status, detail);
|
|
4853
|
+
}
|
|
4667
4854
|
return data;
|
|
4668
4855
|
}
|
|
4669
4856
|
discover() {
|
|
@@ -4729,17 +4916,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
|
|
|
4729
4916
|
const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
|
|
4730
4917
|
if (!child || !secret)
|
|
4731
4918
|
throw new Error(`missing service mapping ${entry.service}`);
|
|
4919
|
+
let endpoint;
|
|
4732
4920
|
if (child.protocol === "http") {
|
|
4733
4921
|
const template = child.namespaces.path;
|
|
4734
4922
|
if (typeof template !== "string")
|
|
4735
4923
|
throw new Error(`missing HTTP namespace mapping ${entry.service}`);
|
|
4736
|
-
|
|
4924
|
+
endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
|
|
4737
4925
|
} else {
|
|
4738
|
-
const
|
|
4739
|
-
if (!
|
|
4926
|
+
const selected = secret.namespaces?.[namespace];
|
|
4927
|
+
if (!selected)
|
|
4740
4928
|
throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
|
|
4741
|
-
|
|
4929
|
+
endpoint = selected;
|
|
4930
|
+
}
|
|
4931
|
+
const url = new URL(endpoint);
|
|
4932
|
+
const field = entry.field ?? "url";
|
|
4933
|
+
let value;
|
|
4934
|
+
switch (field) {
|
|
4935
|
+
case "url":
|
|
4936
|
+
value = endpoint;
|
|
4937
|
+
break;
|
|
4938
|
+
case "hostname":
|
|
4939
|
+
value = url.hostname.replace(/^\[|\]$/g, "");
|
|
4940
|
+
break;
|
|
4941
|
+
case "port":
|
|
4942
|
+
value = url.port;
|
|
4943
|
+
break;
|
|
4944
|
+
case "username":
|
|
4945
|
+
value = decodeURIComponent(url.username);
|
|
4946
|
+
break;
|
|
4947
|
+
case "password":
|
|
4948
|
+
value = decodeURIComponent(url.password);
|
|
4949
|
+
break;
|
|
4950
|
+
case "pathname":
|
|
4951
|
+
value = decodeURIComponent(url.pathname);
|
|
4952
|
+
break;
|
|
4953
|
+
case "database":
|
|
4954
|
+
value = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
4955
|
+
break;
|
|
4956
|
+
default:
|
|
4957
|
+
throw new Error("unsupported connection field");
|
|
4742
4958
|
}
|
|
4959
|
+
if (!value) {
|
|
4960
|
+
if (typeof entry.fallback !== "string")
|
|
4961
|
+
throw new Error(`missing connection component ${field}`);
|
|
4962
|
+
value = entry.fallback;
|
|
4963
|
+
}
|
|
4964
|
+
result[variable] = value;
|
|
4743
4965
|
}
|
|
4744
4966
|
return result;
|
|
4745
4967
|
}
|
|
@@ -4759,14 +4981,14 @@ async function writeConnections(path, values, format = "json") {
|
|
|
4759
4981
|
|
|
4760
4982
|
// ../../adapters/node/dist/fleet-tools.js
|
|
4761
4983
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
4762
|
-
import { createRequire as
|
|
4763
|
-
import { resolve as
|
|
4984
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
4985
|
+
import { resolve as resolve3 } from "node:path";
|
|
4764
4986
|
async function doctorFleet(config, options, probes = []) {
|
|
4765
4987
|
const findings = [];
|
|
4766
4988
|
const plan = [];
|
|
4767
4989
|
try {
|
|
4768
4990
|
const targets = await preflightFleet(config, options);
|
|
4769
|
-
const require2 =
|
|
4991
|
+
const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
|
|
4770
4992
|
for (const [service, target] of targets) {
|
|
4771
4993
|
let version = "development";
|
|
4772
4994
|
try {
|
|
@@ -4821,7 +5043,7 @@ async function doctorFleet(config, options, probes = []) {
|
|
|
4821
5043
|
};
|
|
4822
5044
|
}
|
|
4823
5045
|
async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e4) {
|
|
4824
|
-
const archive =
|
|
5046
|
+
const archive = assertArchiveCompatibility(input, options.archiveCompatibility);
|
|
4825
5047
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4826
5048
|
const isolated = {
|
|
4827
5049
|
...config,
|
|
@@ -4833,7 +5055,7 @@ async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e
|
|
|
4833
5055
|
};
|
|
4834
5056
|
let fleet;
|
|
4835
5057
|
try {
|
|
4836
|
-
fleet = await startFleet(isolated,
|
|
5058
|
+
fleet = await startFleet(isolated, options);
|
|
4837
5059
|
const client = new FleetClient(fleet.manifest.adminBase, {
|
|
4838
5060
|
...config.adminKey ? { adminKey: config.adminKey } : {}
|
|
4839
5061
|
});
|
|
@@ -4972,9 +5194,6 @@ function fleetCommands(load) {
|
|
|
4972
5194
|
|
|
4973
5195
|
// ../../adapters/node/dist/cli.js
|
|
4974
5196
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
4975
|
-
import { createRequire as createRequire3 } from "node:module";
|
|
4976
|
-
import { resolve as resolve3 } from "node:path";
|
|
4977
|
-
import { pathToFileURL } from "node:url";
|
|
4978
5197
|
import { parseArgs } from "node:util";
|
|
4979
5198
|
var optionHelp = (options) => Object.entries(options).map(([name, option]) => {
|
|
4980
5199
|
const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
|
|
@@ -5213,26 +5432,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
|
|
|
5213
5432
|
normalizeBootControls(document, "adminControls");
|
|
5214
5433
|
return document;
|
|
5215
5434
|
};
|
|
5216
|
-
var loadTarget =
|
|
5217
|
-
if (name === own.name)
|
|
5218
|
-
return own;
|
|
5219
|
-
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
5220
|
-
try {
|
|
5221
|
-
let mod;
|
|
5222
|
-
try {
|
|
5223
|
-
mod = await import(specifier);
|
|
5224
|
-
} catch {
|
|
5225
|
-
const fromProject = createRequire3(resolve3(process.cwd(), "package.json"));
|
|
5226
|
-
mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
|
|
5227
|
-
}
|
|
5228
|
-
if (!mod.serveTarget)
|
|
5229
|
-
throw new Error(`${specifier} exports no serveTarget`);
|
|
5230
|
-
return mod.serveTarget;
|
|
5231
|
-
} catch (error) {
|
|
5232
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
5233
|
-
throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
|
|
5234
|
-
}
|
|
5235
|
-
};
|
|
5435
|
+
var loadTarget = (name, own) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name], targets: [own] })(name);
|
|
5236
5436
|
var start = async (target, values, config) => {
|
|
5237
5437
|
const adminPrefix = resolveAdminPrefix(config.adminPrefix);
|
|
5238
5438
|
const runtime = await target.create(values, {
|
|
@@ -5882,9 +6082,14 @@ var sourceArchive = (input) => {
|
|
|
5882
6082
|
throw new Error("corrupt migration source");
|
|
5883
6083
|
return structuredClone(source);
|
|
5884
6084
|
};
|
|
5885
|
-
function archiveCompatibility(input, registry) {
|
|
6085
|
+
function archiveCompatibility(input, registry, policy) {
|
|
5886
6086
|
const source = sourceArchive(input);
|
|
5887
6087
|
const problems = [], steps = [];
|
|
6088
|
+
try {
|
|
6089
|
+
assertCompatibilityTags(source.compatibility, policy);
|
|
6090
|
+
} catch (error) {
|
|
6091
|
+
problems.push(error instanceof Error ? error.message : "invalid application compatibility");
|
|
6092
|
+
}
|
|
5888
6093
|
if (source.format !== "mockingbird.fleet/v1" && !registry.sourceFormats.includes(source.format))
|
|
5889
6094
|
problems.push(`unsupported source format ${source.format}`);
|
|
5890
6095
|
for (const [namespace, ns] of Object.entries(source.namespaces))
|
|
@@ -5936,6 +6141,7 @@ async function migrateArchive(input, registry, parameters = {}) {
|
|
|
5936
6141
|
const content = {
|
|
5937
6142
|
format: "mockingbird.fleet/v1",
|
|
5938
6143
|
sourceId: source.sourceId,
|
|
6144
|
+
...source.compatibility ? { compatibility: source.compatibility } : {},
|
|
5939
6145
|
createdAt: source.createdAt,
|
|
5940
6146
|
namespaces,
|
|
5941
6147
|
migration: {
|
|
@@ -6302,6 +6508,8 @@ var NamespacePool = class {
|
|
|
6302
6508
|
};
|
|
6303
6509
|
|
|
6304
6510
|
export {
|
|
6511
|
+
assertArchiveCompatibility,
|
|
6512
|
+
migrateApplicationArchive,
|
|
6305
6513
|
validateArchive,
|
|
6306
6514
|
inspectArchive,
|
|
6307
6515
|
diffArchives,
|
|
@@ -6327,7 +6535,9 @@ export {
|
|
|
6327
6535
|
serve,
|
|
6328
6536
|
listen,
|
|
6329
6537
|
startFleet,
|
|
6538
|
+
createInstalledServiceLoader,
|
|
6330
6539
|
FleetClientError,
|
|
6540
|
+
FleetClientResponseError,
|
|
6331
6541
|
FleetClient,
|
|
6332
6542
|
renderConnections,
|
|
6333
6543
|
writeConnections,
|
|
@@ -6355,4 +6565,4 @@ export {
|
|
|
6355
6565
|
withFleetRun,
|
|
6356
6566
|
NamespacePool
|
|
6357
6567
|
};
|
|
6358
|
-
//# sourceMappingURL=chunk-
|
|
6568
|
+
//# sourceMappingURL=chunk-Q3J6JUZM.js.map
|