fhir-engine
v0.6.0
Published
FHIR Engine for healthcare data management
Maintainers
Readme
fhir-engine
FHIR Runtime Kernel — Bootstrap and orchestrate the embedded FHIR stack with a single function call.
fhir-engine assembles fhir-definition, fhir-runtime, and fhir-persistence into a running system from a single configuration object.
Features
- One-call bootstrap —
createFhirEngine(config)initializes definitions, runtime, and persistence - Package resolution —
resolvePackages()downloads/caches FHIR IG packages automatically - Plugin system — lifecycle hooks (
init/start/ready/stop) for extensibility - Config file support —
fhir.config.ts/.js/.jsonwith env variable overrides - Multi-adapter — SQLite (native) and PostgreSQL out of the box
- Full-text search — SQLite FTS5 and PostgreSQL tsvector/GIN (v0.6.0+)
- Batch validation —
runtime.validateMany()for bulk resource validation (v0.6.0+) - Reindex with progress —
reindexAllV2()/reindexResourceTypeV2()with progress callbacks (v0.6.0+) - TypeScript-first — full type safety, dual ESM/CJS builds
Install
npm install fhir-enginePeer dependencies
fhir-engine depends on the three upstream FHIR packages:
npm install fhir-definition fhir-runtime fhir-persistencev0.6.0 requires: fhir-definition ≥ 0.6.0, fhir-runtime ≥ 0.9.0, fhir-persistence ≥ 0.6.0
Quick Start
import { createFhirEngine } from "fhir-engine";
const engine = await createFhirEngine({
database: { type: "sqlite", path: ":memory:" },
packages: { path: "./fhir-packages" },
});
// Create a Patient
const patient = await engine.persistence.createResource("Patient", {
resourceType: "Patient",
name: [{ family: "Smith", given: ["John"] }],
gender: "male",
birthDate: "1990-01-15",
});
// Read it back
const read = await engine.persistence.readResource("Patient", patient.id!);
// Shut down
await engine.stop();Config File
Create a fhir.config.ts (or .js / .json) in your project root:
// fhir.config.ts
import { defineConfig } from "fhir-engine";
export default defineConfig({
database: { type: "sqlite", path: "./data/fhir.db" },
packages: { path: "./fhir-packages" },
plugins: [],
});Then bootstrap with zero arguments:
const engine = await createFhirEngine(); // auto-discovers fhir.config.tsEnvironment Variable Overrides
| Variable | Overrides | Example |
| -------------------- | --------------------------------- | ------------------------------- |
| FHIR_DATABASE_TYPE | database.type | sqlite / postgres |
| FHIR_DATABASE_URL | database.path or database.url | :memory: / postgresql://... |
| FHIR_PACKAGES_PATH | packages.path | ./fhir-packages |
Plugin System
Plugins hook into the engine lifecycle:
import { createFhirEngine, FhirEnginePlugin, EngineContext } from "fhir-engine";
const myPlugin: FhirEnginePlugin = {
name: "my-plugin",
async init(ctx: EngineContext) {
// Before persistence init — ctx.persistence is undefined
ctx.logger.info("Plugin initializing...");
},
async start(ctx: EngineContext) {
// After persistence init — ctx.persistence is available
await ctx.persistence!.createResource("Patient", {
resourceType: "Patient",
name: [{ family: "Seed" }],
});
},
async ready(ctx: EngineContext) {
ctx.logger.info("System fully operational");
},
async stop(ctx: EngineContext) {
ctx.logger.info("Cleaning up...");
},
};
const engine = await createFhirEngine({
database: { type: "sqlite", path: ":memory:" },
packages: { path: "./fhir-packages" },
plugins: [myPlugin],
});Lifecycle
init → plugins.init(ctx) — before FhirSystem.initialize()
start → FhirSystem.initialize() — schema + migration
plugins.start(ctx) — ctx.persistence now available
ready → plugins.ready(ctx) — system fully operational
stop → plugins.stop(ctx) — reverse registration order
adapter.close()- init/start/ready errors abort startup with clear message
- stop errors are logged but don't block other plugins
API Reference
createFhirEngine(config?)
Creates and bootstraps a fully initialized FHIR engine.
Parameters:
config(optional) —FhirEngineConfig. If omitted, auto-loads fromfhir.config.*in cwd.
Returns: Promise<FhirEngine>
FhirEngine
| Property | Type | Description |
| --------------- | ------------------------------------------------ | ---------------------------------------------- |
| definitions | DefinitionRegistry | FHIR definitions from fhir-definition |
| runtime | FhirRuntimeInstance | FHIRPath, validation from fhir-runtime |
| persistence | FhirPersistence | CRUD + search + indexing from fhir-persistence |
| adapter | StorageAdapter | Underlying database adapter |
| sdRegistry | StructureDefinitionRegistry | Loaded StructureDefinitions |
| spRegistry | SearchParameterRegistry | Loaded SearchParameters |
| resourceTypes | string[] | Resource types with database tables |
| context | EngineContext | Shared context (same object plugins receive) |
| logger | Logger | Logger instance |
| search() | (type, params, opts?) => Promise<SearchResult> | High-level FHIR search |
| status() | () => FhirEngineStatus | Engine health and status information |
| stop() | () => Promise<void> | Gracefully shut down the engine |
FhirEngineConfig
interface FhirEngineConfig {
database: DatabaseConfig; // sqlite | sqlite-wasm | postgres
packages: PackagesConfig; // { path: string }
igs?: Array<{ name: string; version?: string }>; // IG packages to resolve
packageResolve?: { allowDownload?: boolean }; // resolution options
packageName?: string; // IG migration label
packageVersion?: string; // IG migration version
logger?: Logger; // custom logger (default: console)
plugins?: FhirEnginePlugin[]; // plugins array
}When igs is provided, createFhirEngine() automatically resolves packages before loading definitions:
const engine = await createFhirEngine({
database: { type: "sqlite", path: ":memory:" },
packages: { path: "./fhir-packages" },
igs: [{ name: "hl7.fhir.r4.core", version: "4.0.1" }],
});
// Packages are downloaded/linked into ./fhir-packages/ automaticallyFhirEngineStatus
Returned by engine.status():
interface FhirEngineStatus {
fhirVersions: string[]; // e.g. ['4.0']
loadedPackages: string[]; // e.g. ['[email protected]']
resourceTypes: string[]; // e.g. ['Patient', 'Observation', ...]
databaseType: "sqlite" | "sqlite-wasm" | "postgres";
igAction: "new" | "upgrade" | "consistent";
startedAt: Date;
plugins: string[]; // registered plugin names
}engine.search(resourceType, queryParams, options?)
High-level FHIR search — parses URL query parameters and executes search in one call:
const result = await engine.search("Patient", { name: "Smith", _count: "10" });
console.log(result.resources); // PersistedResource[]
console.log(result.total); // number (if options.total = 'accurate')Search Utilities (re-exported from fhir-persistence)
For lower-level search control:
import { parseSearchRequest, executeSearch } from "fhir-engine";
import type { SearchRequest, SearchResult, SearchOptions } from "fhir-engine";
const request = parseSearchRequest(
"Patient",
{ name: "Smith" },
engine.spRegistry,
);
const result = await executeSearch(engine.adapter, request, engine.spRegistry);FHIRPath Evaluation (re-exported from fhir-runtime)
import {
evalFhirPath,
evalFhirPathBoolean,
evalFhirPathString,
} from "fhir-engine";
const values = evalFhirPath("Patient.name.family", patient); // unknown[]
const active = evalFhirPathBoolean("Patient.active", patient); // boolean
const family = evalFhirPathString("Patient.name.family", patient); // string | undefinedresolvePackages(config, options?)
Ensure FHIR IG packages are available in the project's packages directory.
Resolution order: local directory → system cache (~/.fhir/packages) → FHIR Package Registry download.
import { resolvePackages } from "fhir-engine";
const result = await resolvePackages(config);
console.log(result.success); // true if all resolved
console.log(result.packages); // ResolvedPackage[] with name, version, path, source
console.log(result.errors); // any failures
// Offline mode — cache only, no downloads
const offline = await resolvePackages(config, { allowDownload: false });defineConfig(config)
Type-safe identity helper for config files. Returns the config unchanged.
loadFhirConfig(path?)
Loads config from a file. Auto-discovers fhir.config.ts → .js → .mjs → .json from cwd if no path given.
Database Adapters
| database.type | Adapter | Use Case |
| --------------- | ---------------------- | ---------------------------------- |
| sqlite | BetterSqlite3Adapter | Node.js / Electron / CLI |
| postgres | PostgresAdapter | Production servers (via pg.Pool) |
| sqlite-wasm | — | Removed in v0.4.2 — use sqlite |
PostgreSQL Setup
pg is included as a direct dependency (v0.5.1+), no separate installation needed.
Full-Text Search (v0.6.0+)
Full-text search is automatically enabled by fhir-persistence v0.6.0:
- SQLite — FTS5 virtual tables with trigram tokenizer
- PostgreSQL — tsvector columns with GIN indexes
No configuration required — string search parameters (e.g. Patient?name=Smith) automatically use FTS when available, with fallback to LIKE prefix matching.
Batch Validation (v0.6.0+)
Validate multiple resources in a single call:
const results = await engine.runtime.validateMany(resources, {
concurrency: 4,
});
// BatchValidationResult[]Reindex with Progress (v0.6.0+)
import { reindexAllV2 } from "fhir-engine";
await reindexAllV2(engine.adapter, engine.resourceTypes, (info) => {
console.log(`${info.resourceType}: ${info.processed}/${info.total}`);
});const engine = await createFhirEngine({
database: {
type: "postgres",
url: "postgresql://user:pass@localhost:5432/fhir_db",
max: 20, // pool size (default: 10)
idleTimeoutMillis: 30000, // idle timeout (default: 30s)
},
packages: { path: "./fhir-packages" },
});Requirements
- Node.js >= 18.0.0
- npm >= 9.0.0
License
Upstream Dependency Versions
| Package | Required | Features added in this version |
| ---------------- | -------- | ------------------------------------------------------------------ |
| fhir-definition | ≥ 0.6.0 | Semver range resolution, retry/offline for PackageRegistryClient |
| fhir-runtime | ≥ 0.9.0 | validateMany() batch validation, BatchValidationOptions/Result |
| fhir-persistence | ≥ 0.6.0 | FTS5/tsvector full-text search, reindexAllV2 progress reporting |
