@crvouga/mockingbird-service-rxvortex 1.0.0 → 1.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 +6 -4
- package/README.md +15 -5
- package/dist/{chunk-ZAM35JQO.js → chunk-SNW7NYY6.js} +37 -3
- package/dist/chunk-SNW7NYY6.js.map +7 -0
- package/dist/{chunk-DNELVPK4.js → chunk-VQAJFAUO.js} +124 -18
- package/dist/{chunk-DNELVPK4.js.map → chunk-VQAJFAUO.js.map} +3 -3
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +5 -1
- package/dist/server.d.ts +39 -16
- package/dist/server.js +4 -2
- package/package.json +2 -2
- package/dist/chunk-ZAM35JQO.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-rxvortex
|
|
2
2
|
|
|
3
|
+
## 1.1.0 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- load a preset catalog into a served mock and promote to ready ([471288e](https://github.com/crvouga/mockingbird/commit/471288e46b1f34a62f0c1b7e7a76dc30920c39e7))
|
|
8
|
+
|
|
3
9
|
## 1.0.0 (2026-09-24)
|
|
4
10
|
|
|
5
11
|
### ⚠️ Breaking changes
|
|
6
12
|
|
|
7
13
|
- vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
|
|
8
14
|
|
|
9
|
-
### Dependencies
|
|
10
|
-
|
|
11
|
-
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
-
|
|
13
15
|
## 0.2.0 (2026-09-23)
|
|
14
16
|
|
|
15
17
|
### Features
|
package/README.md
CHANGED
|
@@ -29,9 +29,15 @@ value as `--webhook-secret`.
|
|
|
29
29
|
npx mockingbird-rxvortex serve --port 8791 \
|
|
30
30
|
--webhook-url http://127.0.0.1:3000/prescriptions/webhooks/rxvortex \
|
|
31
31
|
--webhook-secret "$RXVORTEX_WEBHOOK_SECRET" \
|
|
32
|
-
--auto-advance "2000:Fill,Shipping,Delivered"
|
|
32
|
+
--auto-advance "2000:Fill,Shipping,Delivered" \
|
|
33
|
+
--catalog ./preset-catalog.json
|
|
33
34
|
```
|
|
34
35
|
|
|
36
|
+
`--catalog` loads your own preset catalog into every namespace: a JSON array of rows, or the
|
|
37
|
+
`{data: [...]}` body `GET /api/v1/preset-catalog-items` answers with, so a recorded sandbox
|
|
38
|
+
response loads as-is. Only `catalog_id` and `medication_name` are required. `POST /__admin/reset`
|
|
39
|
+
returns to these rows. Pass `--unknown-presets accept` to take any well-formed preset id instead.
|
|
40
|
+
|
|
35
41
|
```ts
|
|
36
42
|
import { createRuntime } from "@crvouga/mockingbird-service-rxvortex"
|
|
37
43
|
|
|
@@ -62,7 +68,7 @@ await post("/__admin/orders/pay_123/transition", { to: "Delivered" })
|
|
|
62
68
|
| Route | Behaviour |
|
|
63
69
|
| --- | --- |
|
|
64
70
|
| `POST /api/v1/generate-access-token` | JSON `{client_id, client_secret}` → `{access_token, token_type: "Bearer", expires_in: 86400}`. Any pair works unless `clients` is set (`PUT /__admin/settings`). Tokens stay valid 24 h on the mock clock; our client caches for 24 h and never refreshes on 401. |
|
|
65
|
-
| `POST /api/v1/orders` | Validates the submit payload against the contract; a violation is 422 `{message, errors: {"patient.phone": ["…"]}}`. An inactive or unknown `preset_catalog_id` is 422. A repeated `order.sender_order_id` is 409. Success: `{success: true, order_tracking_id: "RXV-…", sender_order_id, status: "Created"}` (the tracking id is always a string). |
|
|
71
|
+
| `POST /api/v1/orders` | Validates the submit payload against the contract; a violation is 422 `{message, errors: {"patient.phone": ["…"]}}`. An inactive or unknown `preset_catalog_id` is 422 (under `unknownPresets: "accept"` an unknown UUID becomes an active catalog row named after the request's `medication_name`). A repeated `order.sender_order_id` is 409. Success: `{success: true, order_tracking_id: "RXV-…", sender_order_id, status: "Created"}` (the tracking id is always a string). |
|
|
66
72
|
| `GET /api/v1/orders/{id}` | `id` is the tracking id **or** the sender order id (our payment id, the recovery lookup). Returns `rxstatus`, `orderstatus`, `shipping_status`, `delivered_date`, `trackingnumber`, `shippingservice`, `shippingcarrier`, `shipmenttrackingurl`, `cancellable`, and the id under `order_tracking_id`, `tracking_id` and `orderReferenceID`. |
|
|
67
73
|
| `DELETE /api/v1/orders/{id}` | Cancels while `cancellable` (until shipped), emitting the webhook; otherwise 409. |
|
|
68
74
|
| `GET /api/v1/preset-catalog-items` | `{data: [...]}` rows with `catalog_id`, `medication_name`, `medication_strength`, `package_size`, `quantity`, `quantity_units`, `medication_form`, `route`, `states`, `status`. Includes the custom-cream anchor preset `e404ad76-0f82-4b04-8f25-841650e2e819` and one inactive row. |
|
|
@@ -82,7 +88,9 @@ now, and `PUT /__admin/webhook-endpoints` sets per-namespace receivers.
|
|
|
82
88
|
| Route | Effect |
|
|
83
89
|
| --- | --- |
|
|
84
90
|
| `POST /__admin/orders/:id/transition` | `{to, trackingnumber?, shippingcarrier?, shippingservice?, delivered_date?}`. `to` is a vendor status: `Fill`, `PV1 Complete`, `Compound`, `Out of Stock`, `On Hold`, `Shipping`, `Delivered`, `Cancelled`, `Rejected`, `Error`, or any string (used verbatim). The three status fields move together; shipping generates tracking when none is given. |
|
|
85
|
-
| `PUT /__admin/
|
|
91
|
+
| `PUT /__admin/catalog` | `{items: [{catalog_id, medication_name, medication_strength?, package_size?, quantity?, quantity_units?, medication_form?, route?, states?, status?}], mode?: "replace" \| "merge"}` for the calling namespace (`replace` is the default; `merge` upserts by `catalog_id`). Answers `{count}`, the namespace's row count. `items` may also be sent as `data`. |
|
|
92
|
+
| `GET /__admin/catalog` | The namespace's rows, as `{data: [...]}`. |
|
|
93
|
+
| `PUT /__admin/settings` | `{tokenTtlSeconds?, staticTokens?, clients?, autoAdvance?: {afterMs, path} \| null, unknownPresets?: "reject" \| "accept"}` for the calling namespace. `unknownPresets` defaults to `reject`, as the sandbox does. `staticTokens` admits `RXVORTEX_API_TOKEN` (the catalog client's static bearer). |
|
|
86
94
|
| `POST /__admin/tick` | Apply every auto-advance step that is due on the mock clock (the served mock also ticks every 100 ms). |
|
|
87
95
|
| `GET /__admin/orders` | The namespace's orders. |
|
|
88
96
|
|
|
@@ -104,7 +112,8 @@ the client id they were issued to).
|
|
|
104
112
|
- Real fulfilment timing: nothing moves on its own unless `autoAdvance` is set.
|
|
105
113
|
- Patient and prescriber details are validated, never stored or echoed back.
|
|
106
114
|
- The live catalog: the default rows are synthesised in the live client's field names (no
|
|
107
|
-
sandbox recording exists)
|
|
115
|
+
sandbox recording exists). Load recorded rows with `serve --catalog`, `PUT /__admin/catalog`,
|
|
116
|
+
or the `catalog` option.
|
|
108
117
|
- Refills and multi-order shipments.
|
|
109
118
|
|
|
110
119
|
## API
|
|
@@ -118,7 +127,8 @@ the client id they were issued to).
|
|
|
118
127
|
| `tokenCredential` | function | The client id a bearer token was issued to (how credentials map to namespaces). |
|
|
119
128
|
| `CUSTOM_CREAM_ANCHOR_PRESET_ID` | string | The sandbox custom-cream anchor preset id. |
|
|
120
129
|
| `DEFAULT_CATALOG` | array | The default preset catalog rows. |
|
|
130
|
+
| `parseCatalog`, `parseCatalogItem` | functions | Catalog rows from a JSON array or a recorded `{data: [...]}` body (missing optional fields default to `null`, `states` to `[]`, `status` to `"active"`); throw on a malformed row. |
|
|
121
131
|
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
122
|
-
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http` (auto-advance ticks every 100 ms); the `serve` CLI target; port 8791. |
|
|
132
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT`, `loadCatalogFile` (`./server`) | Node | Serve over `node:http` (auto-advance ticks every 100 ms); the `serve` CLI target; port 8791; read a `--catalog` file. |
|
|
123
133
|
|
|
124
134
|
Part of [mockingbird](https://github.com/crvouga/mockingbird).
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createRuntime
|
|
3
|
-
|
|
2
|
+
createRuntime,
|
|
3
|
+
parseCatalog
|
|
4
|
+
} from "./chunk-VQAJFAUO.js";
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
4
8
|
|
|
5
9
|
// ../../adapters/node/dist/cli.js
|
|
6
10
|
import { readFile } from "node:fs/promises";
|
|
@@ -318,6 +322,19 @@ var createServer2 = async (options = {}) => {
|
|
|
318
322
|
};
|
|
319
323
|
};
|
|
320
324
|
var text = (value) => typeof value === "string" ? value : void 0;
|
|
325
|
+
var loadCatalogFile = (path) => {
|
|
326
|
+
let raw;
|
|
327
|
+
try {
|
|
328
|
+
raw = readFileSync(path, "utf8");
|
|
329
|
+
} catch {
|
|
330
|
+
throw new Error(`rxvortex catalog not found: ${path}`);
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
return parseCatalog(JSON.parse(raw));
|
|
334
|
+
} catch (error) {
|
|
335
|
+
throw new Error(`rxvortex catalog ${path}: ${error.message}`);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
321
338
|
var serveTarget = {
|
|
322
339
|
name: "rxvortex",
|
|
323
340
|
defaultPort: DEFAULT_PORT,
|
|
@@ -337,6 +354,16 @@ var serveTarget = {
|
|
|
337
354
|
value: "<token>",
|
|
338
355
|
description: "A static bearer token to accept (the app's RXVORTEX_API_TOKEN)"
|
|
339
356
|
},
|
|
357
|
+
catalog: {
|
|
358
|
+
type: "string",
|
|
359
|
+
value: "<file.json>",
|
|
360
|
+
description: "Preset catalog every namespace starts with and resets to: a JSON array, or {data: [...]} as GET /api/v1/preset-catalog-items returns"
|
|
361
|
+
},
|
|
362
|
+
"unknown-presets": {
|
|
363
|
+
type: "string",
|
|
364
|
+
value: "<reject|accept>",
|
|
365
|
+
description: "An unknown preset_catalog_id: 422 like the sandbox (reject, default) or add it (accept)"
|
|
366
|
+
},
|
|
340
367
|
"auto-advance": {
|
|
341
368
|
type: "string",
|
|
342
369
|
value: "<ms:Status,Status,\u2026>",
|
|
@@ -348,13 +375,19 @@ var serveTarget = {
|
|
|
348
375
|
const secret = text(values["webhook-secret"]);
|
|
349
376
|
const token = text(values["api-token"]);
|
|
350
377
|
const auto = text(values["auto-advance"]);
|
|
378
|
+
const catalogPath = text(values.catalog);
|
|
379
|
+
const unknownPresets = text(values["unknown-presets"]);
|
|
380
|
+
if (unknownPresets !== void 0 && unknownPresets !== "reject" && unknownPresets !== "accept")
|
|
381
|
+
throw new Error('--unknown-presets must be "reject" or "accept"');
|
|
351
382
|
const plan = auto ? /^(\d+):(.+)$/.exec(auto) : null;
|
|
352
383
|
if (auto && !plan)
|
|
353
384
|
throw new Error('--auto-advance must look like "2000:Fill,Shipping,Delivered"');
|
|
354
385
|
return createRuntime({
|
|
355
386
|
tickMs: 100,
|
|
356
387
|
...url ? { webhooks: { url, ...secret ? { secret } : {} } } : {},
|
|
388
|
+
...catalogPath ? { catalog: loadCatalogFile(catalogPath) } : {},
|
|
357
389
|
settings: {
|
|
390
|
+
...unknownPresets ? { unknownPresets } : {},
|
|
358
391
|
...token ? { staticTokens: [token] } : {},
|
|
359
392
|
...plan ? {
|
|
360
393
|
autoAdvance: {
|
|
@@ -379,6 +412,7 @@ export {
|
|
|
379
412
|
serveCommand,
|
|
380
413
|
DEFAULT_PORT,
|
|
381
414
|
createServer2 as createServer,
|
|
415
|
+
loadCatalogFile,
|
|
382
416
|
serveTarget
|
|
383
417
|
};
|
|
384
|
-
//# sourceMappingURL=chunk-
|
|
418
|
+
//# sourceMappingURL=chunk-SNW7NYY6.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/server.ts", "../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts"],
|
|
4
|
+
"sourcesContent": ["/// <reference types=\"node\" />\nimport { readFileSync } from \"node:fs\"\nimport { type Listening, listen, type ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { type CatalogItem, parseCatalog } from \"./catalog.js\"\nimport { createRuntime, type RxVortexRuntime, type RxVortexRuntimeOptions } from \"./runtime.js\"\n\n/** Port `mockingbird-rxvortex serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8791\n\nexport type RxVortexServerOptions = RxVortexRuntimeOptions & {\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 RxVortexServer = Listening & { runtime: RxVortexRuntime }\n\n/** Serve the RxVortex mock over `node:http`, with auto-advance ticking every 100 ms. */\nexport const createServer = async (\n options: RxVortexServerOptions = {},\n): Promise<RxVortexServer> => {\n const { port, host, ...rest } = options\n const runtime = createRuntime({ tickMs: 100, ...rest })\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return {\n ...listening,\n runtime,\n close: async () => {\n runtime.stop()\n await listening.close()\n },\n }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/**\n * Read a catalog file: a JSON array of rows, or the `{data: [...]}` body\n * `GET /api/v1/preset-catalog-items` answers with, so a recorded response loads as-is.\n */\nexport const loadCatalogFile = (path: string): CatalogItem[] => {\n let raw: string\n try {\n raw = readFileSync(path, \"utf8\")\n } catch {\n throw new Error(`rxvortex catalog not found: ${path}`)\n }\n try {\n return parseCatalog(JSON.parse(raw))\n } catch (error) {\n throw new Error(`rxvortex catalog ${path}: ${(error as Error).message}`)\n }\n}\n\n/** How `serve` (and `serve --config`) builds the RxVortex mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"rxvortex\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"webhook-url\": {\n type: \"string\",\n value: \"<url>\",\n description:\n \"Deliver status webhooks here (e.g. http://127.0.0.1:3000/prescriptions/webhooks/rxvortex)\",\n },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<secret>\",\n description: \"Sent as x-rxvortex-webhook-secret (the app's RXVORTEX_WEBHOOK_SECRET)\",\n },\n \"api-token\": {\n type: \"string\",\n value: \"<token>\",\n description: \"A static bearer token to accept (the app's RXVORTEX_API_TOKEN)\",\n },\n catalog: {\n type: \"string\",\n value: \"<file.json>\",\n description:\n \"Preset catalog every namespace starts with and resets to: a JSON array, or {data: [...]} as GET /api/v1/preset-catalog-items returns\",\n },\n \"unknown-presets\": {\n type: \"string\",\n value: \"<reject|accept>\",\n description:\n \"An unknown preset_catalog_id: 422 like the sandbox (reject, default) or add it (accept)\",\n },\n \"auto-advance\": {\n type: \"string\",\n value: \"<ms:Status,Status,\u2026>\",\n description: 'Walk every new order along a path, e.g. \"2000:Fill,Shipping,Delivered\"',\n },\n },\n create: (values, common) => {\n const url = text(values[\"webhook-url\"])\n const secret = text(values[\"webhook-secret\"])\n const token = text(values[\"api-token\"])\n const auto = text(values[\"auto-advance\"])\n const catalogPath = text(values.catalog)\n const unknownPresets = text(values[\"unknown-presets\"])\n if (unknownPresets !== undefined && unknownPresets !== \"reject\" && unknownPresets !== \"accept\")\n throw new Error('--unknown-presets must be \"reject\" or \"accept\"')\n const plan = auto ? /^(\\d+):(.+)$/.exec(auto) : null\n if (auto && !plan)\n throw new Error('--auto-advance must look like \"2000:Fill,Shipping,Delivered\"')\n return createRuntime({\n tickMs: 100,\n ...(url ? { webhooks: { url, ...(secret ? { secret } : {}) } } : {}),\n ...(catalogPath ? { catalog: loadCatalogFile(catalogPath) } : {}),\n settings: {\n ...(unknownPresets ? { unknownPresets } : {}),\n ...(token ? { staticTokens: [token] } : {}),\n ...(plan\n ? {\n autoAdvance: {\n afterMs: Number(plan[1]),\n path: (plan[2] as string).split(\",\").map((s) => s.trim()),\n },\n }\n : {}),\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 \"auth: POST /api/v1/generate-access-token {client_id, client_secret}, then Authorization: Bearer <token>\",\n \"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<client_id>: <ns>}\",\n ],\n}\n", "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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["createServer"]
|
|
7
|
+
}
|
|
@@ -1490,7 +1490,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
|
1490
1490
|
};
|
|
1491
1491
|
|
|
1492
1492
|
// ../core/dist/version.js
|
|
1493
|
-
var PACKAGE_VERSION = true ? "1.
|
|
1493
|
+
var PACKAGE_VERSION = true ? "1.1.0" : UNRELEASED_VERSION;
|
|
1494
1494
|
|
|
1495
1495
|
// ../core/dist/signing.js
|
|
1496
1496
|
var encoder = new TextEncoder();
|
|
@@ -2587,13 +2587,61 @@ var DEFAULT_CATALOG = [
|
|
|
2587
2587
|
status: "inactive"
|
|
2588
2588
|
}
|
|
2589
2589
|
];
|
|
2590
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2591
|
+
var optionalString = (row, key) => {
|
|
2592
|
+
const value = row[key];
|
|
2593
|
+
if (value === void 0 || value === null) return null;
|
|
2594
|
+
if (typeof value !== "string") throw new Error(`${key} must be a string or null`);
|
|
2595
|
+
return value;
|
|
2596
|
+
};
|
|
2597
|
+
var parseCatalogItem = (value) => {
|
|
2598
|
+
if (!isRecord4(value)) throw new Error("a catalog row must be an object");
|
|
2599
|
+
const { catalog_id, medication_name, quantity, states, status } = value;
|
|
2600
|
+
if (typeof catalog_id !== "string" || catalog_id.length === 0) {
|
|
2601
|
+
throw new Error("catalog_id must be a non-empty string");
|
|
2602
|
+
}
|
|
2603
|
+
if (typeof medication_name !== "string") {
|
|
2604
|
+
throw new Error(`${catalog_id}: medication_name must be a string`);
|
|
2605
|
+
}
|
|
2606
|
+
if (quantity !== void 0 && quantity !== null && typeof quantity !== "number") {
|
|
2607
|
+
throw new Error(`${catalog_id}: quantity must be a number or null`);
|
|
2608
|
+
}
|
|
2609
|
+
if (states !== void 0 && (!Array.isArray(states) || states.some((s) => typeof s !== "string"))) {
|
|
2610
|
+
throw new Error(`${catalog_id}: states must be a list of state codes`);
|
|
2611
|
+
}
|
|
2612
|
+
if (status !== void 0 && status !== "active" && status !== "inactive") {
|
|
2613
|
+
throw new Error(`${catalog_id}: status must be "active" or "inactive"`);
|
|
2614
|
+
}
|
|
2615
|
+
try {
|
|
2616
|
+
return {
|
|
2617
|
+
catalog_id,
|
|
2618
|
+
medication_name,
|
|
2619
|
+
medication_strength: optionalString(value, "medication_strength"),
|
|
2620
|
+
package_size: optionalString(value, "package_size"),
|
|
2621
|
+
quantity: typeof quantity === "number" ? quantity : null,
|
|
2622
|
+
quantity_units: optionalString(value, "quantity_units"),
|
|
2623
|
+
medication_form: optionalString(value, "medication_form"),
|
|
2624
|
+
route: optionalString(value, "route"),
|
|
2625
|
+
states: states ?? [],
|
|
2626
|
+
status: status ?? "active"
|
|
2627
|
+
};
|
|
2628
|
+
} catch (error) {
|
|
2629
|
+
throw new Error(`${catalog_id}: ${error.message}`);
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
var parseCatalog = (value) => {
|
|
2633
|
+
const rows = Array.isArray(value) ? value : isRecord4(value) ? value.data : void 0;
|
|
2634
|
+
if (!Array.isArray(rows)) throw new Error("a catalog must be an array or {data: [...]}");
|
|
2635
|
+
return rows.map(parseCatalogItem);
|
|
2636
|
+
};
|
|
2590
2637
|
|
|
2591
2638
|
// src/state.ts
|
|
2592
2639
|
var DEFAULT_SETTINGS = {
|
|
2593
2640
|
tokenTtlSeconds: 86400,
|
|
2594
2641
|
staticTokens: [],
|
|
2595
2642
|
clients: [],
|
|
2596
|
-
autoAdvance: null
|
|
2643
|
+
autoAdvance: null,
|
|
2644
|
+
unknownPresets: "reject"
|
|
2597
2645
|
};
|
|
2598
2646
|
var RxVortexState = class {
|
|
2599
2647
|
constructor(sqlite, namespace, seed) {
|
|
@@ -2611,15 +2659,25 @@ var RxVortexState = class {
|
|
|
2611
2659
|
ids;
|
|
2612
2660
|
/** Re-apply the catalog and settings after a reset. */
|
|
2613
2661
|
ensureSeeded() {
|
|
2614
|
-
if (this.catalog.count() === 0)
|
|
2615
|
-
for (const item of this.seed.catalog.length > 0 ? this.seed.catalog : DEFAULT_CATALOG) {
|
|
2616
|
-
this.catalog.insert(item.catalog_id, item);
|
|
2617
|
-
}
|
|
2618
|
-
}
|
|
2662
|
+
if (this.catalog.count() === 0) this.putCatalog(this.seed.catalog ?? DEFAULT_CATALOG, "merge");
|
|
2619
2663
|
if (!this.settings.has("settings")) {
|
|
2620
2664
|
this.settings.insert("settings", { ...DEFAULT_SETTINGS, ...this.seed.settings });
|
|
2621
2665
|
}
|
|
2622
2666
|
}
|
|
2667
|
+
/** Load rows: `replace` drops every other row first, `merge` upserts by `catalog_id`. */
|
|
2668
|
+
putCatalog(items, mode) {
|
|
2669
|
+
if (mode === "replace") {
|
|
2670
|
+
for (const row of this.catalog.list()) this.catalog.delete(row.id);
|
|
2671
|
+
}
|
|
2672
|
+
for (const item of items) {
|
|
2673
|
+
if (this.catalog.has(item.catalog_id)) this.catalog.update(item.catalog_id, item);
|
|
2674
|
+
else this.catalog.insert(item.catalog_id, item);
|
|
2675
|
+
}
|
|
2676
|
+
return this.catalog.count();
|
|
2677
|
+
}
|
|
2678
|
+
catalogRows() {
|
|
2679
|
+
return this.catalog.list({ order: "oldest" }).map((row) => row.value);
|
|
2680
|
+
}
|
|
2623
2681
|
current() {
|
|
2624
2682
|
return this.settings.get("settings") ?? DEFAULT_SETTINGS;
|
|
2625
2683
|
}
|
|
@@ -2730,10 +2788,10 @@ var RXVORTEX_PRESETS = {
|
|
|
2730
2788
|
};
|
|
2731
2789
|
var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2732
2790
|
var adminError3 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
|
|
2733
|
-
var
|
|
2791
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2734
2792
|
var parseAutoAdvance = (value) => {
|
|
2735
2793
|
if (value === null) return null;
|
|
2736
|
-
if (!
|
|
2794
|
+
if (!isRecord5(value)) return "autoAdvance must be {afterMs, path} or null";
|
|
2737
2795
|
if (typeof value.afterMs !== "number" || value.afterMs < 0)
|
|
2738
2796
|
return "autoAdvance.afterMs must be ms";
|
|
2739
2797
|
if (!Array.isArray(value.path) || value.path.some((s) => typeof s !== "string")) {
|
|
@@ -2744,7 +2802,7 @@ var parseAutoAdvance = (value) => {
|
|
|
2744
2802
|
var adminRoutes = (runtime) => ({
|
|
2745
2803
|
"GET /orders": ({ namespace }) => json3(200, { orders: runtime.instance(namespace).orders() }),
|
|
2746
2804
|
"POST /orders/:id/transition": ({ params, body, namespace }) => {
|
|
2747
|
-
if (!
|
|
2805
|
+
if (!isRecord5(body) || typeof body.to !== "string") {
|
|
2748
2806
|
return adminError3(
|
|
2749
2807
|
400,
|
|
2750
2808
|
'expected {"to": "<vendor status>", "trackingnumber"?, "shippingcarrier"?}'
|
|
@@ -2760,9 +2818,27 @@ var adminRoutes = (runtime) => ({
|
|
|
2760
2818
|
});
|
|
2761
2819
|
return order ? json3(200, order) : adminError3(404, `no order ${params.id}`);
|
|
2762
2820
|
},
|
|
2821
|
+
"GET /catalog": ({ namespace }) => json3(200, { data: runtime.instance(namespace).state.catalogRows() }),
|
|
2822
|
+
"PUT /catalog": ({ body, namespace }) => {
|
|
2823
|
+
const rows = isRecord5(body) ? body.items ?? body.data : void 0;
|
|
2824
|
+
if (!isRecord5(body) || !Array.isArray(rows)) {
|
|
2825
|
+
return adminError3(400, 'expected {"items": [{catalog_id, medication_name, \u2026}], "mode"?}');
|
|
2826
|
+
}
|
|
2827
|
+
const mode = body.mode ?? "replace";
|
|
2828
|
+
if (mode !== "replace" && mode !== "merge") {
|
|
2829
|
+
return adminError3(400, 'mode must be "replace" or "merge"');
|
|
2830
|
+
}
|
|
2831
|
+
let items;
|
|
2832
|
+
try {
|
|
2833
|
+
items = rows.map(parseCatalogItem);
|
|
2834
|
+
} catch (error) {
|
|
2835
|
+
return adminError3(400, `items: ${error.message}`);
|
|
2836
|
+
}
|
|
2837
|
+
return json3(200, { count: runtime.instance(namespace).state.putCatalog(items, mode) });
|
|
2838
|
+
},
|
|
2763
2839
|
"GET /settings": ({ namespace }) => json3(200, runtime.instance(namespace).state.current()),
|
|
2764
2840
|
"PUT /settings": ({ body, namespace }) => {
|
|
2765
|
-
if (!
|
|
2841
|
+
if (!isRecord5(body)) return adminError3(400, "expected a JSON object");
|
|
2766
2842
|
const patch = {};
|
|
2767
2843
|
if (body.tokenTtlSeconds !== void 0) {
|
|
2768
2844
|
if (typeof body.tokenTtlSeconds !== "number")
|
|
@@ -2776,7 +2852,7 @@ var adminRoutes = (runtime) => ({
|
|
|
2776
2852
|
if (body.clients !== void 0) {
|
|
2777
2853
|
if (!Array.isArray(body.clients))
|
|
2778
2854
|
return adminError3(400, "clients: [{client_id, client_secret}]");
|
|
2779
|
-
patch.clients = body.clients.filter(
|
|
2855
|
+
patch.clients = body.clients.filter(isRecord5).map((c) => ({
|
|
2780
2856
|
client_id: String(c.client_id),
|
|
2781
2857
|
client_secret: String(c.client_secret)
|
|
2782
2858
|
}));
|
|
@@ -2786,6 +2862,12 @@ var adminRoutes = (runtime) => ({
|
|
|
2786
2862
|
if (typeof parsed === "string") return adminError3(400, parsed);
|
|
2787
2863
|
patch.autoAdvance = parsed;
|
|
2788
2864
|
}
|
|
2865
|
+
if (body.unknownPresets !== void 0) {
|
|
2866
|
+
if (body.unknownPresets !== "reject" && body.unknownPresets !== "accept") {
|
|
2867
|
+
return adminError3(400, 'unknownPresets: "reject" | "accept"');
|
|
2868
|
+
}
|
|
2869
|
+
patch.unknownPresets = body.unknownPresets;
|
|
2870
|
+
}
|
|
2789
2871
|
return json3(200, runtime.instance(namespace).state.update(patch));
|
|
2790
2872
|
},
|
|
2791
2873
|
"POST /tick": ({ namespace }) => json3(200, { applied: runtime.instance(namespace).tick() })
|
|
@@ -2843,6 +2925,7 @@ var createRuntime2 = (options = {}) => {
|
|
|
2843
2925
|
// src/index.ts
|
|
2844
2926
|
var RXVORTEX_NAMESPACE = "rxvortex";
|
|
2845
2927
|
var TOKEN_PREFIX = "rxv_";
|
|
2928
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2846
2929
|
var base64url = (value) => toBase64(new TextEncoder().encode(value)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2847
2930
|
var fromBase64url = (value) => {
|
|
2848
2931
|
try {
|
|
@@ -2920,7 +3003,7 @@ var RxVortexAPI = class {
|
|
|
2920
3003
|
this.now = options.now ?? (() => Date.now());
|
|
2921
3004
|
this.onWebhook = options.onWebhook;
|
|
2922
3005
|
this.state = new RxVortexState(sqlite, namespace, {
|
|
2923
|
-
catalog: options.catalog
|
|
3006
|
+
catalog: options.catalog,
|
|
2924
3007
|
settings: options.settings ?? {}
|
|
2925
3008
|
});
|
|
2926
3009
|
const handlers = defineOperations({
|
|
@@ -2928,9 +3011,7 @@ var RxVortexAPI = class {
|
|
|
2928
3011
|
CreateOrder: (context) => this.createOrder(context),
|
|
2929
3012
|
GetOrder: (context) => this.getOrder(context),
|
|
2930
3013
|
CancelOrder: (context) => this.cancelOrder(context),
|
|
2931
|
-
ListPresetCatalogItems: () => jsonRes(200, {
|
|
2932
|
-
data: this.state.catalog.list({ order: "oldest" }).map((row) => row.value)
|
|
2933
|
-
})
|
|
3014
|
+
ListPresetCatalogItems: () => jsonRes(200, { data: this.state.catalogRows() })
|
|
2934
3015
|
});
|
|
2935
3016
|
this.service = createService({
|
|
2936
3017
|
document,
|
|
@@ -3009,9 +3090,10 @@ var RxVortexAPI = class {
|
|
|
3009
3090
|
}
|
|
3010
3091
|
const order = body.order;
|
|
3011
3092
|
const meds = body.medication_requests;
|
|
3093
|
+
const adoptable = (med) => this.state.current().unknownPresets === "accept" && UUID.test(med.preset_catalog_id);
|
|
3012
3094
|
const unknown = meds.find((med) => {
|
|
3013
3095
|
const item = this.state.catalog.get(med.preset_catalog_id);
|
|
3014
|
-
return
|
|
3096
|
+
return item ? item.status !== "active" : !adoptable(med);
|
|
3015
3097
|
});
|
|
3016
3098
|
if (unknown) {
|
|
3017
3099
|
return jsonRes(422, {
|
|
@@ -3033,6 +3115,9 @@ var RxVortexAPI = class {
|
|
|
3033
3115
|
{ ids: { orderId: existing.order_tracking_id } }
|
|
3034
3116
|
);
|
|
3035
3117
|
}
|
|
3118
|
+
for (const med of meds) {
|
|
3119
|
+
if (!this.state.catalog.has(med.preset_catalog_id)) this.adoptPreset(med);
|
|
3120
|
+
}
|
|
3036
3121
|
const now = this.iso();
|
|
3037
3122
|
const created = {
|
|
3038
3123
|
order_tracking_id: this.state.nextTrackingId(),
|
|
@@ -3076,6 +3161,25 @@ var RxVortexAPI = class {
|
|
|
3076
3161
|
{ ids }
|
|
3077
3162
|
);
|
|
3078
3163
|
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Under `unknownPresets: "accept"`, a well-formed preset id the catalog lacks becomes an
|
|
3166
|
+
* active row described by the request, so later submits and catalog reads see it.
|
|
3167
|
+
*/
|
|
3168
|
+
adoptPreset(med) {
|
|
3169
|
+
const item = {
|
|
3170
|
+
catalog_id: med.preset_catalog_id,
|
|
3171
|
+
medication_name: med.medication_name,
|
|
3172
|
+
medication_strength: med.medication_strength ?? null,
|
|
3173
|
+
package_size: null,
|
|
3174
|
+
quantity: med.quantity,
|
|
3175
|
+
quantity_units: med.quantity_units,
|
|
3176
|
+
medication_form: med.medication_form ?? null,
|
|
3177
|
+
route: null,
|
|
3178
|
+
states: [],
|
|
3179
|
+
status: "active"
|
|
3180
|
+
};
|
|
3181
|
+
this.state.putCatalog([item], "merge");
|
|
3182
|
+
}
|
|
3079
3183
|
getOrder(context) {
|
|
3080
3184
|
const order = this.state.findOrder(context.params.orderId ?? "");
|
|
3081
3185
|
if (!order) return jsonRes(404, { message: "Order not found." });
|
|
@@ -3182,10 +3286,12 @@ export {
|
|
|
3182
3286
|
supportedOperationIds,
|
|
3183
3287
|
CUSTOM_CREAM_ANCHOR_PRESET_ID,
|
|
3184
3288
|
DEFAULT_CATALOG,
|
|
3289
|
+
parseCatalogItem,
|
|
3290
|
+
parseCatalog,
|
|
3185
3291
|
RXVORTEX_PRESETS,
|
|
3186
3292
|
createRuntime2 as createRuntime,
|
|
3187
3293
|
RXVORTEX_NAMESPACE,
|
|
3188
3294
|
tokenCredential,
|
|
3189
3295
|
RxVortexAPI
|
|
3190
3296
|
};
|
|
3191
|
-
//# sourceMappingURL=chunk-
|
|
3297
|
+
//# sourceMappingURL=chunk-VQAJFAUO.js.map
|