Role. You are a senior TypeScript test engineer. Your goal is to raise
@fhir-dsl/generator — the FHIR-spec → TypeScript code generator — to
production-grade coverage using vitest. No source changes. No new
runtime dependencies beyond devtime helpers (e.g. tmp/os.tmpdir).
fhir-dsl is a type-safe FHIR monorepo. @fhir-dsl/generator is the
largest package: it downloads FHIR R4/R4B/R5/R6 definitions and published IG
packages, parses StructureDefinitions + SearchParameters into an internal
ResourceModel, and emits a TypeScript tree (resources, datatypes,
primitives, search params, registry, client, profiles, terminology,
schemas for runtime validation).
Public surface (packages/generator/src/index.ts):
generate(options: GeneratorOptions)— the orchestrator.downloadIG,downloadSpec,loadLocalSpec— spec acquisition.parseStructureDefinition,parseSearchParameters,parseProfile.- Model types:
ResourceModel,PropertyModel,BackboneElementModel,SearchParamModel,ProfileModel,ResourceSearchParams.
Internal layout:
parser/—structure-definition.ts(resource-model),profile.ts(differential overlay),search-parameter.ts,type-resolver.ts.emitter/—resource-emitter.ts,terminology-emitter.ts,profile-emitter.ts,registry-emitter.ts,spec-emitter.ts,search-param-emitter.ts,index-emitter.ts, andemitter/schema/for runtime-validator emission.downloader.ts— fetches + caches FHIR packages.generator.ts— wires parse → emit → write.
packages/generator/src/index.tspackages/generator/src/generator.tspackages/generator/src/parser/structure-definition.tspackages/generator/src/parser/profile.tspackages/generator/src/parser/type-resolver.tspackages/generator/src/emitter/resource-emitter.tspackages/generator/src/emitter/schema/schema-emitter.ts- All existing
*.test.tsinpackages/generator/src/
Colocated tests cover most single-unit pieces:
parser/structure-definition.test.ts— property parsing.parser/profile.test.ts— profile differential overlay.parser/search-parameter.test.ts— param parsing.parser/type-resolver.test.ts— canonical → TS mapping.emitter/resource-emitter.test.ts— resource TS emission.emitter/terminology-emitter.test.ts— ValueSet-bound enum emission.emitter/profile-emitter.test.ts— profile TS emission.emitter/registry-emitter.test.ts— resource / search / include registries.emitter/spec-emitter.test.ts— markdown spec emission.emitter/search-param-emitter.test.ts— typed search param map.emitter/index-emitter.test.ts— file / root / client index.emitter/schema/schema-emitter.test.ts— Standard Schema emission.
Read them first — do not duplicate.
Write tests in packages/generator/test/.
- Backbone element nesting: a resource with nested backbone elements
(e.g.
Questionnaire.item.item) parses to the rightBackboneElementModeltree, withpathvalues preserved. - Choice types (
value[x]): a property with multiple types produces exactly onePropertyModelwhereisChoiceType: trueandtypes[]holds every type code. The name stored in the model is the base name (value), notvalueString. contentReference: a property pointing at another element by contentReference is parsed as an array of the referenced type (cross-check the existing test and extend with a second-level ref).- Reference targets:
Reference(Patient | Practitioner)parses into a PropertyModel whose type carries both targets in the expected field. - Required vs optional:
min: 1→isRequired: true;max: "*"→isArray: true;max: "0"→ property is dropped. - Profile differential overlay:
parseProfileapplied to a US Core StructureDefinition narrows cardinality + bindings on the base resource (e.g.identifier.minItemsbecomes 1).
emitResourceproduces a file that imports exactly the cross-type datatypes it uses (no unused imports, no missing ones). Verify by generating a Patient-like model, grepping the output forimportlines, and asserting against the set of used types.emitTerminologyfor a large ValueSet (100+ codes) emits a reasonable TypeScript union (no stack-overflow or runaway recursion).emitProfileSchemahandles a profile that narrows aCoding-bound field to a required binding — the emitted schema has the bound code set closed.- Schema registry emission:
emitSchemaRegistryindexes every resource underresources[Name]: NameSchemaand referencesProfileSchemaRegistrywhenhasProfilesis true.
- Cache hit: a pre-populated cache dir satisfies
downloadSpecwithout network. Usemswor overridefetchto assert no request was made. - Cache miss: when the cache is empty,
downloadSpechits the mocked URL, writes to cache, and returns the parsed spec. - Network error: a fetch that rejects surfaces a descriptive error (the version string and URL appear in the message).
- IG package parsing:
downloadIGacceptshl7.fhir.us.core@6.1.0and parses the name/version correctly; invalid inputs (no-at-sign,@1.0.0,name@) throw.
- Tmpdir smoke test: call
generate()with a minimallocalSpecDirfixture (hand-authored: a couple of StructureDefinitions + one SearchParameter bundle) and assert:- the expected file tree is written (
resources/patient.ts,search-params.ts,registry.ts,client.ts,index.ts); - passing
--validator nativeadditionally writesschemas/__runtime.ts,schemas/resources/patient.schema.ts,schemas/schema-registry.ts, andschemas/index.ts; - passing
igadditionally writesprofiles/; - the emitted TS actually typechecks. Spawn
tsc --noEmitagainst the tmpdir root (or invoke the TS compiler API directly).
- the expected file tree is written (
- Resource order stability: running
generate()twice on the same fixture produces byte-identical output (ordering is deterministic across runs — no Map iteration leak). - Idempotent writes: re-running
generate()to the sameoutDirdoes not leave stale files when a resource is removed from the input.
- FHIR R4 StructureDefinition spec:
https://www.hl7.org/fhir/R4/structuredefinition.html — especially
snapshot.element,differential.element, choice types, slicing, andcontentReference. - FHIR package layout: https://registry.fhir.org/learn and https://confluence.hl7.org/display/FHIR/NPM+Package+Specification.
- FHIR SearchParameter definitions: https://www.hl7.org/fhir/R4/searchparameter.html.
- Standard Schema V1 (for schema emitter assertions): https://standardschema.dev/.
Use these sources to justify edge-case assertions (profile differential semantics, choice-type naming, cardinality rules).
- vitest
globals: true. - The end-to-end test must run in a
mkdtempdirectory and clean up after itself. Timeouts: vitest's default 60s is already set invitest.config.ts— that's enough. - Do not invoke the real network. Use
mswor afetchstub. - For the end-to-end test, use a tiny hand-authored fixture (one Patient-like resource, one SearchParameter bundle, one ValueSet). Do not copy the full R4 spec into the repo.
- Read the listed files + existing colocated tests.
- Organize new tests by area:
parser-edge.test.ts,emitter-resource-imports.test.ts,downloader-cache.test.ts,end-to-end.test.ts,determinism.test.ts. - Gates:
pnpm test pnpm lint pnpm -r typecheck - If the end-to-end test is flaky on CI-like environments, document the
reason and mark it with
.skipIf(process.env.CI)rather than leaving it undeclared.
- Every scenario has ≥1 test.
- End-to-end test produces a typechecking output tree.
- All three gates green.
- No changes to
packages/generator/src/**.
- Refactoring the generator or parser.
- Performance benchmarks.
- Testing every FHIR R4 resource — one representative + one profile is enough for end-to-end.
- Downloading real IG packages over the network.