From 1f4b033d527262f9450b539b1c7a028f98b40e56 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 12:04:08 +0200 Subject: [PATCH 1/9] ref: Remove buildResource helper, use plain object literals in profile createResource Replace `buildResource({...})` calls with `const resource: T = {...}` in generated profile classes. Remove the now-unused `buildResource` export from profile-helpers.ts. --- .../api/writer-generator/typescript/profile-helpers.ts | 7 ------- src/api/writer-generator/typescript/profile.ts | 9 ++++----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/assets/api/writer-generator/typescript/profile-helpers.ts b/assets/api/writer-generator/typescript/profile-helpers.ts index 05a0253e..239ec0d0 100644 --- a/assets/api/writer-generator/typescript/profile-helpers.ts +++ b/assets/api/writer-generator/typescript/profile-helpers.ts @@ -267,13 +267,6 @@ export const ensureSliceDefaults = (items: T[], ...matches: Record(obj: object): T => obj as unknown as T; - /** * Add `canonicalUrl` to `resource.meta.profile` if not already present. * Creates `meta` and `profile` when missing. diff --git a/src/api/writer-generator/typescript/profile.ts b/src/api/writer-generator/typescript/profile.ts index fd0858af..ce774571 100644 --- a/src/api/writer-generator/typescript/profile.ts +++ b/src/api/writer-generator/typescript/profile.ts @@ -221,7 +221,6 @@ const generateProfileHelpersImport = ( const canonicalUrl = flatProfile.identifier.url; const imports: string[] = []; - if (!isPrimitiveIdentifier(flatProfile.base)) imports.push("buildResource"); if (flatProfile.base.name === "Extension" && !!canonicalUrl && collectSubExtensionSlices(flatProfile).length > 0) imports.push("isRawExtensionInput"); if (canonicalUrl && hasMeta) imports.push("ensureProfile"); @@ -462,7 +461,7 @@ const generateFactoryMethods = ( } w.line(); const extensionVar = extSliceField ? "extensionWithDefaults" : "resolvedExtensions"; - w.curlyBlock([`const resource = buildResource<${tsBaseResourceName}>(`], () => { + w.curlyBlock([`const resource: ${tsBaseResourceName} =`], () => { for (const f of allFields) { if (f.name === "extension") continue; w.line(`${f.name}: ${f.value},`); @@ -471,7 +470,7 @@ const generateFactoryMethods = ( if (hasMeta) { w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`); } - }, [")"]); + }); w.lineSM("return resource"); }); @@ -504,14 +503,14 @@ const generateFactoryMethods = ( if (isPrimitiveIdentifier(flatProfile.base)) { w.lineSM(`const resource = undefined as unknown as ${tsBaseResourceName}`); } else { - w.curlyBlock([`const resource = buildResource<${tsBaseResourceName}>(`], () => { + w.curlyBlock([`const resource: ${tsBaseResourceName} =`], () => { for (const f of allFields) { w.line(`${f.name}: ${f.value},`); } if (hasMeta) { w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`); } - }, [")"]); + }); } w.lineSM("return resource"); }); From 1708239ac452605e83f62657ae2c1d32755c0f6d Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 12:04:12 +0200 Subject: [PATCH 2/9] test: Update snapshots for buildResource removal --- .../__snapshots__/typescript.test.ts.snap | 30 ++++++++----------- .../__snapshots__/local-package.test.ts.snap | 5 ++-- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/test/api/write-generator/__snapshots__/typescript.test.ts.snap b/test/api/write-generator/__snapshots__/typescript.test.ts.snap index 2a50335b..616bd22e 100644 --- a/test/api/write-generator/__snapshots__/typescript.test.ts.snap +++ b/test/api/write-generator/__snapshots__/typescript.test.ts.snap @@ -325,7 +325,6 @@ import type { Reference } from "../../hl7-fhir-r4-core/Reference"; export type Observation_bodyweight_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -389,14 +388,14 @@ export class observation_bodyweightProfile { observation_bodyweightProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"code":"29463-7","system":"http://loinc.org"}]}, category: categoryWithDefaults, status: args.status, subject: args.subject, meta: { profile: [observation_bodyweightProfile.canonicalUrl] }, - }) + } return resource; } @@ -540,7 +539,6 @@ export type Observation_bp_Component_SystolicBPSliceFlat = Omit & Quantity; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -619,7 +617,7 @@ export class observation_bpProfile { observation_bpProfile.DiastolicBPSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"code":"85354-9","system":"http://loinc.org"}]}, category: categoryWithDefaults, @@ -627,7 +625,7 @@ export class observation_bpProfile { status: args.status, subject: args.subject, meta: { profile: [observation_bpProfile.canonicalUrl] }, - }) + } return resource; } @@ -837,7 +835,6 @@ import { } from "./Extension_USCoreTribalAffiliationExtension"; import { - buildResource, ensureProfile, extractComplexExtension, isExtension, @@ -885,12 +882,12 @@ export class USCorePatientProfile { } static createResource (args: USCorePatientProfileRaw) : Patient { - const resource = buildResource( { + const resource: Patient = { resourceType: "Patient", identifier: args.identifier, name: args.name, meta: { profile: [USCorePatientProfile.canonicalUrl] }, - }) + } return resource; } @@ -1087,7 +1084,6 @@ export type USCoreBloodPressureProfile_Component_SystolicSliceFlat = Omit & Quantity; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -1166,7 +1162,7 @@ export class USCoreBloodPressureProfile { USCoreBloodPressureProfile.diastolicSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"system":"http://loinc.org","code":"85354-9"}]}, category: categoryWithDefaults, @@ -1174,7 +1170,7 @@ export class USCoreBloodPressureProfile { status: args.status, subject: args.subject, meta: { profile: [USCoreBloodPressureProfile.canonicalUrl] }, - }) + } return resource; } @@ -1468,7 +1464,6 @@ import type { SampledData } from "../../hl7-fhir-r4-core/SampledData"; export type USCoreBodyWeightProfile_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -1532,14 +1527,14 @@ export class USCoreBodyWeightProfile { USCoreBodyWeightProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"system":"http://loinc.org","code":"29463-7"}]}, category: categoryWithDefaults, status: args.status, subject: args.subject, meta: { profile: [USCoreBodyWeightProfile.canonicalUrl] }, - }) + } return resource; } @@ -1782,7 +1777,6 @@ export type USCoreRaceExtension_Extension_DetailedSliceFlat = Omit; import { - buildResource, isRawExtensionInput, applySliceMatch, matchesValue, @@ -1868,10 +1862,10 @@ export class USCoreRaceExtensionProfile { static createResource (args: USCoreRaceExtensionProfileRaw | USCoreRaceExtensionProfileFlat) : Extension { const resolvedExtensions = USCoreRaceExtensionProfile.resolveInput(args ?? {}); - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", extension: resolvedExtensions, - }) + } return resource; } diff --git a/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap b/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap index 1d881bb4..09cf8599 100644 --- a/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap +++ b/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap @@ -41,7 +41,6 @@ export type ExampleTypedBundle_Entry_PatientEntrySliceFlat = BundleEntry; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -91,11 +90,11 @@ export class ExampleTypedBundleProfile { } static createResource (args: ExampleTypedBundleProfileRaw) : Bundle { - const resource = buildResource( { + const resource: Bundle = { resourceType: "Bundle", type: args.type, meta: { profile: [ExampleTypedBundleProfile.canonicalUrl] }, - }) + } return resource; } From 956841afddf3cbad14a7a1d3fbe51797f89dc0f4 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 12:04:17 +0200 Subject: [PATCH 3/9] chore: Regenerate examples for buildResource removal --- .../hl7-fhir-r4-core/profiles/Extension_birthPlace.ts | 5 ++--- .../hl7-fhir-r4-core/profiles/Extension_birthTime.ts | 5 ++--- .../hl7-fhir-r4-core/profiles/Extension_nationality.ts | 5 ++--- .../hl7-fhir-r4-core/profiles/Extension_own_prefix.ts | 5 ++--- .../profiles/Observation_observation_bodyweight.ts | 5 ++--- .../profiles/Observation_observation_bp.ts | 5 ++--- .../profiles/Observation_observation_vitalsigns.ts | 5 ++--- examples/typescript-r4/fhir-types/profile-helpers.ts | 7 ------- .../profiles/Observation_observation_vitalsigns.ts | 5 ++--- .../profiles/Extension_USCoreEthnicityExtension.ts | 5 ++--- .../profiles/Extension_USCoreIndividualSexExtension.ts | 5 ++--- .../profiles/Extension_USCoreInterpreterNeededExtension.ts | 5 ++--- .../profiles/Extension_USCoreRaceExtension.ts | 5 ++--- .../profiles/Extension_USCoreTribalAffiliationExtension.ts | 5 ++--- .../profiles/Observation_USCoreBloodPressureProfile.ts | 5 ++--- .../profiles/Observation_USCoreBodyWeightProfile.ts | 5 ++--- .../profiles/Observation_USCoreVitalSignsProfile.ts | 5 ++--- .../profiles/Patient_USCorePatientProfile.ts | 5 ++--- examples/typescript-us-core/fhir-types/profile-helpers.ts | 7 ------- 19 files changed, 34 insertions(+), 65 deletions(-) diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthPlace.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthPlace.ts index 6d8f3a93..0e5add04 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthPlace.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthPlace.ts @@ -6,7 +6,6 @@ import type { Address } from "../../hl7-fhir-r4-core/Address"; import type { Extension } from "../../hl7-fhir-r4-core/Extension"; import { - buildResource, validateRequired, validateExcluded, validateFixedValue, @@ -47,10 +46,10 @@ export class birthPlaceProfile { } static createResource (args: birthPlaceProfileRaw) : Extension { - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/StructureDefinition/patient-birthPlace", valueAddress: args.valueAddress, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthTime.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthTime.ts index c2961eab..e9eecae6 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthTime.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_birthTime.ts @@ -5,7 +5,6 @@ import type { Extension } from "../../hl7-fhir-r4-core/Extension"; import { - buildResource, validateRequired, validateExcluded, validateFixedValue, @@ -46,10 +45,10 @@ export class birthTimeProfile { } static createResource (args: birthTimeProfileRaw) : Extension { - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/StructureDefinition/patient-birthTime", valueDateTime: args.valueDateTime, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_nationality.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_nationality.ts index 570702cf..063f86d9 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_nationality.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_nationality.ts @@ -7,7 +7,6 @@ import type { Extension } from "../../hl7-fhir-r4-core/Extension"; import type { Period } from "../../hl7-fhir-r4-core/Period"; import { - buildResource, isRawExtensionInput, isExtension, getExtensionValue, @@ -75,10 +74,10 @@ export class nationalityProfile { static createResource (args?: nationalityProfileRaw | nationalityProfileFlat) : Extension { const resolvedExtensions = nationalityProfile.resolveInput(args ?? {}); - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/StructureDefinition/patient-nationality", extension: resolvedExtensions, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_own_prefix.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_own_prefix.ts index 353d6bdb..74839752 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_own_prefix.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Extension_own_prefix.ts @@ -5,7 +5,6 @@ import type { Extension } from "../../hl7-fhir-r4-core/Extension"; import { - buildResource, validateRequired, validateExcluded, validateFixedValue, @@ -46,10 +45,10 @@ export class own_prefixProfile { } static createResource (args: own_prefixProfileRaw) : Extension { - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix", valueString: args.valueString, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bodyweight.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bodyweight.ts index 9e303edb..f02e6c45 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bodyweight.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bodyweight.ts @@ -11,7 +11,6 @@ import type { Reference } from "../../hl7-fhir-r4-core/Reference"; export type Observation_bodyweight_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -75,14 +74,14 @@ export class observation_bodyweightProfile { observation_bodyweightProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"code":"29463-7","system":"http://loinc.org"}]}, category: categoryWithDefaults, status: args.status, subject: args.subject, meta: { profile: [observation_bodyweightProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bp.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bp.ts index 1f20e9da..52f53ce9 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bp.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_bp.ts @@ -13,7 +13,6 @@ export type Observation_bp_Component_SystolicBPSliceFlat = Omit & Quantity; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -92,7 +91,7 @@ export class observation_bpProfile { observation_bpProfile.DiastolicBPSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"code":"85354-9","system":"http://loinc.org"}]}, category: categoryWithDefaults, @@ -100,7 +99,7 @@ export class observation_bpProfile { status: args.status, subject: args.subject, meta: { profile: [observation_bpProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts index 280bc6ff..d8cb909d 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts @@ -10,7 +10,6 @@ import type { Reference } from "../../hl7-fhir-r4-core/Reference"; export type Observation_vitalsigns_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -72,14 +71,14 @@ export class observation_vitalsignsProfile { observation_vitalsignsProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", category: categoryWithDefaults, status: args.status, code: args.code, subject: args.subject, meta: { profile: [observation_vitalsignsProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-r4/fhir-types/profile-helpers.ts b/examples/typescript-r4/fhir-types/profile-helpers.ts index 05a0253e..239ec0d0 100644 --- a/examples/typescript-r4/fhir-types/profile-helpers.ts +++ b/examples/typescript-r4/fhir-types/profile-helpers.ts @@ -267,13 +267,6 @@ export const ensureSliceDefaults = (items: T[], ...matches: Record(obj: object): T => obj as unknown as T; - /** * Add `canonicalUrl` to `resource.meta.profile` if not already present. * Creates `meta` and `profile` when missing. diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts index 280bc6ff..d8cb909d 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/profiles/Observation_observation_vitalsigns.ts @@ -10,7 +10,6 @@ import type { Reference } from "../../hl7-fhir-r4-core/Reference"; export type Observation_vitalsigns_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -72,14 +71,14 @@ export class observation_vitalsignsProfile { observation_vitalsignsProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", category: categoryWithDefaults, status: args.status, code: args.code, subject: args.subject, meta: { profile: [observation_vitalsignsProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreEthnicityExtension.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreEthnicityExtension.ts index d15408b5..f69e5c62 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreEthnicityExtension.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreEthnicityExtension.ts @@ -10,7 +10,6 @@ export type USCoreEthnicityExtension_Extension_DetailedSliceFlat = Omit; import { - buildResource, isRawExtensionInput, applySliceMatch, matchesValue, @@ -96,10 +95,10 @@ export class USCoreEthnicityExtensionProfile { static createResource (args: USCoreEthnicityExtensionProfileRaw | USCoreEthnicityExtensionProfileFlat) : Extension { const resolvedExtensions = USCoreEthnicityExtensionProfile.resolveInput(args ?? {}); - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", extension: resolvedExtensions, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreIndividualSexExtension.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreIndividualSexExtension.ts index 68ca39fb..3f2d5d09 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreIndividualSexExtension.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreIndividualSexExtension.ts @@ -6,7 +6,6 @@ import type { Coding } from "../../hl7-fhir-r4-core/Coding"; import type { Extension } from "../../hl7-fhir-r4-core/Extension"; import { - buildResource, validateRequired, validateExcluded, validateFixedValue, @@ -47,10 +46,10 @@ export class USCoreIndividualSexExtensionProfile { } static createResource (args: USCoreIndividualSexExtensionProfileRaw) : Extension { - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-individual-sex", valueCoding: args.valueCoding, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreInterpreterNeededExtension.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreInterpreterNeededExtension.ts index d2ba5a35..7f76b566 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreInterpreterNeededExtension.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreInterpreterNeededExtension.ts @@ -6,7 +6,6 @@ import type { Coding } from "../../hl7-fhir-r4-core/Coding"; import type { Extension } from "../../hl7-fhir-r4-core/Extension"; import { - buildResource, validateRequired, validateExcluded, validateFixedValue, @@ -47,10 +46,10 @@ export class USCoreInterpreterNeededExtensionProfile { } static createResource (args: USCoreInterpreterNeededExtensionProfileRaw) : Extension { - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-interpreter-needed", valueCoding: args.valueCoding, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreRaceExtension.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreRaceExtension.ts index 0e718682..03e5cc6d 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreRaceExtension.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreRaceExtension.ts @@ -10,7 +10,6 @@ export type USCoreRaceExtension_Extension_DetailedSliceFlat = Omit; import { - buildResource, isRawExtensionInput, applySliceMatch, matchesValue, @@ -96,10 +95,10 @@ export class USCoreRaceExtensionProfile { static createResource (args: USCoreRaceExtensionProfileRaw | USCoreRaceExtensionProfileFlat) : Extension { const resolvedExtensions = USCoreRaceExtensionProfile.resolveInput(args ?? {}); - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", extension: resolvedExtensions, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreTribalAffiliationExtension.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreTribalAffiliationExtension.ts index e79c5c85..a6632772 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreTribalAffiliationExtension.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Extension_USCoreTribalAffiliationExtension.ts @@ -9,7 +9,6 @@ export type USCoreTribalAffiliationExtension_Extension_TribalAffiliationSliceFla export type USCoreTribalAffiliationExtension_Extension_IsEnrolledSliceFlat = Omit; import { - buildResource, isRawExtensionInput, applySliceMatch, matchesValue, @@ -88,10 +87,10 @@ export class USCoreTribalAffiliationExtensionProfile { static createResource (args: USCoreTribalAffiliationExtensionProfileRaw | USCoreTribalAffiliationExtensionProfileFlat) : Extension { const resolvedExtensions = USCoreTribalAffiliationExtensionProfile.resolveInput(args ?? {}); - const resource = buildResource( { + const resource: Extension = { url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-tribal-affiliation", extension: resolvedExtensions, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts index db1e6803..d560fa53 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts @@ -16,7 +16,6 @@ export type USCoreBloodPressureProfile_Component_SystolicSliceFlat = Omit & Quantity; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -95,7 +94,7 @@ export class USCoreBloodPressureProfile { USCoreBloodPressureProfile.diastolicSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"system":"http://loinc.org","code":"85354-9"}]}, category: categoryWithDefaults, @@ -103,7 +102,7 @@ export class USCoreBloodPressureProfile { status: args.status, subject: args.subject, meta: { profile: [USCoreBloodPressureProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts index a36184dc..9284e2b0 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts @@ -14,7 +14,6 @@ import type { SampledData } from "../../hl7-fhir-r4-core/SampledData"; export type USCoreBodyWeightProfile_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -78,14 +77,14 @@ export class USCoreBodyWeightProfile { USCoreBodyWeightProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", code: {"coding":[{"system":"http://loinc.org","code":"29463-7"}]}, category: categoryWithDefaults, status: args.status, subject: args.subject, meta: { profile: [USCoreBodyWeightProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts index 0f4d519f..a63b70c9 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts @@ -14,7 +14,6 @@ import type { SampledData } from "../../hl7-fhir-r4-core/SampledData"; export type USCoreVitalSignsProfile_Category_VSCatSliceFlat = Omit; import { - buildResource, ensureProfile, applySliceMatch, matchesValue, @@ -76,14 +75,14 @@ export class USCoreVitalSignsProfile { USCoreVitalSignsProfile.VSCatSliceMatch, ); - const resource = buildResource( { + const resource: Observation = { resourceType: "Observation", category: categoryWithDefaults, status: args.status, code: args.code, subject: args.subject, meta: { profile: [USCoreVitalSignsProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Patient_USCorePatientProfile.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Patient_USCorePatientProfile.ts index 3d2cc006..5b6957d0 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Patient_USCorePatientProfile.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-us-core/profiles/Patient_USCorePatientProfile.ts @@ -22,7 +22,6 @@ import { } from "./Extension_USCoreTribalAffiliationExtension"; import { - buildResource, ensureProfile, extractComplexExtension, isExtension, @@ -70,12 +69,12 @@ export class USCorePatientProfile { } static createResource (args: USCorePatientProfileRaw) : Patient { - const resource = buildResource( { + const resource: Patient = { resourceType: "Patient", identifier: args.identifier, name: args.name, meta: { profile: [USCorePatientProfile.canonicalUrl] }, - }) + } return resource; } diff --git a/examples/typescript-us-core/fhir-types/profile-helpers.ts b/examples/typescript-us-core/fhir-types/profile-helpers.ts index 05a0253e..239ec0d0 100644 --- a/examples/typescript-us-core/fhir-types/profile-helpers.ts +++ b/examples/typescript-us-core/fhir-types/profile-helpers.ts @@ -267,13 +267,6 @@ export const ensureSliceDefaults = (items: T[], ...matches: Record(obj: object): T => obj as unknown as T; - /** * Add `canonicalUrl` to `resource.meta.profile` if not already present. * Creates `meta` and `profile` when missing. From 409bb508bdc721da586bd843b8bbfd214a85b4c9 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 12:45:39 +0200 Subject: [PATCH 4/9] fix: Patch bundle-type CodeSystem with missing codes in CCDA example The bundle-type CodeSystem across FHIR packages is missing "bundle" and "subscription-notification" codes used by BatchBundle and SubscriptionNotificationBundle profiles. Patch all instances at load time via preprocessPackage. --- examples/typescript-ccda/generate.ts | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/examples/typescript-ccda/generate.ts b/examples/typescript-ccda/generate.ts index ffaa2ca1..bfacae37 100644 --- a/examples/typescript-ccda/generate.ts +++ b/examples/typescript-ccda/generate.ts @@ -7,13 +7,33 @@ import { APIBuilder, prettyReport } from "../../src/api/builder"; const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { if (ctx.kind !== "resource") return ctx; - if (ctx.package.name !== "hl7.cda.uv.core") return ctx; - let str = JSON.stringify(ctx.resource); - str = str.replaceAll( - "http://hl7.org/cda/stds/core/StructureDefinition/IVL_TS", - "http://hl7.org/cda/stds/core/StructureDefinition/IVL-TS", - ); - return { ...ctx, resource: JSON.parse(str) }; + if (ctx.package.name === "hl7.cda.uv.core") { + let str = JSON.stringify(ctx.resource); + str = str.replaceAll( + "http://hl7.org/cda/stds/core/StructureDefinition/IVL_TS", + "http://hl7.org/cda/stds/core/StructureDefinition/IVL-TS", + ); + return { ...ctx, resource: JSON.parse(str) }; + } + // The bundle-type CodeSystem is missing codes "bundle" and + // "subscription-notification" used by BatchBundle and + // SubscriptionNotificationBundle profiles. Patch all instances since + // resolveAny may pick any package's copy of the CodeSystem. + const res = ctx.resource as { url?: string; concept?: { code: string }[] }; + if (res.url === "http://hl7.org/fhir/bundle-type" && res.concept) { + const existing = new Set(res.concept.map((c) => c.code)); + const missing = ["bundle", "subscription-notification"].filter((c) => !existing.has(c)); + if (missing.length > 0) { + return { + ...ctx, + resource: { + ...ctx.resource, + concept: [...res.concept, ...missing.map((code) => ({ code }))], + }, + }; + } + } + return ctx; }; if (require.main === module) { From b7f8881bb8322c57a140df9c02533c13aa98bbca Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 12:59:15 +0200 Subject: [PATCH 5/9] fix: Patch CarePlanAct moodCode binding to use base Act valueset The CarePlanAct profile binds moodCode to an external NLM ValueSet (2.16.840.1.113762.1.4.1267.37) that isn't available in any loaded package, causing the enum to fall back to string. Reuse the base Act binding (v3-xDocumentActMood) instead. --- examples/typescript-ccda/generate.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/typescript-ccda/generate.ts b/examples/typescript-ccda/generate.ts index bfacae37..4a4f0b8e 100644 --- a/examples/typescript-ccda/generate.ts +++ b/examples/typescript-ccda/generate.ts @@ -15,6 +15,19 @@ const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { ); return { ...ctx, resource: JSON.parse(str) }; } + // CarePlanAct profile binds moodCode to an external NLM ValueSet that + // isn't available in any loaded package. Reuse the base Act binding. + if (ctx.package.name === "hl7.cda.us.ccda") { + const res = ctx.resource as { url?: string }; + if (res.url === "http://hl7.org/cda/us/ccda/StructureDefinition/CarePlanAct") { + let str = JSON.stringify(ctx.resource); + str = str.replaceAll( + "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1267.37", + "http://terminology.hl7.org/ValueSet/v3-xDocumentActMood", + ); + return { ...ctx, resource: JSON.parse(str) }; + } + } // The bundle-type CodeSystem is missing codes "bundle" and // "subscription-notification" used by BatchBundle and // SubscriptionNotificationBundle profiles. Patch all instances since From da68d819bd4830bd28da486ee66f6331bf50bbe0 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 13:17:01 +0200 Subject: [PATCH 6/9] fix: Patch GdRelatedPerson to drop Person targets from patient reference The GdRelatedPerson profile widens RelatedPerson.patient to include Person targets, but the base R4 type only allows Patient. Replace Person profile references with Patient to match the base constraint. --- examples/nodge-r4.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/nodge-r4.ts b/examples/nodge-r4.ts index 11c9ca57..9568ae69 100644 --- a/examples/nodge-r4.ts +++ b/examples/nodge-r4.ts @@ -22,7 +22,19 @@ const needsCoreDependency = (name: string): boolean => { }; const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { - if (ctx.kind !== "package") return ctx; + // GdRelatedPerson widens patient reference to include Person, but the + // base R4 RelatedPerson.patient only allows Patient. Drop the Person targets. + if (ctx.kind === "resource") { + const res = ctx.resource as { url?: string }; + if (res.url === "http://ehelse.no/fhir/StructureDefinition/gd-RelatedPerson") { + let str = JSON.stringify(ctx.resource); + str = str.replaceAll("http://hl7.org/fhir/StructureDefinition/Person", "http://hl7.org/fhir/StructureDefinition/Patient"); + str = str.replaceAll("http://hl7.no/fhir/StructureDefinition/no-basis-Person", "http://hl7.org/fhir/StructureDefinition/Patient"); + str = str.replaceAll("http://ehelse.no/fhir/StructureDefinition/gd-Person", "http://hl7.org/fhir/StructureDefinition/Patient"); + return { ...ctx, resource: JSON.parse(str) }; + } + return ctx; + } let json = ctx.packageJson; const name = json.name as string; From b0f0c376aebc28e7c63dace8163338c566c25eef Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 14:16:15 +0200 Subject: [PATCH 7/9] fix: Use Reference for family type references in profiles and base types When a reference target is a family type (e.g. Resource, DomainResource), generate Reference instead of Reference<"Resource">. This makes narrower profile references like Reference<"Patient"> assignable to the base type field. Also fix tryPromoteChoice to resolve reference targets for promoted choice fields via fieldTsType instead of tsTypeFromIdentifier, and align extension setter value types with the extension profile's factory params. --- .../typescript/profile-extensions.ts | 12 +++--- .../writer-generator/typescript/profile.ts | 43 +++++++++++++------ src/api/writer-generator/typescript/utils.ts | 10 +++-- src/api/writer-generator/typescript/writer.ts | 31 ++++++++++--- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/api/writer-generator/typescript/profile-extensions.ts b/src/api/writer-generator/typescript/profile-extensions.ts index e6dfe934..6a0c3f68 100644 --- a/src/api/writer-generator/typescript/profile-extensions.ts +++ b/src/api/writer-generator/typescript/profile-extensions.ts @@ -317,12 +317,12 @@ const generateSingleValueExtensionSetter = (w: TypeScript, tsIndex: TypeSchemaIn const useUpsert = ext.max === "1"; if (extProfileInfo) { - const paramType = `${extProfileInfo.className} | Extension | ${valueType}`; - const extHasValueParam = collectProfileFactoryInfo(tsIndex, extProfileInfo.flatProfile).params.some( - (p) => p.name === valueField, - ); - const elseExpr = extHasValueParam - ? `${extProfileInfo.className}.createResource({ ${valueField}: value as ${valueType} })` + const extFactoryInfo = collectProfileFactoryInfo(tsIndex, extProfileInfo.flatProfile); + const extValueParam = extFactoryInfo.params.find((p) => p.name === valueField); + const resolvedValueType = extValueParam?.tsType ?? valueType; + const paramType = `${extProfileInfo.className} | Extension | ${resolvedValueType}`; + const elseExpr = extValueParam + ? `${extProfileInfo.className}.createResource({ ${valueField}: value as ${resolvedValueType} })` : `{ url: "${ext.url}", ${valueField}: value as ${valueType} } as Extension`; w.curlyBlock(["public", setMethodName, `(value: ${paramType}): this`], () => { w.ifElseChain( diff --git a/src/api/writer-generator/typescript/profile.ts b/src/api/writer-generator/typescript/profile.ts index ce774571..0d4deac1 100644 --- a/src/api/writer-generator/typescript/profile.ts +++ b/src/api/writer-generator/typescript/profile.ts @@ -81,17 +81,27 @@ const tryPromoteChoice = ( fields: NonNullable, params: ProfileFactoryInfo["params"], promotedChoices: Set, + resolveRef?: (ref: TypeIdentifier) => TypeIdentifier, + isFamilyType?: (ref: TypeIdentifier) => boolean, ): void => { if (!isChoiceDeclarationField(field) || !field.required || field.choices.length !== 1) return; const choiceName = field.choices[0]; if (!choiceName) return; const choiceField = fields[choiceName]; if (!choiceField || !isChoiceInstanceField(choiceField)) return; - const tsType = tsTypeFromIdentifier(choiceField.type) + (choiceField.array ? "[]" : ""); + const tsType = fieldTsType(choiceField, resolveRef, isFamilyType); params.push({ name: choiceName, tsType, typeId: choiceField.type }); promotedChoices.add(choiceName); }; +export const mkIsFamilyType = + (tsIndex: TypeSchemaIndex) => + (ref: TypeIdentifier): boolean => { + const schema = tsIndex.resolveType(ref); + if (!schema || !("typeFamily" in schema)) return false; + return (schema.typeFamily?.resources?.length ?? 0) > 0; + }; + export const collectProfileFactoryInfo = ( tsIndex: TypeSchemaIndex, flatProfile: ProfileTypeSchema, @@ -104,6 +114,7 @@ export const collectProfileFactoryInfo = ( const fields = flatProfile.fields ?? {}; const promotedChoices = new Set(); const resolveRef = tsIndex.findLastSpecializationByIdentifier; + const isFamilyType = mkIsFamilyType(tsIndex); if (isResourceIdentifier(flatProfile.base)) { autoFields.push({ name: "resourceType", value: JSON.stringify(flatProfile.base.name) }); @@ -114,7 +125,7 @@ export const collectProfileFactoryInfo = ( if (isChoiceInstanceField(field)) continue; if (isChoiceDeclarationField(field)) { - tryPromoteChoice(field, fields, params, promotedChoices); + tryPromoteChoice(field, fields, params, promotedChoices, resolveRef, isFamilyType); continue; } @@ -123,7 +134,7 @@ export const collectProfileFactoryInfo = ( autoFields.push({ name, value: field.array ? `[${value}]` : value }); fixedFields.add(name); if (isNotChoiceDeclarationField(field) && field.type) { - const tsType = fieldTsType(field, resolveRef); + const tsType = fieldTsType(field, resolveRef, isFamilyType); autoAccessors.push({ name, tsType, typeId: field.type }); } continue; @@ -133,7 +144,7 @@ export const collectProfileFactoryInfo = ( const sliceNames = collectRequiredSliceNames(field); if (sliceNames) { if (field.type) { - const tsType = fieldTsType(field, resolveRef); + const tsType = fieldTsType(field, resolveRef, isFamilyType); sliceAutoFields.push({ name, tsType, @@ -147,17 +158,24 @@ export const collectProfileFactoryInfo = ( } if (field.required) { - const tsType = fieldTsType(field, resolveRef); + const tsType = fieldTsType(field, resolveRef, isFamilyType); params.push({ name, tsType, typeId: field.type }); } } - collectBaseRequiredParams(tsIndex, flatProfile, resolveRef, params, [ - ...autoFields.map((f) => f.name), - ...sliceAutoFields.map((f) => f.name), - ...params.map((f) => f.name), - ...promotedChoices, - ]); + collectBaseRequiredParams( + tsIndex, + flatProfile, + resolveRef, + params, + [ + ...autoFields.map((f) => f.name), + ...sliceAutoFields.map((f) => f.name), + ...params.map((f) => f.name), + ...promotedChoices, + ], + isFamilyType, + ); const accessors = [...autoAccessors, ...collectChoiceAccessors(flatProfile, promotedChoices)]; return { autoFields, sliceAutoFields, params, accessors, fixedFields }; @@ -170,6 +188,7 @@ const collectBaseRequiredParams = ( resolveRef: TypeSchemaIndex["findLastSpecializationByIdentifier"], params: ProfileFactoryInfo["params"], coveredNames: string[], + isFamilyType?: (ref: TypeIdentifier) => boolean, ) => { const covered = new Set(coveredNames); const baseSchema = tsIndex.resolveType(flatProfile.base); @@ -180,7 +199,7 @@ const collectBaseRequiredParams = ( if (isChoiceInstanceField(field)) continue; if (isChoiceDeclarationField(field)) continue; if (isNotChoiceDeclarationField(field) && field.type) { - const tsType = fieldTsType(field, resolveRef); + const tsType = fieldTsType(field, resolveRef, isFamilyType); params.push({ name, tsType, typeId: field.type }); } } diff --git a/src/api/writer-generator/typescript/utils.ts b/src/api/writer-generator/typescript/utils.ts index 256de3ec..2f9414e5 100644 --- a/src/api/writer-generator/typescript/utils.ts +++ b/src/api/writer-generator/typescript/utils.ts @@ -64,6 +64,7 @@ export const resolveFieldTsType = ( field: RegularField | ChoiceFieldInstance, resolveRef?: (ref: TypeIdentifier) => TypeIdentifier, genericFieldMap?: Record, + isFamilyType?: (ref: TypeIdentifier) => boolean, ): string => { if (genericFieldMap?.[tsName]) return genericFieldMap[tsName]; @@ -76,9 +77,9 @@ export const resolveFieldTsType = ( return tsEnumType(field.enum); } if (field.reference && field.reference.length > 0) { - const references = field.reference - .map((ref) => (resolveRef ? resolveRef(ref) : ref)) - .map((ref) => `"${ref.name}"`) + const resolved = field.reference.map((ref) => (resolveRef ? resolveRef(ref) : ref)); + const references = resolved + .map((ref) => (isFamilyType?.(ref) ? `string /* ${ref.name} */` : `"${ref.name}"`)) .join(" | "); return `Reference<${references}>`; } @@ -90,7 +91,8 @@ export const resolveFieldTsType = ( export const fieldTsType = ( field: RegularField | ChoiceFieldInstance, resolveRef?: (ref: TypeIdentifier) => TypeIdentifier, -): string => resolveFieldTsType("", "", field, resolveRef) + (field.array ? "[]" : ""); + isFamilyType?: (ref: TypeIdentifier) => boolean, +): string => resolveFieldTsType("", "", field, resolveRef, undefined, isFamilyType) + (field.array ? "[]" : ""); export const tsTypeFromIdentifier = (id: TypeIdentifier): string => { if (isNestedIdentifier(id)) return tsResourceName(id); diff --git a/src/api/writer-generator/typescript/writer.ts b/src/api/writer-generator/typescript/writer.ts index 63342d8c..bd7ab5aa 100644 --- a/src/api/writer-generator/typescript/writer.ts +++ b/src/api/writer-generator/typescript/writer.ts @@ -15,6 +15,7 @@ import { packageMeta, packageMetaToFhir, type SpecializationTypeSchema, + type TypeIdentifier, type TypeSchema, } from "@root/typeschema/types"; import { groupByPackages, type TypeSchemaIndex } from "@root/typeschema/utils"; @@ -28,7 +29,7 @@ import { tsProfileModuleFileName, tsResourceName, } from "./name"; -import { generateProfileClass, generateProfileImports, generateProfileIndexFile } from "./profile"; +import { generateProfileClass, generateProfileImports, generateProfileIndexFile, mkIsFamilyType } from "./profile"; import { resolveFieldTsType } from "./utils"; export const resolveTsAssets = (fn: string) => { @@ -199,7 +200,11 @@ export class TypeScript extends Writer { this.lineSM(`${extFieldName}?: ${typeExpr}`); } - generateType(tsIndex: TypeSchemaIndex, schema: SpecializationTypeSchema | NestedTypeSchema) { + generateType( + tsIndex: TypeSchemaIndex, + schema: SpecializationTypeSchema | NestedTypeSchema, + isFamilyType?: (ref: TypeIdentifier) => boolean, + ) { let name: string; // Generic types: Reference, Coding, CodeableConcept const genericTypes = ["Reference", "Coding", "CodeableConcept"]; @@ -272,7 +277,14 @@ export class TypeScript extends Writer { this.debugComment(fieldName, ":", field); const tsName = tsFieldName(fieldName); - const tsType = resolveFieldTsType(schema.identifier.name, tsName, field, undefined, genericFieldMap); + const tsType = resolveFieldTsType( + schema.identifier.name, + tsName, + field, + undefined, + genericFieldMap, + isFamilyType, + ); const optionalSymbol = field.required ? "" : "?"; const arraySymbol = field.array ? "[]" : ""; this.lineSM(`${tsName}${optionalSymbol}: ${tsType}${arraySymbol}`); @@ -306,10 +318,14 @@ export class TypeScript extends Writer { }); } - generateNestedTypes(tsIndex: TypeSchemaIndex, schema: SpecializationTypeSchema) { + generateNestedTypes( + tsIndex: TypeSchemaIndex, + schema: SpecializationTypeSchema, + isFamilyType?: (ref: TypeIdentifier) => boolean, + ) { if (schema.nested) { for (const subtype of schema.nested) { - this.generateType(tsIndex, subtype); + this.generateType(tsIndex, subtype, isFamilyType); this.line(); } } @@ -326,17 +342,18 @@ export class TypeScript extends Writer { }); }); } else if (isSpecializationTypeSchema(schema)) { + const isFamilyType = mkIsFamilyType(tsIndex); this.cat(`${tsModuleFileName(schema.identifier)}`, () => { this.generateDisclaimer(); this.generateDependenciesImports(tsIndex, schema); this.generateComplexTypeReexports(schema); - this.generateNestedTypes(tsIndex, schema); + this.generateNestedTypes(tsIndex, schema, isFamilyType); this.comment( "CanonicalURL:", schema.identifier.url, `(pkg: ${packageMetaToFhir(packageMeta(schema))})`, ); - this.generateType(tsIndex, schema); + this.generateType(tsIndex, schema, isFamilyType); this.generateResourceTypePredicate(schema); }); } else { From 79c9f8632f4e8cf8b4f60cafa198e06f8a741820 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 14:16:19 +0200 Subject: [PATCH 8/9] fix: Format nodge-r4.ts replaceAll calls --- examples/nodge-r4.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/nodge-r4.ts b/examples/nodge-r4.ts index 9568ae69..e3c97283 100644 --- a/examples/nodge-r4.ts +++ b/examples/nodge-r4.ts @@ -28,9 +28,18 @@ const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { const res = ctx.resource as { url?: string }; if (res.url === "http://ehelse.no/fhir/StructureDefinition/gd-RelatedPerson") { let str = JSON.stringify(ctx.resource); - str = str.replaceAll("http://hl7.org/fhir/StructureDefinition/Person", "http://hl7.org/fhir/StructureDefinition/Patient"); - str = str.replaceAll("http://hl7.no/fhir/StructureDefinition/no-basis-Person", "http://hl7.org/fhir/StructureDefinition/Patient"); - str = str.replaceAll("http://ehelse.no/fhir/StructureDefinition/gd-Person", "http://hl7.org/fhir/StructureDefinition/Patient"); + str = str.replaceAll( + "http://hl7.org/fhir/StructureDefinition/Person", + "http://hl7.org/fhir/StructureDefinition/Patient", + ); + str = str.replaceAll( + "http://hl7.no/fhir/StructureDefinition/no-basis-Person", + "http://hl7.org/fhir/StructureDefinition/Patient", + ); + str = str.replaceAll( + "http://ehelse.no/fhir/StructureDefinition/gd-Person", + "http://hl7.org/fhir/StructureDefinition/Patient", + ); return { ...ctx, resource: JSON.parse(str) }; } return ctx; From 3f06cce09b292e3e9a1bcf1988cb00bdb8492163 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Fri, 3 Apr 2026 14:17:03 +0200 Subject: [PATCH 9/9] chore: Regenerate examples for family type references --- .../typescript-r4/fhir-types/hl7-fhir-r4-core/Observation.ts | 2 +- .../fhir-types/hl7-fhir-r4-core/Observation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/Observation.ts b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/Observation.ts index fc2a9877..8a6d8753 100644 --- a/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/Observation.ts +++ b/examples/typescript-r4/fhir-types/hl7-fhir-r4-core/Observation.ts @@ -74,7 +74,7 @@ export interface Observation extends DomainResource { effectivePeriod?: Period; effectiveTiming?: Timing; encounter?: Reference<"Encounter">; - focus?: Reference<"Resource">[]; + focus?: Reference[]; hasMember?: Reference<"MolecularSequence" | "Observation" | "QuestionnaireResponse">[]; identifier?: Identifier[]; interpretation?: CodeableConcept<("_GeneticObservationInterpretation" | "CAR" | "Carrier" | "_ObservationInterpretationChange" | "B" | "D" | "U" | "W" | "_ObservationInterpretationExceptions" | "<" | ">" | "AC" | "IE" | "QCF" | "TOX" | "_ObservationInterpretationNormality" | "A" | "AA" | "HH" | "LL" | "H" | "H>" | "HU" | "L" | "L<" | "LU" | "N" | "_ObservationInterpretationSusceptibility" | "I" | "MS" | "NCL" | "NS" | "R" | "SYN-R" | "S" | "SDD" | "SYN-S" | "VS" | "EX" | "HX" | "LX" | "HM" | "ObservationInterpretationDetection" | "IND" | "E" | "NEG" | "ND" | "POS" | "DET" | "ObservationInterpretationExpectation" | "EXP" | "UNE" | "OBX" | "ReactivityObservationInterpretation" | "NR" | "RR" | "WR" | string)>[]; diff --git a/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/Observation.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/Observation.ts index fc2a9877..8a6d8753 100644 --- a/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/Observation.ts +++ b/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/Observation.ts @@ -74,7 +74,7 @@ export interface Observation extends DomainResource { effectivePeriod?: Period; effectiveTiming?: Timing; encounter?: Reference<"Encounter">; - focus?: Reference<"Resource">[]; + focus?: Reference[]; hasMember?: Reference<"MolecularSequence" | "Observation" | "QuestionnaireResponse">[]; identifier?: Identifier[]; interpretation?: CodeableConcept<("_GeneticObservationInterpretation" | "CAR" | "Carrier" | "_ObservationInterpretationChange" | "B" | "D" | "U" | "W" | "_ObservationInterpretationExceptions" | "<" | ">" | "AC" | "IE" | "QCF" | "TOX" | "_ObservationInterpretationNormality" | "A" | "AA" | "HH" | "LL" | "H" | "H>" | "HU" | "L" | "L<" | "LU" | "N" | "_ObservationInterpretationSusceptibility" | "I" | "MS" | "NCL" | "NS" | "R" | "SYN-R" | "S" | "SDD" | "SYN-S" | "VS" | "EX" | "HX" | "LX" | "HM" | "ObservationInterpretationDetection" | "IND" | "E" | "NEG" | "ND" | "POS" | "DET" | "ObservationInterpretationExpectation" | "EXP" | "UNE" | "OBX" | "ReactivityObservationInterpretation" | "NR" | "RR" | "WR" | string)>[];