@openhi/types
v0.0.23
Published
TypeScript types for FHIR and OpenHI domains. Published as a single npm package from the [OpenHI](https://github.com/codedrifters/openhi) monorepo.
Readme
@openhi/types
TypeScript types for FHIR and OpenHI domains. Published as a single npm package from the OpenHI monorepo.
What’s in this package
FHIR R4 types generated from HL7 FHIR R4 JSON Schemas (e.g. Patient, Bundle, CodeSystem, ValueSet, Resource, DomainResource). Use for EHR and FHIR-aligned APIs. The main entry re-exports all types.
Installation
pnpm add @openhi/types
# or
npm install @openhi/typesUsage
import type { Patient, Bundle, CodeableConcept } from "@openhi/types";import type { Patient, HumanName, Identifier } from "@openhi/types";
function formatPatientName(patient: Patient): string {
const name = patient.name?.[0];
return name ? [name.given?.join(" "), name.family].filter(Boolean).join(" ") : "";
}Building and testing (monorepo)
From the repository root:
pnpm install
pnpm build --filter @openhi/types
pnpm test --filter @openhi/typesFrom this package directory (packages/@openhi/types):
pnpm install # at repo root first if needed
pnpm run build
pnpm run testAuthoring OpenHI StructureDefinitions (FSH)
Status: experimental —
experiment/fhir-types-codegen, issue #830. See ADR 2026-04-20-01.
OpenHI-owned FHIR types — extensions, control-plane resources (User, Tenant, Workspace, Membership, RoleAssignment, Role), and future data-plane profiles — are authored as FSH and compiled to StructureDefinition JSON by Sushi. The same generator that consumes hl7.fhir.r4.core also consumes OpenHI SDs, so every OpenHI-authored type flows through one pipeline (ADR §2).
See fsh/README.md for detailed authoring conventions — which FSH keyword to use for each shape, the SUSHI Resource: parent constraint, and the PascalCase Id: invariant the IR builder relies on.
Canonical URL scheme
Every OpenHI-authored StructureDefinition resolves to:
https://openhi.org/fhir/StructureDefinition/{id}This is set once in fsh/sushi-config.yaml via canonical: https://openhi.org/fhir — Sushi appends /StructureDefinition/{id} to produce each SD's URL. Individual FSH files only declare Id:.
Repository layout
packages/@openhi/types/
├── fsh/
│ ├── sushi-config.yaml # canonical URL + Sushi knobs
│ ├── input/fsh/
│ │ ├── *.fsh # data-plane extensions (authored)
│ │ └── control-plane/*.fsh # control-plane resources + terminology
│ └── fsh-generated/ # Sushi output (gitignored)
├── sds/openhi/
│ ├── data-plane/*.json # staged for generator (gitignored)
│ └── control-plane/*.json # staged for generator (gitignored)
└── src/
├── data/ # emitted FHIR + extension accessors
└── control/ # emitted control-plane resources
# (committed — regenerated on every build;
# the drift check in CI fails if hand-edited)Authoring an extension
Single-file example — see fsh/input/fsh/PreferredPronouns.fsh:
Extension: PreferredPronouns
Id: preferred-pronouns
Title: "Preferred Pronouns"
Description: "A patient's preferred pronouns (free text)."
Context: Patient
* value[x] only stringThe generator emits an accessor module per ADR §5 — getPreferredPronouns(patient) / setPreferredPronouns(patient, "they/them") — rather than a subtype of Patient. Extensions with max: "*" get get / add / remove variants instead. The base Patient type stays wire-faithful.
Authoring a specialization
Specializations — new resources, or refinements of a FHIR base like Person → User — use FSH's Logical: or Resource: keyword with a Parent: line so Sushi emits derivation: specialization (the Profile: keyword would produce derivation: constraint instead, which the generator skips). Control-plane files live under fsh/input/fsh/control-plane/.
Resource: is for net-new FHIR resources (Membership, Role, RoleAssignment) and only accepts Resource or DomainResource as a parent — a SUSHI constraint, not a FHIR one. To specialize a concrete FHIR resource (e.g. User extending Person) with a different resourceType literal, use Logical: instead:
Logical: User
Parent: Person
Id: User
Title: "User"
Description: "OHI control plane resource with Person shape and resourceType \"User\"."
* currentTenant 1..1 Reference(Organization) "Tenant the user is currently acting in"
* currentWorkspace 1..1 Reference(Organization) "Workspace within the tenant"The generator treats Logical SDs with a FHIR-resource base as resources-with-resourceType-override: the emitted TypeScript is export type User = Omit<Person, "resourceType"> & { resourceType: "User"; ... } (ADR §2 intersection-type form). Id: User (PascalCase matching the name) is required — Sushi prefixes every snapshot element's path with the id, and the IR builder resolves the root element by path === sd.name. See fsh/README.md for the full guide.
Regenerate
pnpm --filter @openhi/types genThis runs Sushi (with -s so specialization SDs get a snapshot), partitions the resulting StructureDefinition / CodeSystem / ValueSet JSON into sds/openhi/data-plane/ and sds/openhi/control-plane/, and then runs @openhi/types-generator to write TypeScript types, Zod schemas, and extension accessor modules into both src/data/ and src/control/. The same script runs in pre-compile, so the CI build fails (via the existing repo-mutation check) if any emitted file was hand-edited or an upstream SD change wasn't re-committed. The whole pipeline is under 30 seconds cold and a few hundred milliseconds warm.
