diff --git a/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts b/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts index 00d1bf70..fc3456ec 100644 --- a/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts +++ b/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBloodPressureProfile.ts @@ -46,7 +46,7 @@ import { export type USCoreBloodPressureProfileRaw = { status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown"); - subject: Reference<"Patient">; + subject: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>; category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[]; component?: ObservationComponent[]; } @@ -145,11 +145,11 @@ export class USCoreBloodPressureProfile { return this; } - getSubject () : Reference<"Patient"> | undefined { - return this.resource.subject as Reference<"Patient"> | undefined; + getSubject () : Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined { + return this.resource.subject as Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined; } - setSubject (value: Reference<"Patient">) : this { + setSubject (value: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>) : this { Object.assign(this.resource, { subject: value }); return this; } diff --git a/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts b/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts index 781a0f11..f5a33542 100644 --- a/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts +++ b/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreBodyWeightProfile.ts @@ -38,7 +38,7 @@ import { export type USCoreBodyWeightProfileRaw = { status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown"); - subject: Reference<"Patient">; + subject: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>; category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[]; } @@ -119,11 +119,11 @@ export class USCoreBodyWeightProfile { return this; } - getSubject () : Reference<"Patient"> | undefined { - return this.resource.subject as Reference<"Patient"> | undefined; + getSubject () : Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined { + return this.resource.subject as Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined; } - setSubject (value: Reference<"Patient">) : this { + setSubject (value: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>) : this { Object.assign(this.resource, { subject: value }); return this; } diff --git a/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts b/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts index 2ce509e3..7ed3d3df 100644 --- a/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts +++ b/examples/typescript-r4-us-core/fhir-types/hl7-fhir-us-core/profiles/Observation_USCoreVitalSignsProfile.ts @@ -38,7 +38,7 @@ import { export type USCoreVitalSignsProfileRaw = { status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown"); code: CodeableConcept<("2708-6" | "29463-7" | "3140-1" | "3150-0" | "3151-8" | "39156-5" | "59408-5" | "59575-1" | "59576-9" | "77606-2" | "8287-5" | "8289-1" | "8302-2" | "8306-3" | "8310-5" | "8462-4" | "8478-0" | "8480-6" | "8867-4" | "9279-1" | "9843-4" | string)>; - subject: Reference<"Patient">; + subject: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>; category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[]; } @@ -127,11 +127,11 @@ export class USCoreVitalSignsProfile { return this; } - getSubject () : Reference<"Patient"> | undefined { - return this.resource.subject as Reference<"Patient"> | undefined; + getSubject () : Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined { + return this.resource.subject as Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined; } - setSubject (value: Reference<"Patient">) : this { + setSubject (value: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>) : this { Object.assign(this.resource, { subject: value }); return this; } diff --git a/src/api/writer-generator/typescript/utils.ts b/src/api/writer-generator/typescript/utils.ts index fc9ab91a..3e8d9679 100644 --- a/src/api/writer-generator/typescript/utils.ts +++ b/src/api/writer-generator/typescript/utils.ts @@ -80,9 +80,23 @@ export const resolveFieldTsType = ( return tsEnumType(field.enum); } if (field.reference && field.reference.resource.length > 0) { - const resolved = field.reference.resource.map((ref) => (resolveRef ? resolveRef(ref) : ref)); - const references = resolved - .map((ref) => (isFamilyType?.(ref) ? `string /* ${ref.name} */` : `"${ref.name}"`)) + // Profile targets are replaced by their base resource type; keep the + // profile URLs as comments to make server-side validation errors traceable. + const profilesByResource: Record = {}; + if (resolveRef) { + for (const profile of field.reference.profiles ?? []) { + const base = resolveRef(profile).name; + (profilesByResource[base] ??= []).push(profile.url); + } + } + const references = field.reference.resource + .map((original) => { + const ref = resolveRef ? resolveRef(original) : original; + if (isFamilyType?.(ref)) return `string /* ${ref.name} */`; + const profiles = profilesByResource[ref.name]; + if (profiles) return `"${ref.name}" /* ${profiles.join(", ")} */`; + return `"${ref.name}"`; + }) .join(" | "); return `Reference<${references}>`; } diff --git a/test/api/write-generator/__snapshots__/typescript.test.ts.snap b/test/api/write-generator/__snapshots__/typescript.test.ts.snap index c6bc1688..ea68c285 100644 --- a/test/api/write-generator/__snapshots__/typescript.test.ts.snap +++ b/test/api/write-generator/__snapshots__/typescript.test.ts.snap @@ -1364,7 +1364,7 @@ import { export type USCoreBloodPressureProfileRaw = { status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown"); - subject: Reference<"Patient">; + subject: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>; category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[]; component?: ObservationComponent[]; } @@ -1463,11 +1463,11 @@ export class USCoreBloodPressureProfile { return this; } - getSubject () : Reference<"Patient"> | undefined { - return this.resource.subject as Reference<"Patient"> | undefined; + getSubject () : Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined { + return this.resource.subject as Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined; } - setSubject (value: Reference<"Patient">) : this { + setSubject (value: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>) : this { Object.assign(this.resource, { subject: value }); return this; } @@ -1793,7 +1793,7 @@ import { export type USCoreBodyWeightProfileRaw = { status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown"); - subject: Reference<"Patient">; + subject: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>; category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[]; } @@ -1874,11 +1874,11 @@ export class USCoreBodyWeightProfile { return this; } - getSubject () : Reference<"Patient"> | undefined { - return this.resource.subject as Reference<"Patient"> | undefined; + getSubject () : Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined { + return this.resource.subject as Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */> | undefined; } - setSubject (value: Reference<"Patient">) : this { + setSubject (value: Reference<"Patient" /* http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient */>) : this { Object.assign(this.resource, { subject: value }); return this; } diff --git a/test/unit/api/writer-generator/typescript/utils.test.ts b/test/unit/api/writer-generator/typescript/utils.test.ts new file mode 100644 index 00000000..3603bae1 --- /dev/null +++ b/test/unit/api/writer-generator/typescript/utils.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, test } from "bun:test"; +import { fieldTsType } from "@root/api/writer-generator/typescript/utils"; +import type { CanonicalUrl, Name, ProfileIdentifier, RegularField, TypeIdentifier } from "@root/typeschema/types"; + +const makeIdentifier = (kind: TypeIdentifier["kind"], name: string): TypeIdentifier => ({ + kind, + package: "test-package", + version: "1.0.0", + name: name as Name, + url: `http://example.org/StructureDefinition/${name}` as CanonicalUrl, +}); + +const referenceType = makeIdentifier("complex-type", "Reference"); +const patient = makeIdentifier("resource", "Patient"); +const group = makeIdentifier("resource", "Group"); +const usCorePatient = makeIdentifier("profile", "USCorePatient") as ProfileIdentifier; +const testPatient = makeIdentifier("profile", "TestPatient") as ProfileIdentifier; + +const resolveRef = (ref: TypeIdentifier): TypeIdentifier => { + if (ref.name === usCorePatient.name || ref.name === testPatient.name) return patient; + return ref; +}; + +describe("fieldTsType reference targets", () => { + test("plain resource targets have no comment", () => { + const field: RegularField = { type: referenceType, reference: { resource: [patient, group] } }; + expect(fieldTsType(field, resolveRef)).toBe(`Reference<"Patient" | "Group">`); + }); + + test("profile target replaced by its resource type keeps the profile url as a comment", () => { + const field: RegularField = { + type: referenceType, + reference: { resource: [patient], profiles: [usCorePatient] }, + }; + expect(fieldTsType(field, resolveRef)).toBe( + `Reference<"Patient" /* http://example.org/StructureDefinition/USCorePatient */>`, + ); + }); + + test("multiple profiles of the same resource are listed in one comma-separated comment", () => { + const field: RegularField = { + type: referenceType, + reference: { resource: [group, patient], profiles: [usCorePatient, testPatient] }, + }; + expect(fieldTsType(field, resolveRef)).toBe( + `Reference<"Group" | "Patient" /* http://example.org/StructureDefinition/USCorePatient, http://example.org/StructureDefinition/TestPatient */>`, + ); + }); +});