@crvouga/mockingbird-service-rxvortex 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -4
- package/README.md +15 -5
- package/dist/{chunk-DNELVPK4.js → chunk-5KK6W2PE.js} +229 -51
- package/dist/chunk-5KK6W2PE.js.map +7 -0
- package/dist/{chunk-ZAM35JQO.js → chunk-XAWKEXKJ.js} +40 -4
- package/dist/chunk-XAWKEXKJ.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +160 -103
- package/dist/index.js +5 -1
- package/dist/server.d.ts +272 -193
- package/dist/server.js +4 -2
- package/package.json +2 -2
- package/dist/chunk-DNELVPK4.js.map +0 -7
- package/dist/chunk-ZAM35JQO.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-rxvortex
|
|
2
2
|
|
|
3
|
+
## 1.1.1 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- take the rejected-request byte count from content-length, never by reading the body ([a0e5fb2](https://github.com/crvouga/mockingbird/commit/a0e5fb2fc80ab725c3552b612f3f091fd6c857fb))
|
|
8
|
+
- reject a present body by media type, and log what a rejected request sent ([12e351a](https://github.com/crvouga/mockingbird/commit/12e351aba53cfb7b41e31a7809419455fc2902eb))
|
|
9
|
+
|
|
10
|
+
## 1.1.0 (2026-09-25)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- load a preset catalog into a served mock and promote to ready ([471288e](https://github.com/crvouga/mockingbird/commit/471288e46b1f34a62f0c1b7e7a76dc30920c39e7))
|
|
15
|
+
|
|
3
16
|
## 1.0.0 (2026-09-24)
|
|
4
17
|
|
|
5
18
|
### ⚠️ Breaking changes
|
|
6
19
|
|
|
7
20
|
- vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
|
|
8
21
|
|
|
9
|
-
### Dependencies
|
|
10
|
-
|
|
11
|
-
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
-
|
|
13
22
|
## 0.2.0 (2026-09-23)
|
|
14
23
|
|
|
15
24
|
### 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).
|
|
@@ -1366,6 +1366,100 @@ var operationMetadata = (operation) => {
|
|
|
1366
1366
|
|
|
1367
1367
|
// ../core/dist/service.js
|
|
1368
1368
|
import { Hono } from "hono";
|
|
1369
|
+
|
|
1370
|
+
// ../core/dist/validation.js
|
|
1371
|
+
var requestBodyOf = (context) => {
|
|
1372
|
+
const requestBody = context.operation.operation.requestBody;
|
|
1373
|
+
return requestBody ? deref(context.document, requestBody) : void 0;
|
|
1374
|
+
};
|
|
1375
|
+
var mediaTypeMatches = (accepted, actual) => {
|
|
1376
|
+
const pattern = mediaTypeOf(accepted) ?? accepted.toLowerCase();
|
|
1377
|
+
if (pattern === actual || pattern === "*/*")
|
|
1378
|
+
return true;
|
|
1379
|
+
const [type, subtype] = pattern.split("/");
|
|
1380
|
+
const [actualType, actualSubtype] = actual.split("/");
|
|
1381
|
+
if (type !== actualType && type !== "*")
|
|
1382
|
+
return false;
|
|
1383
|
+
if (subtype === void 0 || subtype === "*")
|
|
1384
|
+
return true;
|
|
1385
|
+
if (subtype.startsWith("*+"))
|
|
1386
|
+
return actualSubtype?.endsWith(subtype.slice(1)) === true;
|
|
1387
|
+
return false;
|
|
1388
|
+
};
|
|
1389
|
+
var acceptedKeyFor = (content, mediaType) => {
|
|
1390
|
+
if (!content || mediaType === void 0)
|
|
1391
|
+
return void 0;
|
|
1392
|
+
const keys = Object.keys(content);
|
|
1393
|
+
return keys.find((key) => mediaTypeOf(key) === mediaType) ?? keys.find((key) => mediaTypeMatches(key, mediaType));
|
|
1394
|
+
};
|
|
1395
|
+
var unsupportedMediaType = (context, options = {}) => {
|
|
1396
|
+
if (context.body.kind === "empty" && options.includeEmpty !== true)
|
|
1397
|
+
return void 0;
|
|
1398
|
+
const content = requestBodyOf(context)?.content;
|
|
1399
|
+
if (!content || Object.keys(content).length === 0)
|
|
1400
|
+
return void 0;
|
|
1401
|
+
const mediaType = mediaTypeOf(context.request.headers.get("content-type"));
|
|
1402
|
+
if (acceptedKeyFor(content, mediaType) !== void 0)
|
|
1403
|
+
return void 0;
|
|
1404
|
+
return { mediaType: mediaType ?? null, accepted: Object.keys(content) };
|
|
1405
|
+
};
|
|
1406
|
+
var recorded = /* @__PURE__ */ new WeakMap();
|
|
1407
|
+
var recordedIssues = (request) => recorded.get(request);
|
|
1408
|
+
var bodyIssues = (context, contentType = "application/json") => {
|
|
1409
|
+
const issues = findBodyIssues(context, contentType);
|
|
1410
|
+
if (issues.length > 0)
|
|
1411
|
+
recorded.set(context.request, issues);
|
|
1412
|
+
return issues;
|
|
1413
|
+
};
|
|
1414
|
+
var findBodyIssues = (context, contentType) => {
|
|
1415
|
+
const resolved = requestBodyOf(context);
|
|
1416
|
+
if (!resolved)
|
|
1417
|
+
return [];
|
|
1418
|
+
const unsupported = unsupportedMediaType(context);
|
|
1419
|
+
if (unsupported) {
|
|
1420
|
+
return [
|
|
1421
|
+
{
|
|
1422
|
+
path: "",
|
|
1423
|
+
kind: "media_type",
|
|
1424
|
+
message: unsupported.mediaType === null ? "request body has no content-type" : `request body media type ${unsupported.mediaType} is not supported`
|
|
1425
|
+
}
|
|
1426
|
+
];
|
|
1427
|
+
}
|
|
1428
|
+
const key = acceptedKeyFor(resolved.content, mediaTypeOf(context.request.headers.get("content-type"))) ?? contentType;
|
|
1429
|
+
const schema = resolved.content?.[key]?.schema;
|
|
1430
|
+
if (!schema)
|
|
1431
|
+
return [];
|
|
1432
|
+
if (context.body.kind === "invalid") {
|
|
1433
|
+
return [
|
|
1434
|
+
{
|
|
1435
|
+
path: "",
|
|
1436
|
+
kind: "syntax",
|
|
1437
|
+
message: `request body is not valid ${context.body.mediaType}`
|
|
1438
|
+
}
|
|
1439
|
+
];
|
|
1440
|
+
}
|
|
1441
|
+
const value = context.body.kind === "json" || context.body.kind === "form" || context.body.kind === "text" ? context.body.value : void 0;
|
|
1442
|
+
if (value === void 0) {
|
|
1443
|
+
return resolved.required && context.body.kind === "empty" ? [{ path: "", kind: "required", message: "request body is required" }] : [];
|
|
1444
|
+
}
|
|
1445
|
+
return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), kind: "schema", message: issue.message }));
|
|
1446
|
+
};
|
|
1447
|
+
var issuesByField = (issues) => {
|
|
1448
|
+
const out = {};
|
|
1449
|
+
for (const issue of issues) {
|
|
1450
|
+
const missing = /^missing required property (.+)$/.exec(issue.message);
|
|
1451
|
+
const field = missing ? [issue.path, missing[1]].filter(Boolean).join(".") : issue.path || "body";
|
|
1452
|
+
const message = missing ? `The ${field} field is required.` : `The ${field} field ${issue.message}.`;
|
|
1453
|
+
out[field] = [...out[field] ?? [], message];
|
|
1454
|
+
}
|
|
1455
|
+
return out;
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
// ../core/dist/service.js
|
|
1459
|
+
var withIssues = (request, response) => {
|
|
1460
|
+
const issues = response.status >= 400 ? recordedIssues(request) : void 0;
|
|
1461
|
+
return issues && issues.length > 0 ? annotateResponse(response, { issues }) : response;
|
|
1462
|
+
};
|
|
1369
1463
|
var defineOperations = (handlers) => handlers;
|
|
1370
1464
|
var OperationRegistryError = class extends Error {
|
|
1371
1465
|
problems;
|
|
@@ -1451,9 +1545,7 @@ var createService = (options) => {
|
|
|
1451
1545
|
now
|
|
1452
1546
|
};
|
|
1453
1547
|
const short = await options.before?.(context);
|
|
1454
|
-
|
|
1455
|
-
return short;
|
|
1456
|
-
return handler(context);
|
|
1548
|
+
return withIssues(request, short ?? await handler(context));
|
|
1457
1549
|
};
|
|
1458
1550
|
app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
|
|
1459
1551
|
}
|
|
@@ -1490,7 +1582,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
|
1490
1582
|
};
|
|
1491
1583
|
|
|
1492
1584
|
// ../core/dist/version.js
|
|
1493
|
-
var PACKAGE_VERSION = true ? "1.
|
|
1585
|
+
var PACKAGE_VERSION = true ? "1.1.1" : UNRELEASED_VERSION;
|
|
1494
1586
|
|
|
1495
1587
|
// ../core/dist/signing.js
|
|
1496
1588
|
var encoder = new TextEncoder();
|
|
@@ -2250,8 +2342,15 @@ var createRuntime = (options) => {
|
|
|
2250
2342
|
const started = monotonicNow();
|
|
2251
2343
|
const url = new URL(request.url);
|
|
2252
2344
|
const operationId = operationIdFor(request, url.pathname);
|
|
2345
|
+
const contentLength = request.headers.get("content-length");
|
|
2346
|
+
const received = {
|
|
2347
|
+
contentType: request.headers.get("content-type"),
|
|
2348
|
+
bodyBytes: contentLength !== null && /^\d+$/.test(contentLength) ? Number(contentLength) : null,
|
|
2349
|
+
transferEncoding: request.headers.get("transfer-encoding")
|
|
2350
|
+
};
|
|
2253
2351
|
const log = (status, faultId, response2) => {
|
|
2254
2352
|
const noted = response2 ? responseNotes(response2) : void 0;
|
|
2353
|
+
const rejected = status >= 400 && faultId === void 0;
|
|
2255
2354
|
const entry = {
|
|
2256
2355
|
service: options.name,
|
|
2257
2356
|
namespace,
|
|
@@ -2263,7 +2362,9 @@ var createRuntime = (options) => {
|
|
|
2263
2362
|
unmatched: options.document !== void 0 && operationId === void 0,
|
|
2264
2363
|
...faultId !== void 0 ? { faultId } : {},
|
|
2265
2364
|
...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
|
|
2266
|
-
...noted?.adopted ? { adopted: true } : {}
|
|
2365
|
+
...noted?.adopted ? { adopted: true } : {},
|
|
2366
|
+
...rejected ? { request: { ...received } } : {},
|
|
2367
|
+
...rejected && noted?.issues && noted.issues.length > 0 ? { issues: noted.issues.map((issue) => ({ ...issue })) } : {}
|
|
2267
2368
|
};
|
|
2268
2369
|
metrics.record(entry);
|
|
2269
2370
|
journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
|
|
@@ -2470,35 +2571,6 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
2470
2571
|
}
|
|
2471
2572
|
});
|
|
2472
2573
|
|
|
2473
|
-
// ../core/dist/validation.js
|
|
2474
|
-
var bodyIssues = (context, contentType = "application/json") => {
|
|
2475
|
-
const requestBody = context.operation.operation.requestBody;
|
|
2476
|
-
if (!requestBody)
|
|
2477
|
-
return [];
|
|
2478
|
-
const resolved = deref(context.document, requestBody);
|
|
2479
|
-
const schema = resolved.content?.[contentType]?.schema;
|
|
2480
|
-
if (!schema)
|
|
2481
|
-
return [];
|
|
2482
|
-
const value = context.body.kind === "json" || context.body.kind === "form" ? context.body.value : void 0;
|
|
2483
|
-
if (context.body.kind === "invalid") {
|
|
2484
|
-
return [{ path: "", message: `request body is not valid ${context.body.mediaType}` }];
|
|
2485
|
-
}
|
|
2486
|
-
if (value === void 0) {
|
|
2487
|
-
return resolved.required ? [{ path: "", message: "request body is required" }] : [];
|
|
2488
|
-
}
|
|
2489
|
-
return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), message: issue.message }));
|
|
2490
|
-
};
|
|
2491
|
-
var issuesByField = (issues) => {
|
|
2492
|
-
const out = {};
|
|
2493
|
-
for (const issue of issues) {
|
|
2494
|
-
const missing = /^missing required property (.+)$/.exec(issue.message);
|
|
2495
|
-
const field = missing ? [issue.path, missing[1]].filter(Boolean).join(".") : issue.path || "body";
|
|
2496
|
-
const message = missing ? `The ${field} field is required.` : `The ${field} field ${issue.message}.`;
|
|
2497
|
-
out[field] = [...out[field] ?? [], message];
|
|
2498
|
-
}
|
|
2499
|
-
return out;
|
|
2500
|
-
};
|
|
2501
|
-
|
|
2502
2574
|
// src/generated/openapi.ts
|
|
2503
2575
|
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"RxVortex (Strive) pharmacy API (Mockingbird subset)","description":"Stateful mock subset of the RxVortex / Strive compounding-pharmacy API: client-credentials\\ntoken, order submit, status, cancel, lookup by sender order id (recovery), and the preset\\ncatalog. Hand-authored from the consumer's adapters (the vendor publishes no spec).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (rxvortex-auth.service.ts, rxvortex-fulfillment.adapter.ts, rxvortex-live.client.ts)."}},"servers":[{"url":"https://sandbox.rxvortex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/generate-access-token":{"post":{"operationId":"GenerateAccessToken","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","client_secret"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64}}}}}},"responses":{"200":{"description":"Access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Bad credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}},"/api/v1/orders":{"post":{"operationId":"CreateOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderBody"}}}},"responses":{"200":{"description":"Order accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderResponse"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Duplicate sender_order_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}},"/api/v1/orders/{orderId}":{"parameters":[{"name":"orderId","in":"path","required":true,"description":"The vendor order tracking id, or the sender order id (our payment id).","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"order","missing":"RXV-00000000-MISSING"}}}],"get":{"operationId":"GetOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderStatus"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"delete":{"operationId":"CancelOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelResponse"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Not cancellable in its current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/preset-catalog-items":{"get":{"operationId":"ListPresetCatalogItems","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Preset catalog","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}}}}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenResponse":{"type":"object","required":["access_token","token_type","expires_in"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"}}},"Address":{"type":"object","required":["line1","city","state","postal_code","country"],"properties":{"line1":{"type":"string","minLength":1,"maxLength":80},"line2":{"type":"string","maxLength":80},"city":{"type":"string","minLength":1,"maxLength":40},"state":{"type":"string","pattern":"^[A-Z]{2}$"},"postal_code":{"type":"string","pattern":"^[0-9]{5}(-[0-9]{4})?$"},"country":{"type":"string","enum":["US"]}}},"Patient":{"type":"object","required":["sender_patient_id","first_name","last_name","dob","gender","email","phone","address"],"properties":{"sender_patient_id":{"type":"string","minLength":1,"maxLength":64},"first_name":{"type":"string","minLength":1,"maxLength":40},"last_name":{"type":"string","minLength":1,"maxLength":40},"dob":{"type":"string","pattern":"^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$"},"gender":{"type":"string","enum":["male","female"]},"email":{"type":"string","format":"email","maxLength":120},"phone":{"type":"string","pattern":"^[0-9]{3}-[0-9]{3}-[0-9]{4}$"},"address":{"$ref":"#/components/schemas/Address"}}},"Prescriber":{"type":"object","required":["first_name","last_name","npi"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":40},"last_name":{"type":"string","minLength":1,"maxLength":40},"npi":{"type":"string","pattern":"^[0-9]{10}$"},"dea_number":{"type":"string","maxLength":20},"license_number":{"type":"string","maxLength":20},"license_state":{"type":"string","maxLength":2},"phone":{"type":"string","maxLength":20},"address":{"$ref":"#/components/schemas/Address"}}},"MedicationRequest":{"type":"object","required":["type","preset_catalog_id","sender_med_request_id","medication_name","quantity","quantity_units","days_supply_duration","refills","instructions"],"properties":{"type":{"type":"string","enum":["new"]},"preset_catalog_id":{"type":"string","x-mockingbird-resource-ref":{"type":"catalog_item","missing":"00000000-0000-4000-8000-000000000000"}},"sender_med_request_id":{"type":"string","minLength":1,"maxLength":64},"medication_name":{"type":"string","minLength":1,"maxLength":120},"medication_strength":{"type":"string","maxLength":60},"medication_form":{"type":"string","maxLength":40},"quantity":{"type":"number","exclusiveMinimum":0,"maximum":1000},"quantity_units":{"type":"string","enum":["mL","grams","each"]},"days_supply_duration":{"type":"integer","minimum":1,"maximum":365},"refills":{"type":"integer","minimum":0,"maximum":12},"instructions":{"type":"string","minLength":1,"maxLength":500},"note":{"type":"string","maxLength":1000},"authored_on_datetime":{"type":"string","maxLength":40},"clinical_difference":{"type":"string","maxLength":1000},"schedule_code":{"type":"string","enum":["2","3","4","5"]}}},"ClinicalList":{"type":"object","properties":{"has_known_allergies":{"type":"boolean"},"has_known_diseases":{"type":"boolean"},"has_known_medications":{"type":"boolean"},"entries":{"type":"array","maxItems":5,"items":{"type":"object","required":["description"],"properties":{"description":{"type":"string","maxLength":200}}}}}},"CreateOrderBody":{"type":"object","required":["patient","prescriber","order","medication_requests"],"properties":{"patient":{"$ref":"#/components/schemas/Patient"},"prescriber":{"$ref":"#/components/schemas/Prescriber"},"order":{"type":"object","required":["bill_to","ship_to","sender_order_id"],"properties":{"bill_to":{"type":"string","enum":["practice","patient"]},"ship_to":{"type":"string","enum":["patient","practice"]},"sender_order_id":{"type":"string","minLength":1,"maxLength":64}}},"medication_requests":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/MedicationRequest"}},"clinical":{"type":"object","properties":{"allergies":{"$ref":"#/components/schemas/ClinicalList"},"diseases":{"$ref":"#/components/schemas/ClinicalList"},"medications":{"$ref":"#/components/schemas/ClinicalList"}}},"shipment":{"type":"object","properties":{"recipient_first_name":{"type":"string","maxLength":40},"recipient_last_name":{"type":"string","maxLength":40},"recipient_email":{"type":"string","maxLength":80},"recipient_phone":{"type":"string","maxLength":20},"address":{"$ref":"#/components/schemas/Address"}}}}},"CreateOrderResponse":{"type":"object","required":["success","message","order_tracking_id","sender_order_id","status"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"order_tracking_id":{"type":"string","x-mockingbird-resource":{"type":"order","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sender_order_id":{"type":"string"},"status":{"type":"string"}}},"OrderStatus":{"type":"object","required":["order_tracking_id","tracking_id","orderReferenceID","sender_order_id","rxstatus","orderstatus","shipping_status","cancellable","created_at","updated_at"],"properties":{"order_tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"orderReferenceID":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"sender_order_id":{"type":"string"},"rxstatus":{"type":"string"},"orderstatus":{"type":"string"},"shipping_status":{"type":"string"},"delivered_date":{"type":["string","null"]},"trackingnumber":{"type":["string","null"]},"shippingservice":{"type":["string","null"]},"shippingcarrier":{"type":["string","null"]},"shipmenttrackingurl":{"type":["string","null"]},"cancellable":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CancelResponse":{"type":"object","required":["success","message","order_tracking_id","rxstatus"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"order_tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"rxstatus":{"type":"string"}}},"CatalogItem":{"type":"object","required":["catalog_id","medication_name","status"],"properties":{"catalog_id":{"type":"string","x-mockingbird-resource":{"type":"catalog_item","identity":true}},"medication_name":{"type":"string"},"medication_strength":{"type":["string","null"]},"package_size":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_units":{"type":["string","null"]},"medication_form":{"type":["string","null"]},"route":{"type":["string","null"]},"states":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","inactive"]}}},"ErrorBody":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"success":{"type":"boolean"}}},"ValidationError":{"type":"object","required":["message","errors"],"properties":{"message":{"type":"string"},"errors":{"oneOf":[{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},{"type":"array","items":{"type":"object"}}]}}}}}}`);
|
|
2504
2576
|
var operationIds = ["GenerateAccessToken", "CreateOrder", "GetOrder", "CancelOrder", "ListPresetCatalogItems"];
|
|
@@ -2587,13 +2659,61 @@ var DEFAULT_CATALOG = [
|
|
|
2587
2659
|
status: "inactive"
|
|
2588
2660
|
}
|
|
2589
2661
|
];
|
|
2662
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2663
|
+
var optionalString = (row, key) => {
|
|
2664
|
+
const value = row[key];
|
|
2665
|
+
if (value === void 0 || value === null) return null;
|
|
2666
|
+
if (typeof value !== "string") throw new Error(`${key} must be a string or null`);
|
|
2667
|
+
return value;
|
|
2668
|
+
};
|
|
2669
|
+
var parseCatalogItem = (value) => {
|
|
2670
|
+
if (!isRecord4(value)) throw new Error("a catalog row must be an object");
|
|
2671
|
+
const { catalog_id, medication_name, quantity, states, status } = value;
|
|
2672
|
+
if (typeof catalog_id !== "string" || catalog_id.length === 0) {
|
|
2673
|
+
throw new Error("catalog_id must be a non-empty string");
|
|
2674
|
+
}
|
|
2675
|
+
if (typeof medication_name !== "string") {
|
|
2676
|
+
throw new Error(`${catalog_id}: medication_name must be a string`);
|
|
2677
|
+
}
|
|
2678
|
+
if (quantity !== void 0 && quantity !== null && typeof quantity !== "number") {
|
|
2679
|
+
throw new Error(`${catalog_id}: quantity must be a number or null`);
|
|
2680
|
+
}
|
|
2681
|
+
if (states !== void 0 && (!Array.isArray(states) || states.some((s) => typeof s !== "string"))) {
|
|
2682
|
+
throw new Error(`${catalog_id}: states must be a list of state codes`);
|
|
2683
|
+
}
|
|
2684
|
+
if (status !== void 0 && status !== "active" && status !== "inactive") {
|
|
2685
|
+
throw new Error(`${catalog_id}: status must be "active" or "inactive"`);
|
|
2686
|
+
}
|
|
2687
|
+
try {
|
|
2688
|
+
return {
|
|
2689
|
+
catalog_id,
|
|
2690
|
+
medication_name,
|
|
2691
|
+
medication_strength: optionalString(value, "medication_strength"),
|
|
2692
|
+
package_size: optionalString(value, "package_size"),
|
|
2693
|
+
quantity: typeof quantity === "number" ? quantity : null,
|
|
2694
|
+
quantity_units: optionalString(value, "quantity_units"),
|
|
2695
|
+
medication_form: optionalString(value, "medication_form"),
|
|
2696
|
+
route: optionalString(value, "route"),
|
|
2697
|
+
states: states ?? [],
|
|
2698
|
+
status: status ?? "active"
|
|
2699
|
+
};
|
|
2700
|
+
} catch (error) {
|
|
2701
|
+
throw new Error(`${catalog_id}: ${error.message}`);
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
var parseCatalog = (value) => {
|
|
2705
|
+
const rows = Array.isArray(value) ? value : isRecord4(value) ? value.data : void 0;
|
|
2706
|
+
if (!Array.isArray(rows)) throw new Error("a catalog must be an array or {data: [...]}");
|
|
2707
|
+
return rows.map(parseCatalogItem);
|
|
2708
|
+
};
|
|
2590
2709
|
|
|
2591
2710
|
// src/state.ts
|
|
2592
2711
|
var DEFAULT_SETTINGS = {
|
|
2593
2712
|
tokenTtlSeconds: 86400,
|
|
2594
2713
|
staticTokens: [],
|
|
2595
2714
|
clients: [],
|
|
2596
|
-
autoAdvance: null
|
|
2715
|
+
autoAdvance: null,
|
|
2716
|
+
unknownPresets: "reject"
|
|
2597
2717
|
};
|
|
2598
2718
|
var RxVortexState = class {
|
|
2599
2719
|
constructor(sqlite, namespace, seed) {
|
|
@@ -2611,15 +2731,25 @@ var RxVortexState = class {
|
|
|
2611
2731
|
ids;
|
|
2612
2732
|
/** Re-apply the catalog and settings after a reset. */
|
|
2613
2733
|
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
|
-
}
|
|
2734
|
+
if (this.catalog.count() === 0) this.putCatalog(this.seed.catalog ?? DEFAULT_CATALOG, "merge");
|
|
2619
2735
|
if (!this.settings.has("settings")) {
|
|
2620
2736
|
this.settings.insert("settings", { ...DEFAULT_SETTINGS, ...this.seed.settings });
|
|
2621
2737
|
}
|
|
2622
2738
|
}
|
|
2739
|
+
/** Load rows: `replace` drops every other row first, `merge` upserts by `catalog_id`. */
|
|
2740
|
+
putCatalog(items, mode) {
|
|
2741
|
+
if (mode === "replace") {
|
|
2742
|
+
for (const row of this.catalog.list()) this.catalog.delete(row.id);
|
|
2743
|
+
}
|
|
2744
|
+
for (const item of items) {
|
|
2745
|
+
if (this.catalog.has(item.catalog_id)) this.catalog.update(item.catalog_id, item);
|
|
2746
|
+
else this.catalog.insert(item.catalog_id, item);
|
|
2747
|
+
}
|
|
2748
|
+
return this.catalog.count();
|
|
2749
|
+
}
|
|
2750
|
+
catalogRows() {
|
|
2751
|
+
return this.catalog.list({ order: "oldest" }).map((row) => row.value);
|
|
2752
|
+
}
|
|
2623
2753
|
current() {
|
|
2624
2754
|
return this.settings.get("settings") ?? DEFAULT_SETTINGS;
|
|
2625
2755
|
}
|
|
@@ -2730,10 +2860,10 @@ var RXVORTEX_PRESETS = {
|
|
|
2730
2860
|
};
|
|
2731
2861
|
var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2732
2862
|
var adminError3 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
|
|
2733
|
-
var
|
|
2863
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2734
2864
|
var parseAutoAdvance = (value) => {
|
|
2735
2865
|
if (value === null) return null;
|
|
2736
|
-
if (!
|
|
2866
|
+
if (!isRecord5(value)) return "autoAdvance must be {afterMs, path} or null";
|
|
2737
2867
|
if (typeof value.afterMs !== "number" || value.afterMs < 0)
|
|
2738
2868
|
return "autoAdvance.afterMs must be ms";
|
|
2739
2869
|
if (!Array.isArray(value.path) || value.path.some((s) => typeof s !== "string")) {
|
|
@@ -2744,7 +2874,7 @@ var parseAutoAdvance = (value) => {
|
|
|
2744
2874
|
var adminRoutes = (runtime) => ({
|
|
2745
2875
|
"GET /orders": ({ namespace }) => json3(200, { orders: runtime.instance(namespace).orders() }),
|
|
2746
2876
|
"POST /orders/:id/transition": ({ params, body, namespace }) => {
|
|
2747
|
-
if (!
|
|
2877
|
+
if (!isRecord5(body) || typeof body.to !== "string") {
|
|
2748
2878
|
return adminError3(
|
|
2749
2879
|
400,
|
|
2750
2880
|
'expected {"to": "<vendor status>", "trackingnumber"?, "shippingcarrier"?}'
|
|
@@ -2760,9 +2890,27 @@ var adminRoutes = (runtime) => ({
|
|
|
2760
2890
|
});
|
|
2761
2891
|
return order ? json3(200, order) : adminError3(404, `no order ${params.id}`);
|
|
2762
2892
|
},
|
|
2893
|
+
"GET /catalog": ({ namespace }) => json3(200, { data: runtime.instance(namespace).state.catalogRows() }),
|
|
2894
|
+
"PUT /catalog": ({ body, namespace }) => {
|
|
2895
|
+
const rows = isRecord5(body) ? body.items ?? body.data : void 0;
|
|
2896
|
+
if (!isRecord5(body) || !Array.isArray(rows)) {
|
|
2897
|
+
return adminError3(400, 'expected {"items": [{catalog_id, medication_name, \u2026}], "mode"?}');
|
|
2898
|
+
}
|
|
2899
|
+
const mode = body.mode ?? "replace";
|
|
2900
|
+
if (mode !== "replace" && mode !== "merge") {
|
|
2901
|
+
return adminError3(400, 'mode must be "replace" or "merge"');
|
|
2902
|
+
}
|
|
2903
|
+
let items;
|
|
2904
|
+
try {
|
|
2905
|
+
items = rows.map(parseCatalogItem);
|
|
2906
|
+
} catch (error) {
|
|
2907
|
+
return adminError3(400, `items: ${error.message}`);
|
|
2908
|
+
}
|
|
2909
|
+
return json3(200, { count: runtime.instance(namespace).state.putCatalog(items, mode) });
|
|
2910
|
+
},
|
|
2763
2911
|
"GET /settings": ({ namespace }) => json3(200, runtime.instance(namespace).state.current()),
|
|
2764
2912
|
"PUT /settings": ({ body, namespace }) => {
|
|
2765
|
-
if (!
|
|
2913
|
+
if (!isRecord5(body)) return adminError3(400, "expected a JSON object");
|
|
2766
2914
|
const patch = {};
|
|
2767
2915
|
if (body.tokenTtlSeconds !== void 0) {
|
|
2768
2916
|
if (typeof body.tokenTtlSeconds !== "number")
|
|
@@ -2776,7 +2924,7 @@ var adminRoutes = (runtime) => ({
|
|
|
2776
2924
|
if (body.clients !== void 0) {
|
|
2777
2925
|
if (!Array.isArray(body.clients))
|
|
2778
2926
|
return adminError3(400, "clients: [{client_id, client_secret}]");
|
|
2779
|
-
patch.clients = body.clients.filter(
|
|
2927
|
+
patch.clients = body.clients.filter(isRecord5).map((c) => ({
|
|
2780
2928
|
client_id: String(c.client_id),
|
|
2781
2929
|
client_secret: String(c.client_secret)
|
|
2782
2930
|
}));
|
|
@@ -2786,6 +2934,12 @@ var adminRoutes = (runtime) => ({
|
|
|
2786
2934
|
if (typeof parsed === "string") return adminError3(400, parsed);
|
|
2787
2935
|
patch.autoAdvance = parsed;
|
|
2788
2936
|
}
|
|
2937
|
+
if (body.unknownPresets !== void 0) {
|
|
2938
|
+
if (body.unknownPresets !== "reject" && body.unknownPresets !== "accept") {
|
|
2939
|
+
return adminError3(400, 'unknownPresets: "reject" | "accept"');
|
|
2940
|
+
}
|
|
2941
|
+
patch.unknownPresets = body.unknownPresets;
|
|
2942
|
+
}
|
|
2789
2943
|
return json3(200, runtime.instance(namespace).state.update(patch));
|
|
2790
2944
|
},
|
|
2791
2945
|
"POST /tick": ({ namespace }) => json3(200, { applied: runtime.instance(namespace).tick() })
|
|
@@ -2843,6 +2997,7 @@ var createRuntime2 = (options = {}) => {
|
|
|
2843
2997
|
// src/index.ts
|
|
2844
2998
|
var RXVORTEX_NAMESPACE = "rxvortex";
|
|
2845
2999
|
var TOKEN_PREFIX = "rxv_";
|
|
3000
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2846
3001
|
var base64url = (value) => toBase64(new TextEncoder().encode(value)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2847
3002
|
var fromBase64url = (value) => {
|
|
2848
3003
|
try {
|
|
@@ -2920,7 +3075,7 @@ var RxVortexAPI = class {
|
|
|
2920
3075
|
this.now = options.now ?? (() => Date.now());
|
|
2921
3076
|
this.onWebhook = options.onWebhook;
|
|
2922
3077
|
this.state = new RxVortexState(sqlite, namespace, {
|
|
2923
|
-
catalog: options.catalog
|
|
3078
|
+
catalog: options.catalog,
|
|
2924
3079
|
settings: options.settings ?? {}
|
|
2925
3080
|
});
|
|
2926
3081
|
const handlers = defineOperations({
|
|
@@ -2928,9 +3083,7 @@ var RxVortexAPI = class {
|
|
|
2928
3083
|
CreateOrder: (context) => this.createOrder(context),
|
|
2929
3084
|
GetOrder: (context) => this.getOrder(context),
|
|
2930
3085
|
CancelOrder: (context) => this.cancelOrder(context),
|
|
2931
|
-
ListPresetCatalogItems: () => jsonRes(200, {
|
|
2932
|
-
data: this.state.catalog.list({ order: "oldest" }).map((row) => row.value)
|
|
2933
|
-
})
|
|
3086
|
+
ListPresetCatalogItems: () => jsonRes(200, { data: this.state.catalogRows() })
|
|
2934
3087
|
});
|
|
2935
3088
|
this.service = createService({
|
|
2936
3089
|
document,
|
|
@@ -3009,9 +3162,10 @@ var RxVortexAPI = class {
|
|
|
3009
3162
|
}
|
|
3010
3163
|
const order = body.order;
|
|
3011
3164
|
const meds = body.medication_requests;
|
|
3165
|
+
const adoptable = (med) => this.state.current().unknownPresets === "accept" && UUID.test(med.preset_catalog_id);
|
|
3012
3166
|
const unknown = meds.find((med) => {
|
|
3013
3167
|
const item = this.state.catalog.get(med.preset_catalog_id);
|
|
3014
|
-
return
|
|
3168
|
+
return item ? item.status !== "active" : !adoptable(med);
|
|
3015
3169
|
});
|
|
3016
3170
|
if (unknown) {
|
|
3017
3171
|
return jsonRes(422, {
|
|
@@ -3033,6 +3187,9 @@ var RxVortexAPI = class {
|
|
|
3033
3187
|
{ ids: { orderId: existing.order_tracking_id } }
|
|
3034
3188
|
);
|
|
3035
3189
|
}
|
|
3190
|
+
for (const med of meds) {
|
|
3191
|
+
if (!this.state.catalog.has(med.preset_catalog_id)) this.adoptPreset(med);
|
|
3192
|
+
}
|
|
3036
3193
|
const now = this.iso();
|
|
3037
3194
|
const created = {
|
|
3038
3195
|
order_tracking_id: this.state.nextTrackingId(),
|
|
@@ -3076,6 +3233,25 @@ var RxVortexAPI = class {
|
|
|
3076
3233
|
{ ids }
|
|
3077
3234
|
);
|
|
3078
3235
|
}
|
|
3236
|
+
/**
|
|
3237
|
+
* Under `unknownPresets: "accept"`, a well-formed preset id the catalog lacks becomes an
|
|
3238
|
+
* active row described by the request, so later submits and catalog reads see it.
|
|
3239
|
+
*/
|
|
3240
|
+
adoptPreset(med) {
|
|
3241
|
+
const item = {
|
|
3242
|
+
catalog_id: med.preset_catalog_id,
|
|
3243
|
+
medication_name: med.medication_name,
|
|
3244
|
+
medication_strength: med.medication_strength ?? null,
|
|
3245
|
+
package_size: null,
|
|
3246
|
+
quantity: med.quantity,
|
|
3247
|
+
quantity_units: med.quantity_units,
|
|
3248
|
+
medication_form: med.medication_form ?? null,
|
|
3249
|
+
route: null,
|
|
3250
|
+
states: [],
|
|
3251
|
+
status: "active"
|
|
3252
|
+
};
|
|
3253
|
+
this.state.putCatalog([item], "merge");
|
|
3254
|
+
}
|
|
3079
3255
|
getOrder(context) {
|
|
3080
3256
|
const order = this.state.findOrder(context.params.orderId ?? "");
|
|
3081
3257
|
if (!order) return jsonRes(404, { message: "Order not found." });
|
|
@@ -3182,10 +3358,12 @@ export {
|
|
|
3182
3358
|
supportedOperationIds,
|
|
3183
3359
|
CUSTOM_CREAM_ANCHOR_PRESET_ID,
|
|
3184
3360
|
DEFAULT_CATALOG,
|
|
3361
|
+
parseCatalogItem,
|
|
3362
|
+
parseCatalog,
|
|
3185
3363
|
RXVORTEX_PRESETS,
|
|
3186
3364
|
createRuntime2 as createRuntime,
|
|
3187
3365
|
RXVORTEX_NAMESPACE,
|
|
3188
3366
|
tokenCredential,
|
|
3189
3367
|
RxVortexAPI
|
|
3190
3368
|
};
|
|
3191
|
-
//# sourceMappingURL=chunk-
|
|
3369
|
+
//# sourceMappingURL=chunk-5KK6W2PE.js.map
|