@crvouga/mockingbird-service-fullscript 0.1.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 ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog — @crvouga/mockingbird-service-fullscript
2
+
3
+ ## 0.1.0 (2026-09-22)
4
+
5
+ Initial release.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @crvouga/mockingbird-service-fullscript
2
+
3
+ Stateful mock of the **Fullscript** lab-ordering API for test suites: per-practitioner OAuth
4
+ (consent redirect, `authorization_code` and rotating `refresh_token` grants, revoke), the
5
+ clinic, embeddable session grants, lab orders with tests and results, lab-order events, expiring
6
+ result PDFs, and `Fullscript-Signature` webhooks with the challenge acknowledgement our receiver
7
+ implements. Lab orders move forward only, through exactly Fullscript's states, when a test says
8
+ so.
9
+
10
+ - Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/fullscript/SUPPORT.md)
11
+ - The contract (`openapi.yaml`) is hand-authored from the EMR consumer
12
+ (`fullscript-api-client.ts`, `fullscript-event-model.ts`, `fullscript-webhook-signature.ts`,
13
+ `routers/v1/fullscript/webhook-controller.ts`, `fullscript-result-storage.ts`).
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install -D @crvouga/mockingbird-service-fullscript
19
+ ```
20
+
21
+ ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
22
+ `npx mockingbird-fullscript serve`, `createServer` from `./server` (Node), or `createRuntime`
23
+ with any Fetch server.
24
+
25
+ ## Usage
26
+
27
+ With `FEATURE_FULLSCRIPT_LABS_ENABLED=true`, point `FULLSCRIPT_API_URL` at the mock and
28
+ `FULLSCRIPT_OAUTH_AUTHORIZE_URL` at its `/oauth/authorize`; set `FULLSCRIPT_WEBHOOK_SECRET` and
29
+ `FULLSCRIPT_WEBHOOK_CHALLENGE_KEY` and pass the same values as `--webhook-secret` /
30
+ `--webhook-challenge`. The EMR validates these URLs as **https** and downloads result PDFs only
31
+ over https from allowlisted hosts (by default `fullscript.com`, `fullscript.io` and
32
+ `FULLSCRIPT_API_URL`'s host, which is where the mock serves them), so front the mock with TLS or
33
+ set `--results-base-url`.
34
+
35
+ ```bash
36
+ npx mockingbird-fullscript serve --port 8819 \
37
+ --webhook-url http://127.0.0.1:4000/v1/fullscript/webhooks \
38
+ --webhook-secret "$FULLSCRIPT_WEBHOOK_SECRET" --webhook-challenge "$FULLSCRIPT_WEBHOOK_CHALLENGE_KEY"
39
+ ```
40
+
41
+ ```ts
42
+ import { createRuntime } from "@crvouga/mockingbird-service-fullscript"
43
+
44
+ const fullscript = createRuntime({
45
+ webhooks: { url: "http://127.0.0.1:4000/v1/fullscript/webhooks", secret: "whsec", challenge: "chal" },
46
+ })
47
+ const admin = (path: string, body: unknown) =>
48
+ fullscript.fetch(
49
+ new Request(`https://fullscript.test/__admin${path}`, {
50
+ method: "POST",
51
+ headers: { "content-type": "application/json" },
52
+ body: JSON.stringify(body),
53
+ }),
54
+ )
55
+ const order = (await (await admin("/lab-orders", { patientId: "fs_pat_1", treatmentPlanId: "tp_1" })).json()) as { id: string }
56
+ await admin(`/lab-orders/${order.id}/transition`, { to: "purchased" }) // order.placed + lab_order.updated
57
+ await admin(`/lab-orders/${order.id}/transition`, { to: "results_ready" }) // results + PDF URLs
58
+ ```
59
+
60
+ ### Routes
61
+
62
+ | Route | Behaviour |
63
+ | --- | --- |
64
+ | `GET /oauth/authorize` | Auto-consents as `practitioner_id` (default `prac_mock_1`) and redirects to `redirect_uri?code=…&state=…`. |
65
+ | `POST /api/oauth/token` | JSON `{grant_type, client_id, client_secret, code \| refresh_token, redirect_uri}` → `{oauth: {access_token, token_type: "Bearer", expires_in: 7200, refresh_token, scope, created_at, resource_owner: {id, type, clinic_id}}}`. Codes are single-use, expire in 10 minutes and must match the redirect URI; refresh tokens rotate (a spent one is `invalid_grant`). Errors: 400 `{error: "invalid_grant" \| "invalid_request"}`, 401 `invalid_client`. |
66
+ | `POST /api/oauth/revoke` | `{client_id, client_secret, token}` → 200 `{}`; the token stops working. |
67
+ | `GET /api/clinic` | `{clinic: {id, name}}` of the token's practitioner. |
68
+ | `POST /api/clinic/embeddable/session_grants` | 201 `{secret_token, expires_at}`. |
69
+ | `GET /api/clinic/labs/orders` | `?patient_id=&page[number]=&page[size]=` → `{orders: [{id, state, treatment_plan_id, …}], meta: {current_page, next_page, …}}`. |
70
+ | `GET /api/clinic/labs/orders/{id}` | `{order: {…, collection_method, tests, results: [{id, name, state, pdf_url}], latest_aggregated_result: {id, artifact_id, pdf_url, status}}}`. |
71
+ | `GET /api/events/lab_orders` | `?order_by=DESC\|ASC&page[number]=&page[size]=` → the clinic's `lab_order.updated` events. |
72
+ | `GET /api/events/{id}` | `{event: {id, type, clinic_id, created_at, data}}`. |
73
+ | `GET /results/{artifact}?expires&signature` | The result PDF (`application/pdf`, a real one-page PDF) until the URL expires (15 minutes), then 403. |
74
+
75
+ API errors are `{errors: [{code, message}]}` (401 `unauthorized` / `invalid_token` /
76
+ `token_expired`, 404 `not_found`); our client reads the code.
77
+
78
+ **States** (forward only, jumps allowed): `not_purchased` → `purchased` →
79
+ `schedule_appointment` → `upcoming_appointment` → `processing` → `partial_results` (first
80
+ test's result) → `results_ready` (every result + an aggregated result) → `interpretation_shared`
81
+ → `results_amended` (a new aggregated artifact).
82
+
83
+ ### Webhooks
84
+
85
+ Each move emits `lab_order.updated` (`data.lab_order: {id, state, treatment_plan_id,
86
+ patient_id}`), and the move out of `not_purchased` also emits `order.placed` (`data: {id,
87
+ treatment_plan_ids, patient_id, line_items: [{type: "labs"}]}`). Deliveries post
88
+ `{event_payload: {event}}` with `Fullscript-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret,
89
+ "<t>." + body)>` (wall-clock `t`). With a challenge key, a delivery counts only when the
90
+ receiver answers `{challenge: <key>}` (otherwise it is recorded as 502 and retried).
91
+ `POST /__admin/webhooks/verify` sends Fullscript's empty-body registration ping and reports
92
+ whether each endpoint echoed the challenge. `GET /__admin/webhooks`, `…/events`, `…/replay`,
93
+ `…/flush` as usual.
94
+
95
+ ### Admin (beyond the standard contract)
96
+
97
+ | Route | Effect |
98
+ | --- | --- |
99
+ | `POST /__admin/lab-orders` | `{patientId, id?, clinicId?, treatmentPlanId?, name?, collectionMethod?, tests?: [names]}` → a `not_purchased` order. |
100
+ | `POST /__admin/lab-orders/:id/transition` | `{to: <state>}`; backwards or same-state is 409. |
101
+ | `GET /__admin/lab-orders`, `GET /__admin/events` | The namespace's orders and events. |
102
+ | `POST /__admin/oauth/codes` | `{practitionerId?, clientId?, redirectUri?}` → `{code}` without the browser redirect. |
103
+ | `POST /__admin/practitioners` | `{id, clinicId, type?, clinicName?}`. |
104
+ | `POST /__admin/webhooks/verify` | The registration ping (see Webhooks). |
105
+ | `GET/PUT /__admin/settings` | `{clients?, accessTokenTtlSeconds?, codeTtlSeconds?, pdfUrlTtlSeconds?, resultsBaseUrl?}`. |
106
+
107
+ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`): `token_expired`,
108
+ `invalid_grant`, `rate_limited`, `server_error`, `events_schema_drift`, `pdf_not_pdf`,
109
+ `pdf_redirect`, `pdf_expired`, `connection_drop`, `slow`, `webhook_duplicate`,
110
+ `webhook_reorder`, `webhook_drop`.
111
+
112
+ ### Namespaces
113
+
114
+ A `/ns/<name>/` suffix on `FULLSCRIPT_API_URL` (our client resolves relative `api/…` paths, so
115
+ the prefix survives, and PDF URLs keep it), `x-mockingbird-namespace`, or by OAuth client for
116
+ API calls (`PUT /__admin/credentials {"credentials": {"<FULLSCRIPT_CLIENT_ID>": "<ns>"}}`;
117
+ tokens carry the client they were issued to). Token requests carry the client only in their
118
+ body, so they need the prefix or the header.
119
+
120
+ ### Deliberately not modelled
121
+
122
+ - Catalog, treatment plans, patients, dispensary orders and every non-lab endpoint; the
123
+ embedded widget itself (session grants are opaque tokens).
124
+ - Lab result values (only PDFs), requisitions and appointment booking.
125
+ - Real consent screens: `/oauth/authorize` approves immediately.
126
+
127
+ ## API
128
+
129
+ | Export | Kind | Description |
130
+ | --- | --- | --- |
131
+ | `FullscriptAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `issueCode(practitionerId, clientId, redirectUri)`, `createOrder(input)`, `transition(orderId, state)`, `seedOrder(seed)`, `labOrders()`, `eventsList()`. Options: `sqlite`, `now`, `namespace`, `publicNamespace`, `settings`, `orders`, `onEvent`. |
132
+ | `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: `webhooks: {url, secret, challenge?, retryDelaysMs?, fetch?}`, `settings`, `orders`, `clock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
133
+ | `FULLSCRIPT_PRESETS` | object | Every named fault preset. |
134
+ | `FULLSCRIPT_NAMESPACE` | string | The service name, `"fullscript"`. |
135
+ | `SIGNATURE_HEADER` | string | `Fullscript-Signature`. |
136
+ | `LAB_ORDER_STATES`, `isLabOrderState` | values | The forward-only state order. |
137
+ | `DEFAULT_CLINIC`, `DEFAULT_PRACTITIONER` | objects | The seeded clinic and practitioner. |
138
+ | `RESULT_PDF` | bytes | The PDF every result URL serves. |
139
+ | `issueAccessToken` | function | Mint a self-verifying access token (for tests and parity). |
140
+ | `tokenCredential` | function | The OAuth client a bearer token was issued to (how credentials map to namespaces). |
141
+ | `envelope`, `publicEvent` | functions | An event's webhook body and API shape. |
142
+ | `apiErrors`, `oauthError` | functions | Build Fullscript's API and OAuth error responses. |
143
+ | `document`, `operationIds`, `supportedOperationIds` | values | The OpenAPI contract and its operation ids. |
144
+ | `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--webhook-url`, `--webhook-secret`, `--webhook-challenge`, `--client`, `--results-base-url`); port 8819. |
145
+
146
+ Part of [mockingbird](https://github.com/crvouga/mockingbird).
@@ -0,0 +1,380 @@
1
+ import {
2
+ createRuntime
3
+ } from "./chunk-RJH5A4X5.js";
4
+
5
+ // ../../adapters/node/dist/cli.js
6
+ import { readFile } from "node:fs/promises";
7
+ import { parseArgs } from "node:util";
8
+
9
+ // ../../adapters/node/dist/serve.js
10
+ import { createServer } from "node:http";
11
+ var serve = async (api, options = {}) => {
12
+ const server = createServer(async (req, res) => {
13
+ const chunks = [];
14
+ for await (const chunk of req) {
15
+ chunks.push(Buffer.from(chunk));
16
+ }
17
+ const body = Buffer.concat(chunks);
18
+ const address = server.address();
19
+ const port = typeof address === "object" && address !== null ? address.port : void 0;
20
+ const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
21
+ const raw = req.url ?? "/";
22
+ const url = new URL(raw.replace(/^\/+/, "/"), base);
23
+ const method = req.method ?? "GET";
24
+ const init = { method, headers: req.headers };
25
+ if (method !== "GET" && method !== "HEAD" && body.length > 0) {
26
+ init.body = body;
27
+ }
28
+ const aborted = new AbortController();
29
+ res.once("close", () => {
30
+ if (!res.writableFinished)
31
+ aborted.abort();
32
+ });
33
+ init.signal = aborted.signal;
34
+ const request = new Request(url, init);
35
+ let response;
36
+ try {
37
+ response = await api.fetch(request);
38
+ } catch (error) {
39
+ if (error.code === "MOCKINGBIRD_DROP") {
40
+ req.socket.destroy();
41
+ return;
42
+ }
43
+ res.writeHead(500, { "content-type": "application/json" });
44
+ res.end(JSON.stringify({
45
+ error: {
46
+ type: "mockingbird_internal",
47
+ message: error instanceof Error ? error.message : String(error)
48
+ }
49
+ }));
50
+ return;
51
+ }
52
+ const headers = Object.fromEntries(response.headers);
53
+ const cookies = response.headers.getSetCookie();
54
+ if (cookies.length > 0)
55
+ headers["set-cookie"] = cookies;
56
+ if (!response.body) {
57
+ res.writeHead(response.status, headers);
58
+ res.end();
59
+ return;
60
+ }
61
+ res.writeHead(response.status, headers);
62
+ res.flushHeaders();
63
+ const reader = response.body.getReader();
64
+ try {
65
+ for (; ; ) {
66
+ const { done, value } = await reader.read();
67
+ if (done)
68
+ break;
69
+ if (!res.write(value))
70
+ await new Promise((resolve) => res.once("drain", resolve));
71
+ }
72
+ res.end();
73
+ } catch {
74
+ res.destroy();
75
+ } finally {
76
+ reader.releaseLock();
77
+ }
78
+ });
79
+ await new Promise((resolve, reject) => {
80
+ server.once("error", reject);
81
+ server.listen(options.port ?? 0, options.host, resolve);
82
+ });
83
+ return server;
84
+ };
85
+
86
+ // ../../adapters/node/dist/listen.js
87
+ var listen = async (api, options = {}) => {
88
+ const host = options.host ?? "127.0.0.1";
89
+ const server = await serve(api, { port: options.port ?? 0, host });
90
+ const address = server.address();
91
+ const port = typeof address === "object" && address !== null ? address.port : options.port ?? 0;
92
+ const shown = host.includes(":") ? `[${host}]` : host;
93
+ return {
94
+ url: `http://${shown}:${port}`,
95
+ port,
96
+ host,
97
+ server,
98
+ close: () => new Promise((resolve, reject) => {
99
+ server.close((error) => {
100
+ if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
101
+ reject(error);
102
+ else
103
+ resolve();
104
+ });
105
+ server.closeAllConnections?.();
106
+ })
107
+ };
108
+ };
109
+
110
+ // ../../adapters/node/dist/cli.js
111
+ var optionHelp = (options) => Object.entries(options).map(([name, option]) => {
112
+ const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
113
+ const fallback = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
114
+ return ` ${flag.padEnd(30)} ${option.description}${fallback}`;
115
+ });
116
+ var help = (spec) => [
117
+ `${spec.bin} \u2014 ${spec.description}`,
118
+ "",
119
+ "Usage:",
120
+ ` ${spec.bin} <command> [options]`,
121
+ "",
122
+ "Commands:",
123
+ ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
124
+ "",
125
+ `Run \`${spec.bin} <command> --help\` for a command's options.`
126
+ ].join("\n");
127
+ var commandHelp = (spec, name, command) => [
128
+ `${spec.bin} ${name} \u2014 ${command.summary}`,
129
+ "",
130
+ "Usage:",
131
+ ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,
132
+ ...command.options ? ["", "Options:", ...optionHelp(command.options)] : []
133
+ ].join("\n");
134
+ var runCli = async (spec, argv) => {
135
+ const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
136
+ const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
137
+ const name = words === 2 ? pair : argv[0];
138
+ const rest = argv.slice(words);
139
+ if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
140
+ console.log(help(spec));
141
+ return 0;
142
+ }
143
+ const command = spec.commands[name];
144
+ if (!command) {
145
+ console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
146
+
147
+ ${help(spec)}`);
148
+ return 2;
149
+ }
150
+ if (rest.includes("--help") || rest.includes("-h")) {
151
+ console.log(commandHelp(spec, name, command));
152
+ return 0;
153
+ }
154
+ let parsed;
155
+ try {
156
+ parsed = parseArgs({
157
+ args: rest,
158
+ allowPositionals: true,
159
+ strict: true,
160
+ options: Object.fromEntries(Object.entries(command.options ?? {}).map(([key, option]) => [
161
+ key,
162
+ {
163
+ type: option.type,
164
+ ...option.default !== void 0 ? { default: option.default } : {}
165
+ }
166
+ ]))
167
+ });
168
+ } catch (error) {
169
+ console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
170
+
171
+ ${commandHelp(spec, name, command)}`);
172
+ return 2;
173
+ }
174
+ return command.run(parsed.values, parsed.positionals);
175
+ };
176
+ var COMMON_SERVE_OPTIONS = {
177
+ port: { type: "string", value: "<port>", description: "Port to listen on" },
178
+ host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
179
+ "admin-key": {
180
+ type: "string",
181
+ value: "<key>",
182
+ description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
183
+ },
184
+ seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
185
+ log: {
186
+ type: "string",
187
+ value: "<pretty|json|off>",
188
+ description: "Request log format",
189
+ default: "pretty"
190
+ },
191
+ "log-requests": {
192
+ type: "boolean",
193
+ description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
194
+ },
195
+ config: {
196
+ type: "string",
197
+ value: "<file>",
198
+ description: "Serve every service in a mockingbird.json config instead"
199
+ }
200
+ };
201
+ var formatLog = (format) => {
202
+ if (format === "off")
203
+ return void 0;
204
+ if (format === "json")
205
+ return (entry) => console.log(JSON.stringify(entry));
206
+ return (entry) => {
207
+ const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
208
+ const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
209
+ const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
210
+ const adopted = entry.adopted ? " adopted" : "";
211
+ console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`);
212
+ };
213
+ };
214
+ var asString = (value) => typeof value === "string" ? value : void 0;
215
+ var loadTarget = async (name, own) => {
216
+ if (name === own.name)
217
+ return own;
218
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
219
+ try {
220
+ const mod = await import(specifier);
221
+ if (!mod.serveTarget)
222
+ throw new Error(`${specifier} exports no serveTarget`);
223
+ return mod.serveTarget;
224
+ } catch (error) {
225
+ const reason = error instanceof Error ? error.message : String(error);
226
+ throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
227
+ }
228
+ };
229
+ var start = async (target, values, config) => {
230
+ const runtime = await target.create(values, {
231
+ adminKey: config.adminKey,
232
+ seed: config.seed,
233
+ onLog: formatLog(config.log)
234
+ });
235
+ const listening = await listen(runtime, { port: config.port, host: config.host });
236
+ console.log(`${target.name} mock listening on ${listening.url}`);
237
+ console.log(`${target.name} health: GET ${listening.url}/health`);
238
+ console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
239
+ for (const line of target.banner?.(runtime) ?? [])
240
+ console.log(`${target.name} ${line}`);
241
+ return listening;
242
+ };
243
+ var untilSignal = async (servers) => new Promise((resolve) => {
244
+ const stop = async () => {
245
+ await Promise.allSettled(servers.map((s) => s.close()));
246
+ resolve(0);
247
+ };
248
+ process.once("SIGINT", stop);
249
+ process.once("SIGTERM", stop);
250
+ });
251
+ var serveCommand = (target) => ({
252
+ summary: `Serve the ${target.name} mock over HTTP`,
253
+ options: { ...COMMON_SERVE_OPTIONS, ...target.options },
254
+ async run(values) {
255
+ const log = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
256
+ if (!["pretty", "json", "off"].includes(log)) {
257
+ console.error(`--log must be pretty, json or off (got ${log})`);
258
+ return 2;
259
+ }
260
+ const configPath = asString(values.config);
261
+ if (configPath !== void 0) {
262
+ const config = JSON.parse(await readFile(configPath, "utf8"));
263
+ const servers = [];
264
+ try {
265
+ for (const [name, entry] of Object.entries(config.services ?? {})) {
266
+ const each = await loadTarget(name, target);
267
+ servers.push(await start(each, entry.options ?? {}, {
268
+ port: entry.port ?? each.defaultPort,
269
+ host: entry.host ?? "127.0.0.1",
270
+ ...entry.adminKey !== void 0 ? { adminKey: entry.adminKey } : {},
271
+ ...entry.seed !== void 0 ? { seed: entry.seed } : {},
272
+ log: config.log ?? log
273
+ }));
274
+ }
275
+ } catch (error) {
276
+ await Promise.allSettled(servers.map((s) => s.close()));
277
+ console.error(error instanceof Error ? error.message : String(error));
278
+ return 1;
279
+ }
280
+ return untilSignal(servers);
281
+ }
282
+ const port = asString(values.port);
283
+ const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
284
+ const seed = asString(values.seed);
285
+ let listening;
286
+ try {
287
+ listening = await start(target, values, {
288
+ port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
289
+ host: asString(values.host) ?? "127.0.0.1",
290
+ ...adminKey !== void 0 ? { adminKey } : {},
291
+ ...seed !== void 0 ? { seed } : {},
292
+ log
293
+ });
294
+ } catch (error) {
295
+ console.error(error instanceof Error ? error.message : String(error));
296
+ return 1;
297
+ }
298
+ return untilSignal([listening]);
299
+ }
300
+ });
301
+
302
+ // src/server.ts
303
+ var DEFAULT_PORT = 8819;
304
+ var createServer2 = async (options = {}) => {
305
+ const { port, host, ...rest } = options;
306
+ const runtime = createRuntime(rest);
307
+ const listening = await listen(runtime, {
308
+ port: port ?? 0,
309
+ ...host !== void 0 ? { host } : {}
310
+ });
311
+ return { ...listening, runtime };
312
+ };
313
+ var text = (value) => typeof value === "string" ? value : void 0;
314
+ var serveTarget = {
315
+ name: "fullscript",
316
+ defaultPort: DEFAULT_PORT,
317
+ options: {
318
+ "webhook-url": {
319
+ type: "string",
320
+ value: "<url>",
321
+ description: "Deliver events here (e.g. http://127.0.0.1:4000/v1/fullscript/webhooks)"
322
+ },
323
+ "webhook-secret": {
324
+ type: "string",
325
+ value: "<secret>",
326
+ description: "Signs Fullscript-Signature (the app's FULLSCRIPT_WEBHOOK_SECRET)"
327
+ },
328
+ "webhook-challenge": {
329
+ type: "string",
330
+ value: "<key>",
331
+ description: "Require the receiver to echo {challenge} (FULLSCRIPT_WEBHOOK_CHALLENGE_KEY)"
332
+ },
333
+ client: {
334
+ type: "string",
335
+ value: "<client_id:client_secret>",
336
+ description: "Accept only this OAuth client (FULLSCRIPT_CLIENT_ID:FULLSCRIPT_CLIENT_SECRET); default: any"
337
+ },
338
+ "results-base-url": {
339
+ type: "string",
340
+ value: "<https url>",
341
+ description: "Origin for result PDF URLs (the app downloads only https from allowlisted hosts)"
342
+ }
343
+ },
344
+ create: (values, common) => {
345
+ const url = text(values["webhook-url"]);
346
+ const secret = text(values["webhook-secret"]);
347
+ const challenge = text(values["webhook-challenge"]);
348
+ const client = text(values.client);
349
+ const results = text(values["results-base-url"]);
350
+ const colon = client?.indexOf(":") ?? -1;
351
+ if (client && colon < 1) throw new Error('--client must look like "client_id:client_secret"');
352
+ return createRuntime({
353
+ ...url ? { webhooks: { url, ...secret ? { secret } : {}, ...challenge ? { challenge } : {} } } : {},
354
+ settings: {
355
+ ...client ? {
356
+ clients: [
357
+ { clientId: client.slice(0, colon), clientSecret: client.slice(colon + 1) }
358
+ ]
359
+ } : {},
360
+ ...results ? { resultsBaseUrl: results } : {}
361
+ },
362
+ ...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
363
+ ...common.seed !== void 0 ? { seed: common.seed } : {},
364
+ ...common.onLog ? { onLog: common.onLog } : {}
365
+ });
366
+ },
367
+ banner: () => [
368
+ "oauth: GET /oauth/authorize (auto-consents as prac_mock_1), POST /api/oauth/token, POST /api/oauth/revoke",
369
+ "namespaces: x-mockingbird-namespace, or a /ns/<name>/ suffix on FULLSCRIPT_API_URL"
370
+ ]
371
+ };
372
+
373
+ export {
374
+ runCli,
375
+ serveCommand,
376
+ DEFAULT_PORT,
377
+ createServer2 as createServer,
378
+ serveTarget
379
+ };
380
+ //# sourceMappingURL=chunk-346XA6DM.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts", "../src/server.ts"],
4
+ "sourcesContent": ["import { readFile } from \"node:fs/promises\"\nimport { type ParseArgsConfig, parseArgs } from \"node:util\"\nimport type { RequestLog, ServiceInstance, ServiceRuntime } from \"@crvouga/mockingbird-service\"\nimport { type Listening, listen } from \"./listen.js\"\n\nexport type CliOption = {\n type: \"string\" | \"boolean\"\n description: string\n /** Shown in help; the value placeholder, e.g. `<port>`. */\n value?: string\n default?: string | boolean\n}\n\nexport type CliValues = Record<string, string | boolean | undefined>\n\nexport type CliCommand = {\n summary: string\n usage?: string\n options?: Record<string, CliOption>\n /** Resolves to an exit code; a server command resolves only when it stops. */\n run(values: CliValues, positionals: string[]): Promise<number>\n}\n\nexport type CliSpec = {\n bin: string\n description: string\n commands: Record<string, CliCommand>\n}\n\nconst optionHelp = (options: Record<string, CliOption>): string[] =>\n Object.entries(options).map(([name, option]) => {\n const flag = `--${name}${option.type === \"string\" ? ` ${option.value ?? \"<value>\"}` : \"\"}`\n const fallback = option.default !== undefined ? ` (default: ${String(option.default)})` : \"\"\n return ` ${flag.padEnd(30)} ${option.description}${fallback}`\n })\n\nconst help = (spec: CliSpec): string =>\n [\n `${spec.bin} \u2014 ${spec.description}`,\n \"\",\n \"Usage:\",\n ` ${spec.bin} <command> [options]`,\n \"\",\n \"Commands:\",\n ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),\n \"\",\n `Run \\`${spec.bin} <command> --help\\` for a command's options.`,\n ].join(\"\\n\")\n\nconst commandHelp = (spec: CliSpec, name: string, command: CliCommand): string =>\n [\n `${spec.bin} ${name} \u2014 ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,\n ...(command.options ? [\"\", \"Options:\", ...optionHelp(command.options)] : []),\n ].join(\"\\n\")\n\n/** Parse `argv` against `spec` and run the chosen command. Resolves to an exit code. */\nexport const runCli = async (spec: CliSpec, argv: string[]): Promise<number> => {\n // Two-word commands (`corpus pull`) win over one-word ones.\n const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : undefined\n const words = pair !== undefined && spec.commands[pair] ? 2 : 1\n const name = words === 2 ? pair : argv[0]\n const rest = argv.slice(words)\n if (name === undefined || name === \"--help\" || name === \"-h\" || name === \"help\") {\n console.log(help(spec))\n return 0\n }\n const command = spec.commands[name]\n if (!command) {\n console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}\\n\\n${help(spec)}`)\n return 2\n }\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n console.log(commandHelp(spec, name, command))\n return 0\n }\n let parsed: { values: CliValues; positionals: string[] }\n try {\n parsed = parseArgs({\n args: rest,\n allowPositionals: true,\n strict: true,\n options: Object.fromEntries(\n Object.entries(command.options ?? {}).map(([key, option]) => [\n key,\n {\n type: option.type,\n ...(option.default !== undefined ? { default: option.default } : {}),\n },\n ]),\n ) as ParseArgsConfig[\"options\"],\n }) as { values: CliValues; positionals: string[] }\n } catch (error) {\n console.error(\n `${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}\\n\\n${commandHelp(spec, name, command)}`,\n )\n return 2\n }\n return command.run(parsed.values, parsed.positionals)\n}\n\n// \u2500\u2500 serve \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type LogFormat = \"pretty\" | \"json\" | \"off\"\n\nexport type CommonServeOptions = {\n adminKey: string | undefined\n seed: string | undefined\n onLog: ((entry: RequestLog) => void) | undefined\n}\n\n/**\n * What a service contributes to `serve`: how to build its runtime from CLI flags,\n * and what to say at startup. Every service's `./server` entry exports one as\n * `serveTarget`, which is also how `serve --config` finds services by name.\n */\nexport type ServeTarget = {\n name: string\n defaultPort: number\n /** Serve flags beyond the common ones. */\n options?: Record<string, CliOption>\n create(\n values: CliValues,\n common: CommonServeOptions,\n ): Promise<ServiceRuntime<ServiceInstance>> | ServiceRuntime<ServiceInstance>\n /** Startup lines after the listen address, e.g. the loaded corpus version. */\n banner?(runtime: ServiceRuntime<ServiceInstance>): string[]\n}\n\nconst COMMON_SERVE_OPTIONS: Record<string, CliOption> = {\n port: { type: \"string\", value: \"<port>\", description: \"Port to listen on\" },\n host: { type: \"string\", value: \"<host>\", description: \"Interface to bind\", default: \"127.0.0.1\" },\n \"admin-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)\",\n },\n seed: { type: \"string\", value: \"<seed>\", description: \"Seed for every random choice\" },\n log: {\n type: \"string\",\n value: \"<pretty|json|off>\",\n description: \"Request log format\",\n default: \"pretty\",\n },\n \"log-requests\": {\n type: \"boolean\",\n description:\n \"One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json\",\n },\n config: {\n type: \"string\",\n value: \"<file>\",\n description: \"Serve every service in a mockingbird.json config instead\",\n },\n}\n\nconst formatLog = (format: LogFormat) => {\n if (format === \"off\") return undefined\n if (format === \"json\") return (entry: RequestLog) => console.log(JSON.stringify(entry))\n return (entry: RequestLog) => {\n const op = entry.operationId ?? (entry.unmatched ? \"UNMATCHED\" : \"-\")\n const ns = entry.namespace === \"default\" ? \"\" : ` [${entry.namespace}]`\n const fault = entry.faultId ? ` fault=${entry.faultId}` : \"\"\n const adopted = entry.adopted ? \" adopted\" : \"\"\n console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`,\n )\n }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" ? value : undefined\n\n/** A service entry in `mockingbird.json`. */\nexport type ConfigService = {\n port?: number\n host?: string\n adminKey?: string\n seed?: string\n /** Service-specific serve flags, by long name: `{ \"webhook-url\": \"\u2026\" }`. */\n options?: Record<string, string | boolean>\n}\n\nexport type MockingbirdConfig = {\n /** Keyed by service name: `junction` loads `@crvouga/mockingbird-service-junction`. */\n services: Record<string, ConfigService>\n log?: LogFormat\n}\n\nconst loadTarget = async (name: string, own: ServeTarget): Promise<ServeTarget> => {\n if (name === own.name) return own\n const specifier = `@crvouga/mockingbird-service-${name}/server`\n try {\n const mod = (await import(specifier)) as { serveTarget?: ServeTarget }\n if (!mod.serveTarget) throw new Error(`${specifier} exports no serveTarget`)\n return mod.serveTarget\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `cannot load service \"${name}\": ${reason}. Install @crvouga/mockingbird-service-${name}.`,\n )\n }\n}\n\nconst start = async (\n target: ServeTarget,\n values: CliValues,\n config: { port: number; host: string; adminKey?: string; seed?: string; log: LogFormat },\n): Promise<Listening> => {\n const runtime = await target.create(values, {\n adminKey: config.adminKey,\n seed: config.seed,\n onLog: formatLog(config.log),\n })\n const listening = await listen(runtime, { port: config.port, host: config.host })\n console.log(`${target.name} mock listening on ${listening.url}`)\n console.log(`${target.name} health: GET ${listening.url}/health`)\n console.log(\n `${target.name} admin: ${listening.url}/__admin (${config.adminKey ? \"x-mockingbird-admin-key required\" : \"open \u2014 pass --admin-key to lock\"})`,\n )\n for (const line of target.banner?.(runtime) ?? []) console.log(`${target.name} ${line}`)\n return listening\n}\n\nconst untilSignal = async (servers: Listening[]): Promise<number> =>\n new Promise((resolve) => {\n const stop = async () => {\n await Promise.allSettled(servers.map((s) => s.close()))\n resolve(0)\n }\n process.once(\"SIGINT\", stop)\n process.once(\"SIGTERM\", stop)\n })\n\n/** The standard `serve` command for a service, including multi-service `--config`. */\nexport const serveCommand = (target: ServeTarget): CliCommand => ({\n summary: `Serve the ${target.name} mock over HTTP`,\n options: { ...COMMON_SERVE_OPTIONS, ...target.options },\n async run(values) {\n const log = (\n values[\"log-requests\"] === true ? \"json\" : (asString(values.log) ?? \"pretty\")\n ) as LogFormat\n if (![\"pretty\", \"json\", \"off\"].includes(log)) {\n console.error(`--log must be pretty, json or off (got ${log})`)\n return 2\n }\n const configPath = asString(values.config)\n if (configPath !== undefined) {\n const config = JSON.parse(await readFile(configPath, \"utf8\")) as MockingbirdConfig\n const servers: Listening[] = []\n try {\n for (const [name, entry] of Object.entries(config.services ?? {})) {\n const each = await loadTarget(name, target)\n servers.push(\n await start(each, entry.options ?? {}, {\n port: entry.port ?? each.defaultPort,\n host: entry.host ?? \"127.0.0.1\",\n ...(entry.adminKey !== undefined ? { adminKey: entry.adminKey } : {}),\n ...(entry.seed !== undefined ? { seed: entry.seed } : {}),\n log: config.log ?? log,\n }),\n )\n }\n } catch (error) {\n await Promise.allSettled(servers.map((s) => s.close()))\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal(servers)\n }\n const port = asString(values.port)\n const adminKey = asString(values[\"admin-key\"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY\n const seed = asString(values.seed)\n let listening: Listening\n try {\n listening = await start(target, values, {\n port: port === undefined ? target.defaultPort : Number.parseInt(port, 10),\n host: asString(values.host) ?? \"127.0.0.1\",\n ...(adminKey !== undefined ? { adminKey } : {}),\n ...(seed !== undefined ? { seed } : {}),\n log,\n })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal([listening])\n },\n})\n", "import { createServer } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\n/** Options for {@link serve}. */\nexport type NodeServeOptions = {\n port?: number\n host?: string\n}\n\n/**\n * Serve any Mockingbird {@link FetchAPI} over `node:http`.\n * Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).\n */\nexport const serve = async (api: FetchAPI, options: NodeServeOptions = {}) => {\n const server = createServer(async (req, res) => {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n const body = Buffer.concat(chunks)\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : undefined\n const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`\n const raw = req.url ?? \"/\"\n const url = new URL(raw.replace(/^\\/+/, \"/\"), base)\n const method = req.method ?? \"GET\"\n const init: RequestInit = { method, headers: req.headers as Record<string, string> }\n if (method !== \"GET\" && method !== \"HEAD\" && body.length > 0) {\n init.body = body\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n init.signal = aborted.signal\n const request = new Request(url, init)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n // A `drop` fault: destroy the socket so the client sees the connection die.\n if ((error as { code?: string }).code === \"MOCKINGBIRD_DROP\") {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n )\n return\n }\n // Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers[\"set-cookie\"] = cookies\n if (!response.body) {\n res.writeHead(response.status, headers)\n res.end()\n return\n }\n // Stream the body chunk by chunk: event streams and long-polls must not be buffered.\n res.writeHead(response.status, headers)\n res.flushHeaders()\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!res.write(value)) await new Promise<void>((resolve) => res.once(\"drain\", resolve))\n }\n res.end()\n } catch {\n res.destroy()\n } finally {\n reader.releaseLock()\n }\n })\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(options.port ?? 0, options.host, resolve)\n })\n return server\n}\n", "import type { Server } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\nimport { serve } from \"./serve.js\"\n\nexport type ListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`: a mock should not be reachable off the machine by accident. */\n host?: string\n}\n\n/** A running server, with the address it actually bound. */\nexport type Listening = {\n url: string\n port: number\n host: string\n server: Server\n close(): Promise<void>\n}\n\nexport const listen = async (api: FetchAPI, options: ListenOptions = {}): Promise<Listening> => {\n const host = options.host ?? \"127.0.0.1\"\n const server = await serve(api, { port: options.port ?? 0, host })\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : (options.port ?? 0)\n const shown = host.includes(\":\") ? `[${host}]` : host\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n server,\n close: () =>\n new Promise<void>((resolve, reject) => {\n // Stop accepting first, then drop keep-alive sockets so close() can finish.\n server.close((error) => {\n if (error && (error as { code?: string }).code !== \"ERR_SERVER_NOT_RUNNING\") reject(error)\n else resolve()\n })\n server.closeAllConnections?.()\n }),\n }\n}\n", "/// <reference types=\"node\" />\nimport { type Listening, listen, type ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { createRuntime, type FullscriptRuntime, type FullscriptRuntimeOptions } from \"./runtime.js\"\n\n/** Port `mockingbird-fullscript serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8819\n\nexport type FullscriptServerOptions = FullscriptRuntimeOptions & {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n}\n\nexport type FullscriptServer = Listening & { runtime: FullscriptRuntime }\n\n/** Serve the Fullscript mock over `node:http`. */\nexport const createServer = async (\n options: FullscriptServerOptions = {},\n): Promise<FullscriptServer> => {\n const { port, host, ...rest } = options\n const runtime = createRuntime(rest)\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/** How `serve` (and `serve --config`) builds the Fullscript mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"fullscript\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"webhook-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Deliver events here (e.g. http://127.0.0.1:4000/v1/fullscript/webhooks)\",\n },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<secret>\",\n description: \"Signs Fullscript-Signature (the app's FULLSCRIPT_WEBHOOK_SECRET)\",\n },\n \"webhook-challenge\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require the receiver to echo {challenge} (FULLSCRIPT_WEBHOOK_CHALLENGE_KEY)\",\n },\n client: {\n type: \"string\",\n value: \"<client_id:client_secret>\",\n description:\n \"Accept only this OAuth client (FULLSCRIPT_CLIENT_ID:FULLSCRIPT_CLIENT_SECRET); default: any\",\n },\n \"results-base-url\": {\n type: \"string\",\n value: \"<https url>\",\n description:\n \"Origin for result PDF URLs (the app downloads only https from allowlisted hosts)\",\n },\n },\n create: (values, common) => {\n const url = text(values[\"webhook-url\"])\n const secret = text(values[\"webhook-secret\"])\n const challenge = text(values[\"webhook-challenge\"])\n const client = text(values.client)\n const results = text(values[\"results-base-url\"])\n const colon = client?.indexOf(\":\") ?? -1\n if (client && colon < 1) throw new Error('--client must look like \"client_id:client_secret\"')\n return createRuntime({\n ...(url\n ? { webhooks: { url, ...(secret ? { secret } : {}), ...(challenge ? { challenge } : {}) } }\n : {}),\n settings: {\n ...(client\n ? {\n clients: [\n { clientId: client.slice(0, colon), clientSecret: client.slice(colon + 1) },\n ],\n }\n : {}),\n ...(results ? { resultsBaseUrl: results } : {}),\n },\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner: () => [\n \"oauth: GET /oauth/authorize (auto-consents as prac_mock_1), POST /api/oauth/token, POST /api/oauth/revoke\",\n \"namespaces: x-mockingbird-namespace, or a /ns/<name>/ suffix on FULLSCRIPT_API_URL\",\n ],\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["createServer"]
7
+ }