11import * as Path from "node:path" ;
2- import { extractNameFromCanonical , type TypeSchema } from "@root/typeschema/types" ;
2+ import type { StructureDefinition } from "@atomic-ehr/fhirschema" ;
3+ import type { RichFHIRSchema } from "@root/typeschema/types" ;
4+ import { type CanonicalUrl , extractNameFromCanonical , type TypeSchema } from "@root/typeschema/types" ;
35import type { TypeSchemaIndex } from "@root/typeschema/utils" ;
46import YAML from "yaml" ;
57import { FileSystemWriter , type FileSystemWriterOptions } from "./writer" ;
68
79export interface IntrospectionWriterOptions extends FileSystemWriterOptions {
810 typeSchemas ?: string /** if .ndjson -- put in one file, else -- split into separated files*/ ;
911 typeTree ?: string /** .json or .yaml file */ ;
12+ fhirSchemas ?: string /** if .ndjson -- put in one file, else -- split into separated files*/ ;
13+ structureDefinitions ?: string /** if .ndjson -- put in one file, else -- split into separated files*/ ;
1014}
1115
1216const normalizeFileName = ( str : string ) : string => {
@@ -15,6 +19,43 @@ const normalizeFileName = (str: string): string => {
1519 return res ;
1620} ;
1721
22+ const typeSchemaToJson = ( ts : TypeSchema , pretty : boolean ) : { filename : string ; content : string } => {
23+ const pkgPath = normalizeFileName ( ts . identifier . package ) ;
24+ const name = normalizeFileName ( `${ ts . identifier . name } (${ extractNameFromCanonical ( ts . identifier . url ) } )` ) ;
25+ const baseName = Path . join ( pkgPath , name ) ;
26+
27+ return {
28+ filename : baseName ,
29+ content : JSON . stringify ( ts , null , pretty ? 2 : undefined ) ,
30+ } ;
31+ } ;
32+
33+ const fhirSchemaToJson = ( fs : RichFHIRSchema , pretty : boolean ) : { filename : string ; content : string } => {
34+ const pkgPath = normalizeFileName ( fs . package_meta . name ) ;
35+ const name = normalizeFileName ( `${ fs . name } (${ extractNameFromCanonical ( fs . url ) } )` ) ;
36+ const baseName = Path . join ( pkgPath , name ) ;
37+
38+ return {
39+ filename : baseName ,
40+ content : JSON . stringify ( fs , null , pretty ? 2 : undefined ) ,
41+ } ;
42+ } ;
43+
44+ const structureDefinitionToJson = ( sd : StructureDefinition , pretty : boolean ) : { filename : string ; content : string } => {
45+ const pkgPath = normalizeFileName ( sd . package_name ?? "unknown" ) ;
46+ const name = normalizeFileName ( `${ sd . name } (${ extractNameFromCanonical ( sd . url as CanonicalUrl ) } )` ) ;
47+ const baseName = Path . join ( pkgPath , name ) ;
48+
49+ // HACK: for some reason ID may change between CI and local install
50+ sd = structuredClone ( sd ) ;
51+ sd . id = undefined ;
52+
53+ return {
54+ filename : baseName ,
55+ content : JSON . stringify ( sd , null , pretty ? 2 : undefined ) ,
56+ } ;
57+ } ;
58+
1859export class IntrospectionWriter extends FileSystemWriter < IntrospectionWriterOptions > {
1960 async generate ( tsIndex : TypeSchemaIndex ) : Promise < void > {
2061 if ( this . opts . typeSchemas ) {
@@ -24,9 +65,12 @@ export class IntrospectionWriter extends FileSystemWriter<IntrospectionWriterOpt
2465 this . logger ( ) ?. debug ( `IntrospectionWriter: Generating ${ typeSchemas . length } schemas to ${ outputPath } ` ) ;
2566
2667 if ( Path . extname ( outputPath ) === ".ndjson" ) {
27- this . writeToSingleFile ( typeSchemas , outputPath ) ;
68+ this . writeNdjson ( typeSchemas , outputPath , typeSchemaToJson ) ;
2869 } else {
29- this . writeToSeparateFiles ( typeSchemas , outputPath ) ;
70+ this . writeJsonFiles (
71+ typeSchemas . map ( ( ts ) => typeSchemaToJson ( ts , true ) ) ,
72+ outputPath ,
73+ ) ;
3074 }
3175
3276 this . logger ( ) ?. info ( `Introspection generation completed: ${ typeSchemas . length } schemas written` ) ;
@@ -36,67 +80,74 @@ export class IntrospectionWriter extends FileSystemWriter<IntrospectionWriterOpt
3680 await this . writeTypeTree ( tsIndex ) ;
3781 this . logger ( ) ?. info ( `IntrospectionWriter: Type tree exported to ${ this . opts . typeTree } ` ) ;
3882 }
39- }
40-
41- private async writeToSingleFile ( typeSchemas : TypeSchema [ ] , outputFile : string ) : Promise < void > {
42- this . logger ( ) ?. info ( `Writing introspection data to single file: ${ outputFile } ` ) ;
4383
44- const dir = Path . dirname ( outputFile ) ;
45- const file = Path . basename ( outputFile ) ;
84+ if ( this . opts . fhirSchemas && tsIndex . register ) {
85+ const outputPath = this . opts . fhirSchemas ;
86+ const fhirSchemas = tsIndex . register . allFs ( ) ;
4687
47- this . cd ( dir , ( ) => {
48- this . cat ( file , ( ) => {
49- for ( const ts of typeSchemas ) {
50- const json = JSON . stringify ( ts ) ;
51- this . write ( `${ json } \n` ) ;
52- }
53- } ) ;
54- } ) ;
88+ this . logger ( ) ?. debug ( `IntrospectionWriter: Generating ${ fhirSchemas . length } FHIR schemas to ${ outputPath } ` ) ;
5589
56- this . logger ( ) ?. info ( `Single file output: ${ typeSchemas . length } introspection entries written to ${ outputFile } ` ) ;
57- }
90+ if ( Path . extname ( outputPath ) === ".ndjson" ) {
91+ this . writeNdjson ( fhirSchemas , outputPath , fhirSchemaToJson ) ;
92+ } else {
93+ this . writeJsonFiles (
94+ fhirSchemas . map ( ( fs ) => fhirSchemaToJson ( fs , true ) ) ,
95+ outputPath ,
96+ ) ;
97+ }
5898
59- private async writeToSeparateFiles ( typeSchemas : TypeSchema [ ] , outputDir : string ) : Promise < void > {
60- this . logger ( ) ?. info ( `Writing introspection data to separate files in ${ outputDir } ` ) ;
99+ this . logger ( ) ?. info ( `FHIR schema generation completed: ${ fhirSchemas . length } schemas written` ) ;
100+ }
61101
62- // Group introspection data by package and name
63- const files : Record < string , TypeSchema [ ] > = { } ;
102+ if ( this . opts . structureDefinitions && tsIndex . register ) {
103+ const outputPath = this . opts . structureDefinitions ;
104+ const structureDefinitions = tsIndex . register . allSd ( ) ;
64105
65- for ( const ts of typeSchemas ) {
66- const pkgPath = normalizeFileName ( ts . identifier . package ) ;
67- const name = normalizeFileName ( `${ ts . identifier . name } (${ extractNameFromCanonical ( ts . identifier . url ) } )` ) ;
68- const baseName = Path . join ( pkgPath , name ) ;
106+ this . logger ( ) ?. debug (
107+ `IntrospectionWriter: Generating ${ structureDefinitions . length } StructureDefinitions to ${ outputPath } ` ,
108+ ) ;
69109
70- if ( ! files [ baseName ] ) {
71- files [ baseName ] = [ ] ;
110+ if ( Path . extname ( outputPath ) === ".ndjson" ) {
111+ this . writeNdjson ( structureDefinitions , outputPath , structureDefinitionToJson ) ;
112+ } else {
113+ this . writeJsonFiles (
114+ structureDefinitions . map ( ( sd ) => structureDefinitionToJson ( sd , true ) ) ,
115+ outputPath ,
116+ ) ;
72117 }
73118
74- if ( ! files [ baseName ] . some ( ( e ) => JSON . stringify ( e ) === JSON . stringify ( ts ) ) ) {
75- files [ baseName ] . push ( ts ) ;
76- }
119+ this . logger ( ) ?. info (
120+ `StructureDefinition generation completed: ${ structureDefinitions . length } schemas written` ,
121+ ) ;
77122 }
123+ }
124+
125+ private async writeNdjson < T > (
126+ items : T [ ] ,
127+ outputFile : string ,
128+ toJson : ( item : T , pretty : boolean ) => { filename : string ; content : string } ,
129+ ) : Promise < void > {
130+ this . cd ( Path . dirname ( outputFile ) , ( ) => {
131+ this . cat ( Path . basename ( outputFile ) , ( ) => {
132+ for ( const item of items ) {
133+ const { content } = toJson ( item , false ) ;
134+ this . write ( `${ content } \n` ) ;
135+ }
136+ } ) ;
137+ } ) ;
138+ }
78139
140+ private async writeJsonFiles ( items : { filename : string ; content : string } [ ] , outputDir : string ) : Promise < void > {
79141 this . cd ( outputDir , ( ) => {
80- for ( const [ baseName , schemas ] of Object . entries ( files ) ) {
81- schemas . forEach ( ( schema , index ) => {
82- const fileName =
83- index === 0 ? `${ baseName } .introspection.json` : `${ baseName } -${ index } .introspection.json` ;
84- const dir = Path . dirname ( fileName ) ;
85- const file = Path . basename ( fileName ) ;
86-
87- this . cd ( dir , ( ) => {
88- this . cat ( file , ( ) => {
89- const json = JSON . stringify ( schema , null , 2 ) ;
90- this . write ( json ) ;
91- } ) ;
142+ for ( const { filename, content } of items ) {
143+ const fileName = `${ filename } .json` ;
144+ this . cd ( Path . dirname ( fileName ) , ( ) => {
145+ this . cat ( Path . basename ( fileName ) , ( ) => {
146+ this . write ( content ) ;
92147 } ) ;
93148 } ) ;
94149 }
95150 } ) ;
96-
97- this . logger ( ) ?. info (
98- `Separate files output: ${ typeSchemas . length } introspection entries written to ${ outputDir } in ${ Object . keys ( files ) . length } groups` ,
99- ) ;
100151 }
101152
102153 private async writeTypeTree ( tsIndex : TypeSchemaIndex ) : Promise < void > {
0 commit comments