Problem
We work around FHIR package defects at three separate layers with inconsistent ergonomics and visibility:
Layer 1: preprocessPackage callback (user-controlled, but verbose)
Users write raw JSON.stringify → replaceAll → JSON.parse boilerplate in generate.ts scripts to fix package defects before they enter the system.
Examples:
examples/typescript-ccda/generate.ts: fixes canonical URL typo (IVL_TS → IVL-TS), swaps unavailable NLM ValueSet binding, adds missing codes to bundle-type CodeSystem
examples/nodge-r4.ts: fixes package name typos (simplifier.core.r4.rResources), injects missing hl7.fhir.r4.core dependency, rewrites invalid reference targets in gd-RelatedPerson
Each fix is ~10-20 lines of serialize/deserialize ceremony. The same patterns repeat across projects but aren't reusable.
Layer 2: skip-hack.ts (library-internal, opaque)
Hardcoded skip list in src/typeschema/skip-hack.ts applied during FHIR → TypeSchema transformation. Silently drops canonicals that would fail (e.g., R4 extensions referencing R5-only CodeableReference/Availability, broken R5 shareablecodesystem).
Users can't extend, override, or disable it. Requires a library release to add new entries.
Layer 3: typeSchema() IR transforms (user-controlled, declarative)
Post-generation transforms: treeShake, promoteLogical, resolveCollisions. Well-designed and user-controlled, but promoteLogical is conceptually the same kind of fix as the others — "the package says X, but I need Y."
Common thread
All three layers solve the same problem: FHIR packages are imperfect, and users need a way to patch them. They differ in when the fix applies, how it's expressed, and who controls it.
Types of defects encountered
| Category |
Example |
Current workaround |
| Cross-version type refs |
R4 extension uses CodeableReference (R5-only) |
skip-hack.ts drops the canonical |
| Canonical URL typos |
IVL_TS should be IVL-TS |
replaceAll in preprocessPackage |
| Unavailable ValueSet bindings |
External NLM ValueSet not in loaded packages |
replaceAll in preprocessPackage |
| Missing CodeSystem codes |
bundle-type missing "bundle" code |
Patch concept array in preprocessPackage |
| Missing package dependencies |
Packages not declaring hl7.fhir.r4.core dep |
Inject into packageJson.dependencies |
| Package name typos |
simplifier.core.r4.rResources |
Rename in preprocessPackage |
| Invalid reference targets |
Profile widens refs beyond base spec |
replaceAll in preprocessPackage |
| Logical model → resource |
CDA types need resource treatment |
promoteLogical in typeSchema() |
| Broken upstream schema |
ElementReference in FHIR Schema for R5 |
skip-hack.ts |
What's needed
-
Expose skip-hack.ts to users — let them extend, override, or disable the built-in skip list instead of it being a hidden internal.
-
Provide a set of composable helper functions for the most common preprocessPackage patterns — canonical renaming, dependency injection, CodeSystem patching, etc. — so users can build their preprocess function from small combinators instead of writing raw JSON manipulation each time.
-
Keep the API simple — the current preprocessPackage callback shape from CanonicalManager is good; helpers should just make it easier to build those callbacks.
Problem
We work around FHIR package defects at three separate layers with inconsistent ergonomics and visibility:
Layer 1:
preprocessPackagecallback (user-controlled, but verbose)Users write raw
JSON.stringify→replaceAll→JSON.parseboilerplate ingenerate.tsscripts to fix package defects before they enter the system.Examples:
examples/typescript-ccda/generate.ts: fixes canonical URL typo (IVL_TS→IVL-TS), swaps unavailable NLM ValueSet binding, adds missing codes tobundle-typeCodeSystemexamples/nodge-r4.ts: fixes package name typos (simplifier.core.r4.rResources), injects missinghl7.fhir.r4.coredependency, rewrites invalid reference targets ingd-RelatedPersonEach fix is ~10-20 lines of serialize/deserialize ceremony. The same patterns repeat across projects but aren't reusable.
Layer 2:
skip-hack.ts(library-internal, opaque)Hardcoded skip list in
src/typeschema/skip-hack.tsapplied during FHIR → TypeSchema transformation. Silently drops canonicals that would fail (e.g., R4 extensions referencing R5-onlyCodeableReference/Availability, broken R5shareablecodesystem).Users can't extend, override, or disable it. Requires a library release to add new entries.
Layer 3:
typeSchema()IR transforms (user-controlled, declarative)Post-generation transforms:
treeShake,promoteLogical,resolveCollisions. Well-designed and user-controlled, butpromoteLogicalis conceptually the same kind of fix as the others — "the package says X, but I need Y."Common thread
All three layers solve the same problem: FHIR packages are imperfect, and users need a way to patch them. They differ in when the fix applies, how it's expressed, and who controls it.
Types of defects encountered
CodeableReference(R5-only)skip-hack.tsdrops the canonicalIVL_TSshould beIVL-TSreplaceAllinpreprocessPackagereplaceAllinpreprocessPackagebundle-typemissing"bundle"codeconceptarray inpreprocessPackagehl7.fhir.r4.coredeppackageJson.dependenciessimplifier.core.r4.rResourcespreprocessPackagereplaceAllinpreprocessPackagepromoteLogicalintypeSchema()ElementReferencein FHIR Schema for R5skip-hack.tsWhat's needed
Expose
skip-hack.tsto users — let them extend, override, or disable the built-in skip list instead of it being a hidden internal.Provide a set of composable helper functions for the most common
preprocessPackagepatterns — canonical renaming, dependency injection, CodeSystem patching, etc. — so users can build their preprocess function from small combinators instead of writing raw JSON manipulation each time.Keep the API simple — the current
preprocessPackagecallback shape from CanonicalManager is good; helpers should just make it easier to build those callbacks.