@crvouga/mockingbird-service-rxvortex 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-rxvortex
2
+
3
+ ## 0.1.0 (2026-09-22)
4
+
5
+ Initial release.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @crvouga/mockingbird-service-rxvortex
2
+
3
+ Stateful mock of the **RxVortex (Strive)** compounding-pharmacy API for test suites: the
4
+ client-credentials token, order submit, status, cancel, the recovery lookup by sender order id,
5
+ the preset catalog, and the signed status webhooks the pharmacy posts back. Orders move only
6
+ when a test says so (an admin transition or an auto-advance path on the mock clock), so an eRx
7
+ suite that waited up to 60 s on the real sandbox resolves in milliseconds.
8
+
9
+ - Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/rxvortex/SUPPORT.md)
10
+ - The vendor publishes no spec: the contract (`openapi.yaml`) is hand-authored from the wire
11
+ shapes our consumer reads and writes, and every field fallback it relies on is served.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install -D @crvouga/mockingbird-service-rxvortex
17
+ ```
18
+
19
+ ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
20
+ `npx mockingbird-rxvortex serve`, `createServer` from `./server` (Node), or `createRuntime` with
21
+ any Fetch server.
22
+
23
+ ## Usage
24
+
25
+ Point `RXVORTEX_API_URL` at the mock. Set `RXVORTEX_WEBHOOK_SECRET` in the app and pass the same
26
+ value as `--webhook-secret`.
27
+
28
+ ```bash
29
+ npx mockingbird-rxvortex serve --port 8791 \
30
+ --webhook-url http://127.0.0.1:3000/prescriptions/webhooks/rxvortex \
31
+ --webhook-secret "$RXVORTEX_WEBHOOK_SECRET" \
32
+ --auto-advance "2000:Fill,Shipping,Delivered"
33
+ ```
34
+
35
+ ```ts
36
+ import { createRuntime } from "@crvouga/mockingbird-service-rxvortex"
37
+
38
+ const rx = createRuntime({
39
+ webhooks: { url: "http://127.0.0.1:3000/prescriptions/webhooks/rxvortex", secret: "whsec-test" },
40
+ })
41
+ const post = (path: string, body: unknown, headers: Record<string, string> = {}) =>
42
+ rx.fetch(
43
+ new Request(`http://rxvortex.test${path}`, {
44
+ method: "POST",
45
+ headers: { "content-type": "application/json", ...headers },
46
+ body: JSON.stringify(body),
47
+ }),
48
+ )
49
+
50
+ const { access_token } = (await (
51
+ await post("/api/v1/generate-access-token", { client_id: "geviti", client_secret: "s" })
52
+ ).json()) as { access_token: string }
53
+ // …the app submits POST /api/v1/orders with Authorization: Bearer <access_token>…
54
+
55
+ // Move an order the way the pharmacy would; each step emits the signed status webhook.
56
+ await post("/__admin/orders/pay_123/transition", { to: "Shipping", trackingnumber: "1Z999" })
57
+ await post("/__admin/orders/pay_123/transition", { to: "Delivered" })
58
+ ```
59
+
60
+ ### Routes
61
+
62
+ | Route | Behaviour |
63
+ | --- | --- |
64
+ | `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). |
66
+ | `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
+ | `DELETE /api/v1/orders/{id}` | Cancels while `cancellable` (until shipped), emitting the webhook; otherwise 409. |
68
+ | `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. |
69
+
70
+ ### Webhooks
71
+
72
+ Every status change posts `{event: "order.status_updated", orderReferenceID, order_tracking_id,
73
+ tracking_id, sender_order_id, rxstatus, orderstatus, shipping_status, delivered_date,
74
+ trackingnumber, shippingcarrier, shippingservice, shipmenttrackingurl, updated_at}` with header
75
+ `x-rxvortex-webhook-secret: <secret>` (plain equality, as our receiver checks). Non-2xx answers
76
+ are retried (immediately, 5 s, 5 min, 30 min, 2 h). `GET /__admin/webhooks` lists deliveries,
77
+ `GET /__admin/webhooks/events` the payloads, `POST /__admin/webhooks/flush` runs pending retries
78
+ now, and `PUT /__admin/webhook-endpoints` sets per-namespace receivers.
79
+
80
+ ### Admin (beyond the standard contract)
81
+
82
+ | Route | Effect |
83
+ | --- | --- |
84
+ | `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/settings` | `{tokenTtlSeconds?, staticTokens?, clients?, autoAdvance?: {afterMs, path} \| null}` for the calling namespace. `staticTokens` admits `RXVORTEX_API_TOKEN` (the catalog client's static bearer). |
86
+ | `POST /__admin/tick` | Apply every auto-advance step that is due on the mock clock (the served mock also ticks every 100 ms). |
87
+ | `GET /__admin/orders` | The namespace's orders. |
88
+
89
+ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
90
+ `duplicate_sender_order_id`, `created_but_500` (creates, then 500; recovery succeeds),
91
+ `numeric_tracking_id`, `token_expired`, `stale_error_with_delivered_date`,
92
+ `validation_errors_array`, `validation_errors_object`, `validation_errors_empty`, `server_error`,
93
+ `webhook_duplicate`, `webhook_reorder`, `webhook_drop`.
94
+
95
+ ### Namespaces
96
+
97
+ Our backend's `fetch` cannot add headers, so a namespace can be chosen three ways:
98
+ `x-mockingbird-namespace`, a `/ns/<name>` prefix on `RXVORTEX_API_URL`, or by client id:
99
+ `PUT /__admin/credentials {"credentials": {"<RXVORTEX_CLIENT_ID>": "<namespace>"}}` (tokens carry
100
+ the client id they were issued to).
101
+
102
+ ### Deliberately not modelled
103
+
104
+ - Real fulfilment timing: nothing moves on its own unless `autoAdvance` is set.
105
+ - Patient and prescriber details are validated, never stored or echoed back.
106
+ - The live catalog: the default rows are synthesised in the live client's field names (no
107
+ sandbox recording exists); pass `catalog` to load recorded rows.
108
+ - Refills and multi-order shipments.
109
+
110
+ ## API
111
+
112
+ | Export | Kind | Description |
113
+ | --- | --- | --- |
114
+ | `RxVortexAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `transition(id, {to, …})`, `tick()`, `orders()`. Options: `sqlite`, `now`, `namespace`, `catalog`, `settings`, `onWebhook`. |
115
+ | `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: `webhooks: {url, secret, retryDelaysMs?, fetch?}`, `settings`, `catalog`, `tickMs`, `clock`, `seed`, `adminKey`, `onLog`. |
116
+ | `RXVORTEX_PRESETS` | object | Every named fault preset. |
117
+ | `RXVORTEX_NAMESPACE` | string | The service name, `"rxvortex"`. |
118
+ | `tokenCredential` | function | The client id a bearer token was issued to (how credentials map to namespaces). |
119
+ | `CUSTOM_CREAM_ANCHOR_PRESET_ID` | string | The sandbox custom-cream anchor preset id. |
120
+ | `DEFAULT_CATALOG` | array | The default preset catalog rows. |
121
+ | `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. |
123
+
124
+ Part of [mockingbird](https://github.com/crvouga/mockingbird).