Manage pre-defined, named FUME expressions (mappings) from FHIR packages, servers, or files
npm install @outburn/fume-mapping-provider.fume files (or custom extensions)
fhir-package-explorer
bash
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 mappings
`
Quick Start
`typescript
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: ['hl7.fhir.us.core@6.1.0']
});
// 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
$3
`typescript
// 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');
`
$3
If mappingsFolder is configured, the provider also loads *.json files from that folder as JSON-valued mappings.
- The mapping key is the filename without .json
- The mapping value is the parsed JSON value (object/array/string/number/boolean/null)
- The reserved filename aliases.json is never treated as a mapping
- If both myMap.json and myMap.fume exist, the JSON mapping overrides the text mapping (a warning is logged if a logger is provided)
$3
`typescript
// 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 | undefined
`
Automatic 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 and aliases.json incrementally.
- 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.
$3
`typescript
interface UserMapping {
key: string; // Unique identifier
expression: unknown; // FUME expression (string) or JSON value for *.json
sourceType: 'file' | 'server'; // Origin
source: string; // Absolute file path or full server URL
name?: string; // StructureMap.name
url?: string; // StructureMap.url
}
`
Package Mappings API
$3
`typescript
// Get all package mappings
const mappings = await provider.getPackageMappings();
// Filter by package context
const mappings = await provider.getPackageMappings({
packageContext: 'hl7.fhir.us.core@6.1.0'
});
// Get metadata only (without expressions)
const metadata = await provider.getPackageMappingsMetadata();
`
Aliases API
$3
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.json file in mappingsFolder
- 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
$3
FUME aliases are stored in a ConceptMap resource with this specific useContext:
`typescript
{
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
$3
`typescript
// 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"
// }
`
$3
If mappingsFolder is configured, the provider will look for a special aliases.json file inside it.
Rules:
- If mappingsFolder is not set, file aliases are not supported.
- If aliases.json is missing, no file aliases are loaded.
- If aliases.json exists but is invalid, a warning is logged and the file is ignored.
Example aliases.json:
`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
$3
`typescript
// Reload from configured sources (server and/or mappingsFolder)
await provider.reloadAliases();
`
$3
`typescript
// ConceptMap ā Alias Object
const aliases = provider.conceptMapToAliasObject(conceptMap);
// Alias Object ā ConceptMap
const conceptMap = provider.aliasObjectToConceptMap(aliases, existingConceptMap);
`
Package Mappings API
$3
Automatically tries URL ā ID ā name in order, returns first match:
`typescript
// 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: 'hl7.fhir.us.core@6.1.0'
});
`
$3
`typescript
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:
1. File mapping wins (overrides server mapping)
2. Warning is logged (if logger provided)
3. Refresh checks file first, falls back to server if file deleted
`typescript
// 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
$3
`typescript
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' }
`
$3
`typescript
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)
`
$3
`typescript
const explorer = await FhirPackageExplorer.create({
context: ['hl7.fhir.us.core@6.1.0']
});
const provider = new FumeMappingProvider({
packageExplorer: explorer
});
// No initialize needed for package-only
const mappings = await provider.getPackageMappings();
`
$3
`typescript
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
$3
#### Constructor
`typescript
new FumeMappingProvider(config: FumeMappingProviderConfig)
`
#### Methods
Initialization:
- initialize(): Promise - Load caches and start automatic change tracking
- reloadUserMappings(): Promise - Reload all user mappings
- refreshUserMapping(key: string): Promise - Refresh specific user mapping
- startAutomaticChangeTracking(): void - Start polling + forced resync
- stopAutomaticChangeTracking(): void - Stop polling + forced resync
User Mappings (Cached, Fast):
- getUserMappings(): UserMapping[] - Get all user mappings
- getUserMappingKeys(): string[] - Get all user mapping keys
- getUserMappingsMetadata(): UserMappingMetadata[] - Get metadata only
- getUserMapping(key: string): UserMapping | undefined - Get specific mapping
Package Mappings (On-Demand):
- getPackageMappings(options?: GetPackageMappingOptions): Promise - Get all package mappings
- getPackageMappingsMetadata(options?: GetPackageMappingOptions): Promise - Get metadata only
- getPackageMapping(identifier: string, options?: GetPackageMappingOptions): Promise - Get by identifier
Aliases (Cached, Fast):
- reloadAliases(): Promise - Reload all aliases from server
- getAliases(): AliasObject - Get all cached aliases as single object
- getAliasesWithMetadata(): AliasObjectWithMetadata - Get all cached aliases with metadata
- getAliasResourceId(): string | undefined - Get ConceptMap id for server aliases (if loaded)
Converters:
- getCanonicalBaseUrl(): string - Get canonical base URL used for generated resources
- structureMapToExpression(structureMap: StructureMap): string | null - Extract FUME expression from StructureMap
- expressionToStructureMap(mappingId: string, expression: string): StructureMap - Create StructureMap from expression (uses canonical base URL)
- conceptMapToAliasObject(conceptMap: ConceptMap): AliasObject - Transform ConceptMap to alias object
- aliasObjectToConceptMap(aliases: AliasObject, existingConceptMap?: ConceptMap): ConceptMap - Transform alias object to ConceptMap (uses canonical base URL)
$3
`typescript
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)
}
``