Skip to main content

@fhir-dsl/fhirpath

Overview

@fhir-dsl/fhirpath is a JavaScript Proxy-backed FHIRPath builder. Property access on the proxy either navigates (expr.namePatient.name), invokes a known function (expr.name.where(...)), or terminates (.compile() → string, hidden symbol → PathOp[]). The same proxy class backs both standalone expressions and predicate callbacks, so arithmetic and logical operators are interchangeable between contexts. The evaluate() function walks the AST and runs it against a resource.

Installation

npm install @fhir-dsl/fhirpath

Exports

NameKindOne-liner
fhirpathfunctionRoot builder factory: fhirpath<Patient>("Patient") returns a typed FhirPathExpr.
$context / $resource / $rootResource / $ucum / $index / $totalconstPre-made variable proxies for FHIRPath environment names.
envVarfunctionBuild a FhirPathExpr for a user-supplied %foo env var.
evaluatefunctionEvaluate a compiled op-list against a resource, returning a collection.
EvalOptionsinterface{ strict?, env? } — passed to evaluate().
FhirPathEvaluationErrorclassRaised on invalid $this, unknown env vars, strict-mode singleton failures, etc.
createPredicateProxyfunctionBuild a proxy that records predicate ops for later extraction.
extractPredicatefunctionPull the CompiledPredicate back out of a predicate proxy.
PREDICATE_SYMBOLconstSymbol.for("fhirpath.predicate") — the hidden key used on predicate proxies.
PathOp / NavOp / FilterOp / SubsetOp / CombineOp / StringOp / MathOp / ArithmeticOp / ConversionOp / UtilityOp / OperatorOp / LiteralOp / VarOp / FhirFnOp / AggregateOp / CompiledPredicatetypeDiscriminated unions for the op AST.
FhirPathExpr / FhirPathOps / FhirPathPredicate / PredicateOps / FhirPathResource / FhirTypeMap / IsPrimitive / NavKeys / UnwraptypeBuilder-side type machinery.

API

fhirpath

Signature

function fhirpath<T extends FhirPathResource>(resourceType: string): FhirPathExpr<T>;

Parameters

  • resourceType — The root resource type (e.g. "Patient"). Prepended to every compiled path.
  • Type parameter T — The resource type (or any shape) the builder navigates over.

Returns — A FhirPathExpr<T> proxy. Access fields by property, call functions, or terminate with .compile().

Example

import { fhirpath } from "@fhir-dsl/fhirpath";

const expr = fhirpath<Patient>("Patient")
.name
.where(($this) => $this.use.eq("official"))
.given
.first();

expr.compile();
// → "Patient.name.where(use = 'official').given.first()"

Notes

  • The proxy returns undefined for the then property — this defeats accidental thenable detection if an expression is awaited.
  • toJSON() and toString() both return the compiled path, so expressions serialise cleanly when embedded in JSON queries.

Environment variables — $context, $resource, $rootResource, $ucum, $index, $total, envVar

Signature

const $context: FhirPathExpr<unknown>; // %context
const $resource: FhirPathExpr<unknown>; // %resource
const $rootResource: FhirPathExpr<unknown>; // %rootResource
const $ucum: FhirPathExpr<string>; // %ucum (hardcoded to "http://unitsofmeasure.org")
const $index: FhirPathExpr<number>; // $index (iteration)
const $total: FhirPathExpr<number>; // $total (dual semantics — see notes)

function envVar<T = unknown>(name: string): FhirPathExpr<T>;

Example

import { fhirpath, $this, $total, $ucum, envVar } from "@fhir-dsl/fhirpath";

// Arithmetic inside aggregate — $this and $total are predicate-proxies that
// support arithmetic operators. `$this.add($total)` compiles to "$this + $total".
const sum = fhirpath<Observation>("Observation")
.valueQuantity.value
.aggregate(($this, $total) => $this.add($total), 0);

// User env bag — envVar(name) resolves from options.env at evaluate() time.
const cutoff = envVar<string>("cutoff");

Notes

  • %ucum is hardcoded to http://unitsofmeasure.org; no env entry is needed.
  • $total has dual semantics — inside where/select/repeat it's a number (the collection size); inside aggregate() it's the accumulator collection. The evaluator wraps appropriately.
  • Env bag accepts keys with or without %. { foo: 42 } and { "%foo": 42 } both resolve for %foo. Unknown env vars raise FhirPathEvaluationError.
  • Hidden Symbol.for("fhirpath.ops") on the root expression exposes the op list — used by the core query builder to embed FHIRPath predicates into _filter.

evaluate / EvalOptions / FhirPathEvaluationError

Signature

function evaluate(ops: PathOp[], resource: unknown, options?: EvalOptions): unknown[];

interface EvalOptions {
strict?: boolean; // raise on §4.5 singleton failures instead of returning []
env?: Readonly<Record<string, unknown>>; // %foo / foo — either form works
}

class FhirPathEvaluationError extends Error {}

Parameters

  • ops — the AST extracted from a builder via the hidden Symbol.for("fhirpath.ops").
  • resource — the resource to evaluate against; it also becomes the root of %context, %resource, and %rootResource.
  • options.strict — when true, multi-element inputs passed to a singleton-context operator throw instead of returning an empty collection.
  • options.env — bag for user-supplied %foo / foo lookups.

Returns — A collection (always an array, possibly empty).

Example

import { fhirpath, evaluate } from "@fhir-dsl/fhirpath";

const patient = { resourceType: "Patient", name: [{ use: "official", given: ["Jane"], family: "Doe" }] };
const expr = fhirpath<Patient>("Patient").name.where(($) => $.use.eq("official")).given;
const ops = (expr as any)[Symbol.for("fhirpath.ops")] as PathOp[];
evaluate(ops, patient); // → ["Jane"]

Notes$this cannot be evaluated via a bare VarOp outside a predicate proxy; always build predicates with createPredicateProxy. $index and $total are only defined inside iteration frames — outside, they throw FhirPathEvaluationError.


_field primitive-sibling extensions

Behavior — The FHIRPath evaluator and proxy understand FHIR's primitive-extension convention: Patient._active resolves to Patient.active.extension[] (the sibling-key pattern from FHIR R4 §2.1.0.2.1). Both builder navigation and the evaluator respect this, so you can write fhirpath<Patient>("Patient")._active.extension.where(...) and get the underlying extension array.

Example

const extExpr = fhirpath<Patient>("Patient")._active.extension.where(($) => $.url.eq("http://example.org/reason"));
extExpr.compile(); // → "Patient._active.extension.where(url = 'http://example.org/reason')"

createPredicateProxy / extractPredicate / PREDICATE_SYMBOL

Signature

function createPredicateProxy(path: string, ops: PathOp[]): unknown;
function extractPredicate(proxy: unknown): CompiledPredicate;
const PREDICATE_SYMBOL: unique symbol; // Symbol.for("fhirpath.predicate")

interface CompiledPredicate { ops: PathOp[]; compiledPath: string; }

Example

import { createPredicateProxy, extractPredicate } from "@fhir-dsl/fhirpath";

const $this = createPredicateProxy("$this", []);
const predicate = extractPredicate(($this as any).use.eq("official"));
// predicate.compiledPath === "$this.use = 'official'"

Notes — Used by fhirpath() internally so that .where(cb) callbacks produce the same AST shape as standalone builders. The predicate proxy mirrors the ARITHMETIC_OPS table so $this.add($total) works inside aggregate().