babelfhir-ts 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import { logger } from "../../../logger.js";
5
5
  import { generateSmartAuth, generateSmartClient } from './smartAuthGenerator.js';
6
6
  import { generateReadme, installBaseClientTypes } from './clientReadmeGenerator.js';
7
7
  import { versionSlug } from '../../fhir/versionContext.js';
8
+ import { buildSearchParamInterfaces, getSearchParamsTypeName } from './searchParamHelpers.js';
8
9
  const log = logger.withTag('client');
9
10
  /**
10
11
  * Generate FHIR client code for all resource types
@@ -26,14 +27,14 @@ export function generateClient(options) {
26
27
  fs.mkdirSync(clientDir, { recursive: true });
27
28
  }
28
29
  // Generate each file
29
- generateTypes(clientDir, resourceTypes);
30
- generateResourceReader(clientDir, resourceTypes);
30
+ generateTypes(clientDir, resourceTypes, options.searchParams);
31
+ generateResourceReader(clientDir, resourceTypes, options.searchParams);
31
32
  generateResourceWriter(clientDir, resourceTypes);
32
33
  generateBundleParser(clientDir, resourceTypes);
33
- generateFhirClient(clientDir, resourceTypes);
34
+ generateFhirClient(clientDir, resourceTypes, options.searchParams);
34
35
  generateSmartAuth(clientDir);
35
36
  generateSmartClient(clientDir, resourceTypes);
36
- generateIndex(clientDir, resourceTypes);
37
+ generateIndex(clientDir, resourceTypes, options.searchParams);
37
38
  generateReadme(clientDir);
38
39
  // Install base client type declarations so tsc can resolve @babelfhir-ts/client-<version>
39
40
  installBaseClientTypes(options.outputDir);
@@ -42,10 +43,12 @@ export function generateClient(options) {
42
43
  /**
43
44
  * Generate types.ts
44
45
  */
45
- function generateTypes(clientDir, resourceTypes) {
46
+ function generateTypes(clientDir, resourceTypes, searchParams) {
46
47
  const unionType = resourceTypes
47
48
  .map((rt, idx) => ` ${idx === 0 ? "" : "| "}GeneratedTypes.${rt.profileName}`)
48
49
  .join("\n");
50
+ // Generate per-base-resource-type search param interfaces
51
+ const searchParamInterfaces = buildSearchParamInterfaces(resourceTypes, searchParams);
49
52
  const content = `import type * as GeneratedTypes from "../index.js";
50
53
 
51
54
  /**
@@ -54,9 +57,34 @@ function generateTypes(clientDir, resourceTypes) {
54
57
  export type WithId<T> = { id: string } & T;
55
58
 
56
59
  /**
57
- * Search parameters for FHIR resources
60
+ * Search parameter value type — all values are serialized to query strings.
58
61
  */
59
- export type SearchParams = Record<string, boolean | number | string | string[] | undefined>;
62
+ export type SearchParamValue = boolean | number | string | string[] | undefined;
63
+
64
+ /**
65
+ * Search parameters for FHIR resources (generic untyped version).
66
+ */
67
+ export type SearchParams = Record<string, SearchParamValue>;
68
+
69
+ /**
70
+ * Common search parameters supported by all FHIR resources.
71
+ * Includes an index signature to allow search parameter modifiers (e.g. name:exact).
72
+ */
73
+ export interface CommonSearchParams {
74
+ _id?: SearchParamValue;
75
+ _lastUpdated?: SearchParamValue;
76
+ _tag?: SearchParamValue;
77
+ _profile?: SearchParamValue;
78
+ _security?: SearchParamValue;
79
+ _count?: SearchParamValue;
80
+ _sort?: SearchParamValue;
81
+ _include?: SearchParamValue;
82
+ _revinclude?: SearchParamValue;
83
+ _summary?: SearchParamValue;
84
+ _elements?: SearchParamValue;
85
+ _total?: SearchParamValue;
86
+ [key: string]: SearchParamValue;
87
+ }
60
88
 
61
89
  /**
62
90
  * Bundle of FHIR resources
@@ -80,24 +108,34 @@ export interface Bundle<T> {
80
108
  */
81
109
  export type FhirResource =
82
110
  ${unionType};
83
- `;
111
+
112
+ ${searchParamInterfaces}`;
84
113
  fs.writeFileSync(path.join(clientDir, "types.ts"), content);
85
114
  }
86
115
  /**
87
116
  * Generate resource-reader.ts
88
117
  */
89
- function generateResourceReader(clientDir, resourceTypes) {
118
+ function generateResourceReader(clientDir, resourceTypes, searchParams) {
119
+ // Build reader type aliases with typed search params
90
120
  const readerTypes = resourceTypes
91
- .map((rt) => `export type ${rt.profileName}Reader = FhirResourceSearcher<GeneratedTypes.${rt.profileName}>;`)
121
+ .map((rt) => {
122
+ const spType = getSearchParamsTypeName(rt.baseResourceType, searchParams);
123
+ return `export type ${rt.profileName}Reader = FhirResourceSearcher<GeneratedTypes.${rt.profileName}, ${spType}>;`;
124
+ })
92
125
  .join("\n");
126
+ // Collect unique SearchParams type imports from types.ts
127
+ const spTypeImports = new Set(['SearchParams']);
128
+ for (const rt of resourceTypes) {
129
+ spTypeImports.add(getSearchParamsTypeName(rt.baseResourceType, searchParams));
130
+ }
93
131
  const content = `import type * as GeneratedTypes from "../index.js";
94
132
 
95
- import type { Bundle, SearchParams, WithId } from "./types.js";
133
+ import type { Bundle, ${[...spTypeImports].sort().join(', ')}, WithId } from "./types.js";
96
134
 
97
135
  /**
98
136
  * Generic FHIR resource searcher/reader
99
137
  */
100
- export interface FhirResourceSearcher<T> {
138
+ export interface FhirResourceSearcher<T, S extends SearchParams = SearchParams> {
101
139
  readonly baseUrl: string;
102
140
  readonly resourceType: string;
103
141
 
@@ -109,17 +147,17 @@ export interface FhirResourceSearcher<T> {
109
147
  /**
110
148
  * Search for resources
111
149
  */
112
- search(params?: SearchParams): Promise<Bundle<WithId<T>>>;
150
+ search(params?: S): Promise<Bundle<WithId<T>>>;
113
151
 
114
152
  /**
115
153
  * Search and return first result or undefined
116
154
  */
117
- searchOne(params?: SearchParams): Promise<undefined | WithId<T>>;
155
+ searchOne(params?: S): Promise<undefined | WithId<T>>;
118
156
 
119
157
  /**
120
158
  * Search and return all results (handles pagination)
121
159
  */
122
- searchAll(params?: SearchParams): Promise<WithId<T>[]>;
160
+ searchAll(params?: S): Promise<WithId<T>[]>;
123
161
  }
124
162
 
125
163
  /**
@@ -135,7 +173,7 @@ export type FetchFn = typeof globalThis.fetch;
135
173
  /**
136
174
  * Implementation of FHIR resource reader
137
175
  */
138
- export class FhirResourceReader<T> implements FhirResourceSearcher<T> {
176
+ export class FhirResourceReader<T, S extends SearchParams = SearchParams> implements FhirResourceSearcher<T, S> {
139
177
  private readonly fetchFn: FetchFn;
140
178
 
141
179
  constructor(
@@ -163,7 +201,7 @@ export class FhirResourceReader<T> implements FhirResourceSearcher<T> {
163
201
  return (await response.json()) as WithId<T>;
164
202
  }
165
203
 
166
- async search(params?: SearchParams): Promise<Bundle<WithId<T>>> {
204
+ async search(params?: S): Promise<Bundle<WithId<T>>> {
167
205
  const url = new URL(\`\${this.baseUrl}/\${this.resourceType}\`);
168
206
 
169
207
  if (params) {
@@ -195,12 +233,12 @@ export class FhirResourceReader<T> implements FhirResourceSearcher<T> {
195
233
  return (await response.json()) as Bundle<WithId<T>>;
196
234
  }
197
235
 
198
- async searchOne(params?: SearchParams): Promise<undefined | WithId<T>> {
199
- const bundle = await this.search({ ...params, _count: 1 });
236
+ async searchOne(params?: S): Promise<undefined | WithId<T>> {
237
+ const bundle = await this.search({ ...params, _count: 1 } as S & { _count: number });
200
238
  return bundle.entry?.[0]?.resource;
201
239
  }
202
240
 
203
- async searchAll(params?: SearchParams): Promise<WithId<T>[]> {
241
+ async searchAll(params?: S): Promise<WithId<T>[]> {
204
242
  const results: WithId<T>[] = [];
205
243
  let bundle = await this.search(params);
206
244
 
@@ -364,6 +402,7 @@ export class FhirResourceWriterImpl<T> implements FhirResourceWriter<T> {
364
402
  * Generate bundle-parser.ts
365
403
  */
366
404
  function generateBundleParser(clientDir, resourceTypes) {
405
+ const clientPkg = `@babelfhir-ts/client-${versionSlug()}`;
367
406
  // Group profiles by base resource type
368
407
  const resourceTypeGroups = new Map();
369
408
  for (const rt of resourceTypes) {
@@ -382,6 +421,7 @@ function generateBundleParser(clientDir, resourceTypes) {
382
421
  })
383
422
  .join("\n");
384
423
  const content = `import type * as GeneratedTypes from "../index.js";
424
+ import { BundleParser as BaseBundleParser } from "${clientPkg}";
385
425
  import type { Bundle, FhirResource } from "./types.js";
386
426
 
387
427
  /**
@@ -515,6 +555,15 @@ ${parseByTypeCases}
515
555
  getBundleType(): 'collection' | 'searchset' | 'transaction-response' | 'transaction' | undefined {
516
556
  return this.bundle.type;
517
557
  }
558
+
559
+ /**
560
+ * Resolve a FHIR reference within this Bundle.
561
+ * Supports "ResourceType/id" relative references and full-URL references
562
+ * matched against entry.fullUrl.
563
+ */
564
+ resolveReference<T extends FhirResource>(ref: { reference?: string } | undefined): T | undefined {
565
+ return BaseBundleParser.resolveReference(ref, this.bundle as any) as T | undefined;
566
+ }
518
567
  }
519
568
 
520
569
  /**
@@ -529,12 +578,20 @@ export function parseBundle(bundle: Bundle<FhirResource>): BundleParser {
529
578
  /**
530
579
  * Generate fhir-client.ts
531
580
  */
532
- function generateFhirClient(clientDir, resourceTypes) {
581
+ function generateFhirClient(clientDir, resourceTypes, searchParams) {
582
+ // Collect search param type imports
583
+ const spTypeImports = new Set();
584
+ for (const rt of resourceTypes) {
585
+ const spType = getSearchParamsTypeName(rt.baseResourceType, searchParams);
586
+ if (spType !== 'SearchParams')
587
+ spTypeImports.add(spType);
588
+ }
533
589
  const readerMethods = resourceTypes
534
590
  .map((rt) => {
535
591
  const methodName = rt.profileName.charAt(0).toLowerCase() + rt.profileName.slice(1);
592
+ const spType = getSearchParamsTypeName(rt.baseResourceType, searchParams);
536
593
  return ` ${methodName}() {
537
- return this.forType<GeneratedTypes.${rt.profileName}>("${rt.baseResourceType}");
594
+ return this.forType<GeneratedTypes.${rt.profileName}, ${spType}>("${rt.baseResourceType}");
538
595
  }`;
539
596
  })
540
597
  .join("\n\n");
@@ -548,13 +605,17 @@ function generateFhirClient(clientDir, resourceTypes) {
548
605
  .join("\n\n");
549
606
  const slug = versionSlug();
550
607
  const clientPkg = `@babelfhir-ts/client-${slug}`;
608
+ const spImportLine = spTypeImports.size > 0
609
+ ? `\nimport type { ${[...spTypeImports].sort().join(', ')} } from "./types.js";\n`
610
+ : '';
551
611
  const content = `import {
552
612
  FhirReadClient as BaseFhirReadClient,
553
613
  FhirWriteClient as BaseFhirWriteClient,
554
614
  type FetchFn,
615
+ type SearchParams,
555
616
  } from "${clientPkg}";
556
617
  import type * as GeneratedTypes from "../index.js";
557
-
618
+ ${spImportLine}
558
619
  /**
559
620
  * Profile-specific FHIR Read Client.
560
621
  * Extends the base ${slug.toUpperCase()} read client with typed profile accessors.
@@ -622,13 +683,25 @@ export class FhirClient {
622
683
  /**
623
684
  * Generate index.ts barrel export
624
685
  */
625
- function generateIndex(clientDir, resourceTypes) {
686
+ function generateIndex(clientDir, resourceTypes, searchParams) {
626
687
  const readerTypes = resourceTypes
627
688
  .map((rt) => ` ${rt.profileName}Reader,`)
628
689
  .join("\n");
629
690
  const writerTypes = resourceTypes
630
691
  .map((rt) => ` ${rt.profileName}Writer,`)
631
692
  .join("\n");
693
+ // Collect per-resource SearchParams type exports
694
+ const spTypeExports = new Set();
695
+ if (searchParams && searchParams.size > 0) {
696
+ for (const rt of resourceTypes) {
697
+ const spType = getSearchParamsTypeName(rt.baseResourceType, searchParams);
698
+ if (spType !== 'SearchParams')
699
+ spTypeExports.add(spType);
700
+ }
701
+ }
702
+ const spExportLine = spTypeExports.size > 0
703
+ ? `export type { CommonSearchParams, SearchParamValue, ${[...spTypeExports].sort().join(', ')} } from "./types.js";\n`
704
+ : '';
632
705
  const content = `export { FhirClient, FhirReadClient, FhirWriteClient } from "./fhir-client.js";
633
706
  export { FhirResourceReader } from "./resource-reader.js";
634
707
  export type { FetchFn } from "./resource-reader.js";
@@ -644,7 +717,7 @@ ${writerTypes}
644
717
  export { BundleParser, parseBundle } from "./bundle-parser.js";
645
718
  export type { BundleEntry } from "./bundle-parser.js";
646
719
  export type { Bundle, FhirResource, SearchParams, WithId } from "./types.js";
647
- export { SmartAuth, discoverEndpoints } from "./smart-auth.js";
720
+ ${spExportLine}export { SmartAuth, discoverEndpoints } from "./smart-auth.js";
648
721
  export type { SmartConfig, SmartToken, SmartConfiguration, LaunchMode } from "./smart-auth.js";
649
722
  export { SmartFhirClient } from "./smart-client.js";
650
723
  `;
@@ -226,14 +226,14 @@ export interface Bundle<T = ${fhirNs}.Resource> {
226
226
  entry?: { resource?: T; fullUrl?: string; search?: { mode?: string; score?: number }; request?: { method: string; url: string }; response?: { status: string } }[];
227
227
  }
228
228
 
229
- export declare class FhirResourceReader<T extends ${fhirNs}.Resource> {
229
+ export declare class FhirResourceReader<T extends ${fhirNs}.Resource, S extends SearchParams = SearchParams> {
230
230
  readonly baseUrl: string;
231
231
  readonly resourceType: string;
232
232
  constructor(baseUrl: string, resourceType: string, fetchFn?: FetchFn);
233
233
  read(id: string): Promise<WithId<T>>;
234
- search(params?: SearchParams): Promise<Bundle<WithId<T>>>;
235
- searchOne(params?: SearchParams): Promise<WithId<T> | undefined>;
236
- searchAll(params?: SearchParams): Promise<WithId<T>[]>;
234
+ search(params?: S): Promise<Bundle<WithId<T>>>;
235
+ searchOne(params?: S): Promise<WithId<T> | undefined>;
236
+ searchAll(params?: S): Promise<WithId<T>[]>;
237
237
  }
238
238
 
239
239
  export declare class FhirResourceWriter<T extends ${fhirNs}.Resource> {
@@ -248,7 +248,7 @@ export declare class FhirResourceWriter<T extends ${fhirNs}.Resource> {
248
248
 
249
249
  export declare class FhirReadClient {
250
250
  constructor(baseUrl: string, fetchFn?: FetchFn);
251
- protected forType<T extends ${fhirNs}.Resource>(resourceType: string): FhirResourceReader<T>;
251
+ protected forType<T extends ${fhirNs}.Resource, S extends SearchParams = SearchParams>(resourceType: string): FhirResourceReader<T, S>;
252
252
  }
253
253
 
254
254
  export declare class FhirWriteClient {
@@ -262,6 +262,13 @@ export declare class FhirClient {
262
262
  read(): FhirReadClient;
263
263
  write(): FhirWriteClient;
264
264
  }
265
+
266
+ export declare class BundleParser {
267
+ static getResourcesByType<T extends ${fhirNs}.Resource>(bundle: Bundle<${fhirNs}.Resource>, resourceType: string): WithId<T>[];
268
+ static getFirstResourceByType<T extends ${fhirNs}.Resource>(bundle: Bundle<${fhirNs}.Resource>, resourceType: string): WithId<T> | undefined;
269
+ static getAllResources(bundle: Bundle<${fhirNs}.Resource>): ${fhirNs}.Resource[];
270
+ static resolveReference<T extends ${fhirNs}.Resource>(ref: { reference?: string } | undefined, bundle: Bundle<${fhirNs}.Resource>): T | undefined;
271
+ }
265
272
  `;
266
273
  fs.writeFileSync(path.join(pkgDir, "index.d.ts"), dts);
267
274
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Build per-base-resource-type SearchParams interface declarations for generated types.ts.
3
+ */
4
+ export function buildSearchParamInterfaces(resourceTypes, searchParams) {
5
+ if (!searchParams || searchParams.size === 0)
6
+ return '';
7
+ const baseTypes = [...new Set(resourceTypes.map(rt => rt.baseResourceType))];
8
+ const blocks = [];
9
+ for (const baseType of baseTypes) {
10
+ const params = searchParams.get(baseType);
11
+ if (!params?.length)
12
+ continue;
13
+ // Sort params alphabetically and deduplicate
14
+ const sorted = [...params].sort((a, b) => a.code.localeCompare(b.code));
15
+ const fields = sorted
16
+ .map(p => ` "${p.code}"?: SearchParamValue;`)
17
+ .join("\n");
18
+ blocks.push(`/**
19
+ * Typed search parameters for ${baseType} resources.
20
+ * Generated from SearchParameter definitions in the base FHIR spec and IG.
21
+ */
22
+ export interface ${baseType}SearchParams extends CommonSearchParams {
23
+ ${fields}
24
+ }`);
25
+ }
26
+ return blocks.join("\n\n") + "\n";
27
+ }
28
+ /**
29
+ * Get the SearchParams interface name for a given base resource type,
30
+ * or fall back to SearchParams if none is generated.
31
+ */
32
+ export function getSearchParamsTypeName(baseResourceType, searchParams) {
33
+ if (searchParams?.has(baseResourceType) && searchParams.get(baseResourceType).length > 0) {
34
+ return `${baseResourceType}SearchParams`;
35
+ }
36
+ return 'SearchParams';
37
+ }
@@ -535,6 +535,10 @@ export function processFields(ctx, fields, parentInterfaceName, parentFieldType,
535
535
  const sanitizedBaseResource = baseResource ? sanitizeIdentifier(baseResource) : undefined;
536
536
  const isBaseAbstract = sanitizedBaseResource === 'Base';
537
537
  const extendsClause = (sanitizedBaseResource && !isBaseAbstract && !isPrimitiveType(sanitizedBaseResource)) ? ` extends ${sanitizedBaseResource}` : '';
538
+ // Add resourceType literal for discriminated union support (only for actual FHIR resources, not data types)
539
+ if (ctx.resourceType && rules().isCoreResource(ctx.resourceType) && !deduped.some(l => /^resourceType[?:]/.test(l))) {
540
+ deduped.unshift(`resourceType: '${ctx.resourceType}';`);
541
+ }
538
542
  debug('emit root interface', parentInterfaceName, 'extends', sanitizedBaseResource, 'fields', deduped.length);
539
543
  interfaces.push(`export interface ${parentInterfaceName}${extendsClause} {\n${deduped.map(l => ` ${l}`).join('\n')}\n}`);
540
544
  }
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
3
  import { fileURLToPath } from 'url';
4
- import { extractPackage, readStructureDefinitionsFromDir, readStructureDefinitionsFromDependencies, createPackageFromDir, readValueSetCodesWithDependencies, readValueSetsFromDir, detectFhirVersion, ensureDependenciesDownloaded } from './parser/packageParser.js';
4
+ import { extractPackage, readStructureDefinitionsFromDir, readStructureDefinitionsFromDependencies, createPackageFromDir, readValueSetCodesWithDependencies, readValueSetsFromDir, detectFhirVersion, ensureDependenciesDownloaded, readSearchParametersFromDir } from './parser/packageParser.js';
5
5
  import { fetchStructureDefinitions, fetchStructureDefinition, registerLocalStructureDefinitions, clearLocalStructureDefinitions, collectValueSetBindingUrls } from './parser/sdParser.js';
6
6
  import { ensureDirectoryExists, downloadFile } from './core/utils.js';
7
7
  import { getFhirPackagesCacheDir } from './core/cacheConfig.js';
@@ -9,6 +9,8 @@ import { processStructureDefinition, resetFetchFailureTracking, getFetchFailureC
9
9
  import { logger } from '../logger.js';
10
10
  import { initVersionContext, ctx, versionSlug } from './fhir/versionContext.js';
11
11
  import { DEFAULT_FHIR_VERSION } from './fhir/types.js';
12
+ import { FHIR_VERSIONS } from './fhir/versionRegistry.js';
13
+ import { ensureCorePackage } from './fhir/corePackageResolver.js';
12
14
  import { spawn } from 'child_process';
13
15
  import { buildProfileRegistries, expandValueSetsWithTx, emitValueSetFiles, cleanupStaleValueSetDir, initGenerationContext, enrichValueSetsFromCodeMap, collectReferencedDependencyProfiles, } from './generationHelpers.js';
14
16
  /** Resolve the babelfhir-ts CLI version from its own package.json (used to stamp generated packages). */
@@ -41,6 +43,44 @@ function buildFhirChildTypeMapFromJson() {
41
43
  return result;
42
44
  }
43
45
  export { resetFetchFailureTracking, getFetchFailureCount, getFetchFailureWarning };
46
+ /**
47
+ * Load search parameters from the FHIR core package and the IG's extracted root.
48
+ * Merges both sets, with IG params taking precedence for deduplication by code.
49
+ */
50
+ async function loadSearchParameters(extractedRoot) {
51
+ const slug = versionSlug();
52
+ const coreSpec = FHIR_VERSIONS[slug]?.corePackage;
53
+ const merged = new Map();
54
+ // Load base FHIR search params from the core package
55
+ if (coreSpec) {
56
+ try {
57
+ const coreDir = await ensureCorePackage(coreSpec);
58
+ const coreParams = readSearchParametersFromDir(coreDir);
59
+ for (const [baseType, params] of coreParams) {
60
+ merged.set(baseType, [...params]);
61
+ }
62
+ log.debug(`Loaded ${coreParams.size} base resource type search param groups from core package`);
63
+ }
64
+ catch (err) {
65
+ log.warn(`Could not load core search parameters: ${err.message}`);
66
+ }
67
+ }
68
+ // Load IG-specific search params (may override or extend base)
69
+ const igParams = readSearchParametersFromDir(extractedRoot);
70
+ for (const [baseType, params] of igParams) {
71
+ const existing = merged.get(baseType) || [];
72
+ for (const p of params) {
73
+ if (!existing.some(e => e.code === p.code)) {
74
+ existing.push(p);
75
+ }
76
+ }
77
+ merged.set(baseType, existing);
78
+ }
79
+ if (igParams.size > 0) {
80
+ log.debug(`Loaded ${igParams.size} IG-specific search param groups`);
81
+ }
82
+ return merged;
83
+ }
44
84
  /**
45
85
  * Copy the fhir-<version>.d.ts ambient module declaration into the output directory.
46
86
  * This is required so generated code can import from 'fhir/r4' (or r4b) even when
@@ -461,9 +501,12 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
461
501
  // Generate FHIR client (unless --no-client flag)
462
502
  if (!flags?.noClient) {
463
503
  logger.log('Generating FHIR client...');
504
+ // Load search parameters from base FHIR spec + IG for typed search param generation
505
+ const searchParams = await loadSearchParameters(extractedRoot);
464
506
  const { generateClient } = await import('./emitters/client/clientGenerator.js');
465
507
  generateClient({
466
- outputDir
508
+ outputDir,
509
+ searchParams,
467
510
  });
468
511
  logger.log('FHIR client generated');
469
512
  }
@@ -694,8 +737,9 @@ export async function generateIntoPackageDirect(packageArchivePath, flags) {
694
737
  await generateIndexFile(outputDir);
695
738
  if (!flags?.noClient) {
696
739
  logger.log('Generating FHIR client...');
740
+ const searchParams = await loadSearchParameters(extractedRoot);
697
741
  const { generateClient } = await import('./emitters/client/clientGenerator.js');
698
- generateClient({ outputDir });
742
+ generateClient({ outputDir, searchParams });
699
743
  logger.log('FHIR client generated');
700
744
  }
701
745
  // Generate DICOMweb helpers (--dicomweb flag)
@@ -893,9 +937,11 @@ export async function generateForDirectory(inputDir, outputDir, flags) {
893
937
  // Generate FHIR client (unless --no-client flag)
894
938
  if (!flags?.noClient) {
895
939
  logger.log('Generating FHIR client...');
940
+ const searchParams = await loadSearchParameters(inputDir);
896
941
  const { generateClient } = await import('./emitters/client/clientGenerator.js');
897
942
  generateClient({
898
- outputDir
943
+ outputDir,
944
+ searchParams,
899
945
  });
900
946
  logger.log('FHIR client generated');
901
947
  }
@@ -806,3 +806,39 @@ export function readValueSetsFromDir(extractedRoot) {
806
806
  }
807
807
  return map;
808
808
  }
809
+ /**
810
+ * Read all SearchParameter resources from a package directory.
811
+ * Returns a map of base resource type → array of search parameter definitions.
812
+ */
813
+ export function readSearchParametersFromDir(extractedRoot) {
814
+ const jsonFiles = findJsonFilesByPrefix(extractedRoot, 'SearchParameter-');
815
+ const map = new Map();
816
+ for (const file of jsonFiles) {
817
+ try {
818
+ const content = JSON.parse(fs.readFileSync(file, 'utf-8'));
819
+ if (content.resourceType !== 'SearchParameter')
820
+ continue;
821
+ if (!content.code || !content.type || !content.base?.length)
822
+ continue;
823
+ if (content.status === 'retired')
824
+ continue;
825
+ const param = {
826
+ code: content.code,
827
+ type: content.type,
828
+ base: content.base,
829
+ };
830
+ for (const baseType of param.base) {
831
+ const existing = map.get(baseType) || [];
832
+ // Avoid duplicates by code
833
+ if (!existing.some(p => p.code === param.code)) {
834
+ existing.push(param);
835
+ map.set(baseType, existing);
836
+ }
837
+ }
838
+ }
839
+ catch (err) {
840
+ log.debug(`Skipping invalid JSON while reading SearchParameters ${file}: ${err.message}`);
841
+ }
842
+ }
843
+ return map;
844
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babelfhir-ts",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4/R4B/R5 StructureDefinitions (profiles) directly inside package archives.",
5
5
  "type": "module",
6
6
  "main": "out/src/main.js",