Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .knipignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Test helpers are intentionally exported for use across test files
test/helpers/**

# Keep all exported types - they might be used by TypeScript consumers
**/*.d.ts
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ AIDBOX_LICENSE_ID ?=

TYPECHECK = bunx tsc --noEmit
FORMAT = bunx biome format --write
LINT = bunx biome lint --diagnostic-level=error --write --unsafe
LINT = bunx biome check --write
TEST = bun test

.PHONY: all typecheck test-typeschema test-register test-codegen test-typescript-r4-example
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"noExcessiveCognitiveComplexity": {
"level": "warn",
"options": {
"maxAllowedComplexity": 50
"maxAllowedComplexity": 39
}
}
},
Expand Down
238 changes: 65 additions & 173 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions examples/typescript-r4/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,15 @@ function runDemo() {
const patient = createPatient();
console.log("✓ Created patient:", patient.id);

const observation = createObservation(patient.id!);
if (!patient.id) throw new Error("Failed to create patient");
const observation = createObservation(patient.id);
console.log("✓ Created glucose observation:", observation.id);

console.log(`\n${"=".repeat(60)}`);
console.log("Bodyweight profile attach/extract demo:");
console.log("=".repeat(60));

const bodyweightObs = createBodyWeightObservation(patient.id!);
const bodyweightObs = createBodyWeightObservation(patient.id);
console.log("✓ Created body weight observation with profile:", bodyweightObs.id);

const bundle = createBundle(patient, observation, bodyweightObs);
Expand Down
43 changes: 43 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": [
"src/api/index.ts"
],
"project": [
"src/**/*.ts",
"test/**/*.ts"
],
"ignore": [
"dist/**",
"generated/**",
".typeschema-cache/**",
"**/*.d.ts"
],
"ignoreDependencies": [
],
"ignoreBinaries": [
],
"ignoreExportsUsedInFile": true,
"rules": {
"files": "warn",
"dependencies": "warn",
"devDependencies": "warn",
"unlisted": "error",
"binaries": "warn",
"unresolved": "error",
"exports": "warn",
"types": "warn",
"nsExports": "warn",
"nsTypes": "warn",
"enumMembers": "warn",
"classMembers": "warn",
"duplicates": "error"
},
"compilers": {
"css": "css",
"js": "tsx",
"json": "json",
"svg": "tsx",
"ts": "tsx"
}
}
12 changes: 4 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
"cli": "bun run src/cli/index.ts",
"codegen": "bun run src/cli/index.ts",
"codegen:all": "bun run src/cli/index.ts generate",
"prune": "ts-prune --project tsconfig.json"
"prune": "knip",
"prune:strict": "knip --strict",
"prune:fix": "knip --fix"
},
"repository": {
"type": "git",
Expand All @@ -62,22 +64,16 @@
"dependencies": {
"@atomic-ehr/fhir-canonical-manager": "^0.0.15",
"@atomic-ehr/fhirschema": "^0.0.5",
"@inquirer/prompts": "^7.8.1",
"ajv": "^8.17.1",
"glob": "^12.0.0",
"handlebars": "^4.7.8",
"ora": "^8.2.0",
"picocolors": "^1.1.1",
"yaml": "^2.8.1",
"yargs": "^18.0.0"
},
"devDependencies": {
"@biomejs/biome": "^2.1.4",
"@types/bun": "^1.2.23",
"@types/handlebars": "^4.1.0",
"@types/node": "^22.17.1",
"@types/yargs": "^17.0.33",
"ts-prune": "^0.10.3",
"knip": "^5.27.2",
"tsup": "^8.5.1",
"typescript": "^5.9.2"
}
Expand Down
118 changes: 17 additions & 101 deletions src/api/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ import * as fs from "node:fs";
import * as afs from "node:fs/promises";
import * as Path from "node:path";
import { CanonicalManager } from "@atomic-ehr/fhir-canonical-manager";
import type { GeneratedFile } from "@root/api/generators/base/types";
import { CSharp } from "@root/api/writer-generator/csharp/csharp";
import { registerFromManager } from "@root/typeschema/register";
import { mkTypeSchemaIndex, type TreeShake, treeShake } from "@root/typeschema/utils";
import { generateTypeSchemas, TypeSchemaCache, TypeSchemaGenerator, TypeSchemaParser } from "@typeschema/index";
import { mkTypeSchemaIndex, type TreeShake, type TypeSchemaIndex, treeShake } from "@root/typeschema/utils";
import { generateTypeSchemas, TypeSchemaGenerator, TypeSchemaParser } from "@typeschema/index";
import { extractNameFromCanonical, packageMetaToFhir, packageMetaToNpm, type TypeSchema } from "@typeschema/types";
import type { Config, TypeSchemaConfig } from "../config";
import type { TypeSchemaConfig } from "../config";
import { CodegenLogger, createLogger } from "../utils/codegen-logger";
import type { GeneratorInput } from "./generators/base/BaseGenerator";
import { TypeScriptGenerator as TypeScriptGeneratorDepricated } from "./generators/typescript";
import { TypeScript, type TypeScriptOptions } from "./writer-generator/typescript";
import type { Writer, WriterOptions } from "./writer-generator/writer";

Expand Down Expand Up @@ -57,6 +54,14 @@ export interface GenerationResult {
duration: number;
}

export interface GeneratedFile {
path: string;
filename: string;
timestamp: Date;
}

export type GeneratorInput = { schemas: TypeSchema[]; index: TypeSchemaIndex };

interface Generator {
generate: (input: GeneratorInput) => Promise<GeneratedFile[]>;
setOutputDir: (outputDir: string) => void;
Expand All @@ -65,13 +70,10 @@ interface Generator {

const writerToGenerator = (writerGen: Writer): Generator => {
const getGeneratedFiles = () => {
return writerGen.writtenFiles().map((fn: string) => {
return writerGen.writtenFiles().map((fn: string): GeneratedFile => {
return {
path: Path.normalize(Path.join(writerGen.opts.outputDir, fn)),
filename: fn.replace(/^.*[\\/]/, ""),
content: "",
exports: [],
size: 0,
timestamp: new Date(),
};
});
Expand Down Expand Up @@ -206,7 +208,6 @@ export class APIBuilder {
private schemas: TypeSchema[] = [];
private options: APIBuilderConfig;
private generators: Map<string, Generator> = new Map();
private cache?: TypeSchemaCache;
private pendingOperations: Promise<void>[] = [];
private typeSchemaGenerator?: TypeSchemaGenerator;
private logger: CodegenLogger;
Expand Down Expand Up @@ -238,10 +239,6 @@ export class APIBuilder {
verbose: this.options.verbose,
prefix: "API",
});

if (this.options.cache) {
this.cache = new TypeSchemaCache(this.typeSchemaConfig);
}
}

fromPackage(packageName: string, version?: string): APIBuilder {
Expand All @@ -268,47 +265,6 @@ export class APIBuilder {
return this;
}

typescriptDepricated(
options: {
moduleFormat?: "esm" | "cjs";
generateIndex?: boolean;
includeDocuments?: boolean;
namingConvention?: "PascalCase" | "camelCase";
includeExtensions?: boolean;
includeProfiles?: boolean;
generateValueSets?: boolean;
includeValueSetHelpers?: boolean;
valueSetStrengths?: ("required" | "preferred" | "extensible" | "example")[];
valueSetMode?: "all" | "required-only" | "custom";
valueSetDirectory?: string;
} = {},
): APIBuilder {
const typesOutputDir = `${this.options.outputDir}/types`;

const generator = new TypeScriptGeneratorDepricated({
outputDir: typesOutputDir,
moduleFormat: options.moduleFormat || "esm",
generateIndex: options.generateIndex ?? true,
includeDocuments: options.includeDocuments ?? true,
namingConvention: options.namingConvention || "PascalCase",
includeExtensions: options.includeExtensions ?? false,
includeProfiles: options.includeProfiles ?? false,
generateValueSets: options.generateValueSets ?? false,
includeValueSetHelpers: options.includeValueSetHelpers ?? false,
valueSetStrengths: options.valueSetStrengths ?? ["required"],
logger: this.logger.child("TS"),
valueSetMode: options.valueSetMode ?? "required-only",
valueSetDirectory: options.valueSetDirectory ?? "valuesets",
verbose: this.options.verbose,
validate: true, // Enable validation for debugging
overwrite: this.options.overwrite,
});

this.generators.set("typescript", generator);
this.logger.debug(`Configured TypeScript generator (${options.moduleFormat || "esm"})`);
return this;
}

typescript(userOpts: Partial<TypeScriptOptions>) {
const defaultWriterOpts: WriterOptions = {
logger: this.logger,
Expand Down Expand Up @@ -510,14 +466,11 @@ export class APIBuilder {

private async loadFromFiles(filePaths: string[]): Promise<void> {
if (!this.typeSchemaGenerator) {
this.typeSchemaGenerator = new TypeSchemaGenerator(
{
verbose: this.options.verbose,
logger: this.logger.child("Schema"),
treeshake: this.typeSchemaConfig?.treeshake,
},
this.typeSchemaConfig,
);
this.typeSchemaGenerator = new TypeSchemaGenerator({
verbose: this.options.verbose,
logger: this.logger.child("Schema"),
treeshake: this.typeSchemaConfig?.treeshake,
});
}

const parser = new TypeSchemaParser({
Expand All @@ -526,10 +479,6 @@ export class APIBuilder {

const schemas = await parser.parseFromFiles(filePaths);
this.schemas = [...this.schemas, ...schemas];

if (this.cache) {
this.cache.setMany(schemas);
}
}

private async executeGenerators(result: GenerationResult, input: GeneratorInput): Promise<void> {
Expand All @@ -549,36 +498,3 @@ export class APIBuilder {
}
}
}

/**
* Create an API builder instance from a configuration object
*/
export function createAPIFromConfig(config: Config): APIBuilder {
const builder = new APIBuilder({
outputDir: config.outputDir,
verbose: config.verbose,
overwrite: config.overwrite,
cache: config.cache,
cleanOutput: config.cleanOutput,
typeSchemaConfig: config.typeSchema,
});

// Add packages if specified
if (config.packages && config.packages.length > 0) {
for (const pkg of config.packages) {
builder.fromPackage(pkg);
}
}

// Add files if specified
if (config.files && config.files.length > 0) {
builder.fromFiles(...config.files);
}

// Configure TypeScript generator if specified
if (config.typescript) {
builder.typescriptDepricated(config.typescript);
}

return builder;
}
Loading