@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.
- package/dist/cli/index.js +45 -124
- package/dist/index.d.ts +2130 -62
- package/dist/index.js +5865 -84
- package/dist/index.js.map +1 -0
- package/package.json +3 -7
- package/dist/api/builder.d.ts +0 -154
- package/dist/api/builder.js +0 -341
- package/dist/api/generators/base/BaseGenerator.d.ts +0 -186
- package/dist/api/generators/base/BaseGenerator.js +0 -565
- package/dist/api/generators/base/FileManager.d.ts +0 -88
- package/dist/api/generators/base/FileManager.js +0 -202
- package/dist/api/generators/base/PythonTypeMapper.d.ts +0 -16
- package/dist/api/generators/base/PythonTypeMapper.js +0 -71
- package/dist/api/generators/base/TemplateEngine.d.ts +0 -126
- package/dist/api/generators/base/TemplateEngine.js +0 -133
- package/dist/api/generators/base/TypeMapper.d.ts +0 -129
- package/dist/api/generators/base/TypeMapper.js +0 -153
- package/dist/api/generators/base/TypeScriptTypeMapper.d.ts +0 -51
- package/dist/api/generators/base/TypeScriptTypeMapper.js +0 -232
- package/dist/api/generators/base/builders/DirectoryBuilder.d.ts +0 -99
- package/dist/api/generators/base/builders/DirectoryBuilder.js +0 -215
- package/dist/api/generators/base/builders/FileBuilder.d.ts +0 -160
- package/dist/api/generators/base/builders/FileBuilder.js +0 -406
- package/dist/api/generators/base/builders/IndexBuilder.d.ts +0 -126
- package/dist/api/generators/base/builders/IndexBuilder.js +0 -290
- package/dist/api/generators/base/enhanced-errors.d.ts +0 -84
- package/dist/api/generators/base/enhanced-errors.js +0 -259
- package/dist/api/generators/base/error-handler.d.ts +0 -89
- package/dist/api/generators/base/error-handler.js +0 -243
- package/dist/api/generators/base/errors.d.ts +0 -251
- package/dist/api/generators/base/errors.js +0 -692
- package/dist/api/generators/base/index.d.ts +0 -99
- package/dist/api/generators/base/index.js +0 -160
- package/dist/api/generators/base/types.d.ts +0 -433
- package/dist/api/generators/base/types.js +0 -12
- package/dist/api/generators/types.d.ts +0 -53
- package/dist/api/generators/types.js +0 -4
- package/dist/api/generators/typescript.d.ts +0 -190
- package/dist/api/generators/typescript.js +0 -819
- package/dist/api/index.d.ts +0 -51
- package/dist/api/index.js +0 -50
- package/dist/cli/commands/generate/typescript.d.ts +0 -10
- package/dist/cli/commands/generate/typescript.js +0 -52
- package/dist/cli/commands/generate.d.ts +0 -15
- package/dist/cli/commands/generate.js +0 -159
- package/dist/cli/commands/index.d.ts +0 -29
- package/dist/cli/commands/index.js +0 -100
- package/dist/cli/commands/typeschema/generate.d.ts +0 -19
- package/dist/cli/commands/typeschema/generate.js +0 -124
- package/dist/cli/commands/typeschema.d.ts +0 -10
- package/dist/cli/commands/typeschema.js +0 -47
- package/dist/cli/index.d.ts +0 -9
- package/dist/cli/utils/log.d.ts +0 -10
- package/dist/cli/utils/log.js +0 -23
- package/dist/cli/utils/prompts.d.ts +0 -56
- package/dist/cli/utils/prompts.js +0 -202
- package/dist/cli/utils/spinner.d.ts +0 -110
- package/dist/cli/utils/spinner.js +0 -266
- package/dist/config.d.ts +0 -217
- package/dist/config.js +0 -591
- package/dist/logger.d.ts +0 -157
- package/dist/logger.js +0 -281
- package/dist/typeschema/cache.d.ts +0 -80
- package/dist/typeschema/cache.js +0 -239
- package/dist/typeschema/core/binding.d.ts +0 -11
- package/dist/typeschema/core/binding.js +0 -143
- package/dist/typeschema/core/field-builder.d.ts +0 -12
- package/dist/typeschema/core/field-builder.js +0 -123
- package/dist/typeschema/core/identifier.d.ts +0 -13
- package/dist/typeschema/core/identifier.js +0 -94
- package/dist/typeschema/core/nested-types.d.ts +0 -9
- package/dist/typeschema/core/nested-types.js +0 -94
- package/dist/typeschema/core/transformer.d.ts +0 -11
- package/dist/typeschema/core/transformer.js +0 -235
- package/dist/typeschema/generator.d.ts +0 -43
- package/dist/typeschema/generator.js +0 -264
- package/dist/typeschema/index.d.ts +0 -15
- package/dist/typeschema/index.js +0 -15
- package/dist/typeschema/parser.d.ts +0 -79
- package/dist/typeschema/parser.js +0 -274
- package/dist/typeschema/profile/processor.d.ts +0 -14
- package/dist/typeschema/profile/processor.js +0 -262
- package/dist/typeschema/register.d.ts +0 -21
- package/dist/typeschema/register.js +0 -117
- package/dist/typeschema/types.d.ts +0 -240
- package/dist/typeschema/types.js +0 -19
- package/dist/utils/codegen-logger.d.ts +0 -102
- package/dist/utils/codegen-logger.js +0 -196
- package/dist/utils.d.ts +0 -22
- package/dist/utils.js +0 -42
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Abstract base generator class
|
|
3
|
-
*
|
|
4
|
-
* This is the foundation of the generator system. All language-specific generators
|
|
5
|
-
* extend this class to inherit common functionality while implementing their own
|
|
6
|
-
* specific logic for type mapping, content generation, and validation.
|
|
7
|
-
*/
|
|
8
|
-
import { createLogger } from "../../../utils/codegen-logger";
|
|
9
|
-
import { ErrorHandler, GeneratorErrorBoundary } from "./error-handler";
|
|
10
|
-
import { ConfigurationError, SchemaValidationError } from "./errors";
|
|
11
|
-
import { FileManager } from "./FileManager";
|
|
12
|
-
/**
|
|
13
|
-
* Abstract base generator class with comprehensive functionality
|
|
14
|
-
*
|
|
15
|
-
* Provides common functionality for all generators including:
|
|
16
|
-
* - Schema validation and processing
|
|
17
|
-
* - File management with fluent API
|
|
18
|
-
* - Template processing
|
|
19
|
-
* - Error handling and recovery
|
|
20
|
-
* - Progress monitoring
|
|
21
|
-
* - Performance optimization
|
|
22
|
-
*/
|
|
23
|
-
export class BaseGenerator {
|
|
24
|
-
/** Validated and merged options */
|
|
25
|
-
options;
|
|
26
|
-
/** Logger instance for this generator */
|
|
27
|
-
logger;
|
|
28
|
-
/** File manager for all file operations */
|
|
29
|
-
fileManager;
|
|
30
|
-
/** Template engine for content generation (optional) */
|
|
31
|
-
templateEngine;
|
|
32
|
-
/** Language-specific type mapper */
|
|
33
|
-
typeMapper;
|
|
34
|
-
/** Enhanced error handler for comprehensive error reporting */
|
|
35
|
-
errorHandler;
|
|
36
|
-
/** Error boundary for catching and handling all generator errors */
|
|
37
|
-
errorBoundary;
|
|
38
|
-
/** Progress callback if provided */
|
|
39
|
-
progressCallback;
|
|
40
|
-
/** Generated files tracking */
|
|
41
|
-
generatedFiles = [];
|
|
42
|
-
/** Generation start time for performance metrics */
|
|
43
|
-
generationStartTime = 0;
|
|
44
|
-
/** Cache for expensive operations */
|
|
45
|
-
cache = new Map();
|
|
46
|
-
constructor(options) {
|
|
47
|
-
// Validate configuration first
|
|
48
|
-
const validation = this.validateConfiguration(options);
|
|
49
|
-
if (!validation.isValid) {
|
|
50
|
-
throw new ConfigurationError(`Invalid generator configuration: ${validation.errors.join(", ")}`, "configuration", options);
|
|
51
|
-
}
|
|
52
|
-
// Merge with defaults and store
|
|
53
|
-
this.options = this.mergeWithDefaults(options);
|
|
54
|
-
// Initialize logger
|
|
55
|
-
this.logger =
|
|
56
|
-
options.logger ||
|
|
57
|
-
createLogger({
|
|
58
|
-
prefix: this.getLanguageName(),
|
|
59
|
-
verbose: this.options.verbose || false,
|
|
60
|
-
});
|
|
61
|
-
// Initialize core components
|
|
62
|
-
this.fileManager = this.createFileManager();
|
|
63
|
-
this.templateEngine = this.createTemplateEngine();
|
|
64
|
-
this.typeMapper = this.createTypeMapper();
|
|
65
|
-
// Initialize enhanced error handling
|
|
66
|
-
this.errorHandler = new ErrorHandler({
|
|
67
|
-
logger: this.logger,
|
|
68
|
-
verbose: this.options.verbose || false,
|
|
69
|
-
beginnerMode: this.options.beginnerMode || false,
|
|
70
|
-
outputFormat: this.options.errorFormat || "console",
|
|
71
|
-
});
|
|
72
|
-
this.errorBoundary = new GeneratorErrorBoundary(this.errorHandler);
|
|
73
|
-
this.logger.debug(`${this.getLanguageName()} generator initialized`);
|
|
74
|
-
// Log any configuration warnings
|
|
75
|
-
if (validation.warnings.length > 0) {
|
|
76
|
-
validation.warnings.forEach((warning) => {
|
|
77
|
-
this.logger.warn(`Configuration warning: ${warning}`);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
// ==========================================
|
|
82
|
-
// Optional Abstract Methods - Can be overridden
|
|
83
|
-
// ==========================================
|
|
84
|
-
/**
|
|
85
|
-
* Get generator capabilities - can be overridden for introspection
|
|
86
|
-
*/
|
|
87
|
-
getCapabilities() {
|
|
88
|
-
return {
|
|
89
|
-
language: this.getLanguageName(),
|
|
90
|
-
fileExtensions: [this.getFileExtension()],
|
|
91
|
-
supportsTemplates: true,
|
|
92
|
-
supportsCustomTypeMapping: true,
|
|
93
|
-
supportsIncrementalGeneration: false,
|
|
94
|
-
supportsValidation: true,
|
|
95
|
-
supportedSchemaKinds: ["resource", "complex-type", "profile", "logical"],
|
|
96
|
-
version: "1.0.0",
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Create file manager instance - can be overridden for custom file handling
|
|
101
|
-
*/
|
|
102
|
-
createFileManager() {
|
|
103
|
-
return new FileManager({
|
|
104
|
-
outputDir: this.options.outputDir,
|
|
105
|
-
logger: this.logger.child("FileManager"),
|
|
106
|
-
overwrite: this.options.overwrite,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Create template engine instance - can be overridden for custom templates
|
|
111
|
-
* Returns undefined if template engine is not needed
|
|
112
|
-
*/
|
|
113
|
-
createTemplateEngine() {
|
|
114
|
-
// Default implementation returns undefined (no template engine)
|
|
115
|
-
// Subclasses can override to provide template engine if needed
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
// ==========================================
|
|
119
|
-
// Public API - Main entry points
|
|
120
|
-
// ==========================================
|
|
121
|
-
/**
|
|
122
|
-
* Generate code from TypeSchema documents
|
|
123
|
-
* This is the main method that orchestrates the entire generation process
|
|
124
|
-
* @param schemas - Array of TypeSchema documents
|
|
125
|
-
*/
|
|
126
|
-
async generate(schemas) {
|
|
127
|
-
return this.errorBoundary.withErrorBoundary(async () => {
|
|
128
|
-
this.generationStartTime = performance.now();
|
|
129
|
-
this.generatedFiles = [];
|
|
130
|
-
this.logger.info(`Starting ${this.getLanguageName()} generation for ${schemas.length} schemas`);
|
|
131
|
-
// Phase 1: Schema validation
|
|
132
|
-
this.reportProgress("validation", 0, schemas.length, "Validating schemas...");
|
|
133
|
-
await this.validateSchemas(schemas);
|
|
134
|
-
// Phase 2: Schema processing and filtering
|
|
135
|
-
this.reportProgress("generation", 0, schemas.length, "Processing schemas...");
|
|
136
|
-
const processedSchemas = this.filterAndSortSchemas(schemas);
|
|
137
|
-
this.logger.debug(`Filtered to ${processedSchemas.length} schemas for generation`);
|
|
138
|
-
// Phase 3: Content generation
|
|
139
|
-
await this.generateFiles(processedSchemas);
|
|
140
|
-
// Phase 4: Post-generation hooks
|
|
141
|
-
await this.runPostGenerationHooks();
|
|
142
|
-
this.reportProgress("complete", schemas.length, schemas.length, "Generation complete");
|
|
143
|
-
const duration = performance.now() - this.generationStartTime;
|
|
144
|
-
this.logger.info(`Generated ${this.generatedFiles.length} files in ${duration.toFixed(2)}ms ` +
|
|
145
|
-
`(avg ${(duration / this.generatedFiles.length).toFixed(2)}ms per file)`);
|
|
146
|
-
return this.generatedFiles;
|
|
147
|
-
}, { operationName: "generate" });
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Generate and return content without writing files (useful for testing)
|
|
151
|
-
* @param schemas - Array of TypeSchema documents
|
|
152
|
-
*/
|
|
153
|
-
async build(schemas) {
|
|
154
|
-
// Temporarily disable file writing by mocking the writeFile method
|
|
155
|
-
const originalWriteFile = this.fileManager.writeFile;
|
|
156
|
-
const mockWriteResults = new Map();
|
|
157
|
-
this.fileManager.writeFile = async (path, content) => {
|
|
158
|
-
const result = {
|
|
159
|
-
path: `${this.options.outputDir}/${path}`,
|
|
160
|
-
size: Buffer.byteLength(content, "utf-8"),
|
|
161
|
-
writeTime: 0,
|
|
162
|
-
};
|
|
163
|
-
mockWriteResults.set(path, result);
|
|
164
|
-
return result;
|
|
165
|
-
};
|
|
166
|
-
try {
|
|
167
|
-
const result = await this.generate(schemas);
|
|
168
|
-
// Update file paths to reflect what would have been written
|
|
169
|
-
result.forEach((file) => {
|
|
170
|
-
const mockResult = mockWriteResults.get(file.filename);
|
|
171
|
-
if (mockResult) {
|
|
172
|
-
file.path = mockResult.path;
|
|
173
|
-
file.size = mockResult.size;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
178
|
-
finally {
|
|
179
|
-
// Restore original file writing function
|
|
180
|
-
this.fileManager.writeFile = originalWriteFile;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// ==========================================
|
|
184
|
-
// Fluent API - Builder pattern methods
|
|
185
|
-
// ==========================================
|
|
186
|
-
/**
|
|
187
|
-
* Create a file builder for fluent file generation
|
|
188
|
-
* @param filename - Name of the file to create
|
|
189
|
-
*/
|
|
190
|
-
file(filename) {
|
|
191
|
-
if (!this.templateEngine) {
|
|
192
|
-
throw new Error("Template engine is required for fluent file generation. Override createTemplateEngine() in your generator.");
|
|
193
|
-
}
|
|
194
|
-
const { FileBuilder } = require("./builders/FileBuilder");
|
|
195
|
-
return new FileBuilder({
|
|
196
|
-
filename: this.ensureFileExtension(filename),
|
|
197
|
-
fileManager: this.fileManager,
|
|
198
|
-
templateEngine: this.templateEngine,
|
|
199
|
-
typeMapper: this.typeMapper,
|
|
200
|
-
logger: this.logger.child("FileBuilder"),
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Create a directory builder for batch operations
|
|
205
|
-
* @param path - Directory path relative to output directory
|
|
206
|
-
*/
|
|
207
|
-
directory(path) {
|
|
208
|
-
const { DirectoryBuilder } = require("./builders/DirectoryBuilder");
|
|
209
|
-
return new DirectoryBuilder({
|
|
210
|
-
path,
|
|
211
|
-
fileManager: this.fileManager,
|
|
212
|
-
logger: this.logger.child("DirectoryBuilder"),
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Create an index file builder
|
|
217
|
-
* @param directory - Directory to create index for
|
|
218
|
-
*/
|
|
219
|
-
index(directory = ".") {
|
|
220
|
-
if (!this.templateEngine) {
|
|
221
|
-
throw new Error("Template engine is required for index file generation. Override createTemplateEngine() in your generator.");
|
|
222
|
-
}
|
|
223
|
-
const { IndexBuilder } = require("./builders/IndexBuilder");
|
|
224
|
-
return new IndexBuilder({
|
|
225
|
-
directory,
|
|
226
|
-
fileManager: this.fileManager,
|
|
227
|
-
templateEngine: this.templateEngine,
|
|
228
|
-
logger: this.logger.child("IndexBuilder"),
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Set progress callback for monitoring generation
|
|
233
|
-
* @param callback - Progress callback function
|
|
234
|
-
*/
|
|
235
|
-
onProgress(callback) {
|
|
236
|
-
this.progressCallback = callback;
|
|
237
|
-
return this;
|
|
238
|
-
}
|
|
239
|
-
// ==========================================
|
|
240
|
-
// Configuration and Validation
|
|
241
|
-
// ==========================================
|
|
242
|
-
/**
|
|
243
|
-
* Validate generator configuration
|
|
244
|
-
*/
|
|
245
|
-
validateConfiguration(options) {
|
|
246
|
-
const errors = [];
|
|
247
|
-
const warnings = [];
|
|
248
|
-
const suggestions = [];
|
|
249
|
-
// Required options validation
|
|
250
|
-
if (!options.outputDir) {
|
|
251
|
-
errors.push("outputDir is required");
|
|
252
|
-
suggestions.push("Provide a valid output directory path");
|
|
253
|
-
}
|
|
254
|
-
// Type validation
|
|
255
|
-
if (options.outputDir && typeof options.outputDir !== "string") {
|
|
256
|
-
errors.push("outputDir must be a string");
|
|
257
|
-
}
|
|
258
|
-
if (options.overwrite !== undefined && typeof options.overwrite !== "boolean") {
|
|
259
|
-
errors.push("overwrite must be a boolean");
|
|
260
|
-
}
|
|
261
|
-
if (options.validate !== undefined && typeof options.validate !== "boolean") {
|
|
262
|
-
errors.push("validate must be a boolean");
|
|
263
|
-
}
|
|
264
|
-
// Warnings for suboptimal configuration
|
|
265
|
-
if (options.outputDir && !require("node:path").isAbsolute(options.outputDir)) {
|
|
266
|
-
warnings.push("Using relative path for outputDir - consider using absolute path");
|
|
267
|
-
suggestions.push("Use path.resolve() to convert to absolute path");
|
|
268
|
-
}
|
|
269
|
-
if (options.validate === false) {
|
|
270
|
-
warnings.push("Validation is disabled - this may lead to invalid generated code");
|
|
271
|
-
suggestions.push("Consider enabling validation for better code quality");
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
isValid: errors.length === 0,
|
|
275
|
-
errors,
|
|
276
|
-
warnings,
|
|
277
|
-
suggestions,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Merge options with defaults
|
|
282
|
-
*/
|
|
283
|
-
mergeWithDefaults(options) {
|
|
284
|
-
return {
|
|
285
|
-
overwrite: true,
|
|
286
|
-
validate: true,
|
|
287
|
-
verbose: false,
|
|
288
|
-
beginnerMode: false,
|
|
289
|
-
errorFormat: "console",
|
|
290
|
-
...options,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
// ==========================================
|
|
294
|
-
// Schema Processing
|
|
295
|
-
// ==========================================
|
|
296
|
-
/**
|
|
297
|
-
* Validate schemas before processing
|
|
298
|
-
*/
|
|
299
|
-
async validateSchemas(schemas) {
|
|
300
|
-
if (!this.options.validate) {
|
|
301
|
-
this.logger.debug("Schema validation disabled");
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
this.logger.info(`🔍 Starting schema validation for ${schemas.length} schemas`);
|
|
305
|
-
this.logger.debug("Schema validation enabled - performing comprehensive validation");
|
|
306
|
-
const operations = schemas.map((schema) => () => this.errorBoundary.withErrorBoundary(async () => {
|
|
307
|
-
await this.validateSchema(schema);
|
|
308
|
-
this.reportProgress("validation", schemas.indexOf(schema) + 1, schemas.length, `Validated ${schema.identifier?.name || "schema"}`);
|
|
309
|
-
}, { schema, operationName: "validateSchema" }));
|
|
310
|
-
await this.errorBoundary.withBatchErrorBoundary(operations, {
|
|
311
|
-
operationName: "validateSchemas",
|
|
312
|
-
});
|
|
313
|
-
this.logger.debug(`Successfully validated ${schemas.length} schemas`);
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Validate individual schema
|
|
317
|
-
*/
|
|
318
|
-
async validateSchema(schema) {
|
|
319
|
-
const errors = [];
|
|
320
|
-
const schemaName = schema.identifier?.name || "unknown";
|
|
321
|
-
this.logger.debug(`🔍 Validating schema: ${schemaName} (kind: ${schema.identifier?.kind})`);
|
|
322
|
-
// Basic structure validation
|
|
323
|
-
if (!schema.identifier) {
|
|
324
|
-
errors.push("Schema missing identifier");
|
|
325
|
-
this.logger.warn(`❌ Schema missing identifier: ${JSON.stringify(schema, null, 2).substring(0, 200)}...`);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
if (!schema.identifier.name) {
|
|
329
|
-
errors.push("Schema identifier missing name");
|
|
330
|
-
}
|
|
331
|
-
if (!schema.identifier.kind) {
|
|
332
|
-
errors.push("Schema identifier missing kind");
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
const validKinds = [
|
|
336
|
-
"resource",
|
|
337
|
-
"complex-type",
|
|
338
|
-
"profile",
|
|
339
|
-
"primitive-type",
|
|
340
|
-
"logical",
|
|
341
|
-
"value-set",
|
|
342
|
-
"binding",
|
|
343
|
-
"extension",
|
|
344
|
-
];
|
|
345
|
-
if (!validKinds.includes(schema.identifier.kind)) {
|
|
346
|
-
errors.push(`Schema identifier.kind must be one of: ${validKinds.join(", ")}`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
// Field validation
|
|
351
|
-
if ("fields" in schema && schema.fields) {
|
|
352
|
-
for (const [fieldName, field] of Object.entries(schema.fields)) {
|
|
353
|
-
if (!fieldName.trim()) {
|
|
354
|
-
errors.push("Field name cannot be empty");
|
|
355
|
-
}
|
|
356
|
-
if (!field) {
|
|
357
|
-
errors.push(`Field '${fieldName}' is null or undefined`);
|
|
358
|
-
}
|
|
359
|
-
// Add more field-specific validation as needed
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
// Circular reference detection (make it a warning, not an error for FHIR schemas)
|
|
363
|
-
if (await this.detectCircularReferences(schema)) {
|
|
364
|
-
this.logger.warn(`⚠️ Circular reference detected in schema '${schemaName}' - this may be expected for FHIR primitive types`);
|
|
365
|
-
// Don't add to errors - FHIR schemas often have legitimate circular references
|
|
366
|
-
}
|
|
367
|
-
if (errors.length > 0) {
|
|
368
|
-
this.logger.error(`❌ Schema validation failed for '${schemaName}': ${errors.join(", ")}`);
|
|
369
|
-
this.logger.debug(`Schema details: ${JSON.stringify(schema, null, 2)}`);
|
|
370
|
-
throw new SchemaValidationError(`Schema validation failed for '${schema.identifier?.name || "unknown"}'`, schema, errors);
|
|
371
|
-
}
|
|
372
|
-
this.logger.debug(`✅ Schema validation passed for '${schemaName}'`);
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Detect circular references in schema dependencies
|
|
376
|
-
*/
|
|
377
|
-
async detectCircularReferences(schema) {
|
|
378
|
-
// Simple implementation - can be enhanced for complex cases
|
|
379
|
-
const visited = new Set();
|
|
380
|
-
const visiting = new Set();
|
|
381
|
-
const checkCircular = (currentSchema) => {
|
|
382
|
-
const name = currentSchema.identifier?.name;
|
|
383
|
-
if (!name)
|
|
384
|
-
return false;
|
|
385
|
-
if (visiting.has(name)) {
|
|
386
|
-
return true; // Circular reference found
|
|
387
|
-
}
|
|
388
|
-
if (visited.has(name)) {
|
|
389
|
-
return false; // Already processed
|
|
390
|
-
}
|
|
391
|
-
visiting.add(name);
|
|
392
|
-
// Check field references
|
|
393
|
-
if ("fields" in currentSchema && currentSchema.fields) {
|
|
394
|
-
for (const field of Object.values(currentSchema.fields)) {
|
|
395
|
-
if (field?.type?.name === name) {
|
|
396
|
-
return true; // Self-reference
|
|
397
|
-
}
|
|
398
|
-
// Add more complex reference checking as needed
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
visiting.delete(name);
|
|
402
|
-
visited.add(name);
|
|
403
|
-
return false;
|
|
404
|
-
};
|
|
405
|
-
return checkCircular(schema);
|
|
406
|
-
}
|
|
407
|
-
// ==========================================
|
|
408
|
-
// File Generation
|
|
409
|
-
// ==========================================
|
|
410
|
-
/**
|
|
411
|
-
* Generate files from processed schemas
|
|
412
|
-
*/
|
|
413
|
-
async generateFiles(schemas) {
|
|
414
|
-
const operations = schemas.map((schema, index) => () => this.errorBoundary.withErrorBoundary(async () => {
|
|
415
|
-
const file = await this.generateFileForSchema(schema, index, schemas.length);
|
|
416
|
-
this.generatedFiles.push(file);
|
|
417
|
-
return file;
|
|
418
|
-
}, { schema, operationName: "generateFile" }));
|
|
419
|
-
await this.errorBoundary.withBatchErrorBoundary(operations, {
|
|
420
|
-
operationName: "generateFiles",
|
|
421
|
-
});
|
|
422
|
-
this.logger.debug(`Generated ${this.generatedFiles.length} files`);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Generate a single file from a schema
|
|
426
|
-
*/
|
|
427
|
-
async generateFileForSchema(schema, index, total) {
|
|
428
|
-
const fileStartTime = performance.now();
|
|
429
|
-
// Create template context
|
|
430
|
-
const context = {
|
|
431
|
-
schema,
|
|
432
|
-
typeMapper: this.typeMapper,
|
|
433
|
-
filename: this.typeMapper.formatFileName(schema.identifier?.name || "unknown"),
|
|
434
|
-
language: this.getLanguageName(),
|
|
435
|
-
timestamp: new Date().toISOString(),
|
|
436
|
-
imports: new Map(),
|
|
437
|
-
exports: new Set(),
|
|
438
|
-
};
|
|
439
|
-
// Generate content
|
|
440
|
-
const content = await this.generateSchemaContent(schema, context);
|
|
441
|
-
// Validate content if enabled
|
|
442
|
-
if (this.options.validate) {
|
|
443
|
-
await this.validateContent(content, context);
|
|
444
|
-
}
|
|
445
|
-
// Write file
|
|
446
|
-
const filename = context.filename + this.getFileExtension();
|
|
447
|
-
const writeResult = await this.fileManager.writeFile(filename, content);
|
|
448
|
-
const generationTime = performance.now() - fileStartTime;
|
|
449
|
-
// Report progress
|
|
450
|
-
this.reportProgress("writing", index + 1, total, `Generated ${filename} (${writeResult.size} bytes)`);
|
|
451
|
-
// Create GeneratedFile result
|
|
452
|
-
const generatedFile = {
|
|
453
|
-
path: writeResult.path,
|
|
454
|
-
filename,
|
|
455
|
-
content,
|
|
456
|
-
exports: this.extractExports(content),
|
|
457
|
-
size: writeResult.size,
|
|
458
|
-
timestamp: new Date(),
|
|
459
|
-
metadata: {
|
|
460
|
-
generationTime,
|
|
461
|
-
schemaCount: 1,
|
|
462
|
-
templateName: context.templateName?.toString(),
|
|
463
|
-
warnings: [],
|
|
464
|
-
},
|
|
465
|
-
};
|
|
466
|
-
return generatedFile;
|
|
467
|
-
}
|
|
468
|
-
// ==========================================
|
|
469
|
-
// Helper Methods
|
|
470
|
-
// ==========================================
|
|
471
|
-
/**
|
|
472
|
-
* Ensure filename has correct extension
|
|
473
|
-
*/
|
|
474
|
-
ensureFileExtension(filename) {
|
|
475
|
-
const extension = this.getFileExtension();
|
|
476
|
-
return filename.endsWith(extension) ? filename : `${filename}${extension}`;
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Extract exported symbols from generated content
|
|
480
|
-
* Can be overridden by language-specific implementations
|
|
481
|
-
*/
|
|
482
|
-
extractExports(content) {
|
|
483
|
-
const exports = [];
|
|
484
|
-
// Handle export { name1, name2 } syntax
|
|
485
|
-
const exportListRegex = /export\s*\{\s*([^}]+)\s*\}/g;
|
|
486
|
-
let match;
|
|
487
|
-
while ((match = exportListRegex.exec(content)) !== null) {
|
|
488
|
-
if (match[1]) {
|
|
489
|
-
const names = match[1]
|
|
490
|
-
.split(",")
|
|
491
|
-
.map((name) => name.trim())
|
|
492
|
-
.filter(Boolean);
|
|
493
|
-
exports.push(...names);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
// Handle direct export declarations
|
|
497
|
-
const directExportRegex = /export\s+(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/g;
|
|
498
|
-
while ((match = directExportRegex.exec(content)) !== null) {
|
|
499
|
-
if (match[1]) {
|
|
500
|
-
exports.push(match[1]);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
// Remove duplicates
|
|
504
|
-
return [...new Set(exports)];
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Report progress to callback if provided
|
|
508
|
-
*/
|
|
509
|
-
reportProgress(phase, current, total, message, schema) {
|
|
510
|
-
if (this.progressCallback) {
|
|
511
|
-
this.progressCallback(phase, current, total, message, schema);
|
|
512
|
-
}
|
|
513
|
-
if (message && this.options.verbose) {
|
|
514
|
-
this.logger.debug(`[${phase}] ${message} (${current}/${total})`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Run post-generation hooks
|
|
519
|
-
* Can be overridden to add custom post-processing
|
|
520
|
-
*/
|
|
521
|
-
async runPostGenerationHooks() {
|
|
522
|
-
// Default implementation does nothing
|
|
523
|
-
// Subclasses can override to add custom logic
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Get cached value or compute and cache it
|
|
527
|
-
*/
|
|
528
|
-
getCachedOrCompute(key, computeFn) {
|
|
529
|
-
if (this.cache.has(key)) {
|
|
530
|
-
return this.cache.get(key);
|
|
531
|
-
}
|
|
532
|
-
const result = computeFn();
|
|
533
|
-
if (result instanceof Promise) {
|
|
534
|
-
return result.then((value) => {
|
|
535
|
-
this.cache.set(key, value);
|
|
536
|
-
return value;
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
this.cache.set(key, result);
|
|
541
|
-
return result;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Clear internal cache
|
|
546
|
-
*/
|
|
547
|
-
clearCache() {
|
|
548
|
-
this.cache.clear();
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Get generation statistics
|
|
552
|
-
*/
|
|
553
|
-
getGenerationStats() {
|
|
554
|
-
const totalSize = this.generatedFiles.reduce((sum, file) => sum + file.size, 0);
|
|
555
|
-
const generationTime = performance.now() - this.generationStartTime;
|
|
556
|
-
return {
|
|
557
|
-
filesGenerated: this.generatedFiles.length,
|
|
558
|
-
totalSize,
|
|
559
|
-
averageFileSize: this.generatedFiles.length > 0 ? totalSize / this.generatedFiles.length : 0,
|
|
560
|
-
generationTime,
|
|
561
|
-
averageTimePerFile: this.generatedFiles.length > 0 ? generationTime / this.generatedFiles.length : 0,
|
|
562
|
-
cacheHitRate: 0, // TODO: Implement cache hit tracking
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core file management system with batching and performance optimizations
|
|
3
|
-
*
|
|
4
|
-
* This replaces scattered writeFile calls with a comprehensive file management
|
|
5
|
-
* system that provides better error handling, performance, and maintainability.
|
|
6
|
-
*/
|
|
7
|
-
import type { CodegenLogger } from "../../../utils/codegen-logger";
|
|
8
|
-
import type { FileStats } from "./types";
|
|
9
|
-
export interface FileManagerOptions {
|
|
10
|
-
outputDir: string;
|
|
11
|
-
logger: CodegenLogger;
|
|
12
|
-
overwrite?: boolean;
|
|
13
|
-
batchSize?: number;
|
|
14
|
-
}
|
|
15
|
-
export interface WriteFileResult {
|
|
16
|
-
path: string;
|
|
17
|
-
size: number;
|
|
18
|
-
writeTime: number;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* High-performance file manager with batching and error recovery
|
|
22
|
-
*
|
|
23
|
-
* Features:
|
|
24
|
-
* - Automatic directory creation
|
|
25
|
-
* - Batch operations for better performance
|
|
26
|
-
* - Comprehensive error handling with recovery suggestions
|
|
27
|
-
* - Import path resolution
|
|
28
|
-
* - File existence checks
|
|
29
|
-
*/
|
|
30
|
-
export declare class FileManager {
|
|
31
|
-
private readonly options;
|
|
32
|
-
private readonly logger;
|
|
33
|
-
constructor(options: FileManagerOptions);
|
|
34
|
-
/**
|
|
35
|
-
* Write a file with automatic directory creation
|
|
36
|
-
* @param relativePath Path relative to output directory
|
|
37
|
-
* @param content File content
|
|
38
|
-
* @param options Write options
|
|
39
|
-
*/
|
|
40
|
-
writeFile(relativePath: string, content: string, options?: {
|
|
41
|
-
encoding?: BufferEncoding;
|
|
42
|
-
overwrite?: boolean;
|
|
43
|
-
}): Promise<WriteFileResult>;
|
|
44
|
-
/**
|
|
45
|
-
* Write multiple files in batch for better performance
|
|
46
|
-
* @param files Map of relative path to content
|
|
47
|
-
*/
|
|
48
|
-
writeBatch(files: Map<string, string>): Promise<WriteFileResult[]>;
|
|
49
|
-
/**
|
|
50
|
-
* Ensure directory exists, creating parent directories as needed
|
|
51
|
-
* @param dirPath Full directory path
|
|
52
|
-
*/
|
|
53
|
-
ensureDirectory(dirPath: string): Promise<void>;
|
|
54
|
-
/**
|
|
55
|
-
* Clean directory by removing all contents
|
|
56
|
-
* @param relativePath Path relative to output directory
|
|
57
|
-
*/
|
|
58
|
-
cleanDirectory(relativePath?: string): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Get relative import path between two files
|
|
61
|
-
* @param fromFile Source file path
|
|
62
|
-
* @param toFile Target file path
|
|
63
|
-
*/
|
|
64
|
-
getRelativeImportPath(fromFile: string, toFile: string): string;
|
|
65
|
-
/**
|
|
66
|
-
* Check if a file would be overwritten
|
|
67
|
-
* @param relativePath Path relative to output directory
|
|
68
|
-
*/
|
|
69
|
-
wouldOverwrite(relativePath: string): Promise<boolean>;
|
|
70
|
-
/**
|
|
71
|
-
* Get file statistics
|
|
72
|
-
* @param relativePath Path relative to output directory
|
|
73
|
-
*/
|
|
74
|
-
getFileStats(relativePath: string): Promise<FileStats | null>;
|
|
75
|
-
/**
|
|
76
|
-
* Get output directory
|
|
77
|
-
*/
|
|
78
|
-
getOutputDirectory(): string;
|
|
79
|
-
/**
|
|
80
|
-
* Set batch size for operations
|
|
81
|
-
* @param size Batch size
|
|
82
|
-
*/
|
|
83
|
-
setBatchSize(size: number): void;
|
|
84
|
-
/**
|
|
85
|
-
* Get current batch size
|
|
86
|
-
*/
|
|
87
|
-
getBatchSize(): number;
|
|
88
|
-
}
|