Skip to main content

FHIRPath Expression Builder

@fhir-dsl/fhirpath provides a type-safe FHIRPath expression builder for TypeScript. Navigate FHIR resources with property access, build FHIRPath strings, and evaluate expressions -- all with full autocomplete and compile-time type safety.

Installation

npm install @fhir-dsl/fhirpath

Quick Start

import { fhirpath } from "@fhir-dsl/fhirpath";
import type { Patient } from "./fhir/r4";

// Type-safe path navigation with autocomplete at every step
const expr = fhirpath<Patient>("Patient").name.family;
expr.compile(); // "Patient.name.family"
expr.evaluate(somePatient); // ["Smith", "Doe"]

Expression Predicates

Use callback expressions with $this for filtering, existence checks, and projections:

// where() with expression predicate
fhirpath<Patient>("Patient")
.name.where($this => $this.use.eq("official"))
.family.compile();
// "Patient.name.where($this.use = 'official').family"

// Backward compatible simple form
fhirpath<Patient>("Patient")
.name.where("use", "official")
.family.compile();
// "Patient.name.where(use = 'official').family"

// exists() with criteria
fhirpath<Patient>("Patient")
.name.exists($this => $this.use.eq("official"))
.compile();
// "Patient.name.exists($this.use = 'official')"

// all() -- true if all items match
fhirpath<Patient>("Patient")
.name.all($this => $this.family.exists())
.compile();
// "Patient.name.all($this.family.exists())"

// select() -- project each element
fhirpath<Patient>("Patient")
.name.select($this => $this.family)
.compile();
// "Patient.name.select($this.family)"

Comparison & Boolean Operators

Available inside expression predicates:

// Comparison operators: eq, neq, lt, gt, lte, gte
$this.value.gt(100) // $this.value > 100
$this.status.neq("draft") // $this.status != 'draft'

// Boolean operators: and, or, xor, not, implies
$this.use.eq("official").and($this.family.exists())
$this.active.not()

Collection Operations

Subsetting

fp.name.first() // First element
fp.name.last() // Last element
fp.name.single() // Exactly one element (throws if > 1)
fp.name.tail() // All but first
fp.name.skip(2) // Skip first 2
fp.name.take(3) // Take first 3

Existence & Filtering

fp.name.count() // Number of elements
fp.name.exists() // Non-empty?
fp.name.empty() // Empty?
fp.name.distinct() // Remove duplicates
fp.name.isDistinct() // All values unique?
fp.name.allTrue() // All items are true?
fp.name.anyTrue() // Any item is true?
fp.name.allFalse() // All items are false?
fp.name.anyFalse() // Any item is false?

Combining

fp.name.union(otherExpr) // Merge + deduplicate
fp.name.combine(otherExpr) // Merge with duplicates
fp.name.intersect(otherExpr) // Elements in both
fp.name.exclude(otherExpr) // Elements not in other

String Functions

fp.name.family.upper() // Uppercase
fp.name.family.lower() // Lowercase
fp.name.family.length() // String length
fp.name.family.startsWith("Sm") // Starts with prefix?
fp.name.family.endsWith("th") // Ends with suffix?
fp.name.family.contains("mit") // Contains substring?
fp.name.family.indexOf("mi") // Index of substring
fp.name.family.substring(0, 3) // Extract substring
fp.name.family.replace("Smith", "Jones") // Replace text
fp.name.family.matches("^S.*h$") // Regex match
fp.name.family.replaceMatches("[aeiou]", "*") // Regex replace
fp.name.family.toChars() // Split to characters

Math Functions

fp.valueQuantity.value.abs() // Absolute value
fp.valueQuantity.value.ceiling() // Round up
fp.valueQuantity.value.floor() // Round down
fp.valueQuantity.value.round(2) // Round to precision
fp.valueQuantity.value.truncate() // Truncate to integer
fp.valueQuantity.value.sqrt() // Square root
fp.valueQuantity.value.power(3) // Raise to power
fp.valueQuantity.value.ln() // Natural logarithm
fp.valueQuantity.value.log(10) // Log with base
fp.valueQuantity.value.exp() // e^x

Conversion Functions

fp.active.toBoolean() // Convert to boolean
fp.active.toInteger() // Convert to integer
fp.active.toDecimal() // Convert to decimal
fp.active.toFhirString() // Convert to string (FHIRPath toString)
fp.birthDate.toDate() // Convert to date
fp.birthDate.toDateTime() // Convert to dateTime
fp.active.convertsToBoolean() // Can convert to boolean?
fp.active.convertsToInteger() // Can convert to integer?
// ... and more convertsTo* variants
info

toFhirString() maps to FHIRPath's toString() function. The JS toString() method returns the compiled path string for serialization.

Type Narrowing with ofType()

Filter collections by FHIR type -- critical for polymorphic value[x] fields:

fhirpath<Observation>("Observation")
.value.ofType("Quantity").value
.compile();
// "Observation.value.ofType(Quantity).value"

The FhirTypeMap interface is extensible via declaration merging to add your own types.

Utility Functions

fp.name.trace("debug") // Debug trace (returns input unchanged)
fp.now() // Current dateTime
fp.today() // Current date
fp.timeOfDay() // Current time
fp.name.children() // Immediate children
fp.name.descendants() // All descendants

Conditional: iif()

fp.name.first().iif(
$this => $this.use.eq("official"), // criterion
$this => $this.family, // true result
$this => $this.given, // otherwise result
).compile();
// "Patient.name.first().iif($this.use = 'official', $this.family, $this.given)"

Recursive Navigation: repeat()

fhirpath<Organization>("Organization")
.repeat($this => $this.partOf)
.compile();
// "Organization.repeat($this.partOf)"

FHIRPath Spec Coverage

The package implements approximately 85% of the official FHIRPath specification:

CategoryFunctionsStatus
NavigationProperty access, children, descendantsImplemented
Filteringwhere, select, repeat, ofTypeImplemented
Existenceexists, all, allTrue/anyTrue/allFalse/anyFalse, count, empty, distinct, isDistinctImplemented
Subsettingfirst, last, single, tail, skip, take, intersect, excludeImplemented
Combiningunion, combineImplemented
StringindexOf, substring, startsWith, endsWith, contains, upper, lower, replace, matches, replaceMatches, length, toCharsImplemented
Mathabs, ceiling, exp, floor, ln, log, power, round, sqrt, truncateImplemented
ConversiontoBoolean, toInteger, toDecimal, toString, toDate, toDateTime, toTime, toQuantity, convertsTo*Implemented
Operators=, !=, <, >, <=, >=, and, or, xor, not, implies, is, asImplemented
Utilitytrace, now, today, timeOfDay, iifImplemented
Aggregateaggregate()Not yet
Equality~ (equivalent), !~ (not equivalent)Not yet
Arithmetic+, -, *, /, mod, div (standalone operators)Not yet