@outburn/fume-mapping-provider
v1.1.2
Published
Manage pre-defined, named FUME expressions (mappings) from FHIR packages, servers, or files
Downloads
255
Readme
@outburn/fume-mapping-provider
A TypeScript module for managing pre-defined, named FUME expressions (mappings) from multiple sources: files, FHIR servers, and FHIR packages.
Features
- 🚀 Lightning-fast user mappings: In-memory cache for file and server mappings
- 📁 File mappings: Load from local
.fumefiles (or custom extensions) - 🌐 Server mappings: Load from FHIR servers with automatic pagination
- 📦 Package mappings: Load from FHIR packages via
fhir-package-explorer - 🔄 Smart collision handling: File mappings override server mappings
- 🛰️ Automatic change tracking: Poll files and FHIR server resources with incremental updates
- 🔍 Flexible search: Find package mappings by URL, ID, or name
- 📝 Structured logging: Optional logger interface support
- ✅ FUME validation: Filters StructureMaps by FUME-specific extensions
Architecture
The provider separates mappings into two categories:
User Mappings (File + Server)
- Fast access: Loaded once, cached in memory
- Key-based lookup: Use unique keys for instant retrieval
- Collision resolution: File mappings override server mappings with warnings
- Focused refresh by key: Re-fetch a single mapping from its real source
Package Mappings
- Always fresh: Queried from FPE on demand (FPE handles caching)
- Immutable: Packages never change once installed
- Smart identifier resolution: Try URL → ID → name automatically
- Package filtering: Filter by package context
Installation
npm install @outburn/fume-mapping-provider
# Dependencies based on usage:
npm install fhir-package-explorer # For package mappings
npm install @outburn/fhir-client # For server mappingsQuick Start
import { FumeMappingProvider } from '@outburn/fume-mapping-provider';
import { FhirPackageExplorer } from 'fhir-package-explorer';
import { FhirClient } from '@outburn/fhir-client';
// Setup dependencies
const fhirClient = new FhirClient({ baseUrl: 'https://hapi.fhir.org/baseR4' });
const packageExplorer = await FhirPackageExplorer.create({
context: ['[email protected]']
});
// Create provider
const provider = new FumeMappingProvider({
mappingsFolder: './mappings',
fileExtension: '.fume', // Optional, default is '.fume'
fhirClient: fhirClient,
packageExplorer: packageExplorer,
aliasConceptMapId: 'my-aliases-cm-id', // Optional, skips alias ConceptMap search
canonicalBaseUrl: 'http://example.com', // Optional, default is 'http://example.com'
filePollingIntervalMs: 5000, // Optional, default 5000 (set <= 0 to disable)
serverPollingIntervalMs: 30000, // Optional, default 30000 (set <= 0 to disable)
forcedResyncIntervalMs: 3600000, // Optional, default 1 hour (set <= 0 to disable)
logger: console // Optional
});
// Initialize (loads user mappings into cache)
await provider.initialize();User Mappings API
Load and Cache
// Initialize loads all user mappings (file + server) into memory
await provider.initialize();
// Reload all user mappings
await provider.reloadUserMappings();
// Refresh specific mapping by key (fetches from source)
await provider.refreshUserMapping('my-mapping-key');Static JSON Values (*.json)
If mappingsFolder is configured, the provider also loads *.json files from that folder as static JSON values.
- The key is the filename without
.json - The value is the parsed JSON value (object/array/string/number/boolean/null)
- The reserved filename
aliases.jsonis never treated as a static value - Static JSON values are not treated as mappings and do not override
*.fumemappings; they are exposed via a separate API.
// Reload all static JSON values
await provider.reloadStaticJsonValues();
// Refresh a specific static JSON value by key
await provider.refreshStaticJsonValue('myStaticKey');
// Read from cache
const value = provider.getStaticJsonValue('myStaticKey');
// Returns: StaticJsonValue | undefinedGet User Mappings (Lightning Fast ⚡)
// Get all user mappings
const mappings = provider.getUserMappings();
// Returns: UserMapping[]
// Get all user mapping keys
const keys = provider.getUserMappingKeys();
// Returns: string[]
// Get metadata only (without expressions)
const metadata = provider.getUserMappingsMetadata();
// Returns: UserMappingMetadata[]
// Get specific mapping by key
const mapping = provider.getUserMapping('my-key');
// Returns: UserMapping | undefinedAutomatic Change Tracking
On initialize(), the provider automatically starts polling sources to keep the in-memory cache aligned with files and server resources.
- File polling (default: 5s): detects changes in mapping files, static JSON value files (
*.json), andaliases.jsonincrementally. - Server polling (default: 30s):
- Aliases: conditional read of the alias ConceptMap (ETag/Last-Modified).
- Mappings: StructureMap search with
_lastUpdated.
- Forced resync (default: 1h): full refresh of aliases + mappings, applied incrementally.
Disable any polling loop by setting its interval to <= 0.
UserMapping Structure
interface UserMapping {
key: string; // Unique identifier
expression: string; // FUME expression
sourceType: 'file' | 'server'; // Origin
source: string; // Absolute file path or full server URL
name?: string; // StructureMap.name
url?: string; // StructureMap.url
}
interface StaticJsonValue {
key: string; // Filename without `.json`
value: unknown; // Parsed JSON value
sourceType: 'file'; // Origin
source: string; // Absolute file path
}Package Mappings API
Get Package Mappings
// Get all package mappings
const mappings = await provider.getPackageMappings();
// Filter by package context
const mappings = await provider.getPackageMappings({
packageContext: '[email protected]'
});
// Get metadata only (without expressions)
const metadata = await provider.getPackageMappingsMetadata();Aliases API
Overview
Aliases are simple key-value string mappings stored in a special ConceptMap resource on the FHIR server. Unlike mappings, aliases are:
- Server + File: Loaded from the FHIR server and/or an optional
aliases.jsonfile inmappingsFolder - Consolidated: Always served as a single object
- Cached: Kept fresh via automatic change tracking
When both server and file sources are configured:
- File aliases override server aliases on key collision (a warning is logged if a logger is provided)
- Server aliases override built-in aliases
Alias Resource Structure
FUME aliases are stored in a ConceptMap resource with this specific useContext:
{
code: {
system: "http://snomed.info/sct",
code: "706594005"
},
valueCodeableConcept: {
coding: [{
system: "http://codes.fume.health",
code: "fume"
}]
}
}The server is queried with: GET [baseUrl]/ConceptMap?context=http://codes.fume.health|fume&name=FumeAliases
Get Aliases
// Get all aliases (fast, cached)
const aliases = provider.getAliases();
// Returns: { [key: string]: string }
// Get all aliases with per-alias metadata (source + sourceType)
const aliasesWithMeta = provider.getAliasesWithMetadata();
// Returns: { [key: string]: { value: string; sourceType: 'file'|'server'|'builtIn'; source: string } }
// Get the ConceptMap id used for server aliases (if loaded)
// Downstream consumers can use this id when updating the alias ConceptMap
const aliasResourceId = provider.getAliasResourceId();
// Returns: string | undefined
// Example:
// {
// "patientSystemUrl": "http://example.com/patients",
// "defaultLanguage": "en-US",
// "apiVersion": "v1"
// }File Aliases (aliases.json)
If mappingsFolder is configured, the provider will look for a special aliases.json file inside it.
Rules:
- If
mappingsFolderis not set, file aliases are not supported. - If
aliases.jsonis missing, no file aliases are loaded. - If
aliases.jsonexists but is invalid, a warning is logged and the file is ignored.
Example aliases.json:
{
"patientSystemUrl": "http://example.com/patients",
"defaultLanguage": "en-US",
"apiVersion": "v1"
}Validation:
- Must be a JSON object
- Keys must match
^[A-Za-z0-9_]+$(no whitespace or operators like-or.) - Values must be strings
Reload Aliases
// Reload from configured sources (server and/or mappingsFolder)
await provider.reloadAliases();Transforming Alias Resources
// ConceptMap → Alias Object
const aliases = provider.conceptMapToAliasObject(conceptMap);
// Alias Object → ConceptMap
const conceptMap = provider.aliasObjectToConceptMap(aliases, existingConceptMap);Package Mappings API
Get by Identifier
Automatically tries URL → ID → name in order, returns first match:
// Try to find by URL, ID, or name
const mapping = await provider.getPackageMapping('patient-transform');
// First tries as URL, then ID, then name
// With package filtering
const mapping = await provider.getPackageMapping('patient-transform', {
packageContext: '[email protected]'
});PackageMapping Structure
interface PackageMapping {
id: string; // StructureMap.id
expression: string; // FUME expression
packageId: string; // Package ID
packageVersion: string; // Package version
filename: string; // File in package
name?: string; // StructureMap.name
url?: string; // StructureMap.url
}Collision Handling
When a file mapping has the same key as a server mapping:
- File mapping wins (overrides server mapping)
- Warning is logged (if logger provided)
- Refresh checks file first, falls back to server if file deleted
// File: ./mappings/my-mapping.fume
// expression: InstanceOf: Patient
// Server: StructureMap with id="my-mapping"
// expression: InstanceOf: Patient
// After initialize():
const mapping = provider.getUserMapping('my-mapping');
// mapping.sourceType === 'file'
// mapping.expression === 'InstanceOf: Patient'
// Warning logged: "File mapping 'my-mapping' overrides server mapping"Examples
File-Only Setup
const provider = new FumeMappingProvider({
mappingsFolder: './mappings'
});
await provider.initialize();
const keys = provider.getUserMappingKeys();
// ['mapping1', 'mapping2', 'mapping3']
const mapping = provider.getUserMapping('mapping1');
// { key: 'mapping1', expression: '...', sourceType: 'file', source: '/abs/path/mapping1.fume' }Server-Only Setup
const provider = new FumeMappingProvider({
fhirClient: new FhirClient({ baseUrl: 'https://fhir.server.com' })
});
await provider.initialize();
const mappings = provider.getUserMappings();
// All StructureMaps from server (with FUME extensions)Package-Only Setup
const explorer = await FhirPackageExplorer.create({
context: ['[email protected]']
});
const provider = new FumeMappingProvider({
packageExplorer: explorer
});
// No initialize needed for package-only
const mappings = await provider.getPackageMappings();Combined Setup
const provider = new FumeMappingProvider({
mappingsFolder: './mappings',
fhirClient: fhirClient,
packageExplorer: explorer,
logger: console
});
await provider.initialize(); // Loads user mappings only
// User mappings (fast, cached)
const userKeys = provider.getUserMappingKeys();
const userMapping = provider.getUserMapping('my-key');
// Package mappings (on-demand)
const pkgMappings = await provider.getPackageMappings();
const pkgMapping = await provider.getPackageMapping('patient-transform');API Reference
FumeMappingProvider
Constructor
new FumeMappingProvider(config: FumeMappingProviderConfig)Methods
Initialization:
initialize(): Promise<void>- Load caches and start automatic change trackingreloadUserMappings(): Promise<void>- Reload all user mappingsrefreshUserMapping(key: string): Promise<UserMapping | null>- Refresh specific user mappingstartAutomaticChangeTracking(): void- Start polling + forced resyncstopAutomaticChangeTracking(): void- Stop polling + forced resync
User Mappings (Cached, Fast):
getUserMappings(): UserMapping[]- Get all user mappingsgetUserMappingKeys(): string[]- Get all user mapping keysgetUserMappingsMetadata(): UserMappingMetadata[]- Get metadata onlygetUserMapping(key: string): UserMapping | undefined- Get specific mapping
Package Mappings (On-Demand):
getPackageMappings(options?: GetPackageMappingOptions): Promise<PackageMapping[]>- Get all package mappingsgetPackageMappingsMetadata(options?: GetPackageMappingOptions): Promise<PackageMappingMetadata[]>- Get metadata onlygetPackageMapping(identifier: string, options?: GetPackageMappingOptions): Promise<PackageMapping | null>- Get by identifier
Aliases (Cached, Fast):
reloadAliases(): Promise<void>- Reload all aliases from servergetAliases(): AliasObject- Get all cached aliases as single objectgetAliasesWithMetadata(): AliasObjectWithMetadata- Get all cached aliases with metadatagetAliasResourceId(): string | undefined- Get ConceptMap id for server aliases (if loaded)
Converters:
getCanonicalBaseUrl(): string- Get canonical base URL used for generated resourcesstructureMapToExpression(structureMap: StructureMap): string | null- Extract FUME expression from StructureMapexpressionToStructureMap(mappingId: string, expression: string): StructureMap- Create StructureMap from expression (uses canonical base URL)conceptMapToAliasObject(conceptMap: ConceptMap): AliasObject- Transform ConceptMap to alias objectaliasObjectToConceptMap(aliases: AliasObject, existingConceptMap?: ConceptMap): ConceptMap- Transform alias object to ConceptMap (uses canonical base URL)
Configuration
interface FumeMappingProviderConfig {
mappingsFolder?: string; // Path to .fume files
fileExtension?: string; // Default: '.fume' ('.json' is reserved for aliases.json)
fhirClient?: any; // FHIR client instance
packageExplorer?: any; // FPE instance
logger?: Logger; // Optional logger
aliasConceptMapId?: string; // Optional ConceptMap id for aliases (skips search)
canonicalBaseUrl?: string; // Default: 'http://example.com'
filePollingIntervalMs?: number; // Default: 5000 (set <= 0 to disable)
serverPollingIntervalMs?: number; // Default: 30000 (set <= 0 to disable)
forcedResyncIntervalMs?: number; // Default: 3600000 (set <= 0 to disable)
}License
MIT
© Outburn Ltd. 2022–2025. All Rights Reserved.
