@ovok/core 0.3.4 → 0.3.5

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/README.md CHANGED
@@ -14,6 +14,7 @@ Core TypeScript SDK for healthcare applications. Provides authentication, AI-FHI
14
14
  - 🤖 Bot execution framework
15
15
  - 📋 Questionnaire responses
16
16
  - 📥 Opt-in durable offline measurement queue with idempotent retries
17
+ - 🧭 Runtime discovery of the backend FHIR and public API capabilities
17
18
 
18
19
  ## Installation
19
20
 
@@ -40,6 +41,20 @@ const client = new OvokClient({
40
41
 
41
42
  Full documentation and examples available at **[sdk.ovok.com](https://sdk.ovok.com/)**
42
43
 
44
+ ## Backend capability discovery
45
+
46
+ `@ovok/core` can read the capability statement composed by `../ovok-core` and expose the
47
+ non-FHIR public routes published in Ovok's `ovok-public-api` extension:
48
+
49
+ ```typescript
50
+ const statement = await client.getCapabilityStatement();
51
+ const publicEndpoints = await client.getPublicApiEndpoints();
52
+ ```
53
+
54
+ The exported `OVOK_CORE_REQUIREMENTS` describes the FHIR operations used by the SDK, while
55
+ `OVOK_CORE_API_REQUIREMENTS` describes its ordinary HTTP dependencies. Billing is intentionally
56
+ not part of this SDK contract yet.
57
+
43
58
  ## Offline measurements
44
59
 
45
60
  Pass a durable Medplum storage adapter and opt in to queue measurements while offline:
@@ -1,5 +1,6 @@
1
1
  import { IClientStorage, MedplumClient, MedplumClientOptions, SubscriptionManager } from "@medplum/core";
2
- import { Bundle, Observation } from "@medplum/fhirtypes";
2
+ import { Bundle, CapabilityStatement, Observation } from "@medplum/fhirtypes";
3
+ import { OvokPublicApiEndpoint } from "../conformance/capability";
3
4
  import * as aiFhirMethods from "./ai-fhir/methods";
4
5
  import * as authMethods from "./auth/methods";
5
6
  import * as botMethods from "./bot/methods";
@@ -17,6 +18,7 @@ export type SaveObservationsResult = {
17
18
  status: "queued";
18
19
  queueId: string;
19
20
  };
21
+ export type OvokFhirVersion = "R4" | "R5";
20
22
  export declare class OvokClient extends MedplumClient {
21
23
  protected socialLoginClientId: string;
22
24
  clientStorage: IClientStorage | undefined;
@@ -32,6 +34,10 @@ export declare class OvokClient extends MedplumClient {
32
34
  });
33
35
  /** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
34
36
  saveMeasurement(params: GenerateObservationBodyParams): Promise<SaveObservationsResult>;
37
+ /** Reads Ovok's composed FHIR and public-route capability statement. */
38
+ getCapabilityStatement(version?: OvokFhirVersion): Promise<CapabilityStatement>;
39
+ /** Returns the non-FHIR public routes published by the backend statement. */
40
+ getPublicApiEndpoints(version?: OvokFhirVersion): Promise<OvokPublicApiEndpoint[]>;
35
41
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
36
42
  saveObservations(observations: Observation[]): Promise<SaveObservationsResult>;
37
43
  /** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.OvokClient = exports.MAX_OBSERVATION_BUNDLE_BYTES = exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = void 0;
37
37
  /* eslint-disable @typescript-eslint/no-unsafe-function-type */ // TODO: fix this whenever find time
38
38
  const core_1 = require("@medplum/core");
39
+ const capability_1 = require("../conformance/capability");
39
40
  const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
40
41
  const authMethods = __importStar(require("./auth/methods"));
41
42
  const botMethods = __importStar(require("./bot/methods"));
@@ -72,6 +73,14 @@ class OvokClient extends core_1.MedplumClient {
72
73
  const observations = await this.generateObservationBodiesByMeasurement(params);
73
74
  return this.saveObservations(observations);
74
75
  }
76
+ /** Reads Ovok's composed FHIR and public-route capability statement. */
77
+ async getCapabilityStatement(version = "R4") {
78
+ return this.get(`/fhir/${version}/metadata`);
79
+ }
80
+ /** Returns the non-FHIR public routes published by the backend statement. */
81
+ async getPublicApiEndpoints(version = "R4") {
82
+ return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version));
83
+ }
75
84
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
76
85
  async saveObservations(observations) {
77
86
  const linkedObservations = (0, observation_bundle_utils_1.linkObservationMembers)(observations);
@@ -0,0 +1,34 @@
1
+ export type OvokHttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
2
+ export type OvokApiRequirement = {
3
+ method: OvokHttpMethod;
4
+ path: string;
5
+ group: string;
6
+ };
7
+ /**
8
+ * The non-FHIR HTTP routes used by @ovok/core itself.
9
+ *
10
+ * This is deliberately separate from OVOK_CORE_REQUIREMENTS: a FHIR
11
+ * CapabilityStatement cannot describe ordinary auth, AI, or bot routes.
12
+ * Billing is intentionally absent until Ovok has a supported billing SDK
13
+ * contract.
14
+ */
15
+ export declare const OVOK_CORE_API_REQUIREMENTS: {
16
+ readonly capability: {
17
+ readonly method: "GET";
18
+ readonly path: "/fhir/{version}/metadata";
19
+ readonly group: "FHIR";
20
+ };
21
+ readonly nonFhir: ({
22
+ method: "POST";
23
+ path: string;
24
+ group: string;
25
+ } | {
26
+ method: "GET";
27
+ path: string;
28
+ group: string;
29
+ } | {
30
+ method: "DELETE";
31
+ path: string;
32
+ group: string;
33
+ })[];
34
+ };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OVOK_CORE_API_REQUIREMENTS = void 0;
4
+ /**
5
+ * The non-FHIR HTTP routes used by @ovok/core itself.
6
+ *
7
+ * This is deliberately separate from OVOK_CORE_REQUIREMENTS: a FHIR
8
+ * CapabilityStatement cannot describe ordinary auth, AI, or bot routes.
9
+ * Billing is intentionally absent until Ovok has a supported billing SDK
10
+ * contract.
11
+ */
12
+ exports.OVOK_CORE_API_REQUIREMENTS = {
13
+ capability: {
14
+ method: "GET",
15
+ path: "/fhir/{version}/metadata",
16
+ group: "FHIR",
17
+ },
18
+ nonFhir: [
19
+ { method: "POST", path: "/auth/tenant/Patient/login/start", group: "Auth" },
20
+ { method: "POST", path: "/auth/tenant/Practitioner/login/start", group: "Auth" },
21
+ { method: "POST", path: "/auth/tenant/Patient/login/token", group: "Auth" },
22
+ { method: "POST", path: "/auth/tenant/Practitioner/login/token", group: "Auth" },
23
+ { method: "POST", path: "/auth/tenant/Practitioner/login/mfa", group: "Auth" },
24
+ { method: "POST", path: "/auth/tenant/Patient/register", group: "Auth" },
25
+ { method: "POST", path: "/auth/external/google", group: "Auth" },
26
+ { method: "POST", path: "/auth/external/apple", group: "Auth" },
27
+ { method: "POST", path: "/auth/change-password", group: "Auth" },
28
+ { method: "POST", path: "/v2/auth/reset-password", group: "Auth" },
29
+ { method: "POST", path: "/v2/auth/reset-password/process", group: "Auth" },
30
+ { method: "GET", path: "/auth/session", group: "Auth" },
31
+ { method: "DELETE", path: "/auth/session/current", group: "Auth" },
32
+ { method: "DELETE", path: "/auth/session/:option", group: "Auth" },
33
+ { method: "DELETE", path: "/auth/delete", group: "Auth" },
34
+ { method: "POST", path: "/ai/session", group: "AI" },
35
+ { method: "POST", path: "/ai/fhir/search", group: "AI" },
36
+ { method: "POST", path: "/bots", group: "Bots" },
37
+ ],
38
+ };
@@ -43,12 +43,12 @@ exports.OVOK_CORE_REQUIREMENTS = {
43
43
  * report change where there is none. Bump it with `software.version` when
44
44
  * the declared surface actually moves.
45
45
  */
46
- date: "2026-09-19",
46
+ date: "2026-09-26",
47
47
  kind: "requirements",
48
48
  description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
49
49
  software: {
50
50
  name: "@ovok/core",
51
- version: "0.3.3",
51
+ version: "0.3.4",
52
52
  },
53
53
  fhirVersion: "4.0.1",
54
54
  format: ["json"],
@@ -0,0 +1,15 @@
1
+ import { CapabilityStatement } from "@medplum/fhirtypes";
2
+ /** The extension used by ovok-core to publish non-FHIR public routes. */
3
+ export declare const OVOK_PUBLIC_API_EXTENSION = "https://fhir.ovok.com/fhir/StructureDefinition/ovok-public-api";
4
+ export type OvokPublicApiEndpoint = {
5
+ method: string;
6
+ path: string;
7
+ group: string;
8
+ };
9
+ /**
10
+ * Reads the public non-FHIR route catalog emitted by ../ovok-core.
11
+ *
12
+ * FHIR clients can ignore this extension safely. React applications can use
13
+ * it to feature-detect the rest of the Ovok API without hard-coding routes.
14
+ */
15
+ export declare const getOvokPublicApiEndpoints: (statement: CapabilityStatement) => OvokPublicApiEndpoint[];
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOvokPublicApiEndpoints = exports.OVOK_PUBLIC_API_EXTENSION = void 0;
4
+ /** The extension used by ovok-core to publish non-FHIR public routes. */
5
+ exports.OVOK_PUBLIC_API_EXTENSION = "https://fhir.ovok.com/fhir/StructureDefinition/ovok-public-api";
6
+ const ENDPOINT_EXTENSION = "endpoint";
7
+ const stringValue = (extension) => { var _a; return (_a = extension.valueCode) !== null && _a !== void 0 ? _a : extension.valueString; };
8
+ /**
9
+ * Reads the public non-FHIR route catalog emitted by ../ovok-core.
10
+ *
11
+ * FHIR clients can ignore this extension safely. React applications can use
12
+ * it to feature-detect the rest of the Ovok API without hard-coding routes.
13
+ */
14
+ const getOvokPublicApiEndpoints = (statement) => {
15
+ var _a, _b;
16
+ const publicApi = (_a = statement.extension) === null || _a === void 0 ? void 0 : _a.find((extension) => extension.url === exports.OVOK_PUBLIC_API_EXTENSION);
17
+ return ((_b = publicApi === null || publicApi === void 0 ? void 0 : publicApi.extension) !== null && _b !== void 0 ? _b : []).flatMap((endpoint) => {
18
+ var _a, _b, _c;
19
+ if (endpoint.url !== ENDPOINT_EXTENSION) {
20
+ return [];
21
+ }
22
+ const method = (_a = endpoint.extension) === null || _a === void 0 ? void 0 : _a.find((extension) => extension.url === "method");
23
+ const path = (_b = endpoint.extension) === null || _b === void 0 ? void 0 : _b.find((extension) => extension.url === "path");
24
+ const group = (_c = endpoint.extension) === null || _c === void 0 ? void 0 : _c.find((extension) => extension.url === "group");
25
+ const methodValue = method === undefined ? undefined : stringValue(method);
26
+ const pathValue = path === undefined ? undefined : stringValue(path);
27
+ const groupValue = group === undefined ? undefined : stringValue(group);
28
+ if (methodValue === undefined || pathValue === undefined || groupValue === undefined) {
29
+ return [];
30
+ }
31
+ return [
32
+ {
33
+ method: methodValue.toUpperCase(),
34
+ path: pathValue,
35
+ group: groupValue,
36
+ },
37
+ ];
38
+ });
39
+ };
40
+ exports.getOvokPublicApiEndpoints = getOvokPublicApiEndpoints;
package/dist/index.d.ts CHANGED
@@ -933,3 +933,5 @@ export * from "./client/observation/services";
933
933
  export * from "./client/observation/methods";
934
934
  export { saveMeasurement } from "./client/observation/methods/saveMeasurement";
935
935
  export * from "./conformance/capability-requirements";
936
+ export * from "./conformance/api-requirements";
937
+ export * from "./conformance/capability";
package/dist/index.js CHANGED
@@ -56,3 +56,5 @@ __exportStar(require("./client/observation/methods"), exports);
56
56
  var saveMeasurement_1 = require("./client/observation/methods/saveMeasurement");
57
57
  Object.defineProperty(exports, "saveMeasurement", { enumerable: true, get: function () { return saveMeasurement_1.saveMeasurement; } });
58
58
  __exportStar(require("./conformance/capability-requirements"), exports);
59
+ __exportStar(require("./conformance/api-requirements"), exports);
60
+ __exportStar(require("./conformance/capability"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ovok/core",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",