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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 => { ... }`
Expand Down
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"biome.json",
"package.json",
"examples/*/*.ts",
"examples/*.ts",
".zed/*.json"
]
},
Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 12 additions & 16 deletions examples/nodge-r4.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
Expand All @@ -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,
Expand Down
42 changes: 23 additions & 19 deletions examples/typescript-ccda/generate.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 14 additions & 2 deletions src/api/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -127,6 +132,7 @@ export class APIBuilder {
userOpts: Partial<APIBuilderOptions> & {
manager?: ReturnType<typeof CanonicalManager>;
register?: Register;
preprocessPackage?: (context: PreprocessContext) => PreprocessContext;
logger?: CodegenLogger;
} = {},
) {
Expand All @@ -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",
),
),
};
Expand All @@ -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;
Expand Down
18 changes: 0 additions & 18 deletions src/typeschema/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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,
Expand Down