@crvouga/mockingbird-service-fullscript 3.13.0 → 3.15.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.
@@ -0,0 +1,940 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
18
+
19
+ // <define:__MOCKINGBIRD_ADMIN_BRAND__>
20
+ var define_MOCKINGBIRD_ADMIN_BRAND_default = { vendor: "Fullscript", website: "https://fullscript.com", docs: "https://fullscript.dev", guide: "https://mockingbird.chrisvouga.dev/services/fullscript", logo: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgdmlld0JveD0iMCAwIDI1NiAyNTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxtYXNrIGlkPSJtYXNrMF8yNV84IiBzdHlsZT0ibWFzay10eXBlOmx1bWluYW5jZSIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iMCIgeT0iMTYiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjI1Ij4KPHBhdGggZD0iTTI1NiAxNkgwVjI0MC42NjJIMjU2VjE2WiIgZmlsbD0id2hpdGUiLz4KPC9tYXNrPgo8ZyBtYXNrPSJ1cmwoI21hc2swXzI1XzgpIj4KPHBhdGggZD0iTTIzMC4xNzQgMTAzLjU4OUMyMzAuMTc0IDEwMy41ODkgMjMwLjE3NCAxMDMuOTk4IDIzMC4xNzQgMTA0LjQwN0MyMzAuMTc0IDEwNy42NzcgMjI1LjI1OCAxNzYuMzM1IDE0MC40NTUgMjA3LjM5NVYxNjQuNDgzTDE5MS42NjQgMTA4LjkwMkMxOTYuMTcxIDEwMy45OTggMTk2LjE3MSA5Ni4yMzMzIDE5MC44NDUgOTEuMzI5MkMxODUuOTI5IDg2LjgzMzMgMTc4LjE0NSA4Ni44MzMzIDE3My4yMjkgOTIuMTQ2MUwxNDAuNDU1IDEyOC4xMTFWOTEuMzI5MkMxNDAuNDU1IDkxLjMyOTIgMTQwLjQ1NSA5MS4zMjkyIDE0MC40NTUgOTAuOTIwNEMxNDQuNTUyIDgxLjUyMDQgMTY0LjIxNiA0NC43Mzg5IDIxMi41NTcgNDQuNzM4OUMyMTIuOTY3IDQ0LjczODkgMjEzLjM3NyA0NC43Mzg5IDIxMy43ODcgNDQuNzM4OUMyMzMuMDQxIDY3LjYyNTMgMjMwLjE3NCAxMDMuMTgxIDIzMC4xNzQgMTAzLjU4OVpNMTE1Ljg3NSAxMjguMTExTDgyLjY5MDggOTIuMTQ2MUM3OC4xODQzIDg3LjI0MiA3MC40MDA2IDg2LjgzMzMgNjUuNDg0NSA5MS4zMjkyQzYwLjU2ODUgOTUuODI0NiA2MC4xNTg5IDEwMy45OTggNjQuNjY1MyAxMDguOTAyTDExNS44NzUgMTY0LjQ4M1YyMDYuOTg3QzMxLjA3MiAxNzUuOTI3IDI2LjU2NTYgMTA2Ljg1OSAyNi4xNTU5IDEwMy45OThDMjYuMTU1OSAxMDMuNTg5IDI2LjE1NTkgMTAzLjU4OSAyNi4xNTU5IDEwMy4xODFDMjYuMTU1OSAxMDIuNzcyIDIyLjg3ODUgNjcuMjE2NiA0Mi4xMzMyIDQ0LjMzMDJDNDIuNTQyOCA0NC4zMzAyIDQyLjk1MjUgNDQuMzMwMiA0My43NzE5IDQ0LjMzMDJDOTIuNTIzIDQ0LjMzMDIgMTExLjc3OCA4MS41MjA0IDExNS44NzUgOTAuNTExN0MxMTUuODc1IDkwLjUxMTcgMTE1Ljg3NSA5MC41MTE3IDExNS44NzUgOTAuOTIwNFYxMjguMTExWk0yMjguNTM1IDIzLjQ4NzNDMjI2LjQ4NiAyMS40NDM5IDIyMy42MTkgMjAuMjE3OCAyMjAuNzUxIDE5LjgwOTJDMTc0LjQ1OCAxNi45NDg0IDE0NC4xNDIgNDAuNjUyMSAxMjguMTY1IDYyLjcyMTJDMTEyLjE4NyA0MC42NTIxIDgyLjI4MTIgMTYuOTQ4NCAzNS41Nzg0IDE5LjgwOTJDMzIuNzEwNyAxOS44MDkyIDI5Ljg0MjkgMjEuNDQzOSAyNy43OTQ2IDIzLjQ4NzNDLTIuMTExNjMgNTMuMzIxMyAxLjE2NTc3IDEwMC43MjkgMS41NzU0NSAxMDUuMjI0QzEuNTc1NDUgMTA4LjQ5NCAzLjYyMzgxIDEzMS43ODkgMTguNzgxNyAxNTguNzYyQzMzLjEyMDQgMTg0LjUwOSA2My40MzYxIDIxOC40MyAxMjMuNjU4IDIzNS41OTRDMTI0LjA2OCAyMzUuNTk0IDEyNC40NzggMjM1LjU5NCAxMjQuNDc4IDIzNi4wMDNDMTI0Ljg4NyAyMzYuMDAzIDEyNC44ODcgMjM2LjAwMyAxMjUuMjk3IDIzNi4wMDNDMTI1LjcwNyAyMzYuMDAzIDEyNS43MDcgMjM2LjAwMyAxMjYuMTE2IDIzNi4wMDNDMTI2LjUyNiAyMzYuMDAzIDEyNy4zNDYgMjM2LjAwMyAxMjcuNzU1IDIzNi4wMDNDMTI4LjE2NSAyMzYuMDAzIDEyOC45ODQgMjM2LjAwMyAxMjkuMzk0IDIzNi4wMDNDMTI5LjgwMyAyMzYuMDAzIDEyOS44MDMgMjM2LjAwMyAxMzAuMjEzIDIzNi4wMDNDMTMwLjYyMyAyMzYuMDAzIDEzMC42MjMgMjM2LjAwMyAxMzEuMDMyIDIzNi4wMDNDMTMxLjQ0MiAyMzYuMDAzIDEzMS44NTIgMjM2LjAwMyAxMzIuMjYyIDIzNS41OTRDMjQ0LjEwMiAyMDQuMTI2IDI1My45MzUgMTE0LjIxNSAyNTQuMzQ1IDEwNS42MzNDMjU0Ljc1NCAxMDMuMTgxIDI1OC44NTEgNTQuMTM4NyAyMjguNTM1IDIzLjQ4NzNaIiBmaWxsPSIjODhCMDRCIi8+CjwvZz4KPC9zdmc+Cg==", color: null, description: "Empowering providers and patients with clinical insights. Use Fullscript to order lab tests, manage supplement plans, and track health progress together." };
21
+
22
+ // mockingbird-service-runtime:@crvouga/mockingbird-runtime/service
23
+ var service_exports = {};
24
+ __export(service_exports, {
25
+ PACKAGE_VERSION: () => PACKAGE_VERSION,
26
+ createRuntime: () => createRuntime
27
+ });
28
+ __reExport(service_exports, service_star);
29
+ import * as service_star from "@crvouga/mockingbird-runtime/service";
30
+ import { PACKAGE_VERSION as SHARED_VERSION, createRuntime as sharedRuntime } from "@crvouga/mockingbird-runtime/service";
31
+ var PACKAGE_VERSION = true ? "3.15.0" : SHARED_VERSION;
32
+ var ADMIN_BRAND = typeof define_MOCKINGBIRD_ADMIN_BRAND_default === "object" ? define_MOCKINGBIRD_ADMIN_BRAND_default : void 0;
33
+ var createRuntime = (options) => sharedRuntime({
34
+ ...options,
35
+ version: options.version ?? PACKAGE_VERSION,
36
+ adminBrand: options.adminBrand ?? ADMIN_BRAND
37
+ });
38
+
39
+ // src/generated/openapi.ts
40
+ var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Fullscript API (Mockingbird subset)","description":"Stateful emulator subset of the Fullscript API for lab ordering: per-practitioner OAuth (authorize, token with authorization_code and refresh_token grants, revoke), clinic, embeddable session grants, lab orders, lab-order events, expiring result PDFs, and signed webhooks. Hand-authored from the EMR consumer (fullscript-api-client.ts, fullscript-event-model.ts, fullscript-webhook-signature.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Wire shapes from the consumer app's EMR backend (its Fullscript services and routers); the vendor's API reference is behind a partner login."}},"servers":[{"url":"https://api-us-snd.fullscript.io"}],"security":[{"bearerAuth":[]}],"paths":{"/api/oauth/token":{"post":{"operationId":"OAuthToken","description":"Exchange an authorization code, or rotate a refresh token (the old refresh token stops working).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"invalid_grant / unsupported_grant_type / invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}}}},"/api/oauth/revoke":{"post":{"operationId":"OAuthRevoke","description":"Revoke an access or refresh token (RFC 7009: always 200).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeRequest"}}}}}},"/api/clinic":{"get":{"operationId":"GetClinic","description":"The token's clinic.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Clinic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClinicResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/embeddable/session_grants":{"post":{"operationId":"CreateSessionGrant","description":"A short-lived secret for the embedded Fullscript widget.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"201":{"description":"Grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionGrant"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/labs/orders":{"get":{"operationId":"ListLabOrders","description":"The clinic's lab orders (optionally one patient's), paged.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrdersPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"patient_id","in":"query","required":false,"schema":{"type":"string","enum":["pat_seed_1","pat_seed_2","pat_missing"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/clinic/labs/orders/{orderId}":{"get":{"operationId":"GetLabOrder","description":"One lab order with tests, per-test results and the aggregated result.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrderResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string","enum":["lo_seed_1","lo_seed_2","lo_seed_3","lo_missing"]}}]}},"/api/events/lab_orders":{"get":{"operationId":"ListLabOrderEvents","description":"The clinic's lab_order.updated events, newest first by default.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["DESC","ASC"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/events/{eventId}":{"get":{"operationId":"GetEvent","description":"One event with its data.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","enum":["evt_1","evt_2","evt_3","evt_missing"]}}]}},"/oauth/authorize":{"get":{"operationId":"Authorize","description":"The consent page: auto-approves as the requested (or default) practitioner and redirects to redirect_uri with code and state.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Browser redirect flow; covered by acceptance tests."}},"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"response_type","in":"query","required":true,"schema":{"type":"string","enum":["code"]}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string"}},{"name":"practitioner_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect back with ?code=&state="},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/results/{artifactId}":{"get":{"operationId":"GetResultPdf","description":"An expiring result PDF URL (what pdf_url points at).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Binary PDF behind an expiring signed URL; covered by acceptance tests."}},"parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"string"}},{"name":"signature","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Expired or bad signature"},"404":{"description":"Unknown artifact"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-client"]},"client_secret":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-secret"]},"code":{"type":"string","maxLength":64},"refresh_token":{"type":"string","maxLength":200},"redirect_uri":{"type":"string","maxLength":200,"examples":["https://emr.example.com/v1/fullscript/oauth/callback"]}}},"RevokeRequest":{"type":"object","required":["client_id","client_secret","token"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64},"token":{"type":"string","minLength":1,"maxLength":200}}},"OAuthTokenResponse":{"type":"object","required":["oauth"],"properties":{"oauth":{"type":"object","required":["access_token","token_type","expires_in","refresh_token","scope","created_at","resource_owner"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"},"refresh_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"scope":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"resource_owner":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["Practitioner","Staff"]},"clinic_id":{"type":"string"}}}}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"ApiErrors":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"ClinicResponse":{"type":"object","required":["clinic"],"properties":{"clinic":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}},"SessionGrant":{"type":"object","required":["secret_token","expires_at"],"properties":{"secret_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrderSummary":{"type":"object","required":["id","state","treatment_plan_id","patient_id","created_at","updated_at"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrdersPage":{"type":"object","required":["orders","meta"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/LabOrderSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"LabOrderResponse":{"type":"object","required":["order"],"properties":{"order":{"type":"object","required":["id","state","treatment_plan_id","tests","results","latest_aggregated_result"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"collection_method":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"tests":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"lab_type":{"type":"string"}}}},"results":{"type":"array","items":{"type":"object","required":["id","name","state","pdf_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}},"latest_aggregated_result":{"type":["object","null"],"properties":{"id":{"type":"string"},"artifact_id":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"status":{"type":"string"}}}}}}},"EventSummary":{"type":"object","required":["id","type","clinic_id","created_at"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["lab_order.updated"]},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"EventsPage":{"type":"object","required":["events","meta"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"EventResponse":{"type":"object","required":["event"],"properties":{"event":{"type":"object","required":["id","type","clinic_id","created_at","data"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"data":{"type":"object"}}}}}}}}`);
41
+ var operationIds = ["OAuthToken", "OAuthRevoke", "GetClinic", "CreateSessionGrant", "ListLabOrders", "GetLabOrder", "ListLabOrderEvents", "GetEvent", "Authorize", "GetResultPdf"];
42
+ var supportedOperationIds = ["OAuthToken", "OAuthRevoke", "GetClinic", "CreateSessionGrant", "ListLabOrders", "GetLabOrder", "ListLabOrderEvents", "GetEvent", "Authorize", "GetResultPdf"];
43
+
44
+ // src/pdf.ts
45
+ var RESULT_PDF = new TextEncoder().encode(
46
+ "%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n4 0 obj\n<< /Length 64 >>\nstream\nBT /F1 18 Tf 72 720 Td (Mockingbird Fullscript lab result) Tj ET\nendstream\nendobj\n5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\nxref\n0 6\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n0000000115 00000 n \n0000000241 00000 n \n0000000355 00000 n \ntrailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n425\n%%EOF\n"
47
+ );
48
+
49
+ // src/state.ts
50
+ var LAB_ORDER_STATES = [
51
+ "not_purchased",
52
+ "purchased",
53
+ "schedule_appointment",
54
+ "upcoming_appointment",
55
+ "processing",
56
+ "partial_results",
57
+ "results_ready",
58
+ "interpretation_shared",
59
+ "results_amended"
60
+ ];
61
+ var isLabOrderState = (value) => typeof value === "string" && LAB_ORDER_STATES.includes(value);
62
+ var stateRank = (state) => LAB_ORDER_STATES.indexOf(state);
63
+ var DEFAULT_SETTINGS = {
64
+ clients: [],
65
+ accessTokenTtlSeconds: 7200,
66
+ codeTtlSeconds: 600,
67
+ pdfUrlTtlSeconds: 900,
68
+ resultsBaseUrl: null
69
+ };
70
+ var DEFAULT_CLINIC = {
71
+ id: "clinic_mock_1",
72
+ name: "Acme Mock Clinic",
73
+ created_at: "2025-01-01T00:00:00.000Z"
74
+ };
75
+ var DEFAULT_PRACTITIONER = {
76
+ id: "prac_mock_1",
77
+ type: "Practitioner",
78
+ clinicId: DEFAULT_CLINIC.id
79
+ };
80
+ var FullscriptState = class {
81
+ constructor(sqlite, namespace, seed) {
82
+ this.seed = seed;
83
+ this.practitioners = new service_exports.Collection(sqlite, namespace, "practitioners");
84
+ this.clinics = new service_exports.Collection(sqlite, namespace, "clinics");
85
+ this.codes = new service_exports.Collection(sqlite, namespace, "codes");
86
+ this.refresh = new service_exports.Collection(sqlite, namespace, "refresh_tokens");
87
+ this.revoked = new service_exports.Collection(sqlite, namespace, "revoked_tokens");
88
+ this.orders = new service_exports.Collection(sqlite, namespace, "lab_orders");
89
+ this.events = new service_exports.Collection(sqlite, namespace, "events");
90
+ this.settings = new service_exports.Collection(sqlite, namespace, "settings");
91
+ this.counters = new service_exports.Collection(sqlite, namespace, "counters");
92
+ this.ensureSeeded();
93
+ }
94
+ seed;
95
+ practitioners;
96
+ clinics;
97
+ codes;
98
+ refresh;
99
+ revoked;
100
+ orders;
101
+ events;
102
+ settings;
103
+ counters;
104
+ ensureSeeded() {
105
+ if (this.clinics.count() === 0) this.clinics.insert(DEFAULT_CLINIC.id, DEFAULT_CLINIC);
106
+ if (this.practitioners.count() === 0) {
107
+ this.practitioners.insert(DEFAULT_PRACTITIONER.id, DEFAULT_PRACTITIONER);
108
+ }
109
+ if (!this.settings.has("settings")) {
110
+ this.settings.insert("settings", { ...DEFAULT_SETTINGS, ...this.seed });
111
+ }
112
+ }
113
+ current() {
114
+ return this.settings.get("settings") ?? DEFAULT_SETTINGS;
115
+ }
116
+ update(patch) {
117
+ const next = { ...this.current(), ...patch };
118
+ this.settings.insert("settings", next);
119
+ return next;
120
+ }
121
+ next(kind) {
122
+ const current = this.counters.get("counters") ?? {
123
+ code: 0,
124
+ refresh: 0,
125
+ order: 0,
126
+ event: 0,
127
+ artifact: 0
128
+ };
129
+ const next = { ...current, [kind]: current[kind] + 1 };
130
+ this.counters.insert("counters", next);
131
+ return next[kind];
132
+ }
133
+ };
134
+
135
+ // src/runtime.ts
136
+ var SIGNATURE_HEADER = "Fullscript-Signature";
137
+ var API = "/api/";
138
+ var FULLSCRIPT_PRESETS = {
139
+ token_expired: {
140
+ description: "API calls answer 401 token_expired before the token's 2 h (our token service refreshes)",
141
+ rules: [
142
+ { pathPrefix: "/api/clinic", effect: "token_expired" },
143
+ { pathPrefix: "/api/events", effect: "token_expired" }
144
+ ]
145
+ },
146
+ invalid_grant: {
147
+ description: "The refresh_token grant answers 400 invalid_grant (the practitioner must reconnect)",
148
+ rules: [{ operationId: "OAuthToken", effect: "invalid_grant" }]
149
+ },
150
+ rate_limited: {
151
+ description: "API calls answer 429 rate_limited",
152
+ rules: [
153
+ {
154
+ pathPrefix: API,
155
+ status: 429,
156
+ body: { errors: [{ code: "rate_limited", message: "Too many requests" }] }
157
+ }
158
+ ]
159
+ },
160
+ server_error: {
161
+ description: "API calls answer 500",
162
+ rules: [
163
+ {
164
+ pathPrefix: API,
165
+ status: 500,
166
+ body: { errors: [{ code: "internal_server_error", message: "Something went wrong" }] }
167
+ }
168
+ ]
169
+ },
170
+ events_schema_drift: {
171
+ description: "The lab-order events list answers an unexpected event type (our zod literal rejects it)",
172
+ rules: [{ operationId: "ListLabOrderEvents", effect: "events_schema_drift" }]
173
+ },
174
+ pdf_not_pdf: {
175
+ description: "Result PDF URLs answer 200 text/html (our downloader rejects the content type)",
176
+ rules: [
177
+ {
178
+ operationId: "GetResultPdf",
179
+ status: 200,
180
+ body: "<html>Sign in</html>",
181
+ headers: { "content-type": "text/html" }
182
+ }
183
+ ]
184
+ },
185
+ pdf_redirect: {
186
+ description: "Result PDF URLs answer a 302 (our downloader refuses redirects)",
187
+ rules: [
188
+ {
189
+ operationId: "GetResultPdf",
190
+ status: 302,
191
+ body: "",
192
+ headers: { location: "https://login.fullscript.com/" }
193
+ }
194
+ ]
195
+ },
196
+ pdf_expired: {
197
+ description: "Result PDF URLs answer 403 as if expired",
198
+ rules: [
199
+ {
200
+ operationId: "GetResultPdf",
201
+ status: 403,
202
+ body: "Request has expired",
203
+ headers: { "content-type": "text/plain" }
204
+ }
205
+ ]
206
+ },
207
+ connection_drop: {
208
+ description: "The connection drops before any answer (fetch rejects)",
209
+ rules: [{ pathPrefix: API, drop: true }]
210
+ },
211
+ slow: {
212
+ description: "API calls answer after 12 s (past our client's 10 s timeout)",
213
+ rules: [{ pathPrefix: API, latencyMs: 12e3 }]
214
+ },
215
+ webhook_duplicate: {
216
+ description: "The next webhook is delivered twice",
217
+ webhook: { mode: "duplicate" }
218
+ },
219
+ webhook_reorder: {
220
+ description: "The next two webhooks arrive swapped",
221
+ webhook: { mode: "reorder" }
222
+ },
223
+ webhook_drop: { description: "The next webhook is never delivered", webhook: { mode: "drop" } }
224
+ };
225
+ var json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
226
+ var adminError = (status, message) => json(status, { error: { type: "mockingbird_admin", message } });
227
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
228
+ var echoes = async (response, challenge) => {
229
+ try {
230
+ const body = await response.clone().json();
231
+ return body.challenge === challenge;
232
+ } catch {
233
+ return false;
234
+ }
235
+ };
236
+ var adminRoutes = (send, challenge) => (runtime) => ({
237
+ "POST /oauth/codes": ({ body, namespace }) => {
238
+ const input = isRecord(body) ? body : {};
239
+ const api = runtime.instance(namespace);
240
+ const practitionerId = typeof input.practitionerId === "string" ? input.practitionerId : "prac_mock_1";
241
+ if (!api.state.practitioners.get(practitionerId))
242
+ return adminError(404, `no practitioner ${practitionerId}`);
243
+ const code = api.issueCode(
244
+ practitionerId,
245
+ typeof input.clientId === "string" ? input.clientId : "any",
246
+ typeof input.redirectUri === "string" ? input.redirectUri : null
247
+ );
248
+ return json(201, { code });
249
+ },
250
+ "POST /practitioners": ({ body, namespace }) => {
251
+ if (!isRecord(body) || typeof body.id !== "string" || typeof body.clinicId !== "string") {
252
+ return adminError(
253
+ 400,
254
+ 'expected {"id", "clinicId", "type"?: "Practitioner"|"Staff", "clinicName"?}'
255
+ );
256
+ }
257
+ const api = runtime.instance(namespace);
258
+ if (!api.state.clinics.get(body.clinicId)) {
259
+ api.state.clinics.insert(body.clinicId, {
260
+ id: body.clinicId,
261
+ name: typeof body.clinicName === "string" ? body.clinicName : body.clinicId,
262
+ created_at: new Date(runtime.namespaceClock(namespace).now()).toISOString()
263
+ });
264
+ }
265
+ const practitioner = {
266
+ id: body.id,
267
+ clinicId: body.clinicId,
268
+ type: body.type === "Staff" ? "Staff" : "Practitioner"
269
+ };
270
+ api.state.practitioners.insert(practitioner.id, practitioner);
271
+ return json(201, practitioner);
272
+ },
273
+ "GET /lab-orders": ({ namespace }) => json(200, { orders: runtime.instance(namespace).labOrders() }),
274
+ "POST /lab-orders": ({ body, namespace }) => {
275
+ if (!isRecord(body) || typeof body.patientId !== "string") {
276
+ return adminError(
277
+ 400,
278
+ 'expected {"patientId", "id"?, "clinicId"?, "treatmentPlanId"?, "name"?, "collectionMethod"?, "tests"?: [names]}'
279
+ );
280
+ }
281
+ const text = (key) => typeof body[key] === "string" ? { [key]: body[key] } : {};
282
+ const order = runtime.instance(namespace).createOrder({
283
+ patientId: body.patientId,
284
+ ...text("id"),
285
+ ...text("clinicId"),
286
+ ...text("treatmentPlanId"),
287
+ ...text("name"),
288
+ ...text("collectionMethod"),
289
+ ...Array.isArray(body.tests) ? { tests: body.tests.map(String) } : {}
290
+ });
291
+ return json(201, order);
292
+ },
293
+ "POST /lab-orders/:id/transition": ({ params, body, namespace }) => {
294
+ if (!isRecord(body) || !isLabOrderState(body.to)) {
295
+ return adminError(
296
+ 400,
297
+ 'expected {"to": "<purchased|schedule_appointment|upcoming_appointment|processing|partial_results|results_ready|interpretation_shared|results_amended>"}'
298
+ );
299
+ }
300
+ try {
301
+ return json(200, runtime.instance(namespace).transition(params.id, body.to));
302
+ } catch (error) {
303
+ if (error instanceof service_exports.HttpError) return error.toResponse();
304
+ throw error;
305
+ }
306
+ },
307
+ "GET /events": ({ namespace }) => json(200, { events: runtime.instance(namespace).eventsList() }),
308
+ "POST /webhooks/verify": async ({ namespace }) => {
309
+ const endpoints = runtime.webhooks?.endpoints(namespace) ?? [];
310
+ const results = await Promise.all(
311
+ endpoints.map(async (endpoint) => {
312
+ try {
313
+ const response = await send(
314
+ new Request(endpoint.url, {
315
+ method: "POST",
316
+ headers: { "content-type": "application/json" }
317
+ })
318
+ );
319
+ return {
320
+ url: endpoint.url,
321
+ status: response.status,
322
+ challengeEchoed: challenge ? await echoes(response, challenge) : null
323
+ };
324
+ } catch (error) {
325
+ return {
326
+ url: endpoint.url,
327
+ status: null,
328
+ error: error instanceof Error ? error.message : String(error)
329
+ };
330
+ }
331
+ })
332
+ );
333
+ return json(200, { endpoints: results });
334
+ },
335
+ "GET /settings": ({ namespace }) => json(200, runtime.instance(namespace).state.current()),
336
+ "PUT /settings": ({ body, namespace }) => {
337
+ if (!isRecord(body)) return adminError(400, "expected a JSON object");
338
+ const patch = {};
339
+ for (const key of ["accessTokenTtlSeconds", "codeTtlSeconds", "pdfUrlTtlSeconds"]) {
340
+ if (body[key] !== void 0) {
341
+ if (typeof body[key] !== "number") return adminError(400, `${key}: number`);
342
+ patch[key] = body[key];
343
+ }
344
+ }
345
+ if (body.resultsBaseUrl !== void 0) {
346
+ if (body.resultsBaseUrl !== null && typeof body.resultsBaseUrl !== "string")
347
+ return adminError(400, "resultsBaseUrl: string | null");
348
+ patch.resultsBaseUrl = body.resultsBaseUrl;
349
+ }
350
+ if (body.clients !== void 0) {
351
+ if (!Array.isArray(body.clients))
352
+ return adminError(400, "clients: [{clientId, clientSecret}]");
353
+ patch.clients = body.clients.filter(isRecord).map((c) => ({ clientId: String(c.clientId), clientSecret: String(c.clientSecret) }));
354
+ }
355
+ return json(200, runtime.instance(namespace).state.update(patch));
356
+ }
357
+ });
358
+ var createRuntime2 = (options = {}) => {
359
+ const { retryDelaysMs, fetch: rawSend, challenge, ...endpoint } = options.webhooks ?? { url: "" };
360
+ const send = rawSend ?? ((request) => fetch(request));
361
+ const hub = (0, service_exports.createWebhookHub)({
362
+ signer: service_exports.signers.timestamped(SIGNATURE_HEADER),
363
+ ...retryDelaysMs ? { retryDelaysMs } : {},
364
+ // Fullscript counts a delivery as acknowledged only when the receiver echoes the
365
+ // challenge key; anything else is retried (recorded as a 502).
366
+ fetch: async (request) => {
367
+ const response = await send(request);
368
+ if (!challenge || !response.ok || await echoes(response, challenge)) return response;
369
+ return new Response(null, { status: 502, statusText: "challenge not echoed" });
370
+ },
371
+ endpoints: options.webhooks ? [endpoint] : []
372
+ });
373
+ const runtime = createRuntime({
374
+ name: FULLSCRIPT_NAMESPACE,
375
+ document,
376
+ ...options.sqlite ? { sqlite: options.sqlite } : {},
377
+ ...options.clock ? { clock: options.clock } : {},
378
+ ...options.seed !== void 0 ? { seed: options.seed } : {},
379
+ ...options.adminPrefix !== void 0 ? { adminPrefix: options.adminPrefix } : {},
380
+ ...options.adminKey !== void 0 ? { adminKey: options.adminKey } : {},
381
+ ...options.adminControls !== void 0 ? { adminControls: options.adminControls } : {},
382
+ ...options.onLog ? { onLog: options.onLog } : {},
383
+ credential: tokenCredential,
384
+ presets: FULLSCRIPT_PRESETS,
385
+ webhooks: hub,
386
+ create: ({ sqlite, namespace, publicNamespace, adminPrefix, clock }) => new FullscriptAPI({
387
+ sqlite,
388
+ namespace,
389
+ publicNamespace,
390
+ adminPrefix,
391
+ now: clock.now,
392
+ ...options.settings ? { settings: options.settings } : {},
393
+ ...options.orders ? { orders: options.orders } : {},
394
+ onEvent: (event) => hub.publish({
395
+ namespace: publicNamespace,
396
+ type: event.type,
397
+ id: event.id,
398
+ body: envelope(event)
399
+ })
400
+ }),
401
+ describe: () => ({ webhooks: hub.endpoints("default").length > 0 ? "on" : "off" }),
402
+ admin: adminRoutes(send, challenge)
403
+ });
404
+ return Object.assign(runtime, { webhooks: hub });
405
+ };
406
+
407
+ // src/index.ts
408
+ var FULLSCRIPT_NAMESPACE = "fullscript";
409
+ var SCOPE = "clinic:read labs:read labs:write patients:read";
410
+ var TOKEN_PREFIX = "fsat_";
411
+ var apiErrors = (status, code, message) => (0, service_exports.jsonRes)(status, { errors: [{ code, message }] });
412
+ var oauthError = (status, error, description) => (0, service_exports.jsonRes)(status, { error, error_description: description });
413
+ var INVALID_GRANT = "The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.";
414
+ var INVALID_CLIENT = "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.";
415
+ var base64url = (value) => (0, service_exports.toBase64)(new TextEncoder().encode(value)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
416
+ var fromBase64url = (value) => {
417
+ try {
418
+ return new TextDecoder("utf-8", { fatal: true }).decode(
419
+ (0, service_exports.fromBase64)(value.replace(/-/g, "+").replace(/_/g, "/"))
420
+ );
421
+ } catch {
422
+ return void 0;
423
+ }
424
+ };
425
+ var sign = (payload, issuedAt) => (0, service_exports.opaqueToken)(`fullscript:${payload}:${issuedAt}`, 32);
426
+ var issueAccessToken = (claims, issuedAtSeconds) => {
427
+ const payload = base64url(
428
+ JSON.stringify([claims.clientId, claims.practitionerId, claims.clinicId, claims.type])
429
+ );
430
+ return `${TOKEN_PREFIX}${payload}.${issuedAtSeconds}.${sign(payload, issuedAtSeconds)}`;
431
+ };
432
+ var readAccessToken = (token) => {
433
+ if (!token.startsWith(TOKEN_PREFIX)) return void 0;
434
+ const [payload, issued, signature] = token.slice(TOKEN_PREFIX.length).split(".");
435
+ const issuedAt = Number(issued);
436
+ if (!payload || !Number.isInteger(issuedAt) || signature !== sign(payload, issuedAt))
437
+ return void 0;
438
+ const decoded = fromBase64url(payload);
439
+ try {
440
+ const [clientId, practitionerId, clinicId, type] = JSON.parse(decoded ?? "");
441
+ if (!clientId || !practitionerId || !clinicId) return void 0;
442
+ return {
443
+ clientId,
444
+ practitionerId,
445
+ clinicId,
446
+ type: type === "Staff" ? "Staff" : "Practitioner",
447
+ issuedAt
448
+ };
449
+ } catch {
450
+ return void 0;
451
+ }
452
+ };
453
+ var tokenCredential = (request) => {
454
+ const token = (0, service_exports.bearerToken)(request);
455
+ return token ? readAccessToken(token)?.clientId ?? token : void 0;
456
+ };
457
+ var publicEvent = (event) => ({
458
+ id: event.id,
459
+ type: event.type,
460
+ clinic_id: event.clinic_id,
461
+ created_at: event.created_at,
462
+ data: event.data
463
+ });
464
+ var envelope = (event) => ({ event_payload: { event: publicEvent(event) } });
465
+ var page = (context) => {
466
+ const q = context.query;
467
+ const block = q.page ?? {};
468
+ const number = service_exports.coerce.integer(block.number ?? "1");
469
+ const size = service_exports.coerce.integer(block.size ?? "20");
470
+ return {
471
+ number: number.ok && number.value > 0 ? number.value : 1,
472
+ size: size.ok && size.value > 0 ? Math.min(size.value, 100) : 20
473
+ };
474
+ };
475
+ var meta = (current, size, total) => {
476
+ const totalPages = Math.max(1, Math.ceil(total / size));
477
+ return {
478
+ current_page: current,
479
+ next_page: current < totalPages ? current + 1 : null,
480
+ prev_page: current > 1 ? current - 1 : null,
481
+ total_pages: totalPages,
482
+ total_count: total
483
+ };
484
+ };
485
+ var FullscriptAPI = class {
486
+ app;
487
+ sqlite;
488
+ state;
489
+ service;
490
+ now;
491
+ prefix;
492
+ onEvent;
493
+ constructor(options = {}) {
494
+ const sqlite = (0, service_exports.bootSqlite)(options.sqlite);
495
+ const namespace = options.namespace ?? FULLSCRIPT_NAMESPACE;
496
+ this.now = options.now ?? (() => Date.now());
497
+ this.onEvent = options.onEvent;
498
+ this.prefix = options.publicNamespace && options.publicNamespace !== "default" ? `${options.adminPrefix ?? "/__admin"}/ns/${encodeURIComponent(options.publicNamespace)}` : "";
499
+ this.state = new FullscriptState(sqlite, namespace, options.settings ?? {});
500
+ const handlers = (0, service_exports.defineOperations)({
501
+ OAuthToken: (context) => this.token(context),
502
+ OAuthRevoke: (context) => this.revoke(context),
503
+ GetClinic: (context) => {
504
+ const claims = this.claims(context);
505
+ const clinic = this.state.clinics.get(claims.clinicId);
506
+ if (!clinic) return apiErrors(404, "not_found", "Clinic not found");
507
+ return (0, service_exports.jsonRes)(200, { clinic });
508
+ },
509
+ CreateSessionGrant: (context) => {
510
+ const claims = this.claims(context);
511
+ const expires = this.now() + 5 * 6e4;
512
+ return (0, service_exports.jsonRes)(201, {
513
+ secret_token: `sg_${(0, service_exports.opaqueToken)(`${claims.practitionerId}:${this.now()}`, 40)}`,
514
+ expires_at: new Date(expires).toISOString()
515
+ });
516
+ },
517
+ ListLabOrders: (context) => {
518
+ const claims = this.claims(context);
519
+ const patient = typeof context.query.patient_id === "string" ? context.query.patient_id : void 0;
520
+ const rows = this.state.orders.list({
521
+ order: "oldest",
522
+ where: (o) => o.clinicId === claims.clinicId && (!patient || o.patientId === patient)
523
+ }).map((r) => r.value);
524
+ const p = page(context);
525
+ return (0, service_exports.jsonRes)(200, {
526
+ orders: rows.slice((p.number - 1) * p.size, p.number * p.size).map((o) => this.summary(o)),
527
+ meta: meta(p.number, p.size, rows.length)
528
+ });
529
+ },
530
+ GetLabOrder: (context) => {
531
+ const claims = this.claims(context);
532
+ const order = this.state.orders.get(context.params.orderId ?? "");
533
+ if (!order || order.clinicId !== claims.clinicId)
534
+ return apiErrors(404, "not_found", "Lab order not found");
535
+ return (0, service_exports.annotateResponse)((0, service_exports.jsonRes)(200, { order: this.detail(order, context) }), {
536
+ ids: { orderId: order.id }
537
+ });
538
+ },
539
+ ListLabOrderEvents: (context) => {
540
+ const claims = this.claims(context);
541
+ const ascending = context.query.order_by === "ASC";
542
+ const rows = this.state.events.list({
543
+ order: ascending ? "oldest" : "newest",
544
+ where: (e) => e.clinic_id === claims.clinicId && e.type === "lab_order.updated"
545
+ }).map((r) => r.value);
546
+ const p = page(context);
547
+ const drift = (0, service_exports.faultEffect)(context.request, "events_schema_drift") !== void 0;
548
+ return (0, service_exports.jsonRes)(200, {
549
+ events: rows.slice((p.number - 1) * p.size, p.number * p.size).map((e) => ({
550
+ id: e.id,
551
+ type: drift ? "lab_order.changed" : e.type,
552
+ clinic_id: e.clinic_id,
553
+ created_at: e.created_at
554
+ })),
555
+ meta: meta(p.number, p.size, rows.length)
556
+ });
557
+ },
558
+ GetEvent: (context) => {
559
+ const claims = this.claims(context);
560
+ const event = this.state.events.get(context.params.eventId ?? "");
561
+ if (!event || event.clinic_id !== claims.clinicId)
562
+ return apiErrors(404, "not_found", "Event not found");
563
+ return (0, service_exports.jsonRes)(200, { event: this.publicEvent(event) });
564
+ },
565
+ Authorize: (context) => this.authorize(context),
566
+ GetResultPdf: (context) => this.pdf(context)
567
+ });
568
+ this.service = (0, service_exports.createService)({
569
+ document,
570
+ handlers,
571
+ sqlite,
572
+ namespace,
573
+ now: this.now,
574
+ notFound: () => apiErrors(404, "not_found", "Not Found"),
575
+ onError: (error) => {
576
+ if (error instanceof service_exports.HttpError) return error.toResponse();
577
+ throw error;
578
+ },
579
+ before: (context) => {
580
+ const id = context.operation.operationId;
581
+ if (id === "OAuthToken" || id === "OAuthRevoke" || id === "Authorize" || id === "GetResultPdf") {
582
+ return void 0;
583
+ }
584
+ const token = (0, service_exports.bearerToken)(context.request);
585
+ if (!token)
586
+ return apiErrors(401, "unauthorized", "You need to sign in or authenticate first");
587
+ if ((0, service_exports.faultEffect)(context.request, "token_expired") !== void 0) {
588
+ return apiErrors(401, "token_expired", "The access token expired");
589
+ }
590
+ const claims = readAccessToken(token);
591
+ if (!claims || this.state.revoked.has(token)) {
592
+ return apiErrors(401, "invalid_token", "The access token is invalid");
593
+ }
594
+ if (this.now() / 1e3 >= claims.issuedAt + this.state.current().accessTokenTtlSeconds) {
595
+ return apiErrors(401, "token_expired", "The access token expired");
596
+ }
597
+ return void 0;
598
+ }
599
+ });
600
+ this.app = this.service.app;
601
+ this.sqlite = this.service.sqlite;
602
+ for (const seed of options.orders ?? []) this.seedOrder(seed);
603
+ }
604
+ fetch(request) {
605
+ return this.service.fetch(request);
606
+ }
607
+ async reset() {
608
+ await this.service.reset();
609
+ this.state.ensureSeeded();
610
+ }
611
+ iso() {
612
+ return new Date(this.now()).toISOString();
613
+ }
614
+ claims(context) {
615
+ return readAccessToken((0, service_exports.bearerToken)(context.request) ?? "");
616
+ }
617
+ clientOk(clientId, secret) {
618
+ const clients = this.state.current().clients;
619
+ if (typeof clientId !== "string" || typeof secret !== "string" || !clientId || !secret)
620
+ return false;
621
+ return clients.length === 0 || clients.some((c) => c.clientId === clientId && c.clientSecret === secret);
622
+ }
623
+ body(context) {
624
+ const issues = (0, service_exports.bodyIssues)(context);
625
+ if (issues.length > 0) {
626
+ throw new service_exports.HttpError(400, {
627
+ error: "invalid_request",
628
+ error_description: `The request is missing a required parameter or is otherwise malformed: ${issues.map((i) => `${i.path || "body"} ${i.message}`).join("; ")}`
629
+ });
630
+ }
631
+ return context.body.kind === "json" ? context.body.value : {};
632
+ }
633
+ /** Mint an authorization code for a practitioner (what the consent page does). */
634
+ issueCode(practitionerId, clientId, redirectUri) {
635
+ const code = `code_${(0, service_exports.opaqueToken)(`${practitionerId}:${this.state.next("code")}`, 32)}`;
636
+ this.state.codes.insert(code, {
637
+ code,
638
+ practitionerId,
639
+ clientId,
640
+ redirectUri,
641
+ expiresAtMs: this.now() + this.state.current().codeTtlSeconds * 1e3,
642
+ used: false
643
+ });
644
+ return code;
645
+ }
646
+ tokenPair(practitioner, clientId) {
647
+ const refresh = `fsrt_${(0, service_exports.opaqueToken)(`${practitioner.id}:${this.state.next("refresh")}`, 40)}`;
648
+ this.state.refresh.insert(refresh, {
649
+ token: refresh,
650
+ practitionerId: practitioner.id,
651
+ clientId,
652
+ revoked: false
653
+ });
654
+ const issuedAt = Math.floor(this.now() / 1e3);
655
+ return (0, service_exports.jsonRes)(200, {
656
+ oauth: {
657
+ access_token: issueAccessToken(
658
+ {
659
+ clientId,
660
+ practitionerId: practitioner.id,
661
+ clinicId: practitioner.clinicId,
662
+ type: practitioner.type
663
+ },
664
+ issuedAt
665
+ ),
666
+ token_type: "Bearer",
667
+ expires_in: this.state.current().accessTokenTtlSeconds,
668
+ refresh_token: refresh,
669
+ scope: SCOPE,
670
+ created_at: new Date(issuedAt * 1e3).toISOString(),
671
+ resource_owner: {
672
+ id: practitioner.id,
673
+ type: practitioner.type,
674
+ clinic_id: practitioner.clinicId
675
+ }
676
+ }
677
+ });
678
+ }
679
+ token(context) {
680
+ const body = this.body(context);
681
+ const clientId = String(body.client_id);
682
+ if (!this.clientOk(body.client_id, body.client_secret))
683
+ return oauthError(401, "invalid_client", INVALID_CLIENT);
684
+ if (body.grant_type === "authorization_code") {
685
+ if (typeof body.code !== "string" || !body.code) {
686
+ return oauthError(400, "invalid_request", "Missing required parameter: code.");
687
+ }
688
+ const code = this.state.codes.get(body.code);
689
+ if (!code || code.used || code.clientId !== clientId || this.now() > code.expiresAtMs || code.redirectUri !== null && body.redirect_uri !== code.redirectUri) {
690
+ return oauthError(400, "invalid_grant", INVALID_GRANT);
691
+ }
692
+ this.state.codes.update(code.code, { ...code, used: true });
693
+ const practitioner2 = this.state.practitioners.get(code.practitionerId) ?? DEFAULT_PRACTITIONER;
694
+ return this.tokenPair(practitioner2, clientId);
695
+ }
696
+ if (typeof body.refresh_token !== "string" || !body.refresh_token) {
697
+ return oauthError(400, "invalid_request", "Missing required parameter: refresh_token.");
698
+ }
699
+ if ((0, service_exports.faultEffect)(context.request, "invalid_grant") !== void 0) {
700
+ return oauthError(400, "invalid_grant", INVALID_GRANT);
701
+ }
702
+ const refresh = this.state.refresh.get(body.refresh_token);
703
+ if (!refresh || refresh.revoked || refresh.clientId !== clientId) {
704
+ return oauthError(400, "invalid_grant", INVALID_GRANT);
705
+ }
706
+ this.state.refresh.update(refresh.token, { ...refresh, revoked: true });
707
+ const practitioner = this.state.practitioners.get(refresh.practitionerId) ?? DEFAULT_PRACTITIONER;
708
+ return this.tokenPair(practitioner, clientId);
709
+ }
710
+ revoke(context) {
711
+ const body = this.body(context);
712
+ if (!this.clientOk(body.client_id, body.client_secret))
713
+ return oauthError(401, "invalid_client", INVALID_CLIENT);
714
+ const token = String(body.token);
715
+ const refresh = this.state.refresh.get(token);
716
+ if (refresh) this.state.refresh.update(token, { ...refresh, revoked: true });
717
+ else if (readAccessToken(token)) this.state.revoked.insert(token, { token });
718
+ return (0, service_exports.jsonRes)(200, {});
719
+ }
720
+ authorize(context) {
721
+ const url = context.url;
722
+ const redirect = url.searchParams.get("redirect_uri");
723
+ const clientId = url.searchParams.get("client_id");
724
+ if (!redirect || !clientId || url.searchParams.get("response_type") !== "code") {
725
+ return oauthError(
726
+ 400,
727
+ "invalid_request",
728
+ "client_id, redirect_uri and response_type=code are required."
729
+ );
730
+ }
731
+ const practitionerId = url.searchParams.get("practitioner_id") ?? DEFAULT_PRACTITIONER.id;
732
+ if (!this.state.practitioners.get(practitionerId)) {
733
+ return oauthError(400, "invalid_request", `Unknown practitioner ${practitionerId}.`);
734
+ }
735
+ const code = this.issueCode(practitionerId, clientId, redirect);
736
+ const location = new URL(redirect);
737
+ location.searchParams.set("code", code);
738
+ const state = url.searchParams.get("state");
739
+ if (state !== null) location.searchParams.set("state", state);
740
+ return new Response(null, { status: 302, headers: { location: location.toString() } });
741
+ }
742
+ summary(order) {
743
+ return {
744
+ id: order.id,
745
+ state: order.state,
746
+ treatment_plan_id: order.treatmentPlanId,
747
+ patient_id: order.patientId,
748
+ name: order.name,
749
+ created_at: order.created_at,
750
+ updated_at: order.updated_at
751
+ };
752
+ }
753
+ /** A signed, expiring URL for a result artifact, on the mock itself (or `resultsBaseUrl`). */
754
+ pdfUrl(artifactId, context) {
755
+ const base = this.state.current().resultsBaseUrl ?? `${context.url.origin}${this.prefix}`;
756
+ const expires = this.now() + this.state.current().pdfUrlTtlSeconds * 1e3;
757
+ return `${base.replace(/\/$/, "")}/results/${artifactId}?expires=${expires}&signature=${(0, service_exports.opaqueToken)(`${artifactId}:${expires}`, 24)}`;
758
+ }
759
+ detail(order, context) {
760
+ return {
761
+ ...this.summary(order),
762
+ collection_method: order.collectionMethod,
763
+ tests: order.tests,
764
+ results: order.results.map((r) => ({
765
+ id: r.id,
766
+ name: r.name,
767
+ state: r.state,
768
+ pdf_url: this.pdfUrl(r.artifactId, context)
769
+ })),
770
+ latest_aggregated_result: order.aggregated ? {
771
+ id: order.aggregated.id,
772
+ artifact_id: order.aggregated.artifactId,
773
+ pdf_url: this.pdfUrl(order.aggregated.artifactId, context),
774
+ status: order.aggregated.status
775
+ } : null
776
+ };
777
+ }
778
+ pdf(context) {
779
+ const artifactId = context.params.artifactId ?? "";
780
+ const expires = Number(context.url.searchParams.get("expires"));
781
+ const signature = context.url.searchParams.get("signature");
782
+ if (signature !== (0, service_exports.opaqueToken)(`${artifactId}:${expires}`, 24)) {
783
+ return new Response("Forbidden", { status: 403, headers: { "content-type": "text/plain" } });
784
+ }
785
+ if (this.now() > expires)
786
+ return new Response("Expired", { status: 403, headers: { "content-type": "text/plain" } });
787
+ const known = this.state.orders.list().some(
788
+ ({ value: o }) => o.aggregated?.artifactId === artifactId || o.results.some((r) => r.artifactId === artifactId)
789
+ );
790
+ if (!known) return new Response(null, { status: 404 });
791
+ return new Response(new Blob([RESULT_PDF]), {
792
+ status: 200,
793
+ headers: {
794
+ "content-type": "application/pdf",
795
+ "content-length": String(RESULT_PDF.byteLength)
796
+ }
797
+ });
798
+ }
799
+ publicEvent(event) {
800
+ return {
801
+ id: event.id,
802
+ type: event.type,
803
+ clinic_id: event.clinic_id,
804
+ created_at: event.created_at,
805
+ data: event.data
806
+ };
807
+ }
808
+ emit(type, order, data) {
809
+ const seq = this.state.next("event");
810
+ const event = {
811
+ id: `evt_${seq}`,
812
+ type,
813
+ clinic_id: order.clinicId,
814
+ created_at: this.iso(),
815
+ data,
816
+ seq
817
+ };
818
+ this.state.events.insert(event.id, event);
819
+ this.onEvent?.(event);
820
+ return event;
821
+ }
822
+ /** Create a lab order in `not_purchased` (a practitioner recommended labs in a treatment plan). */
823
+ createOrder(input) {
824
+ const id = input.id ?? `lo_${this.state.next("order")}`;
825
+ const now = this.iso();
826
+ const order = {
827
+ id,
828
+ clinicId: input.clinicId ?? DEFAULT_PRACTITIONER.clinicId,
829
+ patientId: input.patientId,
830
+ treatmentPlanId: input.treatmentPlanId === void 0 ? `tp_${id}` : input.treatmentPlanId,
831
+ name: input.name ?? "Comprehensive Wellness Panel",
832
+ collectionMethod: input.collectionMethod ?? "at_home_phlebotomy",
833
+ state: "not_purchased",
834
+ tests: (input.tests ?? ["Lipid Panel", "Comprehensive Metabolic Panel"]).map((name, i) => ({
835
+ id: `lt_${id}_${i + 1}`,
836
+ name,
837
+ lab_type: "labs"
838
+ })),
839
+ results: [],
840
+ aggregated: null,
841
+ created_at: now,
842
+ updated_at: now
843
+ };
844
+ this.state.orders.insert(id, order);
845
+ return order;
846
+ }
847
+ /**
848
+ * Move an order forward (never back) and emit its events: `order.placed` on purchase and
849
+ * `lab_order.updated` for every move. Results-bearing states attach result artifacts.
850
+ */
851
+ transition(orderId, to) {
852
+ const order = this.state.orders.get(orderId);
853
+ if (!order)
854
+ throw new service_exports.HttpError(404, {
855
+ error: { type: "mockingbird_admin", message: `no lab order ${orderId}` }
856
+ });
857
+ if (stateRank(to) <= stateRank(order.state)) {
858
+ throw new service_exports.HttpError(409, {
859
+ error: {
860
+ type: "mockingbird_admin",
861
+ message: `lab orders only move forward: ${order.state} \u2192 ${to} is not allowed`
862
+ }
863
+ });
864
+ }
865
+ const artifact = () => `art_${(0, service_exports.opaqueToken)(`${order.id}:${this.state.next("artifact")}`, 16)}`;
866
+ const next = { ...order, state: to, updated_at: this.iso() };
867
+ const rank = stateRank(to);
868
+ if (rank >= stateRank("partial_results")) {
869
+ const wanted = rank >= stateRank("results_ready") ? next.tests.length : 1;
870
+ const results = [...next.results];
871
+ for (const test of next.tests.slice(results.length, wanted)) {
872
+ results.push({
873
+ id: `res_${test.id}`,
874
+ name: test.name,
875
+ state: "final",
876
+ artifactId: artifact()
877
+ });
878
+ }
879
+ next.results = results;
880
+ }
881
+ if (rank >= stateRank("results_ready") && (!next.aggregated || to === "results_amended")) {
882
+ next.aggregated = {
883
+ id: `agg_${order.id}${to === "results_amended" ? "_amended" : ""}`,
884
+ artifactId: artifact(),
885
+ status: to === "results_amended" ? "amended" : "final"
886
+ };
887
+ }
888
+ this.state.orders.update(order.id, next);
889
+ if (to === "purchased" || order.state === "not_purchased" && rank > stateRank("purchased")) {
890
+ this.emit("order.placed", next, {
891
+ id: next.id,
892
+ treatment_plan_ids: next.treatmentPlanId ? [next.treatmentPlanId] : [],
893
+ patient_id: next.patientId,
894
+ line_items: [{ type: "labs", lab_type: "labs", name: next.name }]
895
+ });
896
+ }
897
+ this.emit("lab_order.updated", next, {
898
+ lab_order: {
899
+ id: next.id,
900
+ state: next.state,
901
+ treatment_plan_id: next.treatmentPlanId,
902
+ patient_id: next.patientId
903
+ }
904
+ });
905
+ return next;
906
+ }
907
+ seedOrder(seed) {
908
+ const order = this.state.orders.get(seed.id) ?? this.createOrder(seed);
909
+ return seed.state === order.state ? order : this.transition(order.id, seed.state);
910
+ }
911
+ labOrders() {
912
+ return this.state.orders.list({ order: "oldest" }).map((r) => r.value);
913
+ }
914
+ eventsList() {
915
+ return this.state.events.list({ order: "oldest" }).map((r) => r.value);
916
+ }
917
+ };
918
+
919
+ export {
920
+ document,
921
+ operationIds,
922
+ supportedOperationIds,
923
+ RESULT_PDF,
924
+ LAB_ORDER_STATES,
925
+ isLabOrderState,
926
+ DEFAULT_CLINIC,
927
+ DEFAULT_PRACTITIONER,
928
+ SIGNATURE_HEADER,
929
+ FULLSCRIPT_PRESETS,
930
+ createRuntime2 as createRuntime,
931
+ FULLSCRIPT_NAMESPACE,
932
+ apiErrors,
933
+ oauthError,
934
+ issueAccessToken,
935
+ tokenCredential,
936
+ publicEvent,
937
+ envelope,
938
+ FullscriptAPI
939
+ };
940
+ //# sourceMappingURL=chunk-CJAYCSOW.js.map