Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Aidbox Client and Typegen Tutorials
164 changes: 164 additions & 0 deletions docs/tutorials/aidbox-client-and-typegen-tutorial/aidbox-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Aidbox Client in TypeScript

## Install the aidbox-client dependency

{% code %}
```sh
npm install @health-samurai/aidbox-client
```
{% endcode %}

By default, the client comes with a selection of predefined types from the HL7 FHIR R4 Core package.

## Usage

First, create the client:

{% code %}
```typescript
import { makeClient } from "@health-samurai/aidbox-client"

const aidbox = makeClient({ baseurl: "https://aidbox.address/" })
```
{% endcode %}

The `makeClient` constructor accepts an object with the following properties:

- `baseurl`: URL to the Aidbox server
- `onRawResponseHook`: a function that runs after the raw response is received from the server.

After that, the client is ready to be used.

### Untyped raw requests

To make a request that doesn't assume any response type, use the `rawRequest` method:

{% code %}
```typescript
const result = await aidbox.rawRequest({
method: "GET",
url: "/fhir/Patient/pt-1",
})
```
{% endcode %}

The response is an object of the `AidboxRawResponse` type with the following fields:

- `response`: a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object
- `responseHeaders`: a `Record<string, string>` object with response headers
- `duration`: a `number` of milliseconds it took for the server to receive and respond to the request
- `request`: a request body formatted as it was sent to the server (for debugging purposes)

The `response` field can then be used to receive the response body as JSON, text, or read manually.

If the server responded with a non-2XX code, this function throws an `AidboxErrorResponse` object.
See [errors](#errors) for more information on error handling.

### Typed requests

To make a request that returns a given type, use the `request` method:

{% code %}
```typescript
const result = await aidbox.request<Patient>({
method: "GET",
url: "/fhir/Patient/pt-1",
})
```
{% endcode %}

Typed requests can return either the requested type, or the `OperationOutcome`, if the server responded with one.

The returned object will be of type `AidboxResponse<T>`, and its only difference from `AidboxRawResponse` is that it contains an additional field `responseBody` of type `T`, specified in the request.

For example, when we request an existing `Patient`, the server responds with the patient object:

{% code %}
```typescript
const patient1: Patient | OperationOutcome = await aidbox.request<Patient>({
method: "GET",
url: "/fhir/Patient/pt-1",
}).then((result) => result.responseBody)

// patient1:
{
"name": [{
"family": "John2"
}],
"id": "pt-1",
"resourceType": "Patient",
"meta": {
"lastUpdated": "2025-11-17T15:01:46.339168Z",
"versionId": "109",
"extension": [{
"url": "ex:createdAt",
"valueInstant": "2025-11-14T11:46:27.128067Z"
}]
}
}
```
{% endcode %}

However, when we request a non-existing patient, we get an operation outcome:

{% code %}
```typescript
const patient2: Patient | OperationOutcome = await aidbox.request<Patient>({
method: "GET",
url: "/fhir/Patient/non-existent-patient",
}).then((result) => result.responseBody)

// patient2:
{
"resourceType": "OperationOutcome",
"id": "not-found",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Resource Patient/non-existent-patient not found</p></div>"
},
"issue": [{
"severity": "fatal",
"code": "not-found",
"diagnostics": "Resource Patient/non-existent-patient not found"
}]
}
```
{% endcode %}

In cases when the error occurred, but the server did not respond with the `OperationOutcome`, this function throws an `AidboxErrorResponse` object.

### Errors

Errors are represented either as `AidboxErrorResponse` or `AidboxClientError`.

`AidboxErrorResponse` is thrown when the request was sent but an error occurred during response processing.
This class extends the `Error` class, and contains one additional field: `rawResponse`, which is an instance of the `Response` class.

`AidboxClientError` is thrown if the error happened before the request was sent, or if an unexpected error happened during the request.
The `cause` field may contain the original error in the latter case.

## Response preprocessing

The `makeClient` constructor accepts a function as the `onResponse` argument, which will be called on every response from the server.
This function accepts a `Response`, and doesn't return anything.
Its purpose is to handle termination of request processing, based on some information in the `Response` object, for example, authentication redirecting:

{% code %}
```typescript
const baseurl = "https://aidbox.address/"

function authHandler(response: Response): void {
if (response.status === 401 || response.status === 403) {
const encodedLocation = btoa(window.location.href);
const redirectTo = `${baseurl}/auth/login?redirect_to=${encodedLocation}`;
window.location.href = redirectTo;
throw redirect({ href: redirectTo });
}
}

makeClient<Bundle, OperationOutcome, User>({
baseurl,
onResponse: makeAuthHandler(baseurl),
})
```
{% endcode %}
142 changes: 142 additions & 0 deletions docs/tutorials/aidbox-client-and-typegen-tutorial/codegen-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Codegen - generate FHIR types for TypeScript

## Install the codegen dependency

You'll need `typescript`, `tsx`, and `@atomic-ehr/codegen` as dev dependencies:

{% code %}
```sh
npm install -D typescript tsx @atomic-ehr/codegen
```
{% endcode %}

## Project configuration

Make sure that your `package.json` specifies `"type": "module"` in it.

## Usage

To use codegen, we'll need a script that sets up various parameters for type generation:

{% code %}
```typescript
import { APIBuilder } from '@atomic-ehr/codegen';

const builder = new APIBuilder()
.fromPackage("hl7.fhir.r4.core", "4.0.1")
.typescript({})
.outputTo("./typescript-r4/fhir-types");

const report = await builder.generate();
console.log(report);

if (!report.success) {
console.error("FHIR types generation failed.");
process.exit(1);
}
```
{% endcode %}

To run:

```sh
tsx generate-types.ts
# or, if tsx isn't available globally
npm exec tsx generate-types.ts
```

This will generate all FHIR R4 Core types.

## Specify a different FHIR version

There are two ways of specifying FHIR version: `fromPackage` and `fromPackageRef`.

- The `fromPackage` method is used to specify the published package and version.
- The `fromPackageRef` is used to specify a package archive URL.

For example, here's how types for SQL on FHIR can be generated:

{% code %}
```typescript
// --- 8< ---

const builder = new APIBuilder()
.fromPackageRef("https://build.fhir.org/ig/FHIR/sql-on-fhir-v2/package.tgz")
.typescript({})
.outputTo("./typescript-r5/fhir-types");

const report = await builder.generate();

// --- 8< ---
```
{% endcode %}

This will bring in the `sql-on-fhir-v2` package, and all of its dependencies, which include `hl7.fhir.r5.core`.

## Tree-shaking

Not all types are needed at all times, so we can specify what types we want to use in our application:

{% code %}
```typescript
// --- 8< ---

const builder = new APIBuilder()
.fromPackageRef("https://build.fhir.org/ig/FHIR/sql-on-fhir-v2/package.tgz")
.typescript({})
.outputTo("./typescript-r5/fhir-types")
.treeShake({
"hl7.fhir.r5.core": {
"http://hl7.org/fhir/StructureDefinition/OperationOutcome": {},
"http://hl7.org/fhir/StructureDefinition/Bundle": {},
"http://hl7.org/fhir/StructureDefinition/Resource": {},
},
"org.sql-on-fhir.ig": {
"https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition": {},
},
});

const report = await builder.generate();

// --- 8< ---
```
{% endcode %}

This generates only `OperationOutcome`, `Bundle`, and `Resource` types, and their dependencies.

Sometimes, it's handy to skip generating some dependencies based on type fields, for example we can skip generating types for extensions:

{% code %}
```typescript
// --- 8< ---

const builder = new APIBuilder()
.fromPackage("hl7.fhir.r4.core", "4.0.1")
.typescript({})
.outputTo("./typescript-r4/fhir-types")
.treeShake({
"hl7.fhir.r4.core": {
"http://hl7.org/fhir/StructureDefinition/Resource": {},
"http://hl7.org/fhir/StructureDefinition/Bundle": {},
"http://hl7.org/fhir/StructureDefinition/OperationOutcome": {},
"http://hl7.org/fhir/StructureDefinition/DomainResource": {
ignoreFields: ["extension", "modifierExtension"],
},
"http://hl7.org/fhir/StructureDefinition/BackboneElement": {
ignoreFields: ["modifierExtension"],
},
"http://hl7.org/fhir/StructureDefinition/Element": {
ignoreFields: ["extension"],
},
},
})

const report = await builder.generate();

// --- 8< ---
```
{% endcode %}

# Further reading

Please refer to the [codegen](https://github.com/atomic-ehr/codegen) repo [documentation](https://github.com/atomic-ehr/codegen/tree/main/docs) for additional information about configuration and usage.
Loading