npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@outburn/structure-navigator

v1.9.4

Published

Navigate and resolve FHIR element definitions using FSH-like paths

Readme

@outburn/structure-navigator

Navigate and resolve FHIR ElementDefinitions from StructureDefinition snapshots using FSH-like paths.

This library wraps a fhir-snapshot-generator instance and adds:

  • FSH-style path traversal (a.b.c)
  • Slice selection (extension[race])
  • Polymorphic resolution shortcuts (valueString, value[Quantity], value[x])
  • Virtual slice / profile rebasing (extension[us-core-race], value[SimpleQuantity])
  • contentReference rebasing (e.g. Bundle.entry.link.url)

Installation

This package has peer dependencies:

npm i @outburn/structure-navigator fhir-snapshot-generator fhir-package-explorer @outburn/types

Quickstart

Create a FhirPackageExplorer, then a FhirSnapshotGenerator, then a FhirStructureNavigator.

import { FhirSnapshotGenerator } from 'fhir-snapshot-generator';
import { FhirPackageExplorer } from 'fhir-package-explorer';
import { FhirStructureNavigator } from '@outburn/structure-navigator';

const fpe = await FhirPackageExplorer.create({
	context: ['[email protected]'],
	cachePath: './.fhir-cache',
	fhirVersion: '4.0.1'
});

const fsg = await FhirSnapshotGenerator.create({
	fhirVersion: '4.0.1',
	cacheMode: 'lazy',
	fpe
});

const nav = new FhirStructureNavigator(fsg);

const el = await nav.getElement('Patient', 'identifier.assigner.display');
console.log(el.path); // "Reference.display"

const children = await nav.getChildren('Patient', 'identifier');
console.log(children.map(c => c.path));

API

new FhirStructureNavigator(fsg, logger?, cacheOptions?)

  • fsg: a FhirSnapshotGenerator.
  • logger (optional): { debug, info, warn, error }.
  • cacheOptions (optional): External cache implementations for DI (see Cache Architecture below).

Cache Architecture

The navigator implements a two-tier caching strategy:

  1. Inner LRU Layer: Fast, in-memory LRU cache for ultra-hot entries
  2. External Layer (optional): Pluggable external cache (e.g., LMDB) via dependency injection

Cache Types

Four independent cache interfaces can be injected:

interface NavigatorCacheOptions {
  snapshotCache?: ICache<any>;
  typeMetaCache?: ICache<FileIndexEntryWithPkg>;
  elementCache?: ICache<EnrichedElementDefinition>;
  childrenCache?: ICache<EnrichedElementDefinition[]>;
  lruSizes?: {
    snapshot?: number;
    typeMeta?: number;
    element?: number;
    children?: number;
  };
}

Cache Interface

The ICache<T> interface supports array-based keys for LMDB compatibility:

interface ICache<T> {
  get(key: (string | number)[]): Promise<T | undefined> | T | undefined;
  set(key: (string | number)[], value: T): Promise<void> | void;
  has(key: (string | number)[]): Promise<boolean> | boolean;
  delete(key: (string | number)[]): Promise<boolean> | boolean;
  clear(): Promise<void> | void;
}

Keys are structured as arrays to avoid string concatenation/splitting overhead in LMDB implementations.

LRU Sizing

Each cache always has an in-memory LRU hot layer, regardless of whether an external cache is provided. Providing an external cache does not change the in-memory LRU sizing; it simply adds an optional "cold" layer that entries can be promoted from.

Default LRU sizes:

| Cache Type | Default LRU size | |------------|------------------| | Snapshot | 100 | | TypeMeta | 500 | | Element | 2000 | | Children | 500 |

You can override any of these (entry counts) via cacheOptions.lruSizes:

const nav = new FhirStructureNavigator(fsg, logger, {
  lruSizes: {
    // keep more element path resolutions hot in memory
    element: 5000,

    // keep fewer snapshots hot (lower memory footprint)
    snapshot: 50
  }
});

Package Context Namespacing

Element and children caches include a package context namespace (from FPE.getNormalizedRootPackages()) in their keys. This ensures safe sharing of external caches between navigator instances with different package contexts.

The snapshot and typeMeta caches already include package information in their keys, so no additional namespacing is needed.

Example: Custom Cache Implementation

import { ICache } from '@outburn/structure-navigator';

class MyLMDBCache<T> implements ICache<T> {
  async get(key: (string | number)[]): Promise<T | undefined> {
    // Use key array directly with LMDB range queries
    return await this.db.get(key);
  }
  
  async set(key: (string | number)[], value: T): Promise<void> {
    await this.db.put(key, value);
  }
  
  // ... implement other methods
}

const nav = new FhirStructureNavigator(fsg, logger, {
  snapshotCache: new MyLMDBCache(),
  elementCache: new MyLMDBCache(),
  childrenCache: new MyLMDBCache()
});

getElement(snapshotId, fshPath)

Resolves a single element using an FSH-like path.

  • snapshotId: either
    • a string (StructureDefinition id or canonical url), e.g. "us-core-patient", "Patient", "http://.../StructureDefinition/...", or
    • a FileIndexEntryWithPkg (package id/version + filename), as used by fhir-package-explorer.
  • fshPath: FSH-like path string (see below).

Returns an EnrichedElementDefinition.

getChildren(snapshotId, fshPath)

Returns the direct children of the resolved element.

  • Use "." to get children of the root element.

getFsg(), getFpe(), getLogger()

Access the underlying snapshot generator, package explorer, and logger.

Returned element shape

The navigator enriches each returned element with metadata useful for tooling:

  • __fromDefinition: canonical URL of the StructureDefinition the element ultimately came from
  • __corePackage: the “core” package identifier used for resolving base types
  • __packageId / __packageVersion: package that contributed the resolved snapshot
  • __name: computed “FSH-ish” name(s)
    • For polymorphic value[x], __name is inferred like valueString, valueQuantity, etc.
    • For contentReference elements, __name is inferred from the reference target.
  • type[].__kind: best-effort kind info (primitive-type, complex-type, resource, logical, system, …)

Note: the navigator also strips a set of verbose fields (like definition, comment, mapping, …) from elements when caching snapshots.

Path syntax (FSH-like)

Paths are dot-separated segments. Dots inside [...] are not treated as separators.

1) Normal element navigation

await nav.getElement('us-core-patient', 'gender');
await nav.getElement('Patient', 'address.city');

2) Deep navigation across types (rebasing)

If an element’s type points to another StructureDefinition (base type or profile), traversal “rebases” into that snapshot.

Examples:

  • identifier.value.extension rebases from Identifier.value (string) into StructureDefinition/string.
  • identifier.assigner.identifier.assigner.display rebases through Reference/Identifier back and forth.
const el = await nav.getElement('us-core-patient', 'identifier.value.extension');
console.log(el.path); // "string.extension"

3) Slices: element[sliceName]

FSH slice selection is supported:

await nav.getElement('us-core-patient', 'extension[race]');
await nav.getElement('us-core-patient', 'extension[race].url');

4) Polymorphic elements ([x])

For polymorphic elements like Extension.value[x], you can select a type in several ways.

a) Shortcut suffix form: valueString, valueQuantity, …

const el = await nav.getElement('Extension', 'valueString');
// resolves Extension.value[x] and narrows type to string

b) Bracket type form: value[string], value[Quantity], value[CodeableConcept]

await nav.getElement('Extension', 'value[string]');
await nav.getElement('Extension', 'value[Quantity]');
await nav.getElement('Extension', 'value[CodeableConcept]');

c) Base polymorphic element: value[x]

await nav.getElement('Extension', 'value[x]'); // returns the polymorphic head with all possible types

d) Traverse into a selected polymorphic type

await nav.getElement('Extension', 'valueQuantity.value');
await nav.getElement('Extension', 'value[Quantity].value');
await nav.getElement('Extension', 'valueReference.identifier.system');

5) Real polymorphic slices

Some profiles define real slices on polymorphics, e.g. Extension.value[x]:valueString. The navigator will return the real slice element when it exists:

const profile = 'http://example.org/StructureDefinition/ExtensionWithPolySlices';
await nav.getElement(profile, 'valueString');
await nav.getElement(profile, 'value[string]');
await nav.getElement(profile, 'value[valueString]');

If a slice exists for some types but not others, selecting a non-sliced type still works and returns the narrowed head.

6) Virtual slices (profile rebasing): element[SomeProfile]

If the text inside brackets is not a real slice name, the navigator will try to resolve it as a StructureDefinition (by id in core package context, or by canonical URL).

If it resolves, it is treated as a “virtual slice” and traversal continues in that profile snapshot.

await nav.getElement('Patient', 'extension[us-core-race]');
await nav.getElement('Patient', 'extension[http://hl7.org/fhir/us/core/StructureDefinition/us-core-race]');
await nav.getElement('Patient', 'extension[us-core-race].url');

Virtual slicing is also supported on polymorphics when the profile’s base type is allowed:

await nav.getElement('Observation', 'value[SimpleQuantity]');
await nav.getElement('Observation', 'value[SimpleQuantity].value');

7) contentReference rebasing

FHIR allows elements to reference other elements via contentReference (e.g. Bundle.entry.link has #Bundle.link). When encountered, traversal rebases through the referenced element and continues.

await nav.getElement('Bundle', 'entry.link');
await nav.getElement('Bundle', 'entry.link.url');
await nav.getElement('Questionnaire', 'item.item.item.item.linkId');

Getting children

Use getChildren() to fetch direct children.

// Root children
const rootChildren = await nav.getChildren('us-core-patient', '.');

// Children of a resolved path
const idChildren = await nav.getChildren('us-core-patient', 'identifier');

// Children also work through rebasing/polymorphics/slices/contentReference
await nav.getChildren('Extension', 'valueString');
await nav.getChildren('Patient', 'extension[us-core-race]');
await nav.getChildren('Bundle', 'entry.link.extension');

Errors and gotchas

  • If a segment can’t be found, getElement() throws.
  • If you use a virtual slice/profile whose base type is not allowed by the parent element’s type[], it throws with an “Expected one of …” message.
    • Example: Observation.value[bp] throws because the resolved profile type is not permitted.
  • getChildren() throws for choice-type elements that still have multiple possible types (because children are ambiguous).
  • getChildren('.', ...) is supported only via fshPath = "." (root).

License

Apache-2.0 (see LICENSE).