@@ -42,64 +42,7 @@ export const resolveTsAssets = (fn: string) => {
4242 return Path . resolve ( __dirname , "../../../.." , "assets" , "api" , "writer-generator" , "typescript" , fn ) ;
4343} ;
4444
45- export type GenericInfo = {
46- paramList : { name : string ; constraint : string } [ ] ;
47- /** fieldName (ts-name) -> generic param name, used by resolveFieldTsType to substitute the field's type */
48- fieldMap : Record < string , string > ;
49- /** fieldName (ts-name) -> generic args suffix to append (e.g. "<T>") when the field references a generic nested type */
50- nestedArgsByField : Record < string , string > ;
51- } ;
52-
53- const computeGenericInfo = (
54- tsIndex : TypeSchemaIndex ,
55- schema : SpecializationTypeSchema | NestedTypeSchema ,
56- nestedGenericInfos : Record < string , GenericInfo > ,
57- ) : GenericInfo => {
58- const typeFamilyFields : { fieldName : string ; familyTypeName : string } [ ] = [ ] ;
59- const nestedFields : { fieldName : string ; nestedName : string ; info : GenericInfo } [ ] = [ ] ;
60- for ( const [ fieldName , field ] of Object . entries ( schema . fields ?? { } ) ) {
61- if ( isChoiceDeclarationField ( field ) || ! field . type ) continue ;
62- const tsName = tsFieldName ( fieldName ) ;
63- if ( isNestedIdentifier ( field . type ) ) {
64- const nestedInfo = nestedGenericInfos [ field . type . name ] ;
65- if ( nestedInfo && nestedInfo . paramList . length > 0 ) {
66- nestedFields . push ( { fieldName : tsName , nestedName : field . type . name , info : nestedInfo } ) ;
67- }
68- continue ;
69- }
70- const fieldTypeSchema = tsIndex . resolveType ( field . type ) ;
71- if ( isSpecializationTypeSchema ( fieldTypeSchema ) && ( fieldTypeSchema . typeFamily ?. resources ?. length ?? 0 ) > 0 ) {
72- typeFamilyFields . push ( { fieldName : tsName , familyTypeName : field . type . name } ) ;
73- }
74- }
75-
76- const fieldMap : Record < string , string > = { } ;
77- const nestedArgsByField : Record < string , string > = { } ;
78- const paramList : { name : string ; constraint : string } [ ] = [ ] ;
79- const [ first ] = typeFamilyFields ;
80- if ( typeFamilyFields . length === 1 && nestedFields . length === 0 && first ) {
81- fieldMap [ first . fieldName ] = "T" ;
82- paramList . push ( { name : "T" , constraint : first . familyTypeName } ) ;
83- } else {
84- for ( const tf of typeFamilyFields ) {
85- const paramName = `T${ uppercaseFirstLetter ( tf . fieldName ) } ` ;
86- fieldMap [ tf . fieldName ] = paramName ;
87- paramList . push ( { name : paramName , constraint : tf . familyTypeName } ) ;
88- }
89- }
90- // Inherit nested type's generic params by reusing the same parameter names.
91- // Only propagate when the parent doesn't already define a param with the same name/constraint.
92- for ( const nf of nestedFields ) {
93- const passThrough : string [ ] = [ ] ;
94- for ( const np of nf . info . paramList ) {
95- const existing = paramList . find ( ( p ) => p . name === np . name ) ;
96- if ( ! existing ) paramList . push ( { name : np . name , constraint : np . constraint } ) ;
97- passThrough . push ( np . name ) ;
98- }
99- nestedArgsByField [ nf . fieldName ] = `<${ passThrough . join ( ", " ) } >` ;
100- }
101- return { paramList, fieldMap, nestedArgsByField } ;
102- } ;
45+ type GenericParam = { name : string ; constraint : string } ;
10346
10447export type TypeScriptOptions = {
10548 lineWidth ?: number ;
@@ -264,23 +207,64 @@ export class TypeScript extends Writer<TypeScriptOptions> {
264207 tsIndex : TypeSchemaIndex ,
265208 schema : SpecializationTypeSchema | NestedTypeSchema ,
266209 isFamilyType ?: ( ref : TypeIdentifier ) => boolean ,
267- nestedGenericInfos : Record < string , GenericInfo > = { } ,
268- ) : GenericInfo {
210+ nestedGenericParams : Record < string , GenericParam [ ] > = { } ,
211+ ) : GenericParam [ ] {
269212 let name : string ;
270213 // Generic types: Reference, Coding, CodeableConcept
271214 const genericTypes = [ "Reference" , "Coding" , "CodeableConcept" ] ;
272- if ( genericTypes . includes ( schema . identifier . name ) ) {
215+ const isHardcodedGeneric = genericTypes . includes ( schema . identifier . name ) ;
216+ if ( isHardcodedGeneric ) {
273217 name = `${ schema . identifier . name } <T extends string = string>` ;
274218 } else {
275219 name = tsResourceName ( schema . identifier ) ;
276220 }
277221
278- const isHardcodedGeneric = genericTypes . includes ( schema . identifier . name ) ;
279- const genericInfo : GenericInfo = isHardcodedGeneric
280- ? { paramList : [ ] , fieldMap : { } , nestedArgsByField : { } }
281- : computeGenericInfo ( tsIndex , schema , nestedGenericInfos ) ;
282- if ( ! isHardcodedGeneric && genericInfo . paramList . length > 0 ) {
283- const declParams = genericInfo . paramList . map ( ( p ) => `${ p . name } extends ${ p . constraint } = ${ p . constraint } ` ) ;
222+ // Collect type-family fields (e.g. `resource: Resource` -> needs T extends Resource)
223+ // and references to generic nested types (e.g. `entry: BundleEntry` when BundleEntry is generic).
224+ const typeFamilyFields : { fieldName : string ; familyTypeName : string } [ ] = [ ] ;
225+ const nestedFields : { fieldName : string ; params : GenericParam [ ] } [ ] = [ ] ;
226+ if ( ! isHardcodedGeneric ) {
227+ for ( const [ fieldName , field ] of Object . entries ( schema . fields ?? { } ) ) {
228+ if ( isChoiceDeclarationField ( field ) || ! field . type ) continue ;
229+ const tsName = tsFieldName ( fieldName ) ;
230+ if ( isNestedIdentifier ( field . type ) ) {
231+ const params = nestedGenericParams [ field . type . name ] ;
232+ if ( params ?. length ) nestedFields . push ( { fieldName : tsName , params } ) ;
233+ continue ;
234+ }
235+ const fieldTypeSchema = tsIndex . resolveType ( field . type ) ;
236+ if (
237+ isSpecializationTypeSchema ( fieldTypeSchema ) &&
238+ ( fieldTypeSchema . typeFamily ?. resources ?. length ?? 0 ) > 0
239+ ) {
240+ typeFamilyFields . push ( { fieldName : tsName , familyTypeName : field . type . name } ) ;
241+ }
242+ }
243+ }
244+
245+ // Build generic params from type-family fields, then pass-through nested-type params.
246+ const fieldMap : Record < string , string > = { } ;
247+ const paramList : GenericParam [ ] = [ ] ;
248+ const [ first ] = typeFamilyFields ;
249+ if ( typeFamilyFields . length === 1 && nestedFields . length === 0 && first ) {
250+ fieldMap [ first . fieldName ] = "T" ;
251+ paramList . push ( { name : "T" , constraint : first . familyTypeName } ) ;
252+ } else {
253+ for ( const tf of typeFamilyFields ) {
254+ const paramName = `T${ uppercaseFirstLetter ( tf . fieldName ) } ` ;
255+ fieldMap [ tf . fieldName ] = paramName ;
256+ paramList . push ( { name : paramName , constraint : tf . familyTypeName } ) ;
257+ }
258+ }
259+ const nestedArgsByField : Record < string , string > = { } ;
260+ for ( const nf of nestedFields ) {
261+ for ( const np of nf . params ) {
262+ if ( ! paramList . find ( ( p ) => p . name === np . name ) ) paramList . push ( np ) ;
263+ }
264+ nestedArgsByField [ nf . fieldName ] = `<${ nf . params . map ( ( p ) => p . name ) . join ( ", " ) } >` ;
265+ }
266+ if ( ! isHardcodedGeneric && paramList . length > 0 ) {
267+ const declParams = paramList . map ( ( p ) => `${ p . name } extends ${ p . constraint } = ${ p . constraint } ` ) ;
284268 name += `<${ declParams . join ( ", " ) } >` ;
285269 }
286270
@@ -290,7 +274,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
290274 this . debugComment ( schema . identifier ) ;
291275 if ( ! schema . fields && ! extendsClause && ! isResourceTypeSchema ( schema ) ) {
292276 this . lineSM ( `export type ${ name } = object` ) ;
293- return genericInfo ;
277+ return paramList ;
294278 }
295279 this . curlyBlock ( [ "export" , "interface" , name , extendsClause ] , ( ) => {
296280 if ( isResourceTypeSchema ( schema ) ) {
@@ -322,12 +306,12 @@ export class TypeScript extends Writer<TypeScriptOptions> {
322306 tsName ,
323307 field ,
324308 undefined ,
325- genericInfo . fieldMap ,
309+ fieldMap ,
326310 isFamilyType ,
327311 ) ;
328312 const optionalSymbol = field . required ? "" : "?" ;
329313 const arraySymbol = field . array ? "[]" : "" ;
330- const nestedArgs = genericInfo . nestedArgsByField [ tsName ] ?? "" ;
314+ const nestedArgs = nestedArgsByField [ tsName ] ?? "" ;
331315 this . lineSM ( `${ tsName } ${ optionalSymbol } : ${ tsType } ${ nestedArgs } ${ arraySymbol } ` ) ;
332316
333317 if ( this . withPrimitiveTypeExtension ( schema ) ) {
@@ -337,7 +321,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
337321 }
338322 }
339323 } ) ;
340- return genericInfo ;
324+ return paramList ;
341325 }
342326
343327 withPrimitiveTypeExtension ( schema : TypeSchema | NestedTypeSchema ) : boolean {
@@ -364,16 +348,16 @@ export class TypeScript extends Writer<TypeScriptOptions> {
364348 tsIndex : TypeSchemaIndex ,
365349 schema : SpecializationTypeSchema ,
366350 isFamilyType ?: ( ref : TypeIdentifier ) => boolean ,
367- ) : Record < string , GenericInfo > {
368- const nestedGenericInfos : Record < string , GenericInfo > = { } ;
351+ ) : Record < string , GenericParam [ ] > {
352+ const nestedGenericParams : Record < string , GenericParam [ ] > = { } ;
369353 if ( schema . nested ) {
370354 for ( const subtype of schema . nested ) {
371- const info = this . generateType ( tsIndex , subtype , isFamilyType , nestedGenericInfos ) ;
372- nestedGenericInfos [ subtype . identifier . name ] = info ;
355+ const params = this . generateType ( tsIndex , subtype , isFamilyType , nestedGenericParams ) ;
356+ nestedGenericParams [ subtype . identifier . name ] = params ;
373357 this . line ( ) ;
374358 }
375359 }
376- return nestedGenericInfos ;
360+ return nestedGenericParams ;
377361 }
378362
379363 generateResourceModule ( tsIndex : TypeSchemaIndex , schema : TypeSchema ) {
@@ -392,13 +376,13 @@ export class TypeScript extends Writer<TypeScriptOptions> {
392376 this . generateDisclaimer ( ) ;
393377 this . generateDependenciesImports ( tsIndex , schema ) ;
394378 this . generateComplexTypeReexports ( schema ) ;
395- const nestedGenericInfos = this . generateNestedTypes ( tsIndex , schema , isFamilyType ) ;
379+ const nestedGenericParams = this . generateNestedTypes ( tsIndex , schema , isFamilyType ) ;
396380 this . comment (
397381 "CanonicalURL:" ,
398382 schema . identifier . url ,
399383 `(pkg: ${ packageMetaToFhir ( packageMeta ( schema ) ) } )` ,
400384 ) ;
401- this . generateType ( tsIndex , schema , isFamilyType , nestedGenericInfos ) ;
385+ this . generateType ( tsIndex , schema , isFamilyType , nestedGenericParams ) ;
402386 this . generateResourceTypePredicate ( schema ) ;
403387 } ) ;
404388 } else {
0 commit comments