diff --git a/CLAUDE.md b/CLAUDE.md index 7af5686b..f561cd7e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,7 +95,7 @@ FHIR Package → TypeSchema Generator → TypeSchema Format → Code Generators - Uses strict TypeScript with latest ESNext features - Module format: ESM with `"type": "module"` in package.json - Build target: Node.js with Bun bundler -- Biome for linting/formatting (tabs, double quotes) +- Biome for linting/formatting (spaces, double quotes) ### Coding Style - Use arrow function syntax for new functions: `const foo = (): ReturnType => { ... }` diff --git a/biome.json b/biome.json index 0832a45d..bd763df1 100644 --- a/biome.json +++ b/biome.json @@ -16,6 +16,7 @@ "biome.json", "package.json", "examples/*/*.ts", + "examples/*.ts", ".zed/*.json" ] }, diff --git a/bun.lock b/bun.lock index 49bb254c..16b8f5e2 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "@atomic-ehr/codegen", "dependencies": { - "@atomic-ehr/fhir-canonical-manager": "^0.0.20", + "@atomic-ehr/fhir-canonical-manager": "^0.0.21", "@atomic-ehr/fhirschema": "^0.0.8", "mustache": "^4.2.0", "picocolors": "^1.1.1", @@ -30,7 +30,7 @@ "rollup": ">=4.59.0", }, "packages": { - "@atomic-ehr/fhir-canonical-manager": ["@atomic-ehr/fhir-canonical-manager@0.0.20", "", { "peerDependencies": { "typescript": "^5" }, "bin": { "fcm": "dist/cli/index.js" } }, "sha512-fDvHAkY8KWh7qPg/zKWelnixkE7sWkXMoESzx4YC1ndMiX9Hd9bWXVV9SycEL2NbarU0rMchlKDCo/LkvecsnQ=="], + "@atomic-ehr/fhir-canonical-manager": ["@atomic-ehr/fhir-canonical-manager@0.0.21", "", { "peerDependencies": { "typescript": "^5" }, "bin": { "fcm": "dist/cli/index.js" } }, "sha512-MTmPXWixNJ6Wa2b9AqTeo4wg37w9u9ebDnVj0eRDwspDrNHhcgM/XYtEV3Oio7Mnzam3n9m0IPsto8iPQ87ilA=="], "@atomic-ehr/fhirschema": ["@atomic-ehr/fhirschema@0.0.8", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-RB3ZlFHYYfP4ZaOA0YStGaxrm3T1MzpJLPAzAxW/7u2yfdmQz160e3mGYVv0mglHKtGhZ0fuuk2qqhDwUiGncg=="], diff --git a/examples/nodge-r4.ts b/examples/nodge-r4.ts index 64e29240..11c9ca57 100644 --- a/examples/nodge-r4.ts +++ b/examples/nodge-r4.ts @@ -1,7 +1,7 @@ // Run this script using Bun CLI with: // bun run scripts/generate-fhir-types.ts -import { CanonicalManager, type PreprocessPackageContext } from "@atomic-ehr/fhir-canonical-manager"; +import type { PreprocessContext } from "@atomic-ehr/fhir-canonical-manager"; import { APIBuilder, prettyReport } from "../src/api/builder"; // Fix known package name typos (in-memory transformation) @@ -21,8 +21,9 @@ const needsCoreDependency = (name: string): boolean => { ); }; -const preprocessPackage = ({ packageJson }: PreprocessPackageContext): PreprocessPackageContext => { - let json = packageJson; +const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { + if (ctx.kind !== "package") return ctx; + let json = ctx.packageJson; const name = json.name as string; // Fix package name typos @@ -44,25 +45,20 @@ const preprocessPackage = ({ packageJson }: PreprocessPackageContext): Preproces } } - return { packageJson: json }; + return { kind: "package", packageJson: json }; }; if (require.main === module) { console.log("📦 Generating FHIR R4 Core Types..."); - const manager = CanonicalManager({ - packages: [ - "hl7.fhir.r4.core@4.0.1", - "ehelse.fhir.no.grunndata@2.3.5", - "hl7.fhir.no.basis@2.2.2", - "sfm.030322@2.0.1", - ], - workingDir: ".codegen-cache/canonical-manager-cache", - registry: "https://packages.simplifier.net", + const builder = new APIBuilder({ preprocessPackage, - }); - - const builder = new APIBuilder({ manager }) + registry: "https://packages.simplifier.net", + }) + .fromPackage("hl7.fhir.r4.core", "4.0.1") + .fromPackage("ehelse.fhir.no.grunndata", "2.3.5") + .fromPackage("hl7.fhir.no.basis", "2.2.2") + .fromPackage("sfm.030322", "2.0.1") .throwException() .typescript({ withDebugComment: false, diff --git a/examples/typescript-ccda/generate.ts b/examples/typescript-ccda/generate.ts index b13f869a..ffaa2ca1 100644 --- a/examples/typescript-ccda/generate.ts +++ b/examples/typescript-ccda/generate.ts @@ -1,32 +1,36 @@ // Run this script using Bun CLI with: // bun run scripts/generate-fhir-types.ts -import { registerFromPackageMetas } from "@root/typeschema/register"; +import { CanonicalManager, type PreprocessContext } from "@atomic-ehr/fhir-canonical-manager"; +import { registerFromManager } from "@root/typeschema/register"; import { APIBuilder, prettyReport } from "../../src/api/builder"; +const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { + if (ctx.kind !== "resource") return ctx; + if (ctx.package.name !== "hl7.cda.uv.core") return ctx; + let str = JSON.stringify(ctx.resource); + str = str.replaceAll( + "http://hl7.org/cda/stds/core/StructureDefinition/IVL_TS", + "http://hl7.org/cda/stds/core/StructureDefinition/IVL-TS", + ); + return { ...ctx, resource: JSON.parse(str) }; +}; + if (require.main === module) { console.log("📦 Generating CCDA Types..."); - const registry = await registerFromPackageMetas( - [ - { name: "hl7.fhir.r4.core", version: "4.0.1" }, - { name: "hl7.cda.us.ccda", version: "5.0.0-ballot" }, - // { name: "hl7.cda.uv.core", version: "2.0.2-sd" }, - ], - {}, - ); - if (registry === undefined) throw new Error("Failed to register package"); - - registry.patchSd((pkg, sd) => { - if (pkg.name !== "hl7.cda.uv.core") return sd; - let str = JSON.stringify(sd); - str = str.replaceAll( - "http://hl7.org/cda/stds/core/StructureDefinition/IVL_TS", - "http://hl7.org/cda/stds/core/StructureDefinition/IVL-TS", - ); - return JSON.parse(str); + const manager = CanonicalManager({ + packages: [], + workingDir: ".codegen-cache/canonical-manager-cache", + preprocessPackage, }); + // Initialize manager with packages to discover CDA resources + await manager.addPackages("hl7.fhir.r4.core@4.0.1", "hl7.cda.us.ccda@5.0.0-ballot"); + const ref2meta = await manager.init(); + const packageMetas = Object.values(ref2meta); + + const registry = await registerFromManager(manager, { focusedPackages: packageMetas }); const cdaResources = registry .allSd() .filter((sd) => { diff --git a/package.json b/package.json index 960049be..c938ca24 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "homepage": "https://github.com/atomic-ehr/codegen#readme", "dependencies": { - "@atomic-ehr/fhir-canonical-manager": "^0.0.20", + "@atomic-ehr/fhir-canonical-manager": "^0.0.21", "@atomic-ehr/fhirschema": "^0.0.8", "mustache": "^4.2.0", "picocolors": "^1.1.1", diff --git a/src/api/builder.ts b/src/api/builder.ts index 82576dcf..146333d8 100644 --- a/src/api/builder.ts +++ b/src/api/builder.ts @@ -8,7 +8,12 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as Path from "node:path"; -import { CanonicalManager, type LocalPackageConfig, type TgzPackageConfig } from "@atomic-ehr/fhir-canonical-manager"; +import { + CanonicalManager, + type LocalPackageConfig, + type PreprocessContext, + type TgzPackageConfig, +} from "@atomic-ehr/fhir-canonical-manager"; import { CSharp, type CSharpGeneratorOptions } from "@root/api/writer-generator/csharp/csharp"; import { Python, type PythonGeneratorOptions } from "@root/api/writer-generator/python"; import { generateTypeSchemas } from "@root/typeschema"; @@ -127,6 +132,7 @@ export class APIBuilder { userOpts: Partial & { manager?: ReturnType; register?: Register; + preprocessPackage?: (context: PreprocessContext) => PreprocessContext; logger?: CodegenLogger; } = {}, ) { @@ -144,7 +150,12 @@ export class APIBuilder { ...defaultOpts, ...Object.fromEntries( Object.entries(userOpts).filter( - ([k, v]) => v !== undefined && k !== "manager" && k !== "register" && k !== "logger", + ([k, v]) => + v !== undefined && + k !== "manager" && + k !== "register" && + k !== "preprocessPackage" && + k !== "logger", ), ), }; @@ -166,6 +177,7 @@ export class APIBuilder { workingDir: ".codegen-cache/canonical-manager-cache", registry: userOpts.registry, dropCache: userOpts.dropCanonicalManagerCache, + preprocessPackage: userOpts.preprocessPackage, }); this.logger = userOpts.logger ?? createLogger({ prefix: "API", level: opts.logLevel }); this.options = opts; diff --git a/src/typeschema/register.ts b/src/typeschema/register.ts index 205aed5a..82bed46c 100644 --- a/src/typeschema/register.ts +++ b/src/typeschema/register.ts @@ -26,7 +26,6 @@ export type Register = { resolveFsGenealogy(pkg: PackageMeta, canonicalUrl: CanonicalUrl): RichFHIRSchema[]; resolveFsSpecializations(pkg: PackageMeta, canonicalUrl: CanonicalUrl): RichFHIRSchema[]; allSd(): RichStructureDefinition[]; - patchSd(fn: (pkg: PackageMeta, sd: StructureDefinition) => StructureDefinition): void; /** Returns all FHIRSchemas from all packages in the resolver */ allFs(): RichFHIRSchema[]; /** Returns all ValueSets from all packages in the resolver */ @@ -312,23 +311,6 @@ export const registerFromManager = async ( ) .filter((r): r is RichStructureDefinition => isStructureDefinition(r)) .sort((sd1, sd2) => sd1.url.localeCompare(sd2.url)), - patchSd: (fn: (pkg: PackageMeta, sd: StructureDefinition) => StructureDefinition) => { - Object.values(resolver).flatMap((pkgIndex) => - Object.values(pkgIndex.canonicalResolution).forEach((resolutions) => { - resolutions.forEach((e) => { - if (isStructureDefinition(e.resource)) { - const sd = e.resource as StructureDefinition; - const newSd = fn(pkgIndex.pkg, sd); - if (sd.url !== newSd.url) - throw new Error(`Patch update StructureDefinition URL: ${sd.url} !== ${newSd.url}`); - e.resource = newSd; - } - }); - }), - ); - enrichResolver(resolver); - cachedResolutionTree = undefined; - }, allFs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.fhirSchemas)), allVs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.valueSets)), resolveVs,