UNPKG

@oystehr/sdk

Version:

Oystehr SDK

385 lines (297 loc) 14.7 kB
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://zapehr-public-files.s3.amazonaws.com/osytehr-logo-dark.svg"> <source media="(prefers-color-scheme: dark)" srcset="https://zapehr-public-files.s3.amazonaws.com/osytehr-logo-dark.svg"> <img alt="Oystehr Logo" src="https://zapehr-public-files.s3.amazonaws.com/oystehr-logo-white.svg" width=300px> </picture> </p> <p align="center">The complete health-tech developer platform.</p> <p align="center"> <a href="https://www.oystehr.com/"><b>Website</b></a> <a href="https://docs.oystehr.com/"><b>Documentation</b></a> </p> <div align="center"> [![Version](https://badgen.net/npm/v/@oystehr/sdk)](https://npmjs.com/package/@oystehr/sdk) [![Compatible Node Versions](https://badgen.net/npm/node/@oystehr/sdk)](https://npmjs.com/package/@oystehr/sdk) [![Install Size](https://badgen.net/packagephobia/install/@oystehr/sdk)](https://packagephobia.com/result?p=%40oystehr%2Fsdk) [![Minified Size](https://badgen.net/bundlephobia/min/@oystehr/sdk)](https://bundlephobia.com/package/@oystehr/sdk) [![Downloads / Month](https://badgen.net/npm/dm/@oystehr/sdk)](https://npmjs.com/package/@oystehr/sdk) [![License](https://badgen.net/npm/license/@oystehr/sdk)](https://npmjs.com/package/@oystehr/sdk) </div> # Oystehr SDK The Oystehr SDK is a TypeScript library for interacting with Oystehr's [FHIR and Project APIs](https://api-reference.oystehr.com). While you can always make raw HTTP requests to Oystehr's APIs from any language, this SDK provides several advantages for developers: - TypeScript types &mdash; The SDK provides complete typing for requests and responses across all of Oystehr's APIs. This gives you helpful autocomplete and build-time type checking. - Convenience functions &mdash; The SDK provides convenience functions for doing common tasks like uploading and downloading files with Z3. - FHIR Support &mdash; The SDK supports the R4B and R5 versions of FHIR and includes types and helpers for interacting with the Oystehr FHIR API. ## Installation ```bash npm install @oystehr/sdk ``` ## Usage 1. Import the SDK. 2. Initialize the SDK with a Oystehr access token. ([Learn how to get an access token](https://docs.oystehr.com/oystehr/core-documentation/authenticating-api-requests)). 3. Invoke any of Oystehr's APIs with a function call. ```typescript filename="Example of using the SDK" import Oystehr from '@oystehr/sdk'; import { Patient } from 'fhir/r4b'; const oystehr = new Oystehr({ accessToken: 'your_access_token', }); // Create a Patient FHIR resource const patient = await oystehr.fhir.create<Patient>({ resourceType: 'Patient', }); // Upload a zambda try { const zambda = await oystehr.zambda.create({ name: 'new zambda', triggerMethod: 'http_open' }); const fileHandle = await fs.open('./path/to/my/zambda.zip') const file = new Blob([await fileHandle.readFile()]); await oystehr.zambda.uploadFile({ id: createZambda.data.id, file, }) } catch (err) { // Handle error thrown by Oystehr or JS in some way } ``` ### SDK Config Object Instantiating the SDK requires a configuration object, though all fields are optional. ```typescript interface OystehrConfig { /** * Access token for your user, M2M, or developer account. */ accessToken?: string; /** * Optional Oystehr Project ID. Required for developer accessTokens. */ projectId?: string; /** * Optionally provide a custom fetch implementation. This must conform to the * built-in node.js fetch implementation (Undici fetch). * @param req * @returns */ fetch?: (req: Request) => Promise<Response>; /** * Optional configuration for retrying request in case of error. Defaults to * `{ retries: 3 }` if not provided. */ retry?: { /** * Number of retries. */ retries: number; /** * Optional, non-async function to call when a request is retried. * @param attempt * @returns */ onRetry?: (attempt: number) => void; /** * Optional list of additional errors to retry for. The Oystehr SDK will always * retry for status codes 408, 429, 500, 502, 503, and 504. */ retryOn?: number[]; }; } ``` ## SDK Features ### Unauthenticated Usage All Oystehr endpoints require authentication to access except for public Zambdas that you upload to your project. You can invoke these Zambdas without passing an authentication token to the SDK: ```typescript filename="Example of unauthenticated usage" import Oystehr from '@oystehr/sdk'; const oystehr = new Oystehr({}); // Execute the Zambda const result = await oystehr.zambda.executePublic({ id: 'your_oystehr_zambda_id', additional: 'params', }); // Do something with your Zambda result console.log(JSON.stringify(result, null, 2)); ``` ### Retries The Oystehr SDK will retry on a variety of errors by default: * Common Node.js and Undici networking errors * Rate limiting status codes (`408`, `429`) * Potentially ephemeral upstream status codes (`500`, `502`, `503`, `504`) Note that the Oystehr FHIR API is eventually consistent, so if you are retrieving or updating a resource immediately after creating it, it's recommended to retry on `404` and `412` status codes as well. To retry on additional status codes, change the number of retries, or register a callback for each retry, pass options to the Oystehr SDK configuration object as shown above. ### Oystehr Types The SDK includes input and output types for all non-FHIR Oystehr services, as well as some reusable "component" types. These types provide a single interface for inputs &mdash; no need to worry about whether a value goes into a path parameters or the request body. ```typescript filename="Example of a reusable user invite wrapper" import Oystehr, { UserInviteParams, UserInviteResponse } from '@oystehr/sdk'; // Hard-coded application ID from environment const APP_ID = process.env.OYSTEHR_APP_ID; // Elsewhere in your project, initialize an Oystehr client // with the currently-authenticated user's access token const oystehr = new Oystehr({ accessToken: accessTokenFromAuthn }); // Define a reusable helper function to add practitioners // to your application, omitting the `applicationId` field // since you will override it. async function inviteUserToMyApp( oystehr: Oystehr, input: Omit<UserInviteParams, 'applicationId'> ): Promise<UserInviteResponse> { return oystehr.user.invite({ ...input, applicationId: APP_ID, }); } ``` ### FHIR Types The SDK supports both the R4B and R5 versions of FHIR from one shared set of generic functions. You shouldn't need to mix these types, since each Oystehr project can only have one FHIR version, but the example below shows how the SDK handles types for each version. ```typescript filename="Example of using FHIR types with the SDK" import Oystehr from '@oystehr/sdk'; import { Encounter as EncounterR4B } from 'fhir/r4b'; import { Encounter as EncounterR5 } from 'fhir/r5'; const oystehr = new Oystehr({ accessToken: 'your_access_token', }); const encounter4 = await oystehr.fhir.create<EncounterR4B>({ resourceType: 'Encounter', status: 'arrived', class: {}, }); /* * R4B Encounters do not have `virtualService` so this shows an error: * Argument of type '{ resourceType: "Encounter"; status: "arrived"; class: {}; virtualService: {}; }' is not assignable to parameter of type 'Encounter'. * Object literal may only specify known properties, and 'virtualService' does not exist in type 'Encounter'.ts(2345) */ const vs = encounter4.virtualService; const encounter5 = await oystehr.fhir.create<EncounterR5>({ resourceType: 'Encounter', /* * R5 Encounters have different enum values for `status` so this shows an error: * Type '"arrived"' is not assignable to type '"planned" | "in-progress" | "cancelled" | "entered-in-error" | "unknown" | "on-hold" | "completed" | "discharged" | "discontinued"'.ts(2322) */ status: 'arrived', class: [], }); ``` ### FHIR Bundles When performing a FHIR search, the resulting type is `Bundle`. The Oystehr SDK has a convenience function for unwrapping the bundle into an array of the base type. ```typescript import Oystehr from '@oystehr/sdk'; import { Patient } from 'fhir/r4b'; const patientBundle/*: Bundle<Patient>*/ = await oystehr.fhir.search<Patient>({ resourceType: 'Patient', params: [ { name: 'name', value: 'sam', }, ], }); const patients/*: Patient[]*/ = patientBundle.unbundle(); ``` ### Batch Requests The Oystehr FHIR service supports two kinds of [batch requests](https://docs.oystehr.com/oystehr/services/fhir/batch-transaction/): batch and transaction. In the SDK, the syntax to use these is the same. PATCH requests [behave differently in batch requests](https://docs.oystehr.com/oystehr/services/fhir/batch-transaction#patch). The Oystehr SDK allows you to pass either pre-encoded `Binary` resources or JSON Patch operations directly. Here's an example of a transaction that patches an appointment and then retrieves a list of today's appointments: ```typescript import dayjs from 'dayjs'; import Oystehr, { BatchInputGetRequest, BatchInputPatchRequest } from '@oystehr/sdk'; const oystehr = new Oystehr({ accessToken: '<your_access_token>', }); const patchAppointment: BatchInputPatchRequest = { method: 'PATCH', url: '/Appointment/some_appointment_id', operations: [{ op: 'replace', path: '/start', value: dayjs().format('YYYY-MM-DDTHH:mm:ss.SSSZ'), }], }; const getAppointments: BatchInputGetRequest = { method: 'GET', url: `/Appointment?date=sa${dayjs().startOf('day').format('YYYY-MM-DDTHH:mm:ss.SSSZ')}`, }; await oystehr.fhir.transaction<Appointment>({ requests: [patchAppointment, getAppointments ]}); ``` Since the SDK batch functions return `Bundle`s, they also support the `unbundle()` helper: ```typescript const txBundle = await oystehr.fhir.transaction<Appointment>({ requests: [patchAppointment, getAppointments ]}); const results/*: Appointment[]*/ = txBundle.unbundle(); ``` ### Batch Request Error Handling Batch and transaction requests produce errors differently. For transactions, if there is an error processing any part of the transaction, all operations will be rolled back and the SDK will throw an `OystehrFHIRError` with the `OperationOutcome` object in the `error` field: ```typescript try { const txBundle = await oystehr.fhir.transaction<Appointment>({ requests: [patchAppointment, getAppointments ]}); // ... } catch (err) { const error: OperationOutcome = (err as OystehrFHIRError).error; // ... } ``` Batch errors are returned in-line with successful responses in the `Bundle`. The SDK has a helper function to pull errors out of the bundle result: ```typescript const batchBundle = await oystehr.fhir.batch<Appointment>({ requests: [patchAppointment, getAppointments ]}); const batchResources/*: Appointment[]*/ = batchBundle.unbundle(); const batchErrors/*: OperationOutcome[]*/ = batchBundle.errors(); ``` ### FHIR Search and Batch Types When writing complex search and batch requests, you may end up with requests that return more than one type of FHIR Resource. To handle this, the Oystehr FHIR SDK supports generic types on all functions. Use type inference and [type predicates](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to pull out individual collections of resources and ensure they have the correct type. ```typescript import Oystehr from '@oystehr/sdk'; import { Encounter, Location } from 'fhir/r4b'; const bundle/*: Bundle<Encounter | Location>*/ = await oystehr.fhir.search<Encounter | Location>({ resourceType: 'Encounter', params: [{ name: '_include', value: 'Encounter.location', }], }); const results/*: (Encounter | Location)[]*/ = bundle.unbundle(); const encounters/*: Encounter[]*/ = results.filter((res): res is Encounter => res.resourceType === 'Encounter'); const locations/*: Location[]*/ = results.filter((res): res is Location => res.resourceType === 'Location'); ``` ### Optimistic Locking The Oystehr FHIR API and SDK supports [FHIR's optimistic locking specification](https://build.fhir.org/http.html#concurrency). To use optimistic locking, pass a valid [FHIR version ID](https://build.fhir.org/resource.html#metadata) to the request object on your SDK function call. ```typescript import Oystehr from '@oystehr/sdk'; import { Patient } from 'fhir/r4b'; const oystehr = new Oystehr({ accessToken: 'your_access_token', }); // Create a Patient FHIR resource const patient = await oystehr.fhir.create<Patient>({ resourceType: 'Patient', }); // Fails if patient has been updated by a separate process const updatedPatient = await oystehr.fhir.update<Patient>({ resourceType: 'Patient' id: patient.id, active: true, }, { optimisticLockingVersionId: patient.meta?.versionId }); ``` ## Convenience Functions ### Z3 Under the hood, uploading and downloading files with [Z3](/oystehr/services/z3) is a two-step process: 1. Create a presigned URL to upload or download the file ([API Reference](https://api-reference.oystehr.com/reference/post_z3-bucketname-objectpath)). 2. Upload or download the file using the presigned URL. The SDK provides convenience functions which combine these steps into a single function call to make it a one-liner: ```typescript copy filename="Upload a file to Z3 with the SDK" await oystehr.z3.uploadFile( { bucketName: 'your-bucket-name', 'objectPath+': 'path/to/your-filename', file: someFileBlob } ); ``` In the example, `someFileBlob` is a [Blob](https://nodejs.org/api/buffer.html#class-blob). ```typescript copy filename="Download a file from Z3 with the SDK" const downloadedBuffer = await oystehr.z3.downloadFile( { bucketName: 'your-bucket-name', 'objectPath+': 'path/to/your-filename' } ); ``` ### Zambda Uploading code for your Zambda Functions is very similar to uploading a file with Z3. After creating your `.zip` archive, use the `uploadFile()` function to deploy your code. ```typescript copy filename="Deploy a code zip to a Zambda Function" const zambdaFile = fs.readFileSync('build/your-zambda-code.zip'); await oystehr.zambda.uploadFile({ id: yourZambdaId, file: new Blob([zambdaFile]), }); ``` ## Migration Guide If you have questions about migrating to a new version of the Oystehr SDK, check out our [migration guide](https://docs.oystehr.com/oystehr/core-documentation/typescript-sdk/migration-guide). ## Contact Questions? Join our [public Slack](https://join.slack.com/t/oystehr/shared_invite/zt-1se6hw93b-olDYmAzDGDhU5ibnEugCmA) or contact [support](mailto:support@oystehr.com).