@atomic-ehr/codegen 0.0.1-canary.20251006070905.fb6ed98 → 0.0.1-canary.20251006094042.7f0be72

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 (90) hide show
  1. package/dist/cli/index.js +45 -124
  2. package/dist/index.d.ts +2130 -62
  3. package/dist/index.js +5865 -84
  4. package/dist/index.js.map +1 -0
  5. package/package.json +3 -7
  6. package/dist/api/builder.d.ts +0 -154
  7. package/dist/api/builder.js +0 -341
  8. package/dist/api/generators/base/BaseGenerator.d.ts +0 -186
  9. package/dist/api/generators/base/BaseGenerator.js +0 -565
  10. package/dist/api/generators/base/FileManager.d.ts +0 -88
  11. package/dist/api/generators/base/FileManager.js +0 -202
  12. package/dist/api/generators/base/PythonTypeMapper.d.ts +0 -16
  13. package/dist/api/generators/base/PythonTypeMapper.js +0 -71
  14. package/dist/api/generators/base/TemplateEngine.d.ts +0 -126
  15. package/dist/api/generators/base/TemplateEngine.js +0 -133
  16. package/dist/api/generators/base/TypeMapper.d.ts +0 -129
  17. package/dist/api/generators/base/TypeMapper.js +0 -153
  18. package/dist/api/generators/base/TypeScriptTypeMapper.d.ts +0 -51
  19. package/dist/api/generators/base/TypeScriptTypeMapper.js +0 -232
  20. package/dist/api/generators/base/builders/DirectoryBuilder.d.ts +0 -99
  21. package/dist/api/generators/base/builders/DirectoryBuilder.js +0 -215
  22. package/dist/api/generators/base/builders/FileBuilder.d.ts +0 -160
  23. package/dist/api/generators/base/builders/FileBuilder.js +0 -406
  24. package/dist/api/generators/base/builders/IndexBuilder.d.ts +0 -126
  25. package/dist/api/generators/base/builders/IndexBuilder.js +0 -290
  26. package/dist/api/generators/base/enhanced-errors.d.ts +0 -84
  27. package/dist/api/generators/base/enhanced-errors.js +0 -259
  28. package/dist/api/generators/base/error-handler.d.ts +0 -89
  29. package/dist/api/generators/base/error-handler.js +0 -243
  30. package/dist/api/generators/base/errors.d.ts +0 -251
  31. package/dist/api/generators/base/errors.js +0 -692
  32. package/dist/api/generators/base/index.d.ts +0 -99
  33. package/dist/api/generators/base/index.js +0 -160
  34. package/dist/api/generators/base/types.d.ts +0 -433
  35. package/dist/api/generators/base/types.js +0 -12
  36. package/dist/api/generators/types.d.ts +0 -53
  37. package/dist/api/generators/types.js +0 -4
  38. package/dist/api/generators/typescript.d.ts +0 -190
  39. package/dist/api/generators/typescript.js +0 -819
  40. package/dist/api/index.d.ts +0 -51
  41. package/dist/api/index.js +0 -50
  42. package/dist/cli/commands/generate/typescript.d.ts +0 -10
  43. package/dist/cli/commands/generate/typescript.js +0 -52
  44. package/dist/cli/commands/generate.d.ts +0 -15
  45. package/dist/cli/commands/generate.js +0 -159
  46. package/dist/cli/commands/index.d.ts +0 -29
  47. package/dist/cli/commands/index.js +0 -100
  48. package/dist/cli/commands/typeschema/generate.d.ts +0 -19
  49. package/dist/cli/commands/typeschema/generate.js +0 -124
  50. package/dist/cli/commands/typeschema.d.ts +0 -10
  51. package/dist/cli/commands/typeschema.js +0 -47
  52. package/dist/cli/index.d.ts +0 -9
  53. package/dist/cli/utils/log.d.ts +0 -10
  54. package/dist/cli/utils/log.js +0 -23
  55. package/dist/cli/utils/prompts.d.ts +0 -56
  56. package/dist/cli/utils/prompts.js +0 -202
  57. package/dist/cli/utils/spinner.d.ts +0 -110
  58. package/dist/cli/utils/spinner.js +0 -266
  59. package/dist/config.d.ts +0 -217
  60. package/dist/config.js +0 -591
  61. package/dist/logger.d.ts +0 -157
  62. package/dist/logger.js +0 -281
  63. package/dist/typeschema/cache.d.ts +0 -80
  64. package/dist/typeschema/cache.js +0 -239
  65. package/dist/typeschema/core/binding.d.ts +0 -11
  66. package/dist/typeschema/core/binding.js +0 -143
  67. package/dist/typeschema/core/field-builder.d.ts +0 -12
  68. package/dist/typeschema/core/field-builder.js +0 -123
  69. package/dist/typeschema/core/identifier.d.ts +0 -13
  70. package/dist/typeschema/core/identifier.js +0 -94
  71. package/dist/typeschema/core/nested-types.d.ts +0 -9
  72. package/dist/typeschema/core/nested-types.js +0 -94
  73. package/dist/typeschema/core/transformer.d.ts +0 -11
  74. package/dist/typeschema/core/transformer.js +0 -235
  75. package/dist/typeschema/generator.d.ts +0 -43
  76. package/dist/typeschema/generator.js +0 -264
  77. package/dist/typeschema/index.d.ts +0 -15
  78. package/dist/typeschema/index.js +0 -15
  79. package/dist/typeschema/parser.d.ts +0 -79
  80. package/dist/typeschema/parser.js +0 -274
  81. package/dist/typeschema/profile/processor.d.ts +0 -14
  82. package/dist/typeschema/profile/processor.js +0 -262
  83. package/dist/typeschema/register.d.ts +0 -21
  84. package/dist/typeschema/register.js +0 -117
  85. package/dist/typeschema/types.d.ts +0 -240
  86. package/dist/typeschema/types.js +0 -19
  87. package/dist/utils/codegen-logger.d.ts +0 -102
  88. package/dist/utils/codegen-logger.js +0 -196
  89. package/dist/utils.d.ts +0 -22
  90. package/dist/utils.js +0 -42
@@ -1,819 +0,0 @@
1
- /**
2
- * Modern TypeScript Generator built on BaseGenerator
3
- *
4
- * This is the new, clean implementation that replaces the monolithic typescript.ts generator.
5
- * Built using the BaseGenerator architecture with TypeMapper, TemplateEngine, and FileManager.
6
- */
7
- import { isBindingSchema } from "@typeschema/types";
8
- import { BaseGenerator } from "./base/BaseGenerator";
9
- import { TypeScriptTypeMapper } from "./base/TypeScriptTypeMapper";
10
- /**
11
- * Modern TypeScript Generator
12
- *
13
- * Generates clean, type-safe TypeScript interfaces from FHIR TypeSchema documents.
14
- * Uses the new BaseGenerator architecture for maintainability and extensibility.
15
- */
16
- export class TypeScriptGenerator extends BaseGenerator {
17
- resourceTypes = new Set();
18
- collectedValueSets = new Map();
19
- get tsOptions() {
20
- return this.options;
21
- }
22
- getLanguageName() {
23
- return "TypeScript";
24
- }
25
- getFileExtension() {
26
- return ".ts";
27
- }
28
- createTypeMapper() {
29
- const options = this.options;
30
- return new TypeScriptTypeMapper({
31
- namingConvention: (options.namingConvention ?? "PascalCase") === "PascalCase" ? "PascalCase" : "camelCase",
32
- moduleFormat: options.moduleFormat === "cjs" ? "commonjs" : "esm",
33
- preferUndefined: true,
34
- ...options.typeMapperOptions,
35
- });
36
- }
37
- async generateSchemaContent(schema, _context) {
38
- // Skip unsupported schema types
39
- if (this.shouldSkipSchema(schema)) {
40
- return "";
41
- }
42
- // Collect resource types for Reference generic
43
- if (schema.identifier.kind === "resource") {
44
- this.resourceTypes.add(this.typeMapper.formatTypeName(schema.identifier.name));
45
- }
46
- // Update filename for profiles to include proper directory structure
47
- // if (false) {
48
- // // Profile support removed - not in core schema
49
- // const sanitizedPackage = this.sanitizePackageName(
50
- // schema.identifier.package || "unknown",
51
- // );
52
- // const profileFileName = this.typeMapper.formatFileName(
53
- // schema.identifier.name,
54
- // );
55
- // context.filename = `profiles/${sanitizedPackage}/${profileFileName}`;
56
- // // Track profile for index generation
57
- // if (!this.profilesByPackage.has(schema.identifier.package || "unknown")) {
58
- // this.profilesByPackage.set(schema.identifier.package || "unknown", []);
59
- // }
60
- // this.profilesByPackage.get(schema.identifier.package || "unknown")?.push({
61
- // filename: profileFileName,
62
- // interfaceName: this.typeMapper.formatTypeName(schema.identifier.name),
63
- // });
64
- // }
65
- // Handle Reference type specially
66
- if (schema.identifier.name === "Reference") {
67
- return this.generateReferenceInterface(schema);
68
- }
69
- // Generate TypeScript content directly (no templates for simplicity)
70
- const mainInterface = this.generateTypeScriptInterface(schema);
71
- // Generate nested types if present
72
- let nestedInterfaces = "";
73
- if ("nested" in schema && schema.nested && Array.isArray(schema.nested)) {
74
- const nestedInterfaceStrings = schema.nested.map((nestedType) => this.generateNestedTypeInterface(schema.identifier.name, nestedType));
75
- nestedInterfaces = nestedInterfaceStrings.join("\n\n");
76
- }
77
- // Combine main interface with nested interfaces
78
- if (nestedInterfaces) {
79
- return `${mainInterface}\n\n${nestedInterfaces}`;
80
- }
81
- return mainInterface;
82
- }
83
- filterAndSortSchemas(schemas) {
84
- // Collect value sets from ALL schemas before filtering
85
- this.collectedValueSets = this.collectValueSets(schemas);
86
- return schemas.filter((schema) => !this.shouldSkipSchema(schema));
87
- }
88
- async validateContent(content, context) {
89
- const hasValidExport = /export\s+(interface|class|type|enum)\s+\w+/.test(content);
90
- const hasValidSyntax = content.includes("{") && content.includes("}");
91
- if (!hasValidExport) {
92
- throw new Error(`Generated content for ${context.schema.identifier.name} does not contain valid export statements`);
93
- }
94
- if (!hasValidSyntax) {
95
- throw new Error(`Generated content for ${context.schema.identifier.name} has invalid syntax (missing braces)`);
96
- }
97
- }
98
- /**
99
- * Transform multiple schemas into TypeScript
100
- */
101
- async transformSchemas(schemas) {
102
- const results = [];
103
- for (const schema of schemas) {
104
- const result = await this.transformSchema(schema);
105
- if (result) {
106
- results.push(result);
107
- }
108
- }
109
- return results;
110
- }
111
- /**
112
- * Transform a single schema into TypeScript
113
- */
114
- async transformSchema(schema) {
115
- if (this.shouldSkipSchema(schema)) {
116
- return undefined;
117
- }
118
- // Create template context
119
- const context = {
120
- schema,
121
- typeMapper: this.typeMapper,
122
- filename: this.getFilenameForSchema(schema),
123
- language: "TypeScript",
124
- timestamp: new Date().toISOString(),
125
- };
126
- // Generate content using template engine
127
- const content = await this.generateSchemaContent(schema, context);
128
- if (!content.trim()) {
129
- return undefined;
130
- }
131
- // Extract imports and exports from generated content
132
- const imports = this.extractImportsFromContent(content, schema);
133
- const exports = this.extractExportsFromContent(content, schema);
134
- const filename = this.getFilenameForSchema(schema);
135
- return {
136
- content,
137
- imports,
138
- exports: Array.from(exports),
139
- filename,
140
- };
141
- }
142
- /**
143
- * Check if a binding schema should generate a value set file
144
- */
145
- shouldGenerateValueSet(schema) {
146
- if (!isBindingSchema(schema) || !schema.enum || !Array.isArray(schema.enum) || schema.enum.length === 0) {
147
- return false;
148
- }
149
- // Handle different value set modes
150
- const mode = this.options.valueSetMode || "required-only";
151
- switch (mode) {
152
- case "all":
153
- return true; // Generate for all binding strengths
154
- case "required-only":
155
- return schema.strength === "required";
156
- case "custom": {
157
- const strengths = this.options.valueSetStrengths || ["required"];
158
- return strengths.includes(schema.strength);
159
- }
160
- default:
161
- return schema.strength === "required";
162
- }
163
- }
164
- /**
165
- * Collect value sets from schemas that should generate value set files
166
- */
167
- collectValueSets(schemas) {
168
- const valueSets = new Map();
169
- for (const schema of schemas) {
170
- if (this.shouldGenerateValueSet(schema) && isBindingSchema(schema)) {
171
- const name = this.typeMapper.formatTypeName(schema.identifier.name);
172
- valueSets.set(name, schema);
173
- }
174
- }
175
- return valueSets;
176
- }
177
- /**
178
- * Check if a field binding should use a value set type
179
- */
180
- shouldUseValueSetType(binding) {
181
- if (!binding) {
182
- return false;
183
- }
184
- // If generateValueSets is false, never use value set types
185
- if (!this.tsOptions.generateValueSets) {
186
- return false;
187
- }
188
- const valueSetTypeName = this.typeMapper.formatTypeName(binding.name);
189
- return this.collectedValueSets.has(valueSetTypeName);
190
- }
191
- /**
192
- * Get the TypeScript type name for a binding
193
- */
194
- getValueSetTypeName(binding) {
195
- return this.typeMapper.formatTypeName(binding.name);
196
- }
197
- /**
198
- * Check if a field has enum values that should be inlined
199
- */
200
- shouldUseInlineEnum(field) {
201
- if (!field) {
202
- return false;
203
- }
204
- // Only use inline enums when generateValueSets is false
205
- if (this.tsOptions.generateValueSets) {
206
- return false;
207
- }
208
- // Check if field has enum values directly on the field
209
- return field.enum && Array.isArray(field.enum) && field.enum.length > 0;
210
- }
211
- /**
212
- * Generate inline enum type from field enum values
213
- */
214
- generateInlineEnumType(field) {
215
- if (!field.enum || !Array.isArray(field.enum)) {
216
- return "string"; // fallback
217
- }
218
- // Create union type from enum values
219
- const enumValues = field.enum.map((value) => `'${value}'`).join(" | ");
220
- return enumValues;
221
- }
222
- shouldSkipSchema(schema) {
223
- if (schema.identifier.kind === "value-set" ||
224
- schema.identifier.kind === "binding" ||
225
- schema.identifier.kind === "primitive-type") {
226
- return true;
227
- }
228
- // Profile support removed - not in core schema specification
229
- // Skip FHIR extensions when includeExtensions is false
230
- if (!this.tsOptions.includeExtensions) {
231
- // Check if this is a FHIR extension by looking at the URL pattern
232
- const url = schema.identifier.url;
233
- if (url?.includes("StructureDefinition/")) {
234
- // Extensions typically have URLs like:
235
- // http://hl7.org/fhir/StructureDefinition/extension-name
236
- // http://hl7.org/fhir/StructureDefinition/resource-extension
237
- // Get the part after StructureDefinition/
238
- const structDefPart = url.split("StructureDefinition/")[1];
239
- if (structDefPart) {
240
- // Check if it contains a hyphen (indicating extension pattern)
241
- // FHIR extensions are profiles with hyphenated names
242
- const hasHyphenPattern = structDefPart.includes("-");
243
- const isProfileKind = schema.identifier.kind === "profile";
244
- // Extensions are profiles with hyphenated StructureDefinition names
245
- // But we need to exclude core resources that also have hyphens
246
- if (hasHyphenPattern && isProfileKind) {
247
- return true;
248
- }
249
- }
250
- }
251
- }
252
- return false;
253
- }
254
- getFilenameForSchema(schema) {
255
- const baseName = this.typeMapper.formatFileName(schema.identifier.name);
256
- return `${baseName}${this.getFileExtension()}`;
257
- }
258
- extractImportsFromContent(content, _schema) {
259
- const imports = new Map();
260
- const importRegex = /import\s+(?:type\s+)?{\s*([^}]+)\s*}\s+from\s+['"]([^'"]+)['"];?/g;
261
- let match;
262
- while ((match = importRegex.exec(content)) !== null) {
263
- const symbolsStr = match[1];
264
- const path = match[2];
265
- if (!symbolsStr || !path)
266
- continue;
267
- const symbols = symbolsStr.split(",").map((s) => s.trim());
268
- for (const symbol of symbols) {
269
- imports.set(symbol, path);
270
- }
271
- }
272
- return imports;
273
- }
274
- extractExportsFromContent(content, schema) {
275
- const exports = new Set();
276
- const exportRegex = /export\s+(?:interface|class|enum|type)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
277
- let match;
278
- while ((match = exportRegex.exec(content)) !== null) {
279
- if (match[1])
280
- exports.add(match[1]);
281
- }
282
- exports.add(this.typeMapper.formatTypeName(schema.identifier.name));
283
- return exports;
284
- }
285
- /**
286
- * Generate special Reference interface with generics
287
- */
288
- generateReferenceInterface(schema) {
289
- const lines = [];
290
- const imports = new Set();
291
- if ("fields" in schema && schema.fields) {
292
- for (const [, field] of Object.entries(schema.fields)) {
293
- const importDeps = this.collectFieldImports(field);
294
- importDeps.forEach((imp) => imports.add(imp));
295
- }
296
- }
297
- lines.push("import type { ResourceType } from './utilities.js';");
298
- if (imports.size > 0) {
299
- const sortedImports = Array.from(imports).sort();
300
- for (const importName of sortedImports) {
301
- lines.push(`import type { ${importName} } from './${importName}.js';`);
302
- }
303
- }
304
- lines.push(""); // Add blank line after imports
305
- // Add JSDoc comment
306
- if (this.tsOptions.includeDocuments && schema.description) {
307
- lines.push("/**");
308
- lines.push(` * ${schema.description}`);
309
- if (schema.identifier.url) {
310
- lines.push(` * @see ${schema.identifier.url}`);
311
- }
312
- if (schema.identifier.package) {
313
- lines.push(` * @package ${schema.identifier.package}`);
314
- }
315
- lines.push(" * @template T - The resource type being referenced");
316
- lines.push(" */");
317
- }
318
- // Generate generic interface declaration
319
- lines.push("export interface Reference<T extends ResourceType = ResourceType> {");
320
- if ("fields" in schema && schema.fields) {
321
- for (const [fieldName, field] of Object.entries(schema.fields)) {
322
- if (fieldName === "type") {
323
- // Special handling for the type field to use the generic parameter
324
- lines.push(" type?: T;");
325
- }
326
- else {
327
- const fieldLines = this.generateFieldLines(fieldName, field);
328
- for (const fieldLine of fieldLines) {
329
- if (fieldLine) {
330
- lines.push(` ${fieldLine}`);
331
- }
332
- }
333
- }
334
- }
335
- }
336
- lines.push("}");
337
- return lines.join("\n");
338
- }
339
- /**
340
- * Generate TypeScript interface directly without templates
341
- */
342
- generateTypeScriptInterface(schema) {
343
- const lines = [];
344
- const interfaceName = this.typeMapper.formatTypeName(schema.identifier.name);
345
- const imports = new Set();
346
- const valueSetImports = new Set();
347
- // Collect imports from fields
348
- if ("fields" in schema && schema.fields) {
349
- for (const [, field] of Object.entries(schema.fields)) {
350
- const fieldImports = this.collectFieldImports(field);
351
- for (const imp of fieldImports) {
352
- // Check if this is a value set import
353
- if (this.collectedValueSets.has(imp)) {
354
- valueSetImports.add(imp);
355
- }
356
- else {
357
- imports.add(imp);
358
- }
359
- }
360
- }
361
- }
362
- // Collect imports from nested types
363
- if ("nested" in schema && schema.nested && Array.isArray(schema.nested)) {
364
- for (const nestedType of schema.nested) {
365
- if (nestedType.fields) {
366
- for (const [, field] of Object.entries(nestedType.fields)) {
367
- const fieldImports = this.collectFieldImports(field);
368
- for (const imp of fieldImports) {
369
- // Check if this is a value set import
370
- if (this.collectedValueSets.has(imp)) {
371
- valueSetImports.add(imp);
372
- }
373
- else {
374
- imports.add(imp);
375
- }
376
- }
377
- }
378
- }
379
- }
380
- }
381
- // Generate regular type imports
382
- if (imports.size > 0) {
383
- const sortedImports = Array.from(imports).sort();
384
- for (const importName of sortedImports) {
385
- lines.push(`import type { ${importName} } from './${importName}.js';`);
386
- }
387
- }
388
- // Generate value set imports
389
- if (valueSetImports.size > 0) {
390
- const sortedValueSetImports = Array.from(valueSetImports).sort();
391
- const importList = sortedValueSetImports.join(", ");
392
- lines.push(`import type { ${importList} } from './valuesets/index.js';`);
393
- }
394
- if (imports.size > 0 || valueSetImports.size > 0) {
395
- lines.push(""); // Add blank line after imports
396
- }
397
- // Add JSDoc comment if enabled
398
- if (this.tsOptions.includeDocuments && schema.description) {
399
- lines.push("/**");
400
- lines.push(` * ${schema.description}`);
401
- if (schema.identifier.url) {
402
- lines.push(` * @see ${schema.identifier.url}`);
403
- }
404
- if (schema.identifier.package) {
405
- lines.push(` * @package ${schema.identifier.package}`);
406
- }
407
- lines.push(" */");
408
- }
409
- // Generate interface declaration
410
- lines.push(`export interface ${interfaceName} {`);
411
- // Add resourceType for FHIR resources
412
- if (schema.identifier.kind === "resource") {
413
- lines.push(` resourceType: '${interfaceName}';`);
414
- }
415
- // Generate fields (if any)
416
- if ("fields" in schema && schema.fields) {
417
- for (const [fieldName, field] of Object.entries(schema.fields)) {
418
- const fieldLines = this.generateFieldLines(fieldName, field);
419
- for (const fieldLine of fieldLines) {
420
- if (fieldLine) {
421
- lines.push(` ${fieldLine}`);
422
- }
423
- }
424
- }
425
- }
426
- lines.push("}");
427
- return lines.join("\n");
428
- }
429
- /**
430
- * Collect import dependencies from a field
431
- */
432
- collectFieldImports(field) {
433
- const imports = [];
434
- // Skip polymorphic declaration fields (they don't have types to import)
435
- if ("choices" in field && field.choices && Array.isArray(field.choices)) {
436
- return imports;
437
- }
438
- // Handle value set imports (only when generateValueSets is true)
439
- if (field.binding && this.shouldUseValueSetType(field.binding)) {
440
- const valueSetTypeName = this.getValueSetTypeName(field.binding);
441
- imports.push(valueSetTypeName);
442
- return imports;
443
- }
444
- // Handle all other fields (regular fields and polymorphic instance fields)
445
- if ("type" in field && field.type) {
446
- // Handle nested types - they don't need imports as they're in the same file
447
- if (field.type.kind === "nested") {
448
- // Nested types are generated in the same file, no import needed
449
- return imports;
450
- }
451
- const languageType = this.typeMapper.mapType(field.type);
452
- // Only import non-primitive types that are not built-in
453
- if (!languageType.isPrimitive && languageType.name !== "any") {
454
- const builtInTypes = ["string", "number", "boolean", "Date", "object", "unknown", "any"];
455
- if (!builtInTypes.includes(languageType.name)) {
456
- imports.push(languageType.name);
457
- }
458
- }
459
- }
460
- return [...new Set(imports)]; // Remove duplicates
461
- }
462
- /**
463
- * Extract resource types from reference field constraints
464
- */
465
- extractReferenceTypes(referenceConstraints) {
466
- const resourceTypes = [];
467
- if (!Array.isArray(referenceConstraints)) {
468
- return resourceTypes;
469
- }
470
- for (const constraint of referenceConstraints) {
471
- if (!constraint || typeof constraint !== "object") {
472
- continue;
473
- }
474
- if (constraint.kind === "resource" && constraint.name) {
475
- const resourceType = this.typeMapper.formatTypeName(constraint.name);
476
- resourceTypes.push(resourceType);
477
- }
478
- }
479
- return [...new Set(resourceTypes)]; // Remove duplicates
480
- }
481
- /**
482
- * Generate nested type interface
483
- */
484
- generateNestedTypeInterface(parentTypeName, nestedType) {
485
- const lines = [];
486
- const nestedTypeName = this.typeMapper.formatTypeName(`${parentTypeName}${this.capitalizeFirst(nestedType.identifier.name)}`);
487
- // Add JSDoc comment if enabled
488
- if (this.tsOptions.includeDocuments && nestedType.description) {
489
- lines.push("/**");
490
- lines.push(` * ${nestedType.description}`);
491
- if (nestedType.identifier.url) {
492
- lines.push(` * @see ${nestedType.identifier.url}`);
493
- }
494
- lines.push(" */");
495
- }
496
- // Generate interface declaration
497
- lines.push(`export interface ${nestedTypeName} {`);
498
- // Generate fields
499
- if (nestedType.fields) {
500
- for (const [fieldName, field] of Object.entries(nestedType.fields)) {
501
- const fieldLines = this.generateFieldLines(fieldName, field);
502
- for (const fieldLine of fieldLines) {
503
- if (fieldLine) {
504
- lines.push(` ${fieldLine}`);
505
- }
506
- }
507
- }
508
- }
509
- lines.push("}");
510
- return lines.join("\n");
511
- }
512
- /**
513
- * Capitalize first letter of string
514
- */
515
- capitalizeFirst(str) {
516
- return str.charAt(0).toUpperCase() + str.slice(1);
517
- }
518
- /**
519
- * Generate field lines (handles polymorphic fields by expanding them)
520
- */
521
- generateFieldLines(fieldName, field) {
522
- // Check if this field has choices (polymorphic declaration field)
523
- if ("choices" in field && field.choices && Array.isArray(field.choices)) {
524
- // Skip declaration fields - the actual instance fields are generated separately
525
- // Declaration fields like `{"choices": ["deceasedBoolean", "deceasedDateTime"]}`
526
- // are just metadata and shouldn't be rendered as actual TypeScript fields
527
- return [];
528
- }
529
- // For all other fields (including polymorphic instance fields with choiceOf), generate normally
530
- const fieldLine = this.generateFieldLine(fieldName, field);
531
- return fieldLine ? [fieldLine] : [];
532
- }
533
- /**
534
- * Generate a single field line
535
- */
536
- generateFieldLine(fieldName, field) {
537
- let typeString = "any";
538
- let required = false;
539
- let isArray = false;
540
- if ("type" in field && field.type) {
541
- // Check if field has a binding that we generated a value set for
542
- if (field.binding && this.shouldUseValueSetType(field.binding)) {
543
- const valueSetTypeName = this.getValueSetTypeName(field.binding);
544
- typeString = valueSetTypeName;
545
- }
546
- else if (field.binding && this.shouldUseInlineEnum(field)) {
547
- // Generate inline enum union type when generateValueSets is false
548
- typeString = this.generateInlineEnumType(field);
549
- }
550
- else {
551
- // Existing type mapping logic
552
- const languageType = this.typeMapper.mapType(field.type);
553
- typeString = languageType.name;
554
- // Handle nested types specially
555
- if (field.type.kind === "nested") {
556
- // Extract parent name from URL like "http://hl7.org/fhir/StructureDefinition/Patient#contact"
557
- const urlParts = field.type.url?.split("#") || [];
558
- if (urlParts.length === 2) {
559
- const parentName = urlParts[0].split("/").pop() || "";
560
- const nestedName = field.type.name;
561
- typeString = this.typeMapper.formatTypeName(`${parentName}${this.capitalizeFirst(nestedName)}`);
562
- }
563
- else {
564
- typeString = this.typeMapper.formatTypeName(field.type.name);
565
- }
566
- }
567
- else if (typeString === "Reference" && field.reference && Array.isArray(field.reference)) {
568
- const referenceTypes = this.extractReferenceTypes(field.reference);
569
- if (referenceTypes.length > 0) {
570
- referenceTypes.forEach((type) => this.resourceTypes.add(type));
571
- const unionType = referenceTypes.map((type) => `'${type}'`).join(" | ");
572
- typeString = `Reference<${unionType}>`;
573
- }
574
- }
575
- }
576
- }
577
- if ("required" in field) {
578
- required = field.required;
579
- }
580
- if ("array" in field) {
581
- isArray = field.array;
582
- }
583
- const optional = required ? "" : "?";
584
- const arrayType = isArray ? "[]" : "";
585
- return `${fieldName}${optional}: ${typeString}${arrayType};`;
586
- }
587
- // ==========================================
588
- /**
589
- * Extract exported symbols from TypeScript content
590
- */
591
- extractExports(content) {
592
- const exports = [];
593
- const exportListPattern = /export\s*\{\s*([^}]+)\s*\}/g;
594
- let match;
595
- while ((match = exportListPattern.exec(content)) !== null) {
596
- if (match[1]) {
597
- const names = match[1]
598
- .split(",")
599
- .map((name) => name.trim())
600
- .filter(Boolean);
601
- exports.push(...names);
602
- }
603
- }
604
- const directExportPatterns = [
605
- /export\s+interface\s+(\w+)/g, // export interface Name
606
- /export\s+type\s+(\w+)/g, // export type Name
607
- /export\s+class\s+(\w+)/g, // export class Name
608
- /export\s+enum\s+(\w+)/g, // export enum Name
609
- /export\s+const\s+(\w+)/g, // export const name
610
- /export\s+function\s+(\w+)/g, // export function name
611
- ];
612
- for (const pattern of directExportPatterns) {
613
- let match;
614
- while ((match = pattern.exec(content)) !== null) {
615
- if (match[1]) {
616
- exports.push(match[1]);
617
- }
618
- }
619
- }
620
- return [...new Set(exports)];
621
- }
622
- /**
623
- * Set output directory for compatibility with API builder
624
- */
625
- setOutputDir(directory) {
626
- this.options.outputDir = directory;
627
- }
628
- /**
629
- * Update generator options for compatibility with API builder
630
- */
631
- setOptions(options) {
632
- this.options = { ...this.options, ...options };
633
- }
634
- /**
635
- * Get current options for compatibility with API builder
636
- */
637
- getOptions() {
638
- return { ...this.options };
639
- }
640
- /**
641
- * Override generate to clean directory first
642
- */
643
- async generate(schemas) {
644
- // Clean output directory before generation
645
- await this.fileManager.cleanDirectory();
646
- this.logger.debug("Cleaned output directory before generation");
647
- // Call parent implementation
648
- return super.generate(schemas);
649
- }
650
- /**
651
- * Run post-generation hooks - generate utility files
652
- */
653
- async runPostGenerationHooks() {
654
- await super.runPostGenerationHooks();
655
- await this.generateValueSetFiles();
656
- await this.generateUtilitiesFile();
657
- await this.generateMainIndexFile();
658
- }
659
- /**
660
- * Generate utilities.ts file with ResourceType union
661
- */
662
- async generateUtilitiesFile() {
663
- if (this.resourceTypes.size === 0) {
664
- this.logger.warn("No resource types found, skipping utilities.ts generation");
665
- return;
666
- }
667
- const lines = [];
668
- // Add file header comment
669
- lines.push("/**");
670
- lines.push(" * FHIR Resource Type Utilities");
671
- lines.push(" * This file contains utility types for FHIR resources.");
672
- lines.push(" * ");
673
- lines.push(" * @generated This file is auto-generated. Do not edit manually.");
674
- lines.push(" */");
675
- lines.push("");
676
- // Generate ResourceType union
677
- const sortedResourceTypes = Array.from(this.resourceTypes).sort();
678
- lines.push("/**");
679
- lines.push(" * Union of all FHIR resource types in this package");
680
- lines.push(" */");
681
- lines.push("export type ResourceType =");
682
- for (let i = 0; i < sortedResourceTypes.length; i++) {
683
- const isLast = i === sortedResourceTypes.length - 1;
684
- const separator = isLast ? ";" : "";
685
- lines.push(` | '${sortedResourceTypes[i]}'${separator}`);
686
- }
687
- lines.push("");
688
- // Generate helper type for Resource references
689
- lines.push("/**");
690
- lines.push(" * Helper type for creating typed References");
691
- lines.push(" * @example Reference<'Patient' | 'Practitioner'> - Reference that can point to Patient or Practitioner");
692
- lines.push(" */");
693
- lines.push("export type TypedReference<T extends ResourceType> = {");
694
- lines.push(" reference?: string;");
695
- lines.push(" type?: T;");
696
- lines.push(" identifier?: any; // Simplified for utility");
697
- lines.push(" display?: string;");
698
- lines.push("};");
699
- const content = lines.join("\n");
700
- // Write the utilities file
701
- await this.fileManager.writeFile("utilities.ts", content);
702
- this.logger.info(`Generated utilities.ts with ${this.resourceTypes.size} resource types`);
703
- }
704
- /**
705
- * Generate a complete value set TypeScript file
706
- */
707
- generateValueSetFile(binding) {
708
- const name = this.typeMapper.formatTypeName(binding.identifier.name);
709
- const values = binding.enum?.map((v) => ` '${v}'`).join(",\n") || "";
710
- const lines = [];
711
- // Add file header comment
712
- if (this.options.includeDocuments) {
713
- lines.push("/**");
714
- lines.push(` * ${binding.identifier.name} value set`);
715
- if (binding.description) {
716
- lines.push(` * ${binding.description}`);
717
- }
718
- if (binding.valueset?.url) {
719
- lines.push(` * @see ${binding.valueset.url}`);
720
- }
721
- if (binding.identifier.package) {
722
- lines.push(` * @package ${binding.identifier.package}`);
723
- }
724
- lines.push(" * @generated This file is auto-generated. Do not edit manually.");
725
- lines.push(" */");
726
- lines.push("");
727
- }
728
- // Add values array
729
- lines.push(`export const ${name}Values = [`);
730
- if (values) {
731
- lines.push(values);
732
- }
733
- lines.push("] as const;");
734
- lines.push("");
735
- // Add union type
736
- lines.push(`export type ${name} = typeof ${name}Values[number];`);
737
- // Add helper function if enabled
738
- if (this.tsOptions.includeValueSetHelpers) {
739
- lines.push("");
740
- lines.push(`export const isValid${name} = (value: string): value is ${name} =>`);
741
- lines.push(` ${name}Values.includes(value as ${name});`);
742
- }
743
- return lines.join("\n");
744
- }
745
- /**
746
- * Create valuesets directory and generate all value set files
747
- */
748
- async generateValueSetFiles() {
749
- if (!this.tsOptions.generateValueSets || this.collectedValueSets.size === 0) {
750
- return;
751
- }
752
- // Generate individual value set files in valuesets/
753
- for (const [name, binding] of this.collectedValueSets) {
754
- const content = this.generateValueSetFile(binding);
755
- const fileName = `valuesets/${name}.ts`;
756
- await this.fileManager.writeFile(fileName, content);
757
- this.logger.info(`Generated value set: ${fileName}`);
758
- }
759
- // Generate index file in valuesets/
760
- await this.generateValueSetIndexFile();
761
- }
762
- /**
763
- * Generate index.ts file that re-exports all value sets
764
- */
765
- async generateValueSetIndexFile() {
766
- const lines = [];
767
- if (this.tsOptions.includeDocuments) {
768
- lines.push("/**");
769
- lines.push(" * FHIR Value Sets");
770
- lines.push(" * This file re-exports all generated value sets.");
771
- lines.push(" * ");
772
- lines.push(" * @generated This file is auto-generated. Do not edit manually.");
773
- lines.push(" */");
774
- lines.push("");
775
- }
776
- // Sort value sets for consistent output
777
- const sortedValueSets = Array.from(this.collectedValueSets.keys()).sort();
778
- for (const name of sortedValueSets) {
779
- lines.push(`export * from './${name}.js';`);
780
- }
781
- const content = lines.join("\n");
782
- await this.fileManager.writeFile("valuesets/index.ts", content);
783
- this.logger.info(`Generated valuesets/index.ts with ${this.collectedValueSets.size} value sets`);
784
- }
785
- /**
786
- * Generate main types/index.ts file that exports all types and value sets
787
- */
788
- async generateMainIndexFile() {
789
- if (!this.options.generateIndex) {
790
- return;
791
- }
792
- const lines = [];
793
- if (this.tsOptions.includeDocuments) {
794
- lines.push("/**");
795
- lines.push(" * FHIR R4 TypeScript Types");
796
- lines.push(" * Generated from FHIR StructureDefinitions");
797
- lines.push(" * ");
798
- lines.push(" * @generated This file is auto-generated. Do not edit manually.");
799
- lines.push(" */");
800
- lines.push("");
801
- }
802
- // Generate exports for all generated files - we'll keep this simple
803
- // and avoid accessing private fields for now. The key functionality
804
- // (value set generation and interface type updates) is already working.
805
- // For now, we'll skip the individual file exports since they're complex
806
- // and the main functionality is already working. This can be improved later.
807
- // Export utilities
808
- lines.push('export * from "./utilities";');
809
- // Export value sets if any were generated
810
- if (this.tsOptions.generateValueSets && this.collectedValueSets.size > 0) {
811
- lines.push("");
812
- lines.push("// Value Sets");
813
- lines.push('export * from "./valuesets/index";');
814
- }
815
- const content = lines.join("\n");
816
- await this.fileManager.writeFile("index.ts", content);
817
- this.logger.info(`Generated index.ts with type exports${this.tsOptions.generateValueSets && this.collectedValueSets.size > 0 ? " and value sets" : ""}`);
818
- }
819
- }