Skip to content

Commit 7e5484e

Browse files
committed
feat: support logger log level configuration
1 parent 7adfde0 commit 7e5484e

7 files changed

Lines changed: 251 additions & 50 deletions

File tree

examples/typescript-us-core/generate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
// bun run examples/typescript-us-core/generate.ts
33

44
import { APIBuilder } from "../../src/api/builder";
5+
import { LogLevel } from "../../src/utils/codegen-logger";
56

67
if (require.main === module) {
78
console.log("📦 Generating FHIR R4 Core Types...");
89

910
const builder = new APIBuilder()
10-
.verbose()
1111
.throwException()
12+
.logLevel(LogLevel.INFO)
1213
.fromPackage("hl7.fhir.us.core", "6.1.0")
1314
.typescript({ withDebugComment: false, withProfiles: true })
1415
.outputTo("./examples/typescript-us-core/fhir-types")

src/api/builder.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { generateTypeSchemas, TypeSchemaCache, TypeSchemaGenerator, TypeSchemaPa
1717
import { extractNameFromCanonical, packageMetaToFhir, packageMetaToNpm, type TypeSchema } from "@typeschema/types";
1818
import type { Config, TypeSchemaConfig } from "../config";
1919
import { formatDuration } from "../utils";
20-
import { CodegenLogger, createLogger } from "../utils/codegen-logger";
20+
import { CodegenLogger, createLogger, type LogLevel } from "../utils/codegen-logger";
2121
import type { GeneratorInput } from "./generators/base/BaseGenerator";
2222
import { TypeScriptGenerator as TypeScriptGeneratorDepricated } from "./generators/typescript";
2323
import * as TS2 from "./writer-generator/typescript/index";
@@ -378,6 +378,11 @@ export class APIBuilder {
378378
return this;
379379
}
380380

381+
logLevel(level: LogLevel): APIBuilder {
382+
this.logger?.configure({ logLevel: level });
383+
return this;
384+
}
385+
381386
throwException(enabled = true): APIBuilder {
382387
this.options.throwException = enabled;
383388
return this;

src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type {
2020
PackageMeta as PackageInfo,
2121
TypeSchema,
2222
} from "@typeschema/types";
23+
export { LogLevel } from "../utils/codegen-logger";
2324
// Export types and interfaces
2425
export type {
2526
APIBuilderOptions,

src/api/writer-generator/typescript/field-accessor.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ function isChoiceFieldInstance(field: Field): field is ChoiceFieldInstance {
2424
/**
2525
* Check if profile field adds constraints compared to base resource
2626
*/
27-
function fieldAddsConstraints(
28-
profileField: Field,
29-
baseField: Field | undefined,
30-
): boolean {
27+
function fieldAddsConstraints(profileField: Field, baseField: Field | undefined): boolean {
3128
if (!baseField) return true; // New field added by profile
3229
if (isChoiceDeclarationField(profileField) || isChoiceDeclarationField(baseField)) return false;
3330

src/typeschema/core/transformer.ts

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -221,38 +221,38 @@ function extractExtensions(fhirSchema: RichFHIRSchema, logger?: CodegenLogger):
221221

222222
const extensions: ProfileExtension[] = [];
223223

224-
// Extensions are elements with path containing "extension:" or "modifierExtension:"
224+
// Look for extension elements with slicing
225225
for (const [path, element] of Object.entries(fhirSchema.elements)) {
226-
if (path.includes("extension:") || path.includes("modifierExtension:")) {
227-
// Extract extension slice name
228-
const sliceName = path.split(":")[1];
229-
if (!sliceName) continue;
230-
231-
// Get extension URL from type profile
232-
let extensionUrl: string | string[] | undefined;
233-
if (element.type) {
234-
const types = Array.isArray(element.type) ? element.type : [element.type];
235-
for (const t of types) {
236-
const typeObj = typeof t === "string" ? { code: t } : t;
237-
if (typeObj.code === "Extension" && typeObj.profile) {
238-
extensionUrl = typeObj.profile;
239-
break;
240-
}
226+
// Check if this is an extension or modifierExtension element with slices
227+
if (
228+
(path === "extension" ||
229+
path === "modifierExtension" ||
230+
path.endsWith(".extension") ||
231+
path.endsWith(".modifierExtension")) &&
232+
element.slicing?.slices
233+
) {
234+
// Extract each slice as an extension
235+
for (const [sliceName, slice] of Object.entries(element.slicing.slices)) {
236+
// Slice contains {match, schema} - we need the schema part
237+
const sliceSchema = (slice as any).schema;
238+
if (!sliceSchema) continue;
239+
240+
// Get extension URL from slice schema
241+
const extensionUrl = sliceSchema.url;
242+
243+
if (!extensionUrl) {
244+
logger?.warn(`Cannot determine URL for extension slice '${sliceName}' in ${fhirSchema.url}`);
245+
continue;
241246
}
242-
}
243247

244-
if (!extensionUrl) {
245-
logger?.warn(`Cannot determine URL for extension ${sliceName} in ${fhirSchema.url}`);
246-
continue;
248+
extensions.push({
249+
path: sliceName,
250+
profile: extensionUrl,
251+
min: sliceSchema.min,
252+
max: sliceSchema.max !== undefined ? String(sliceSchema.max) : undefined,
253+
mustSupport: sliceSchema.mustSupport,
254+
});
247255
}
248-
249-
extensions.push({
250-
path: sliceName,
251-
profile: extensionUrl,
252-
min: element.min,
253-
max: element.max !== undefined ? String(element.max) : undefined,
254-
mustSupport: element.mustSupport,
255-
});
256256
}
257257
}
258258

@@ -298,9 +298,31 @@ function transformProfile(register: Register, fhirSchema: RichFHIRSchema, logger
298298

299299
// Debug extension extraction for us-core-patient
300300
if (logger && fhirSchema.url?.includes("us-core-patient")) {
301-
const extPaths = Object.keys(fhirSchema.elements || {}).filter(p => p.toLowerCase().includes("extension"));
301+
const extPaths = Object.keys(fhirSchema.elements || {}).filter((p) => p.toLowerCase().includes("extension"));
302302
logger.debug(` [DEBUG] Extension-related paths in elements: ${extPaths.slice(0, 10).join(", ")}`);
303303
logger.debug(` [DEBUG] Total extension paths: ${extPaths.length}`);
304+
305+
// Check the extension element structure
306+
const extElement = fhirSchema.elements?.extension;
307+
if (extElement) {
308+
logger.debug(` [DEBUG] extension element has slicing: ${extElement.slicing ? "YES" : "NO"}`);
309+
logger.debug(` [DEBUG] extension element type: ${JSON.stringify(extElement.type).substring(0, 100)}`);
310+
if (extElement.slicing && extElement.slicing.slices) {
311+
logger.debug(` [DEBUG] slicing discriminator: ${JSON.stringify(extElement.slicing.discriminator)}`);
312+
logger.debug(
313+
` [DEBUG] slicing slices: ${JSON.stringify(Object.keys(extElement.slicing.slices || {}))}`,
314+
);
315+
316+
// Log first slice structure
317+
const firstSliceKey = Object.keys(extElement.slicing.slices)[0];
318+
if (firstSliceKey) {
319+
const firstSlice = extElement.slicing.slices[firstSliceKey];
320+
logger.debug(
321+
` [DEBUG] First slice (${firstSliceKey}): ${JSON.stringify(firstSlice).substring(0, 200)}`,
322+
);
323+
}
324+
}
325+
}
304326
}
305327

306328
// Build nested types

src/utils/codegen-logger.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface LogOptions {
1919
timestamp?: boolean;
2020
verbose?: boolean;
2121
suppressLoggingLevel?: LogLevel[] | "all";
22+
logLevel?: LogLevel;
2223
}
2324

2425
/**
@@ -29,20 +30,29 @@ export class CodegenLogger {
2930
private dryWarnSet: Set<string> = new Set();
3031

3132
constructor(options: LogOptions = {}) {
33+
// Backward compatibility: if verbose is set, use DEBUG level
34+
const logLevel = options.logLevel ?? (options.verbose ? LogLevel.DEBUG : LogLevel.INFO);
35+
3236
this.options = {
3337
timestamp: false,
3438
verbose: false,
3539
...options,
40+
logLevel, // Use the computed logLevel (respects options.logLevel or options.verbose)
3641
};
3742
}
3843

39-
private static consoleLevelsMap: Record<LogLevel, Function> = {
40-
[LogLevel.INFO]: console.log,
41-
[LogLevel.WARN]: console.warn,
42-
[LogLevel.ERROR]: console.error,
43-
[LogLevel.DEBUG]: console.log,
44-
[LogLevel.SILENT]: () => {},
45-
};
44+
private getConsoleMethod(level: LogLevel): (message: string) => void {
45+
switch (level) {
46+
case LogLevel.WARN:
47+
return console.warn;
48+
case LogLevel.ERROR:
49+
return console.error;
50+
case LogLevel.SILENT:
51+
return () => {};
52+
default:
53+
return console.log;
54+
}
55+
}
4656

4757
private formatMessage(level: string, message: string, color: (str: string) => string): string {
4858
const timestamp = this.options.timestamp ? `${pc.gray(new Date().toLocaleTimeString())} ` : "";
@@ -51,14 +61,19 @@ export class CodegenLogger {
5161
}
5262

5363
private isSuppressed(level: LogLevel): boolean {
54-
return (
55-
this.options.suppressLoggingLevel === "all" || this.options.suppressLoggingLevel?.includes(level) || false
56-
);
64+
// Check if this level is explicitly suppressed
65+
if (this.options.suppressLoggingLevel === "all" || this.options.suppressLoggingLevel?.includes(level)) {
66+
return true;
67+
}
68+
69+
// Check if the message level is below the configured log level
70+
const configuredLevel = this.options.logLevel ?? LogLevel.INFO;
71+
return level < configuredLevel;
5772
}
5873

5974
private tryWriteToConsole(level: LogLevel, formattedMessage: string): void {
6075
if (this.isSuppressed(level)) return;
61-
const logFn = CodegenLogger.consoleLevelsMap[level] || console.log;
76+
const logFn = this.getConsoleMethod(level);
6277
logFn(formattedMessage);
6378
}
6479

@@ -105,12 +120,10 @@ export class CodegenLogger {
105120
}
106121

107122
/**
108-
* Debug message (only shows in verbose mode)
123+
* Debug message (only shows when logLevel is DEBUG)
109124
*/
110125
debug(message: string): void {
111-
if (this.options.verbose) {
112-
this.tryWriteToConsole(LogLevel.DEBUG, this.formatMessage("🐛", message, pc.magenta));
113-
}
126+
this.tryWriteToConsole(LogLevel.DEBUG, this.formatMessage("🐛", message, pc.magenta));
114127
}
115128

116129
/**

0 commit comments

Comments
 (0)