44 * The profile slices Bundle.entry[] by resource type:
55 * - PatientEntry (min: 1, max: 1) — entry where resource is Patient
66 * - OrganizationEntry (min: 0, max: *) — entry where resource is Organization
7+ *
8+ * Generic type parameters (BundleEntry<Patient>, BundleEntry<Organization>) let
9+ * the compiler narrow `entry.resource` to the concrete resource type — no casts needed.
710 */
811
912import { describe , expect , test } from "bun:test" ;
1013import { ExampleTypedBundleProfile } from "./fhir-types/example-folder-structures/profiles/Bundle_ExampleTypedBundle" ;
14+ import type { BundleEntry } from "./fhir-types/hl7-fhir-r4-core/Bundle" ;
15+ import type { DomainResource } from "./fhir-types/hl7-fhir-r4-core/DomainResource" ;
1116import type { Organization } from "./fhir-types/hl7-fhir-r4-core/Organization" ;
1217import type { Patient } from "./fhir-types/hl7-fhir-r4-core/Patient" ;
1318
@@ -80,9 +85,10 @@ describe("type-discriminated bundle slices", () => {
8085 expect ( bundle . toResource ( ) . entry ) . toHaveLength ( 2 ) ;
8186 } ) ;
8287
83- test ( "set/get PatientEntry with full BundleEntry input" , ( ) => {
88+ test ( "set/get PatientEntry with full BundleEntry<Patient> input" , ( ) => {
8489 const bundle = createBundle ( ) ;
85- bundle . setPatientEntry ( { fullUrl : "urn:uuid:p1" , resource : smithPatient } ) ;
90+ const input : BundleEntry < Patient > = { fullUrl : "urn:uuid:p1" , resource : smithPatient } ;
91+ bundle . setPatientEntry ( input ) ;
8692
8793 const raw = bundle . getPatientEntry ( "raw" ) ! ;
8894 expect ( raw . fullUrl ) . toBe ( "urn:uuid:p1" ) ;
@@ -93,9 +99,10 @@ describe("type-discriminated bundle slices", () => {
9399 expect ( flat . resource ) . toEqual ( smithPatient ) ;
94100 } ) ;
95101
96- test ( "set/get OrganizationEntry with full BundleEntry input" , ( ) => {
102+ test ( "set/get OrganizationEntry with full BundleEntry<Organization> input" , ( ) => {
97103 const bundle = createBundle ( ) ;
98- bundle . setOrganizationEntry ( { fullUrl : "urn:uuid:o1" , resource : acmeOrg } ) ;
104+ const input : BundleEntry < Organization > = { fullUrl : "urn:uuid:o1" , resource : acmeOrg } ;
105+ bundle . setOrganizationEntry ( input ) ;
99106
100107 const raw = bundle . getOrganizationEntry ( "raw" ) ! ;
101108 expect ( raw . fullUrl ) . toBe ( "urn:uuid:o1" ) ;
@@ -106,3 +113,50 @@ describe("type-discriminated bundle slices", () => {
106113 expect ( flat . resource ) . toEqual ( acmeOrg ) ;
107114 } ) ;
108115} ) ;
116+
117+ describe ( "generic type-family fields — compile-time narrowing" , ( ) => {
118+ test ( "BundleEntry<Patient>.resource is Patient (access Patient-specific fields without cast)" , ( ) => {
119+ const bundle = createBundle ( ) ;
120+ bundle . setPatientEntry ( { resource : smithPatient } ) ;
121+
122+ const entry = bundle . getPatientEntry ( ) ! ;
123+ // entry.resource is Patient — .name is available directly, no cast needed
124+ const family : string | undefined = entry . resource ?. name ?. [ 0 ] ?. family ;
125+ expect ( family ) . toBe ( "Smith" ) ;
126+ } ) ;
127+
128+ test ( "BundleEntry<Organization>.resource is Organization (access Organization-specific fields without cast)" , ( ) => {
129+ const bundle = createBundle ( ) ;
130+ bundle . setOrganizationEntry ( { resource : acmeOrg } ) ;
131+
132+ const entry = bundle . getOrganizationEntry ( ) ! ;
133+ // entry.resource is Organization — .name is string, not HumanName[]
134+ const name : string | undefined = entry . resource ?. name ;
135+ expect ( name ) . toBe ( "Acme Corp" ) ;
136+ } ) ;
137+
138+ test ( "BundleEntry<T> defaults to BundleEntry<Resource> — unparameterized usage unchanged" , ( ) => {
139+ const entry : BundleEntry = { resource : smithPatient } ;
140+ expect ( entry . resource ?. resourceType ) . toBe ( "Patient" ) ;
141+ } ) ;
142+
143+ test ( "DomainResource<T> narrows contained to T[]" , ( ) => {
144+ const container : DomainResource < Patient > = {
145+ resourceType : "Patient" ,
146+ contained : [ smithPatient , jonesPatient ] ,
147+ } ;
148+ // contained is Patient[] — .name available directly
149+ const family : string | undefined = container . contained ?. [ 0 ] ?. name ?. [ 0 ] ?. family ;
150+ expect ( family ) . toBe ( "Smith" ) ;
151+ } ) ;
152+
153+ test ( "BundleEntry<Patient> rejects Organization at compile time" , ( ) => {
154+ const patientEntry : BundleEntry < Patient > = { resource : smithPatient } ;
155+ expect ( patientEntry . resource ?. resourceType ) . toBe ( "Patient" ) ;
156+
157+ // Uncomment to verify compile error:
158+ // @ts -expect-error — Organization is not assignable to Patient
159+ const _bad : BundleEntry < Patient > = { resource : acmeOrg } ;
160+ void _bad ;
161+ } ) ;
162+ } ) ;
0 commit comments