@atomic-ehr/codegen 0.0.1-canary.20250821160126.c552195 → 0.0.1-canary.20250822150706.c3b8669

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.
Files changed (122) hide show
  1. package/dist/api/builder.d.ts +3 -3
  2. package/dist/api/builder.d.ts.map +1 -1
  3. package/dist/api/builder.js +374 -0
  4. package/dist/api/generators/base/BaseGenerator.d.ts +4 -4
  5. package/dist/api/generators/base/BaseGenerator.d.ts.map +1 -1
  6. package/dist/api/generators/base/BaseGenerator.js +572 -0
  7. package/dist/api/generators/base/FileManager.d.ts +2 -2
  8. package/dist/api/generators/base/FileManager.d.ts.map +1 -1
  9. package/dist/api/generators/base/FileManager.js +204 -0
  10. package/dist/api/generators/base/PythonTypeMapper.d.ts +2 -2
  11. package/dist/api/generators/base/PythonTypeMapper.d.ts.map +1 -1
  12. package/dist/api/generators/base/PythonTypeMapper.js +71 -0
  13. package/dist/api/generators/base/TemplateEngine.d.ts +1 -1
  14. package/dist/api/generators/base/TemplateEngine.d.ts.map +1 -1
  15. package/dist/api/generators/base/TemplateEngine.js +133 -0
  16. package/dist/api/generators/base/TypeMapper.js +153 -0
  17. package/dist/api/generators/base/TypeScriptTypeMapper.d.ts +1 -1
  18. package/dist/api/generators/base/TypeScriptTypeMapper.d.ts.map +1 -1
  19. package/dist/api/generators/base/TypeScriptTypeMapper.js +232 -0
  20. package/dist/api/generators/base/builders/DirectoryBuilder.d.ts +4 -4
  21. package/dist/api/generators/base/builders/DirectoryBuilder.d.ts.map +1 -1
  22. package/dist/api/generators/base/builders/DirectoryBuilder.js +215 -0
  23. package/dist/api/generators/base/builders/FileBuilder.d.ts +2 -2
  24. package/dist/api/generators/base/builders/FileBuilder.d.ts.map +1 -1
  25. package/dist/api/generators/base/builders/FileBuilder.js +408 -0
  26. package/dist/api/generators/base/builders/IndexBuilder.d.ts +2 -2
  27. package/dist/api/generators/base/builders/IndexBuilder.d.ts.map +1 -1
  28. package/dist/api/generators/base/builders/IndexBuilder.js +290 -0
  29. package/dist/api/generators/base/enhanced-errors.d.ts +2 -2
  30. package/dist/api/generators/base/enhanced-errors.d.ts.map +1 -1
  31. package/dist/api/generators/base/enhanced-errors.js +259 -0
  32. package/dist/api/generators/base/error-handler.d.ts +1 -1
  33. package/dist/api/generators/base/error-handler.d.ts.map +1 -1
  34. package/dist/api/generators/base/error-handler.js +243 -0
  35. package/dist/api/generators/base/errors.d.ts +2 -2
  36. package/dist/api/generators/base/errors.d.ts.map +1 -1
  37. package/dist/api/generators/base/errors.js +694 -0
  38. package/dist/api/generators/base/index.d.ts +22 -22
  39. package/dist/api/generators/base/index.d.ts.map +1 -1
  40. package/dist/api/generators/base/index.js +161 -0
  41. package/dist/api/generators/base/types.d.ts +2 -2
  42. package/dist/api/generators/base/types.d.ts.map +1 -1
  43. package/dist/api/generators/base/types.js +12 -0
  44. package/dist/api/generators/rest-client.d.ts +2 -2
  45. package/dist/api/generators/rest-client.d.ts.map +1 -1
  46. package/dist/api/generators/rest-client.js +847 -0
  47. package/dist/api/generators/search-parameter-enhancer.d.ts +1 -1
  48. package/dist/api/generators/search-parameter-enhancer.d.ts.map +1 -1
  49. package/dist/api/generators/search-parameter-enhancer.js +801 -0
  50. package/dist/api/generators/types.js +4 -0
  51. package/dist/api/generators/typescript.d.ts +3 -3
  52. package/dist/api/generators/typescript.d.ts.map +1 -1
  53. package/dist/api/generators/typescript.js +537 -0
  54. package/dist/api/generators/validation-generator.js +632 -0
  55. package/dist/api/index.d.ts +10 -10
  56. package/dist/api/index.d.ts.map +1 -1
  57. package/dist/api/index.js +51 -0
  58. package/dist/cli/commands/generate/typescript.d.ts +1 -1
  59. package/dist/cli/commands/generate/typescript.d.ts.map +1 -1
  60. package/dist/cli/commands/generate/typescript.js +52 -0
  61. package/dist/cli/commands/generate.d.ts +5 -12
  62. package/dist/cli/commands/generate.d.ts.map +1 -1
  63. package/dist/cli/commands/generate.js +158 -0
  64. package/dist/cli/commands/index.d.ts +2 -1
  65. package/dist/cli/commands/index.d.ts.map +1 -1
  66. package/dist/cli/commands/index.js +100 -0
  67. package/dist/cli/commands/typeschema/generate.js +130 -0
  68. package/dist/cli/commands/typeschema.js +48 -0
  69. package/dist/cli/index.js +12 -8664
  70. package/dist/cli/utils/log.d.ts +2 -2
  71. package/dist/cli/utils/log.d.ts.map +1 -1
  72. package/dist/cli/utils/log.js +23 -0
  73. package/dist/cli/utils/prompts.js +224 -0
  74. package/dist/cli/utils/spinner.js +270 -0
  75. package/dist/config.d.ts +22 -2
  76. package/dist/config.d.ts.map +1 -1
  77. package/dist/config.js +703 -0
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +84 -38
  81. package/dist/logger.js +290 -0
  82. package/dist/typeschema/cache.d.ts +2 -2
  83. package/dist/typeschema/cache.d.ts.map +1 -1
  84. package/dist/typeschema/cache.js +285 -0
  85. package/dist/typeschema/core/binding.d.ts +1 -1
  86. package/dist/typeschema/core/binding.d.ts.map +1 -1
  87. package/dist/typeschema/core/binding.js +187 -0
  88. package/dist/typeschema/core/field-builder.d.ts +1 -1
  89. package/dist/typeschema/core/field-builder.d.ts.map +1 -1
  90. package/dist/typeschema/core/field-builder.js +259 -0
  91. package/dist/typeschema/core/identifier.js +117 -0
  92. package/dist/typeschema/core/nested-types.d.ts +1 -1
  93. package/dist/typeschema/core/nested-types.d.ts.map +1 -1
  94. package/dist/typeschema/core/nested-types.js +111 -0
  95. package/dist/typeschema/core/transformer.d.ts +2 -2
  96. package/dist/typeschema/core/transformer.d.ts.map +1 -1
  97. package/dist/typeschema/core/transformer.js +345 -0
  98. package/dist/typeschema/generator.d.ts +3 -3
  99. package/dist/typeschema/generator.d.ts.map +1 -1
  100. package/dist/typeschema/generator.js +352 -0
  101. package/dist/typeschema/index.d.ts +14 -14
  102. package/dist/typeschema/index.d.ts.map +1 -1
  103. package/dist/typeschema/index.js +92 -0
  104. package/dist/typeschema/parser.d.ts +2 -2
  105. package/dist/typeschema/parser.d.ts.map +1 -1
  106. package/dist/typeschema/parser.js +310 -0
  107. package/dist/typeschema/profile/processor.d.ts +1 -1
  108. package/dist/typeschema/profile/processor.d.ts.map +1 -1
  109. package/dist/typeschema/profile/processor.js +268 -0
  110. package/dist/typeschema/schema.js +456 -0
  111. package/dist/typeschema/type-schema.types.js +39 -0
  112. package/dist/typeschema/types.js +4 -0
  113. package/dist/typeschema/utils.d.ts +1 -1
  114. package/dist/typeschema/utils.d.ts.map +1 -1
  115. package/dist/typeschema/utils.js +13 -0
  116. package/dist/typeschema/value-set/processor.d.ts +1 -1
  117. package/dist/typeschema/value-set/processor.d.ts.map +1 -1
  118. package/dist/typeschema/value-set/processor.js +168 -0
  119. package/dist/utils/codegen-logger.js +204 -0
  120. package/dist/utils.js +42 -0
  121. package/package.json +15 -4
  122. package/dist/index-e7pfye24.js +0 -8532
@@ -0,0 +1,801 @@
1
+ /**
2
+ * Search Parameter Enhancer
3
+ *
4
+ * Generates enhanced search parameter types and interfaces for FHIR resources.
5
+ * Provides better type safety, validation, and developer experience for search operations.
6
+ */
7
+ import { createLogger } from "../../utils/codegen-logger.js";
8
+ /**
9
+ * Search Parameter Enhancer class
10
+ *
11
+ * Generates enhanced type-safe search parameters and validation helpers
12
+ * for FHIR REST client operations.
13
+ */
14
+ export class SearchParameterEnhancer {
15
+ resourceTypes = new Set();
16
+ resourceSearchParams = new Map();
17
+ autocompleteEnabled;
18
+ valueSetEnumsEnabled;
19
+ logger;
20
+ availableEnumTypes = new Map();
21
+ static BASE_PARAM_NAMES = [
22
+ "_count",
23
+ "_offset",
24
+ "_sort",
25
+ "_summary",
26
+ "_elements",
27
+ "_lastUpdated",
28
+ "_profile",
29
+ "_security",
30
+ "_tag",
31
+ "_id",
32
+ "_text",
33
+ "_content",
34
+ ];
35
+ constructor(options) {
36
+ this.autocompleteEnabled = !!options?.autocomplete;
37
+ this.valueSetEnumsEnabled = !!options?.valueSetEnums;
38
+ this.logger = options?.logger || createLogger({ prefix: "SearchParam" });
39
+ this.logger.debug(`SearchParameterEnhancer initialized: autocomplete=${this.autocompleteEnabled}, valueSetEnums=${this.valueSetEnumsEnabled}`);
40
+ }
41
+ /**
42
+ * Generate per-resource SearchParamName unions (for IDE autocomplete)
43
+ */
44
+ generateSearchParamNameUnions() {
45
+ const baseUnion = SearchParameterEnhancer.BASE_PARAM_NAMES.map((n) => `'${n}'`).join(" | ");
46
+ const parts = [];
47
+ parts.push(`/**\n * Base search parameter names available for all resources\n */`);
48
+ parts.push(`export type BaseSearchParamName = ${baseUnion};`);
49
+ parts.push("");
50
+ const resourceTypesArray = Array.from(this.resourceTypes).sort();
51
+ for (const [resourceType, params] of this.resourceSearchParams.entries()) {
52
+ const specificNames = Array.from(new Set(params.map((p) => p.name))).sort();
53
+ const specificUnion = specificNames.map((n) => `'${n}'`).join(" | ");
54
+ const typeName = `${resourceType}SearchParamName`;
55
+ if (specificUnion.length > 0) {
56
+ parts.push(`/**\n * Search parameter names for ${resourceType}\n */`);
57
+ parts.push(`export type ${typeName} = BaseSearchParamName | ${specificUnion};`);
58
+ }
59
+ else {
60
+ parts.push(`export type ${typeName} = BaseSearchParamName;`);
61
+ }
62
+ parts.push("");
63
+ }
64
+ // Generic mapping type
65
+ const mappingLines = resourceTypesArray
66
+ .map((t) => `\tT extends '${t}' ? ${t}SearchParamName :`)
67
+ .join("\n");
68
+ parts.push(`/**\n * Generic search parameter name union for a given resource type\n */`);
69
+ parts.push(`export type SearchParamName<T extends ResourceTypes> =\n${mappingLines}\n\tBaseSearchParamName;`);
70
+ return parts.join("\n");
71
+ }
72
+ /**
73
+ * Collect resource types and their search parameters from schemas
74
+ */
75
+ collectResourceData(schemas) {
76
+ this.resourceTypes.clear();
77
+ this.resourceSearchParams.clear();
78
+ for (const schema of schemas) {
79
+ if (schema.identifier.kind === "resource" &&
80
+ schema.identifier.name !== "DomainResource" &&
81
+ schema.identifier.name !== "Resource") {
82
+ this.resourceTypes.add(schema.identifier.name);
83
+ this.collectSearchParameters(schema);
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Collect search parameters for a specific resource type
89
+ */
90
+ collectSearchParameters(schema) {
91
+ const resourceType = schema.identifier.name;
92
+ const searchParams = [];
93
+ // Add common search parameters based on FHIR specification
94
+ this.addCommonSearchParameters(resourceType, searchParams);
95
+ this.resourceSearchParams.set(resourceType, searchParams);
96
+ }
97
+ /**
98
+ * Add common search parameters based on resource type
99
+ */
100
+ addCommonSearchParameters(resourceType, searchParams) {
101
+ // Add resource-specific search parameters based on FHIR R4 specification
102
+ switch (resourceType) {
103
+ case "Patient":
104
+ searchParams.push({
105
+ name: "active",
106
+ type: "token",
107
+ description: "Whether the patient record is active",
108
+ }, {
109
+ name: "address",
110
+ type: "string",
111
+ description: "A server defined search that may match any of the string fields in the Address",
112
+ }, {
113
+ name: "address-city",
114
+ type: "string",
115
+ description: "A city specified in an address",
116
+ }, {
117
+ name: "address-country",
118
+ type: "string",
119
+ description: "A country specified in an address",
120
+ }, {
121
+ name: "address-postalcode",
122
+ type: "string",
123
+ description: "A postalCode specified in an address",
124
+ }, {
125
+ name: "address-state",
126
+ type: "string",
127
+ description: "A state specified in an address",
128
+ }, {
129
+ name: "address-use",
130
+ type: "token",
131
+ description: "A use code specified in an address",
132
+ }, {
133
+ name: "birthdate",
134
+ type: "date",
135
+ description: "The patient's date of birth",
136
+ }, {
137
+ name: "death-date",
138
+ type: "date",
139
+ description: "The date of death has been provided and satisfies this search value",
140
+ }, {
141
+ name: "deceased",
142
+ type: "token",
143
+ description: "This patient has been marked as deceased, or as a death date entered",
144
+ }, {
145
+ name: "email",
146
+ type: "token",
147
+ description: "A value in an email contact",
148
+ }, {
149
+ name: "family",
150
+ type: "string",
151
+ description: "A portion of the family name of the patient",
152
+ }, {
153
+ name: "gender",
154
+ type: "token",
155
+ description: "Gender of the patient",
156
+ }, {
157
+ name: "general-practitioner",
158
+ type: "reference",
159
+ target: ["Organization", "Practitioner", "PractitionerRole"],
160
+ description: "Patient's nominated general practitioner",
161
+ }, {
162
+ name: "given",
163
+ type: "string",
164
+ description: "A portion of the given name of the patient",
165
+ }, {
166
+ name: "identifier",
167
+ type: "token",
168
+ description: "A patient identifier",
169
+ }, {
170
+ name: "language",
171
+ type: "token",
172
+ description: "Language code (irrespective of use value)",
173
+ }, {
174
+ name: "link",
175
+ type: "reference",
176
+ target: ["Patient", "RelatedPerson"],
177
+ description: "All patients linked to the given patient",
178
+ }, {
179
+ name: "name",
180
+ type: "string",
181
+ description: "A server defined search that may match any of the string fields in the HumanName",
182
+ }, {
183
+ name: "organization",
184
+ type: "reference",
185
+ target: ["Organization"],
186
+ description: "The organization that is the custodian of the patient record",
187
+ }, {
188
+ name: "phone",
189
+ type: "token",
190
+ description: "A value in a phone contact",
191
+ }, {
192
+ name: "phonetic",
193
+ type: "string",
194
+ description: "A portion of either family or given name using some kind of phonetic matching algorithm",
195
+ }, {
196
+ name: "telecom",
197
+ type: "token",
198
+ description: "The value in any kind of telecom details of the patient",
199
+ });
200
+ break;
201
+ case "Observation":
202
+ searchParams.push({
203
+ name: "category",
204
+ type: "token",
205
+ description: "The classification of the type of observation",
206
+ }, {
207
+ name: "code",
208
+ type: "token",
209
+ description: "The code of the observation type",
210
+ }, {
211
+ name: "component-code",
212
+ type: "token",
213
+ description: "The component code of the observation type",
214
+ }, {
215
+ name: "component-data-absent-reason",
216
+ type: "token",
217
+ description: "The reason why the expected value in the element Observation.component.value[x] is missing",
218
+ }, {
219
+ name: "component-value-concept",
220
+ type: "token",
221
+ description: "The value of the component observation, if the value is a CodeableConcept",
222
+ }, {
223
+ name: "component-value-quantity",
224
+ type: "quantity",
225
+ description: "The value of the component observation, if the value is a Quantity, or a SampledData",
226
+ }, {
227
+ name: "data-absent-reason",
228
+ type: "token",
229
+ description: "The reason why the expected value in the element Observation.value[x] is missing",
230
+ }, {
231
+ name: "date",
232
+ type: "date",
233
+ description: "Obtained date/time. If the obtained element is a period, a date that falls in the period",
234
+ }, {
235
+ name: "derived-from",
236
+ type: "reference",
237
+ target: [
238
+ "DocumentReference",
239
+ "ImagingStudy",
240
+ "Media",
241
+ "QuestionnaireResponse",
242
+ "Observation",
243
+ "MolecularSequence",
244
+ ],
245
+ description: "Related measurements the observation is made from",
246
+ }, {
247
+ name: "device",
248
+ type: "reference",
249
+ target: ["Device", "DeviceMetric"],
250
+ description: "The Device that generated the observation data",
251
+ }, {
252
+ name: "encounter",
253
+ type: "reference",
254
+ target: ["Encounter"],
255
+ description: "Encounter related to the observation",
256
+ }, {
257
+ name: "focus",
258
+ type: "reference",
259
+ target: ["Resource"],
260
+ description: "The focus of an observation when the focus is not the patient of record",
261
+ }, {
262
+ name: "has-member",
263
+ type: "reference",
264
+ target: [
265
+ "Observation",
266
+ "QuestionnaireResponse",
267
+ "MolecularSequence",
268
+ ],
269
+ description: "Related resource that belongs to the Observation group",
270
+ }, {
271
+ name: "identifier",
272
+ type: "token",
273
+ description: "The unique id for a particular observation",
274
+ }, {
275
+ name: "method",
276
+ type: "token",
277
+ description: "The method used for the observation",
278
+ }, {
279
+ name: "part-of",
280
+ type: "reference",
281
+ target: [
282
+ "MedicationAdministration",
283
+ "MedicationDispense",
284
+ "MedicationStatement",
285
+ "Procedure",
286
+ "Immunization",
287
+ "ImagingStudy",
288
+ ],
289
+ description: "Part of referenced event",
290
+ }, {
291
+ name: "patient",
292
+ type: "reference",
293
+ target: ["Patient"],
294
+ description: "The subject that the observation is about (if patient)",
295
+ }, {
296
+ name: "performer",
297
+ type: "reference",
298
+ target: [
299
+ "Practitioner",
300
+ "PractitionerRole",
301
+ "Organization",
302
+ "CareTeam",
303
+ "Patient",
304
+ "RelatedPerson",
305
+ ],
306
+ description: "Who performed the observation",
307
+ }, {
308
+ name: "specimen",
309
+ type: "reference",
310
+ target: ["Specimen"],
311
+ description: "Specimen used for this observation",
312
+ }, {
313
+ name: "status",
314
+ type: "token",
315
+ description: "The status of the observation",
316
+ }, {
317
+ name: "subject",
318
+ type: "reference",
319
+ target: ["Patient", "Group", "Device", "Location"],
320
+ description: "The subject that the observation is about",
321
+ }, {
322
+ name: "value-concept",
323
+ type: "token",
324
+ description: "The value of the observation, if the value is a CodeableConcept",
325
+ }, {
326
+ name: "value-date",
327
+ type: "date",
328
+ description: "The value of the observation, if the value is a date or period of time",
329
+ }, {
330
+ name: "value-quantity",
331
+ type: "quantity",
332
+ description: "The value of the observation, if the value is a Quantity, or a SampledData",
333
+ }, {
334
+ name: "value-string",
335
+ type: "string",
336
+ description: "The value of the observation, if the value is a string, and also searches in CodeableConcept.text",
337
+ });
338
+ break;
339
+ case "Organization":
340
+ searchParams.push({
341
+ name: "active",
342
+ type: "token",
343
+ description: "Is the Organization record active",
344
+ }, {
345
+ name: "address",
346
+ type: "string",
347
+ description: "A server defined search that may match any of the string fields in the Address",
348
+ }, {
349
+ name: "address-city",
350
+ type: "string",
351
+ description: "A city specified in an address",
352
+ }, {
353
+ name: "address-country",
354
+ type: "string",
355
+ description: "A country specified in an address",
356
+ }, {
357
+ name: "address-postalcode",
358
+ type: "string",
359
+ description: "A postal code specified in an address",
360
+ }, {
361
+ name: "address-state",
362
+ type: "string",
363
+ description: "A state specified in an address",
364
+ }, {
365
+ name: "address-use",
366
+ type: "token",
367
+ description: "A use code specified in an address",
368
+ }, {
369
+ name: "endpoint",
370
+ type: "reference",
371
+ target: ["Endpoint"],
372
+ description: "Technical endpoints providing access to services operated for the organization",
373
+ }, {
374
+ name: "identifier",
375
+ type: "token",
376
+ description: "Any identifier for the organization (not the accreditation issuer's identifier)",
377
+ }, {
378
+ name: "name",
379
+ type: "string",
380
+ description: "A portion of the organization's name or alias",
381
+ }, {
382
+ name: "partof",
383
+ type: "reference",
384
+ target: ["Organization"],
385
+ description: "An organization of which this organization forms a part",
386
+ }, {
387
+ name: "phonetic",
388
+ type: "string",
389
+ description: "A portion of the organization's name using some kind of phonetic matching algorithm",
390
+ }, {
391
+ name: "type",
392
+ type: "token",
393
+ description: "A code for the type of organization",
394
+ });
395
+ break;
396
+ case "Practitioner":
397
+ searchParams.push({
398
+ name: "active",
399
+ type: "token",
400
+ description: "Whether the practitioner record is active",
401
+ }, {
402
+ name: "address",
403
+ type: "string",
404
+ description: "A server defined search that may match any of the string fields in the Address",
405
+ }, {
406
+ name: "address-city",
407
+ type: "string",
408
+ description: "A city specified in an address",
409
+ }, {
410
+ name: "address-country",
411
+ type: "string",
412
+ description: "A country specified in an address",
413
+ }, {
414
+ name: "address-postalcode",
415
+ type: "string",
416
+ description: "A postal code specified in an address",
417
+ }, {
418
+ name: "address-state",
419
+ type: "string",
420
+ description: "A state specified in an address",
421
+ }, {
422
+ name: "address-use",
423
+ type: "token",
424
+ description: "A use code specified in an address",
425
+ }, {
426
+ name: "communication",
427
+ type: "token",
428
+ description: "One of the languages that the practitioner can communicate with",
429
+ }, {
430
+ name: "email",
431
+ type: "token",
432
+ description: "A value in an email contact",
433
+ }, {
434
+ name: "family",
435
+ type: "string",
436
+ description: "A portion of the family name",
437
+ }, {
438
+ name: "gender",
439
+ type: "token",
440
+ description: "Gender of the practitioner",
441
+ }, {
442
+ name: "given",
443
+ type: "string",
444
+ description: "A portion of the given name",
445
+ }, {
446
+ name: "identifier",
447
+ type: "token",
448
+ description: "A practitioner's Identifier",
449
+ }, {
450
+ name: "name",
451
+ type: "string",
452
+ description: "A server defined search that may match any of the string fields in the HumanName",
453
+ }, {
454
+ name: "phone",
455
+ type: "token",
456
+ description: "A value in a phone contact",
457
+ }, {
458
+ name: "phonetic",
459
+ type: "string",
460
+ description: "A portion of either family or given name using some kind of phonetic matching algorithm",
461
+ }, {
462
+ name: "telecom",
463
+ type: "token",
464
+ description: "The value in any kind of contact",
465
+ });
466
+ break;
467
+ default:
468
+ // Add common search parameters for all resources
469
+ searchParams.push({
470
+ name: "identifier",
471
+ type: "token",
472
+ description: "Resource identifier",
473
+ });
474
+ break;
475
+ }
476
+ }
477
+ /**
478
+ * Pre-populate enum types by processing all search parameters
479
+ */
480
+ preprocessEnumTypes() {
481
+ // Clear any existing enum types
482
+ this.availableEnumTypes.clear();
483
+ // Process all search parameters to populate enum types
484
+ for (const [resourceType, searchParams,] of this.resourceSearchParams.entries()) {
485
+ for (const param of searchParams) {
486
+ if (param.type === "token" && this.valueSetEnumsEnabled) {
487
+ // Generate enum type name based on FHIR naming convention: ResourceFieldValues
488
+ const enumTypeName = `${resourceType}${this.toPascalCase(param.name)}Values`;
489
+ // Add the enum type to our available types
490
+ // The TypeScript generator will have already created these if they exist
491
+ this.availableEnumTypes.set(`${resourceType}${param.name}`, enumTypeName);
492
+ }
493
+ }
494
+ }
495
+ }
496
+ /**
497
+ * Convert string to PascalCase
498
+ */
499
+ toPascalCase(str) {
500
+ return str
501
+ .split(/[-_\s]/)
502
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
503
+ .join("");
504
+ }
505
+ /**
506
+ * Generate enhanced search parameter types
507
+ */
508
+ generateEnhancedSearchTypes() {
509
+ const resourceTypesArray = Array.from(this.resourceTypes).sort();
510
+ // Pre-populate enum types by processing all search parameters
511
+ this.preprocessEnumTypes();
512
+ const enumImports = this.valueSetEnumsEnabled && this.availableEnumTypes.size > 0
513
+ ? `import type { ${Array.from(new Set(this.availableEnumTypes.values())).sort().join(", ")} } from '../types/utility';\n`
514
+ : "";
515
+ return `/**
516
+ * Enhanced Search Parameter Types
517
+ *
518
+ * Type-safe search parameters with modifiers and validation for FHIR resources.
519
+ * Generated automatically from FHIR schemas.
520
+ */
521
+
522
+ import type { ResourceTypes } from '../types';
523
+ ${enumImports}
524
+
525
+ /**
526
+ * Search parameter modifier types for enhanced type safety
527
+ */
528
+ export interface SearchModifiers {
529
+ /** String search modifiers */
530
+ StringModifier:
531
+ | { exact: string }
532
+ | { contains: string }
533
+ | { missing: boolean };
534
+
535
+ /** Date parameter with prefix support */
536
+ DateParameter:
537
+ | string
538
+ | { gt: string }
539
+ | { lt: string }
540
+ | { ge: string }
541
+ | { le: string }
542
+ | { eq: string }
543
+ | { ne: string }
544
+ | { missing: boolean };
545
+
546
+ /** Token parameter for coded values */
547
+ TokenParameter:
548
+ | string
549
+ | { system: string; code: string }
550
+ | { code: string }
551
+ | { system: string }
552
+ | { missing: boolean };
553
+
554
+ /** Token search options (for better autocomplete when enum values exist) */
555
+ TokenSearchOptions:
556
+ | { system: string; code: string }
557
+ | { code: string }
558
+ | { system: string }
559
+ | { missing: boolean };
560
+
561
+ /** Reference parameter for resource references */
562
+ ReferenceParameter:
563
+ | string
564
+ | { reference: string }
565
+ | { identifier: string }
566
+ | { missing: boolean };
567
+
568
+ /** Number parameter with range support */
569
+ NumberParameter:
570
+ | number
571
+ | { gt: number }
572
+ | { lt: number }
573
+ | { ge: number }
574
+ | { le: number }
575
+ | { eq: number }
576
+ | { ne: number }
577
+ | { missing: boolean };
578
+
579
+ /** Quantity parameter for measurements */
580
+ QuantityParameter:
581
+ | number
582
+ | string
583
+ | { value: number; unit?: string; system?: string; code?: string }
584
+ | { missing: boolean };
585
+ }
586
+
587
+ /**
588
+ * Base search parameters available for all resources
589
+ */
590
+ export interface BaseEnhancedSearchParams {
591
+ /** Number of results to return */
592
+ _count?: number;
593
+ /** Pagination offset */
594
+ _offset?: number;
595
+ /** Sort order */
596
+ _sort?: string | string[];
597
+ /** Summary mode */
598
+ _summary?: 'true' | 'false' | 'text' | 'data' | 'count';
599
+ /** Elements to include */
600
+ _elements?: string | string[];
601
+ /** Filter by last updated */
602
+ _lastUpdated?: SearchModifiers['DateParameter'];
603
+ /** Profile filter */
604
+ _profile?: string | string[];
605
+ /** Security label filter */
606
+ _security?: string | string[];
607
+ /** Tag filter */
608
+ _tag?: SearchModifiers['TokenParameter'] | SearchModifiers['TokenParameter'][];
609
+ /** Filter by ID */
610
+ _id?: string | string[];
611
+ /** Text search */
612
+ _text?: string;
613
+ /** Content search */
614
+ _content?: string;
615
+ }
616
+
617
+ /**
618
+ * Enhanced search parameters union type for all resources
619
+ */
620
+ export type EnhancedSearchParams<T extends ResourceTypes> =
621
+ ${resourceTypesArray.map((type) => ` T extends '${type}' ? ${type}SearchParams :`).join("\n")}
622
+ BaseEnhancedSearchParams;
623
+
624
+ ${this.generateResourceSpecificSearchInterfaces()}
625
+
626
+ ${this.autocompleteEnabled ? this.generateSearchParamNameUnions() : ""}
627
+
628
+ /**
629
+ * Type-safe search parameter validation helpers
630
+ */
631
+ export class SearchParameterValidator {
632
+ /**
633
+ * Validate search parameters for a specific resource type
634
+ */
635
+ static validate<T extends ResourceTypes>(
636
+ resourceType: T,
637
+ params: EnhancedSearchParams<T>
638
+ ): { valid: boolean; errors: string[] } {
639
+ const errors: string[] = [];
640
+
641
+ // Basic validation logic
642
+ if (params._count !== undefined && (params._count < 0 || params._count > 1000)) {
643
+ errors.push('_count must be between 0 and 1000');
644
+ }
645
+
646
+ if (params._offset !== undefined && params._offset < 0) {
647
+ errors.push('_offset must be non-negative');
648
+ }
649
+
650
+ return {
651
+ valid: errors.length === 0,
652
+ errors
653
+ };
654
+ }
655
+
656
+ /**
657
+ * Build URL search parameters from enhanced search params
658
+ */
659
+ static buildSearchParams<T extends ResourceTypes>(
660
+ resourceType: T,
661
+ params: EnhancedSearchParams<T>
662
+ ): URLSearchParams {
663
+ const searchParams = new URLSearchParams();
664
+
665
+ for (const [key, value] of Object.entries(params)) {
666
+ if (value === undefined || value === null) continue;
667
+
668
+ if (Array.isArray(value)) {
669
+ value.forEach(v => searchParams.append(key, String(v)));
670
+ } else if (typeof value === 'object') {
671
+ // Handle complex parameter objects
672
+ if ('exact' in value) {
673
+ searchParams.append(key + ':exact', String(value.exact));
674
+ } else if ('contains' in value) {
675
+ searchParams.append(key + ':contains', String(value.contains));
676
+ } else if ('missing' in value) {
677
+ searchParams.append(key + ':missing', String(value.missing));
678
+ } else if ('gt' in value) {
679
+ searchParams.append(key + ':gt', String(value.gt));
680
+ } else if ('lt' in value) {
681
+ searchParams.append(key + ':lt', String(value.lt));
682
+ } else if ('ge' in value) {
683
+ searchParams.append(key + ':ge', String(value.ge));
684
+ } else if ('le' in value) {
685
+ searchParams.append(key + ':le', String(value.le));
686
+ } else if ('eq' in value) {
687
+ searchParams.append(key + ':eq', String(value.eq));
688
+ } else if ('ne' in value) {
689
+ searchParams.append(key + ':ne', String(value.ne));
690
+ } else if ('system' in value && 'code' in value) {
691
+ searchParams.append(key, \`\${value.system}|\${value.code}\`);
692
+ } else if ('system' in value) {
693
+ searchParams.append(key, \`\${value.system}|\`);
694
+ } else if ('code' in value) {
695
+ searchParams.append(key, \`|\${value.code}\`);
696
+ } else if ('reference' in value) {
697
+ searchParams.append(key, String(value.reference));
698
+ } else if ('identifier' in value) {
699
+ searchParams.append(key + ':identifier', String(value.identifier));
700
+ }
701
+ } else {
702
+ searchParams.append(key, String(value));
703
+ }
704
+ }
705
+
706
+ return searchParams;
707
+ }
708
+ }`;
709
+ }
710
+ /**
711
+ * Generate resource-specific search parameter interfaces
712
+ */
713
+ generateResourceSpecificSearchInterfaces() {
714
+ const interfaces = [];
715
+ for (const [resourceType, searchParams,] of this.resourceSearchParams.entries()) {
716
+ interfaces.push(this.generateResourceSearchInterface(resourceType, searchParams));
717
+ }
718
+ return interfaces.join("\n\n");
719
+ }
720
+ /**
721
+ * Generate search interface for a specific resource type
722
+ */
723
+ generateResourceSearchInterface(resourceType, searchParams) {
724
+ const interfaceFields = [];
725
+ // Add base search parameters
726
+ interfaceFields.push(" // Base search parameters");
727
+ interfaceFields.push(" _count?: number;");
728
+ interfaceFields.push(" _offset?: number;");
729
+ interfaceFields.push(" _sort?: string | string[];");
730
+ interfaceFields.push(" _summary?: 'true' | 'false' | 'text' | 'data' | 'count';");
731
+ interfaceFields.push(" _elements?: string | string[];");
732
+ interfaceFields.push(" _lastUpdated?: SearchModifiers['DateParameter'];");
733
+ interfaceFields.push(" _profile?: string | string[];");
734
+ interfaceFields.push(" _security?: string | string[];");
735
+ interfaceFields.push(" _tag?: SearchModifiers['TokenParameter'] | SearchModifiers['TokenParameter'][];");
736
+ interfaceFields.push(" _id?: string | string[];");
737
+ interfaceFields.push(" _text?: string;");
738
+ interfaceFields.push(" _content?: string;");
739
+ interfaceFields.push("");
740
+ // Add resource-specific parameters
741
+ if (searchParams.length > 0) {
742
+ interfaceFields.push(` // ${resourceType}-specific search parameters`);
743
+ for (const param of searchParams) {
744
+ const typeMapping = this.getTypeScriptTypeForSearchParameter(resourceType, param);
745
+ const comment = param.description ? ` /** ${param.description} */` : "";
746
+ interfaceFields.push(`${comment}`);
747
+ interfaceFields.push(` '${param.name}'?: ${typeMapping};`);
748
+ }
749
+ }
750
+ return `/**
751
+ * Enhanced search parameters for ${resourceType} resources
752
+ */
753
+ export interface ${resourceType}SearchParams extends BaseEnhancedSearchParams {
754
+ ${interfaceFields.join("\n")}
755
+ }`;
756
+ }
757
+ /**
758
+ * Map FHIR search parameter types to TypeScript enhanced types
759
+ */
760
+ getTypeScriptTypeForSearchParameter(resourceType, param) {
761
+ switch (param.type) {
762
+ case "string":
763
+ return "string | SearchModifiers['StringModifier']";
764
+ case "number":
765
+ return "number | SearchModifiers['NumberParameter']";
766
+ case "date":
767
+ return "string | SearchModifiers['DateParameter']";
768
+ case "token":
769
+ if (this.valueSetEnumsEnabled) {
770
+ // Look up enum type name from our preprocessing
771
+ const enumTypeName = this.availableEnumTypes.get(`${resourceType}${param.name}`);
772
+ if (enumTypeName) {
773
+ // Use the separate TokenSearchOptions type to avoid expanding the full union
774
+ // This should help TypeScript prioritize the enum values in autocomplete
775
+ return `${enumTypeName} | SearchModifiers['TokenSearchOptions']`;
776
+ }
777
+ }
778
+ return "string | SearchModifiers['TokenParameter']";
779
+ case "reference":
780
+ if (param.target && param.target.length > 0) {
781
+ // Specific target types are not encoded at type level for ReferenceParameter to keep it simple
782
+ return `string | SearchModifiers['ReferenceParameter']`;
783
+ }
784
+ return "string | SearchModifiers['ReferenceParameter']";
785
+ case "quantity":
786
+ return "number | string | SearchModifiers['QuantityParameter']";
787
+ case "uri":
788
+ return "string";
789
+ case "composite":
790
+ return "string";
791
+ default:
792
+ return "string";
793
+ }
794
+ }
795
+ /**
796
+ * Get collected resource types
797
+ */
798
+ getResourceTypes() {
799
+ return this.resourceTypes;
800
+ }
801
+ }