diff --git a/assets/api/writer-generator/typescript/profile-helpers.ts b/assets/api/writer-generator/typescript/profile-helpers.ts index 05a0253ed..239ec0d0e 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/examples/nodge-r4.ts b/examples/nodge-r4.ts index 11c9ca571..e3c972834 100644 --- a/examples/nodge-r4.ts +++ b/examples/nodge-r4.ts @@ -22,7 +22,28 @@ 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; diff --git a/examples/typescript-ccda/generate.ts b/examples/typescript-ccda/generate.ts index ffaa2ca11..4a4f0b8ef 100644 --- a/examples/typescript-ccda/generate.ts +++ b/examples/typescript-ccda/generate.ts @@ -7,13 +7,46 @@ 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) }; + } + // 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 + // 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) { 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 fc2a98775..8a6d8753c 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-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 6d8f3a936..0e5add040 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 c2961eab4..e9eecae65 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 570702cfb..063f86d91 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 353d6bdbd..74839752a 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 9e303edbf..f02e6c459 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 1f20e9da2..52f53ce95 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 280bc6ff2..d8cb909d1 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 05a0253ed..239ec0d0e 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/Observation.ts b/examples/typescript-us-core/fhir-types/hl7-fhir-r4-core/Observation.ts index fc2a98775..8a6d8753c 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)>[]; 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 280bc6ff2..d8cb909d1 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 d15408b57..f69e5c62c 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 68ca39fb8..3f2d5d09f 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 d2ba5a35e..7f76b5666 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 0e7186820..03e5cc6dd 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 e79c5c853..a6632772a 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 db1e68033..d560fa539 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 a36184dc3..9284e2b06 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 0f4d519fb..a63b70c9b 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 3d2cc006c..5b6957d0a 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 05a0253ed..239ec0d0e 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. diff --git a/src/api/writer-generator/typescript/profile-extensions.ts b/src/api/writer-generator/typescript/profile-extensions.ts index e6dfe9344..6a0c3f682 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 fd0858afe..0d4deac1c 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 }); } } @@ -221,7 +240,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 +480,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 +489,7 @@ const generateFactoryMethods = ( if (hasMeta) { w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`); } - }, [")"]); + }); w.lineSM("return resource"); }); @@ -504,14 +522,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"); }); diff --git a/src/api/writer-generator/typescript/utils.ts b/src/api/writer-generator/typescript/utils.ts index 256de3ece..2f9414e54 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 63342d8c9..bd7ab5aa9 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 { diff --git a/test/api/write-generator/__snapshots__/typescript.test.ts.snap b/test/api/write-generator/__snapshots__/typescript.test.ts.snap index 2a50335be..616bd22eb 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 1d881bb49..09cf85994 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; }