/** * Shared FHIR version abstractions. * * Every version-specific directory (r4/, r4b/, r5/) provides a `context` * module that satisfies this interface, allowing generators and parsers * to remain version-agnostic. * * Since issue #53, per-version data is derived automatically from the FHIR * core package's base StructureDefinitions (see {@link corePackageResolver}). * When downloading fails (offline), pre-computed fallback caches under * `fallback/.json` are used instead. */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { FHIR_VERSIONS } from './versionRegistry.js'; import { resolveAndDerive, deserializeDerivedData } from './corePackageResolver.js'; import type { DerivedVersionData } from './corePackageResolver.js'; import { createRules, VALUESET_THRESHOLDS } from './createRules.js'; import { logger } from '../../logger.js'; export type FhirVersionSlug = 'r4' | 'r4b' | 'r5'; /** * Rules interface for FHIR type mappings, cardinality, and inference. */ export interface FhirRules { isFhirType: (name: string) => boolean; mapTypeToTS: (type: string) => string; isPrimitiveType: (type: string) => boolean; isPrimitiveLike: (t?: string) => boolean; isFhirPrimitiveType: (type: string) => boolean; isCoreResource: (name: string) => boolean; isFhirFieldArray: (parentType: string, field: string) => boolean | undefined; inferFhirChildType: ( parentType: string, field: string, fhirChildTypeMap?: Map> ) => string | undefined; } /** * Version-specific context that generators and parsers use to resolve * version-dependent behaviour without hardcoding. */ export interface FhirVersionContext { /** Full FHIR version string, e.g. '4.0.1', '4.3.0', '5.0.0' */ version: string; /** Short slug used in import paths and package names */ versionSlug: FhirVersionSlug; /** FHIR type rules for this version */ rules: FhirRules; /** Primitive FHIR types that cannot have nested child properties */ primitiveTypes: ReadonlySet; /** Core resource type names (Resource, DomainResource, Patient, …) */ coreResourceBases: ReadonlySet; /** ValueSet size thresholds */ valueSetThresholds: { readonly UNION_TYPE: number; readonly STORE_CODES: number; }; /** Data-type field definitions (derived from core package SDs) */ dataTypes: Record>; /** Canonical list of FHIR interface names */ interfaceNames: string[]; /** Canonical list of FHIR resource names */ resourceNames: string[]; } /** * Resolve a FhirVersionContext from a slug. * * 1. Try deriving from the FHIR core package (cached or downloaded). * 2. If that fails (offline / network error), fall back to the pre-computed * JSON cache shipped in `fallback/.json`. */ export async function resolveVersionContext(slug: FhirVersionSlug): Promise { const config = FHIR_VERSIONS[slug]; if (!config) { throw new Error( `Unsupported FHIR version: ${slug}. Supported: ${Object.keys(FHIR_VERSIONS).join(', ')}`, ); } let derived: DerivedVersionData; try { derived = await resolveAndDerive(config.corePackage); } catch (err) { derived = loadFallbackCache(slug, err as Error); } const rules = createRules(derived); return { version: config.version, versionSlug: config.slug, rules, primitiveTypes: derived.primitiveTypes, coreResourceBases: new Set(['Resource', 'DomainResource', ...derived.resourceNames]), valueSetThresholds: VALUESET_THRESHOLDS, dataTypes: derived.dataTypes, interfaceNames: derived.interfaceNames, resourceNames: derived.resourceNames, }; } const log = logger.withTag('fhir-version'); /** * Load a pre-computed fallback cache for the given FHIR version slug. * Falls back gracefully with a warning; throws if no fallback exists. */ function loadFallbackCache(slug: FhirVersionSlug, cause: Error): DerivedVersionData { const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fallbackPath = path.join(__dirname, 'fallback', `${slug}.json`); if (!fs.existsSync(fallbackPath)) { throw new Error( `Core package derivation failed and no offline fallback exists for FHIR ${slug}. ` + `Run 'npx tsx scripts/generate-fallback-cache.ts ${slug}' to create one. ` + `Original error: ${cause.message}`, { cause }, ); } log.warn( `Core package download failed, using offline fallback for ${slug}: ${cause.message}`, ); const raw = JSON.parse(fs.readFileSync(fallbackPath, 'utf-8')); return deserializeDerivedData(raw); } /** Default FHIR version when none is specified */ export const DEFAULT_FHIR_VERSION: FhirVersionSlug = 'r4';