@crvouga/mockingbird-service-junction 1.8.2 → 2.0.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 +39 -0
- package/DISCOVERY.md +55 -0
- package/README.md +4 -4
- package/SUPPORT.md +50 -0
- package/dist/chunk-7LPCTLIJ.js +1027 -0
- package/dist/chunk-7LPCTLIJ.js.map +7 -0
- package/dist/{chunk-ADSZBAJZ.js → chunk-FZVFIPML.js} +785 -259
- package/dist/chunk-FZVFIPML.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +35 -4
- package/dist/index.js +1 -1
- package/dist/server.d.ts +66 -5
- package/dist/server.js +2 -2
- package/openapi.yaml +3707 -0
- package/package.json +29 -4
- package/dist/chunk-4QHCRJO4.js +0 -540
- package/dist/chunk-4QHCRJO4.js.map +0 -7
- package/dist/chunk-ADSZBAJZ.js.map +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/mockingbird-service-junction",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Stateful mock of the Junction (Vital) user API driven by its OpenAPI contract and verified by differential property tests against the Junction sandbox.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"README.md",
|
|
11
|
-
"CHANGELOG.md"
|
|
11
|
+
"CHANGELOG.md",
|
|
12
|
+
"DISCOVERY.md",
|
|
13
|
+
"SUPPORT.md",
|
|
14
|
+
"openapi.yaml"
|
|
12
15
|
],
|
|
13
16
|
"main": "./dist/index.js",
|
|
14
17
|
"types": "./dist/index.d.ts",
|
|
@@ -62,7 +65,29 @@
|
|
|
62
65
|
"parityTier": "cold",
|
|
63
66
|
"parityInputs": [
|
|
64
67
|
"PARITY_FAILURE_SEED_REGISTRY.json"
|
|
65
|
-
]
|
|
68
|
+
],
|
|
69
|
+
"discovery": {
|
|
70
|
+
"guide": "DISCOVERY.md",
|
|
71
|
+
"behavior": "README.md",
|
|
72
|
+
"capabilities": "SUPPORT.md",
|
|
73
|
+
"contract": "openapi.yaml",
|
|
74
|
+
"types": "dist/index.d.ts",
|
|
75
|
+
"oracle": {
|
|
76
|
+
"kind": "Live vendor API or sandbox",
|
|
77
|
+
"command": "bun run parity:service -- junction",
|
|
78
|
+
"note": "Run from a Mockingbird checkout; credentials come only from .env.local or GitHub Actions secrets. Missing credentials exit 2."
|
|
79
|
+
},
|
|
80
|
+
"introspection": [
|
|
81
|
+
"GET /__admin/health",
|
|
82
|
+
"GET /__admin",
|
|
83
|
+
"GET /__admin/state",
|
|
84
|
+
"GET /__admin/requests",
|
|
85
|
+
"GET /__admin/metrics",
|
|
86
|
+
"GET /__admin/faults/presets",
|
|
87
|
+
"GET /__admin/ui"
|
|
88
|
+
],
|
|
89
|
+
"reporting": "https://github.com/crvouga/mockingbird/blob/main/docs/REPORTING_ISSUES.md"
|
|
90
|
+
}
|
|
66
91
|
},
|
|
67
92
|
"scripts": {
|
|
68
93
|
"generate": "bun ../../openapi/codegen/dist/cli.js",
|
|
@@ -86,7 +111,7 @@
|
|
|
86
111
|
"verify": "bun src/cli.ts verify"
|
|
87
112
|
},
|
|
88
113
|
"dependencies": {
|
|
89
|
-
"@crvouga/mockingbird-service-sqlite": "
|
|
114
|
+
"@crvouga/mockingbird-service-sqlite": "2.0.0",
|
|
90
115
|
"hono": "4.11.9"
|
|
91
116
|
},
|
|
92
117
|
"devDependencies": {
|
package/dist/chunk-4QHCRJO4.js
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IDENTITY_MODES,
|
|
3
|
-
createRuntime,
|
|
4
|
-
defaultBillingTypesFrom,
|
|
5
|
-
pullCorpus
|
|
6
|
-
} from "./chunk-ADSZBAJZ.js";
|
|
7
|
-
import {
|
|
8
|
-
defaultCorpus
|
|
9
|
-
} from "./chunk-C2UGIPPM.js";
|
|
10
|
-
import {
|
|
11
|
-
parseSealedCorpus
|
|
12
|
-
} from "./chunk-ISDNA2TU.js";
|
|
13
|
-
|
|
14
|
-
// src/server.ts
|
|
15
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
16
|
-
|
|
17
|
-
// ../../adapters/node/dist/cli.js
|
|
18
|
-
import { readFile } from "node:fs/promises";
|
|
19
|
-
import { parseArgs } from "node:util";
|
|
20
|
-
|
|
21
|
-
// ../../adapters/node/dist/serve.js
|
|
22
|
-
import { createServer } from "node:http";
|
|
23
|
-
var serve = async (api, options = {}) => {
|
|
24
|
-
const server = createServer(async (req, res) => {
|
|
25
|
-
const chunks = [];
|
|
26
|
-
for await (const chunk of req) {
|
|
27
|
-
chunks.push(Buffer.from(chunk));
|
|
28
|
-
}
|
|
29
|
-
const body = Buffer.concat(chunks);
|
|
30
|
-
const address = server.address();
|
|
31
|
-
const port = typeof address === "object" && address !== null ? address.port : void 0;
|
|
32
|
-
const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
|
|
33
|
-
const raw = req.url ?? "/";
|
|
34
|
-
const url = new URL(raw.replace(/^\/+/, "/"), base);
|
|
35
|
-
const method = req.method ?? "GET";
|
|
36
|
-
const init = { method, headers: req.headers };
|
|
37
|
-
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
|
38
|
-
init.body = body;
|
|
39
|
-
}
|
|
40
|
-
const aborted = new AbortController();
|
|
41
|
-
res.once("close", () => {
|
|
42
|
-
if (!res.writableFinished)
|
|
43
|
-
aborted.abort();
|
|
44
|
-
});
|
|
45
|
-
init.signal = aborted.signal;
|
|
46
|
-
const request = new Request(url, init);
|
|
47
|
-
let response;
|
|
48
|
-
try {
|
|
49
|
-
response = await api.fetch(request);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
if (error.code === "MOCKINGBIRD_DROP") {
|
|
52
|
-
req.socket.destroy();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
res.writeHead(500, { "content-type": "application/json" });
|
|
56
|
-
res.end(JSON.stringify({
|
|
57
|
-
error: {
|
|
58
|
-
type: "mockingbird_internal",
|
|
59
|
-
message: error instanceof Error ? error.message : String(error)
|
|
60
|
-
}
|
|
61
|
-
}));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const headers = Object.fromEntries(response.headers);
|
|
65
|
-
const cookies = response.headers.getSetCookie();
|
|
66
|
-
if (cookies.length > 0)
|
|
67
|
-
headers["set-cookie"] = cookies;
|
|
68
|
-
if (!response.body) {
|
|
69
|
-
res.writeHead(response.status, headers);
|
|
70
|
-
res.end();
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
res.writeHead(response.status, headers);
|
|
74
|
-
res.flushHeaders();
|
|
75
|
-
const reader = response.body.getReader();
|
|
76
|
-
try {
|
|
77
|
-
for (; ; ) {
|
|
78
|
-
const { done, value } = await reader.read();
|
|
79
|
-
if (done)
|
|
80
|
-
break;
|
|
81
|
-
if (!res.write(value))
|
|
82
|
-
await new Promise((resolve) => res.once("drain", resolve));
|
|
83
|
-
}
|
|
84
|
-
res.end();
|
|
85
|
-
} catch {
|
|
86
|
-
res.destroy();
|
|
87
|
-
} finally {
|
|
88
|
-
reader.releaseLock();
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
await new Promise((resolve, reject) => {
|
|
92
|
-
server.once("error", reject);
|
|
93
|
-
server.listen(options.port ?? 0, options.host, resolve);
|
|
94
|
-
});
|
|
95
|
-
return server;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// ../../adapters/node/dist/listen.js
|
|
99
|
-
var listen = async (api, options = {}) => {
|
|
100
|
-
const host = options.host ?? "127.0.0.1";
|
|
101
|
-
const server = await serve(api, { port: options.port ?? 0, host });
|
|
102
|
-
const address = server.address();
|
|
103
|
-
const port = typeof address === "object" && address !== null ? address.port : options.port ?? 0;
|
|
104
|
-
const shown = host.includes(":") ? `[${host}]` : host;
|
|
105
|
-
return {
|
|
106
|
-
url: `http://${shown}:${port}`,
|
|
107
|
-
port,
|
|
108
|
-
host,
|
|
109
|
-
server,
|
|
110
|
-
close: () => new Promise((resolve, reject) => {
|
|
111
|
-
server.close((error) => {
|
|
112
|
-
if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
|
|
113
|
-
reject(error);
|
|
114
|
-
else
|
|
115
|
-
resolve();
|
|
116
|
-
});
|
|
117
|
-
server.closeAllConnections?.();
|
|
118
|
-
})
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// ../../adapters/node/dist/cli.js
|
|
123
|
-
var optionHelp = (options) => Object.entries(options).map(([name, option]) => {
|
|
124
|
-
const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
|
|
125
|
-
const fallback = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
|
|
126
|
-
return ` ${flag.padEnd(30)} ${option.description}${fallback}`;
|
|
127
|
-
});
|
|
128
|
-
var help = (spec) => [
|
|
129
|
-
`${spec.bin} \u2014 ${spec.description}`,
|
|
130
|
-
"",
|
|
131
|
-
"Usage:",
|
|
132
|
-
` ${spec.bin} <command> [options]`,
|
|
133
|
-
"",
|
|
134
|
-
"Commands:",
|
|
135
|
-
...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
|
|
136
|
-
"",
|
|
137
|
-
`Run \`${spec.bin} <command> --help\` for a command's options.`
|
|
138
|
-
].join("\n");
|
|
139
|
-
var commandHelp = (spec, name, command) => [
|
|
140
|
-
`${spec.bin} ${name} \u2014 ${command.summary}`,
|
|
141
|
-
"",
|
|
142
|
-
"Usage:",
|
|
143
|
-
` ${command.usage ?? `${spec.bin} ${name} [options]`}`,
|
|
144
|
-
...command.options ? ["", "Options:", ...optionHelp(command.options)] : []
|
|
145
|
-
].join("\n");
|
|
146
|
-
var runCli = async (spec, argv) => {
|
|
147
|
-
const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
|
|
148
|
-
const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
|
|
149
|
-
const name = words === 2 ? pair : argv[0];
|
|
150
|
-
const rest = argv.slice(words);
|
|
151
|
-
if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
|
|
152
|
-
console.log(help(spec));
|
|
153
|
-
return 0;
|
|
154
|
-
}
|
|
155
|
-
const command = spec.commands[name];
|
|
156
|
-
if (!command) {
|
|
157
|
-
console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
|
|
158
|
-
|
|
159
|
-
${help(spec)}`);
|
|
160
|
-
return 2;
|
|
161
|
-
}
|
|
162
|
-
if (rest.includes("--help") || rest.includes("-h")) {
|
|
163
|
-
console.log(commandHelp(spec, name, command));
|
|
164
|
-
return 0;
|
|
165
|
-
}
|
|
166
|
-
let parsed;
|
|
167
|
-
try {
|
|
168
|
-
parsed = parseArgs({
|
|
169
|
-
args: rest,
|
|
170
|
-
allowPositionals: true,
|
|
171
|
-
strict: true,
|
|
172
|
-
options: Object.fromEntries(Object.entries(command.options ?? {}).map(([key, option]) => [
|
|
173
|
-
key,
|
|
174
|
-
{
|
|
175
|
-
type: option.type,
|
|
176
|
-
...option.default !== void 0 ? { default: option.default } : {}
|
|
177
|
-
}
|
|
178
|
-
]))
|
|
179
|
-
});
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
182
|
-
|
|
183
|
-
${commandHelp(spec, name, command)}`);
|
|
184
|
-
return 2;
|
|
185
|
-
}
|
|
186
|
-
return command.run(parsed.values, parsed.positionals);
|
|
187
|
-
};
|
|
188
|
-
var COMMON_SERVE_OPTIONS = {
|
|
189
|
-
port: { type: "string", value: "<port>", description: "Port to listen on" },
|
|
190
|
-
host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
|
|
191
|
-
"admin-key": {
|
|
192
|
-
type: "string",
|
|
193
|
-
value: "<key>",
|
|
194
|
-
description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
|
|
195
|
-
},
|
|
196
|
-
seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
|
|
197
|
-
log: {
|
|
198
|
-
type: "string",
|
|
199
|
-
value: "<pretty|json|off>",
|
|
200
|
-
description: "Request log format",
|
|
201
|
-
default: "pretty"
|
|
202
|
-
},
|
|
203
|
-
"log-requests": {
|
|
204
|
-
type: "boolean",
|
|
205
|
-
description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
|
|
206
|
-
},
|
|
207
|
-
config: {
|
|
208
|
-
type: "string",
|
|
209
|
-
value: "<file>",
|
|
210
|
-
description: "Serve every service in a mockingbird.json config instead"
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
var formatLog = (format) => {
|
|
214
|
-
if (format === "off")
|
|
215
|
-
return void 0;
|
|
216
|
-
if (format === "json")
|
|
217
|
-
return (entry) => console.log(JSON.stringify(entry));
|
|
218
|
-
return (entry) => {
|
|
219
|
-
const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
|
|
220
|
-
const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
|
|
221
|
-
const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
|
|
222
|
-
const adopted = entry.adopted ? " adopted" : "";
|
|
223
|
-
const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
|
|
224
|
-
const issues = entry.issues?.length ? ` issues=${entry.issues.slice(0, 3).map((issue) => `${issue.path || "$"}: ${issue.message}`).join("; ")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : ""}` : "";
|
|
225
|
-
console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
|
|
226
|
-
};
|
|
227
|
-
};
|
|
228
|
-
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
229
|
-
var loadTarget = async (name, own) => {
|
|
230
|
-
if (name === own.name)
|
|
231
|
-
return own;
|
|
232
|
-
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
233
|
-
try {
|
|
234
|
-
const mod = await import(specifier);
|
|
235
|
-
if (!mod.serveTarget)
|
|
236
|
-
throw new Error(`${specifier} exports no serveTarget`);
|
|
237
|
-
return mod.serveTarget;
|
|
238
|
-
} catch (error) {
|
|
239
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
240
|
-
throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
var start = async (target, values, config) => {
|
|
244
|
-
const runtime = await target.create(values, {
|
|
245
|
-
adminKey: config.adminKey,
|
|
246
|
-
seed: config.seed,
|
|
247
|
-
onLog: formatLog(config.log)
|
|
248
|
-
});
|
|
249
|
-
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
250
|
-
target.listening?.(listening);
|
|
251
|
-
console.log(`${target.name} mock listening on ${listening.url}`);
|
|
252
|
-
console.log(`${target.name} health: GET ${listening.url}/health`);
|
|
253
|
-
console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
|
|
254
|
-
console.log(`${target.name} admin ui: ${listening.url}/__admin/ui`);
|
|
255
|
-
for (const line of target.banner?.(runtime) ?? [])
|
|
256
|
-
console.log(`${target.name} ${line}`);
|
|
257
|
-
return listening;
|
|
258
|
-
};
|
|
259
|
-
var untilSignal = async (servers) => new Promise((resolve) => {
|
|
260
|
-
const stop = async () => {
|
|
261
|
-
await Promise.allSettled(servers.map((s) => s.close()));
|
|
262
|
-
resolve(0);
|
|
263
|
-
};
|
|
264
|
-
process.once("SIGINT", stop);
|
|
265
|
-
process.once("SIGTERM", stop);
|
|
266
|
-
});
|
|
267
|
-
var serveCommand = (target) => ({
|
|
268
|
-
summary: `Serve the ${target.name} mock over HTTP`,
|
|
269
|
-
options: { ...COMMON_SERVE_OPTIONS, ...target.options },
|
|
270
|
-
async run(values) {
|
|
271
|
-
const log = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
|
|
272
|
-
if (!["pretty", "json", "off"].includes(log)) {
|
|
273
|
-
console.error(`--log must be pretty, json or off (got ${log})`);
|
|
274
|
-
return 2;
|
|
275
|
-
}
|
|
276
|
-
const configPath = asString(values.config);
|
|
277
|
-
if (configPath !== void 0) {
|
|
278
|
-
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
279
|
-
const servers = [];
|
|
280
|
-
try {
|
|
281
|
-
for (const [name, entry] of Object.entries(config.services ?? {})) {
|
|
282
|
-
const each = await loadTarget(name, target);
|
|
283
|
-
servers.push(await start(each, entry.options ?? {}, {
|
|
284
|
-
port: entry.port ?? each.defaultPort,
|
|
285
|
-
host: entry.host ?? "127.0.0.1",
|
|
286
|
-
...entry.adminKey !== void 0 ? { adminKey: entry.adminKey } : {},
|
|
287
|
-
...entry.seed !== void 0 ? { seed: entry.seed } : {},
|
|
288
|
-
log: config.log ?? log
|
|
289
|
-
}));
|
|
290
|
-
}
|
|
291
|
-
} catch (error) {
|
|
292
|
-
await Promise.allSettled(servers.map((s) => s.close()));
|
|
293
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
294
|
-
return 1;
|
|
295
|
-
}
|
|
296
|
-
return untilSignal(servers);
|
|
297
|
-
}
|
|
298
|
-
const port = asString(values.port);
|
|
299
|
-
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
300
|
-
const seed = asString(values.seed);
|
|
301
|
-
let listening;
|
|
302
|
-
try {
|
|
303
|
-
listening = await start(target, values, {
|
|
304
|
-
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
305
|
-
host: asString(values.host) ?? "127.0.0.1",
|
|
306
|
-
...adminKey !== void 0 ? { adminKey } : {},
|
|
307
|
-
...seed !== void 0 ? { seed } : {},
|
|
308
|
-
log
|
|
309
|
-
});
|
|
310
|
-
} catch (error) {
|
|
311
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
312
|
-
return 1;
|
|
313
|
-
}
|
|
314
|
-
return untilSignal([listening]);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// src/server.ts
|
|
319
|
-
var DEFAULT_PORT = 8787;
|
|
320
|
-
var loadCorpus = async (source) => {
|
|
321
|
-
if (typeof source !== "string") return source;
|
|
322
|
-
if (source === "none") return void 0;
|
|
323
|
-
if (source === "default") return defaultCorpus;
|
|
324
|
-
const raw = await readFile2(source, "utf8").catch(() => void 0);
|
|
325
|
-
if (raw === void 0) throw new Error(`junction mock corpus not found: ${source}`);
|
|
326
|
-
return parseSealedCorpus(JSON.parse(raw));
|
|
327
|
-
};
|
|
328
|
-
var createServer2 = async (options = {}) => {
|
|
329
|
-
const { port, host, corpus: source, ...rest } = options;
|
|
330
|
-
const corpus = await loadCorpus(source ?? "default");
|
|
331
|
-
const runtime = createRuntime({ ...rest, ...corpus ? { corpus } : {} });
|
|
332
|
-
const listening = await listen(runtime, {
|
|
333
|
-
port: port ?? 0,
|
|
334
|
-
...host !== void 0 ? { host } : {}
|
|
335
|
-
});
|
|
336
|
-
return { ...listening, runtime };
|
|
337
|
-
};
|
|
338
|
-
var asString2 = (value) => typeof value === "string" && value !== "" ? value : void 0;
|
|
339
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
340
|
-
var count = (value, flag) => {
|
|
341
|
-
if (value === void 0) return void 0;
|
|
342
|
-
if (!/^\d+$/.test(value)) throw new Error(`${flag} must be a non-negative integer (got ${value})`);
|
|
343
|
-
return Number.parseInt(value, 10);
|
|
344
|
-
};
|
|
345
|
-
var readJson = async (path) => {
|
|
346
|
-
const raw = await readFile2(path, "utf8").catch(() => void 0);
|
|
347
|
-
if (raw === void 0) throw new Error(`junction mock: file not found: ${path}`);
|
|
348
|
-
try {
|
|
349
|
-
return JSON.parse(raw);
|
|
350
|
-
} catch (error) {
|
|
351
|
-
throw new Error(`junction mock: ${path} is not JSON (${error.message})`);
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
var retryDelays = (value) => {
|
|
355
|
-
if (value === void 0) return void 0;
|
|
356
|
-
const delays = value.split(",").map((part) => Number.parseInt(part.trim(), 10));
|
|
357
|
-
if (delays.some((delay) => !Number.isFinite(delay) || delay < 0)) {
|
|
358
|
-
throw new Error(`--webhook-retry-delays must be comma-separated milliseconds (got ${value})`);
|
|
359
|
-
}
|
|
360
|
-
return delays;
|
|
361
|
-
};
|
|
362
|
-
var billingDefaults = (value) => {
|
|
363
|
-
if (value === void 0) return void 0;
|
|
364
|
-
const entries = value.split(",").map((part) => part.split("=").map((side) => side.trim()));
|
|
365
|
-
if (entries.some((entry) => entry.length !== 2 || !entry[0] || !entry[1])) {
|
|
366
|
-
throw new Error(`--default-billing-type must be lab=billing_type pairs (got ${value})`);
|
|
367
|
-
}
|
|
368
|
-
try {
|
|
369
|
-
return defaultBillingTypesFrom(Object.fromEntries(entries));
|
|
370
|
-
} catch (error) {
|
|
371
|
-
throw new Error(`--default-billing-type: ${error.message}`);
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
var serveTarget = {
|
|
375
|
-
name: "junction",
|
|
376
|
-
defaultPort: DEFAULT_PORT,
|
|
377
|
-
options: {
|
|
378
|
-
corpus: {
|
|
379
|
-
type: "string",
|
|
380
|
-
value: "<default|none|file>",
|
|
381
|
-
description: "Corpus to serve: the shipped one, none (synthetic), or a pulled file",
|
|
382
|
-
default: "default"
|
|
383
|
-
},
|
|
384
|
-
geo: {
|
|
385
|
-
type: "string",
|
|
386
|
-
value: "<corpus|synthetic>",
|
|
387
|
-
description: "Unknown ZIPs: refuse (corpus) or invent coverage (synthetic)"
|
|
388
|
-
},
|
|
389
|
-
"lab-accounts": {
|
|
390
|
-
type: "string",
|
|
391
|
-
value: "<file>",
|
|
392
|
-
description: 'Lab accounts to route orders through: a JSON array, or { "presets": [...], "accounts": [...] }'
|
|
393
|
-
},
|
|
394
|
-
"team-id": {
|
|
395
|
-
type: "string",
|
|
396
|
-
value: "<uuid>",
|
|
397
|
-
description: "Team the mock answers as (default: the corpus's recorded team)"
|
|
398
|
-
},
|
|
399
|
-
"max-users": {
|
|
400
|
-
type: "string",
|
|
401
|
-
value: "<n>",
|
|
402
|
-
description: "Enforce the sandbox's live-user cap (default: unlimited)"
|
|
403
|
-
},
|
|
404
|
-
identity: {
|
|
405
|
-
type: "string",
|
|
406
|
-
value: "<strict|adopt-users>",
|
|
407
|
-
description: "Unknown user ids: 404 (strict) or create on first use",
|
|
408
|
-
default: "strict"
|
|
409
|
-
},
|
|
410
|
-
"default-billing-type": {
|
|
411
|
-
type: "string",
|
|
412
|
-
value: "<lab=type,\u2026>",
|
|
413
|
-
description: "billing_type an order for that lab gets when it omits one (default: client_bill), e.g. bioreference=patient_bill_passthrough"
|
|
414
|
-
},
|
|
415
|
-
fixtures: {
|
|
416
|
-
type: "string",
|
|
417
|
-
value: "<file>",
|
|
418
|
-
description: 'Users and orders every namespace starts with: { "users": [...], "orders": [...] }'
|
|
419
|
-
},
|
|
420
|
-
"journal-size": {
|
|
421
|
-
type: "string",
|
|
422
|
-
value: "<n>",
|
|
423
|
-
description: "Requests each namespace's journal keeps (GET /__admin/requests)",
|
|
424
|
-
default: "1000"
|
|
425
|
-
},
|
|
426
|
-
"seed-url": {
|
|
427
|
-
type: "string",
|
|
428
|
-
value: "<url>",
|
|
429
|
-
description: "Pull a corpus from this Junction team at boot (prefer a committed corpus)"
|
|
430
|
-
},
|
|
431
|
-
"seed-key": {
|
|
432
|
-
type: "string",
|
|
433
|
-
value: "<key>",
|
|
434
|
-
description: "API key for --seed-url (env MOCKINGBIRD_JUNCTION_SEED_KEY)"
|
|
435
|
-
},
|
|
436
|
-
"webhook-url": { type: "string", value: "<url>", description: "Deliver signed webhooks here" },
|
|
437
|
-
"webhook-secret": {
|
|
438
|
-
type: "string",
|
|
439
|
-
value: "<whsec_\u2026>",
|
|
440
|
-
description: "Svix signing secret (env MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET)"
|
|
441
|
-
},
|
|
442
|
-
"webhook-retry-delays": {
|
|
443
|
-
type: "string",
|
|
444
|
-
value: "<ms,ms,\u2026>",
|
|
445
|
-
description: "Delay before each delivery attempt (default: Svix's schedule)"
|
|
446
|
-
},
|
|
447
|
-
"webhook-scope": {
|
|
448
|
-
type: "string",
|
|
449
|
-
value: "<scope>",
|
|
450
|
-
description: "Sent as x-mockingbird-scope on every delivery"
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
async create(values, common) {
|
|
454
|
-
const seedUrl = asString2(values["seed-url"]);
|
|
455
|
-
const seedKey = asString2(values["seed-key"]) ?? process.env.MOCKINGBIRD_JUNCTION_SEED_KEY;
|
|
456
|
-
let corpus;
|
|
457
|
-
if (seedUrl !== void 0) {
|
|
458
|
-
if (seedKey === void 0) throw new Error("--seed-url needs --seed-key");
|
|
459
|
-
console.log(
|
|
460
|
-
`junction pulling a corpus from ${seedUrl} (commit one with \`corpus pull\` to skip this)`
|
|
461
|
-
);
|
|
462
|
-
corpus = await pullCorpus({ baseUrl: seedUrl, apiKey: seedKey });
|
|
463
|
-
} else {
|
|
464
|
-
corpus = await loadCorpus(asString2(values.corpus) ?? "default");
|
|
465
|
-
}
|
|
466
|
-
const geo = asString2(values.geo);
|
|
467
|
-
if (geo !== void 0 && geo !== "corpus" && geo !== "synthetic") {
|
|
468
|
-
throw new Error(`--geo must be corpus or synthetic (got ${geo})`);
|
|
469
|
-
}
|
|
470
|
-
const accountsFile = asString2(values["lab-accounts"]);
|
|
471
|
-
const labAccounts = accountsFile === void 0 ? void 0 : await readJson(accountsFile);
|
|
472
|
-
const fixturesFile = asString2(values.fixtures);
|
|
473
|
-
const fixtures = fixturesFile === void 0 ? void 0 : await readJson(fixturesFile);
|
|
474
|
-
const teamId = asString2(values["team-id"]);
|
|
475
|
-
if (teamId !== void 0 && !UUID.test(teamId)) {
|
|
476
|
-
throw new Error(`--team-id must be a UUID (got ${teamId})`);
|
|
477
|
-
}
|
|
478
|
-
const maxUsers = count(asString2(values["max-users"]), "--max-users");
|
|
479
|
-
const journalSize = count(asString2(values["journal-size"]), "--journal-size");
|
|
480
|
-
const identity = asString2(values.identity);
|
|
481
|
-
if (identity !== void 0 && !IDENTITY_MODES.includes(identity)) {
|
|
482
|
-
throw new Error(`--identity must be one of ${IDENTITY_MODES.join(", ")} (got ${identity})`);
|
|
483
|
-
}
|
|
484
|
-
const defaultBillingTypes = billingDefaults(asString2(values["default-billing-type"]));
|
|
485
|
-
const webhookUrl = asString2(values["webhook-url"]);
|
|
486
|
-
const webhookSecret = asString2(values["webhook-secret"]) ?? process.env.MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET;
|
|
487
|
-
if (webhookUrl !== void 0 && webhookSecret === void 0) {
|
|
488
|
-
throw new Error("--webhook-url needs --webhook-secret (the whsec_\u2026 your receiver verifies)");
|
|
489
|
-
}
|
|
490
|
-
const delays = retryDelays(asString2(values["webhook-retry-delays"]));
|
|
491
|
-
const scope = asString2(values["webhook-scope"]);
|
|
492
|
-
return createRuntime({
|
|
493
|
-
...corpus ? { corpus } : {},
|
|
494
|
-
...geo ? { geo } : {},
|
|
495
|
-
...labAccounts ? { labAccounts } : {},
|
|
496
|
-
...fixtures ? { fixtures } : {},
|
|
497
|
-
...teamId !== void 0 ? { teamId } : {},
|
|
498
|
-
...maxUsers !== void 0 ? { limits: { maxUsers } } : {},
|
|
499
|
-
...identity !== void 0 ? { identity } : {},
|
|
500
|
-
...defaultBillingTypes ? { defaultBillingTypes } : {},
|
|
501
|
-
...journalSize !== void 0 ? { journalSize } : {},
|
|
502
|
-
...webhookUrl !== void 0 && webhookSecret !== void 0 ? {
|
|
503
|
-
webhooks: {
|
|
504
|
-
url: webhookUrl,
|
|
505
|
-
secret: webhookSecret,
|
|
506
|
-
...delays ? { retryDelaysMs: delays } : {},
|
|
507
|
-
...scope ? { headers: { "x-mockingbird-scope": scope } } : {}
|
|
508
|
-
}
|
|
509
|
-
} : {},
|
|
510
|
-
...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
|
|
511
|
-
...common.seed !== void 0 ? { seed: common.seed } : {},
|
|
512
|
-
...common.onLog ? { onLog: common.onLog } : {}
|
|
513
|
-
});
|
|
514
|
-
},
|
|
515
|
-
banner(runtime) {
|
|
516
|
-
const junction = runtime;
|
|
517
|
-
const api = junction.instance();
|
|
518
|
-
const info = api.corpusInfo();
|
|
519
|
-
return [
|
|
520
|
-
info ? `corpus ${info.label}: ${info.observations} observations, ${info.zips} ZIPs, ${info.labTests} lab tests, ${info.labAccounts} lab accounts (recorded ${info.recordedAt} from ${info.source})` : "corpus none: synthetic catalog and coverage",
|
|
521
|
-
`geo ${api.geoMode}${api.geoMode === "corpus" ? " (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)" : ""}`,
|
|
522
|
-
`team ${api.teamId}`,
|
|
523
|
-
`lab accounts: ${api.labAccounts().length}`,
|
|
524
|
-
`limits: ${Object.entries(api.limits).filter(([, value]) => value !== null && value !== false).map(([key, value]) => `${key}=${String(value)}`).join(", ") || "none (sandbox caps off)"}`,
|
|
525
|
-
`identity ${api.identity}`,
|
|
526
|
-
`webhooks: ${junction.webhooks ? "signed delivery on" : "off"}`,
|
|
527
|
-
"auth: any x-vital-api-key value (e.g. sk_us_mockingbird)"
|
|
528
|
-
];
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
export {
|
|
533
|
-
runCli,
|
|
534
|
-
serveCommand,
|
|
535
|
-
DEFAULT_PORT,
|
|
536
|
-
loadCorpus,
|
|
537
|
-
createServer2 as createServer,
|
|
538
|
-
serveTarget
|
|
539
|
-
};
|
|
540
|
-
//# sourceMappingURL=chunk-4QHCRJO4.js.map
|