Skip to content

Export Writer base class and TypeSchema types for custom generators #134

@niquola

Description

@niquola

Problem

I'm building a FHIR app framework (proc-ts) where every function lives in its own file with export default. I need to generate FHIR types that follow this convention:

// fhir/Patient.ts — our convention: one default export per file
export interface Patient { ... }
export default function isPatient(resource: unknown): resource is Patient {
  return resource?.resourceType === "Patient";
}

The built-in .typescript() generator produces export const isPatient = ... which doesn't work for our module system. I want to write a custom writer that generates files in our format.

What I tried

  1. Extend TypeScript class — not possible, TypeScript is not exported from the package
  2. Extend Writer base class — not possible, Writer is not exported
  3. Use .mustache() API — works for simple cases but lacks programmatic control (conditions, name transformations, type resolution)
  4. Post-process .typescript() output — current workaround, but fragile regex replacement

What's needed

Export the writer infrastructure so users can build custom generators:

Minimum viable exports

// These are currently internal but needed to write a custom generator:
export { Writer, FileSystemWriter } from "./api/writer-generator/writer";
export type { WriterOptions, FileSystemWriterOptions, FileBuffer } from "./api/writer-generator/writer";

// TypeSchema types needed to iterate over FHIR definitions:
export type {
  TypeSchema, SpecializationTypeSchema, NestedTypeSchema,
  TypeIdentifier, Field, EnumDefinition, CanonicalUrl,
} from "./typeschema/types";
export {
  isResourceTypeSchema, isComplexTypeIdentifier, isPrimitiveIdentifier,
  isSpecializationTypeSchema, isChoiceDeclarationField,
} from "./typeschema/types";

// Utilities for grouping/sorting schemas:
export { groupByPackages, sortAsDeclarationSequence } from "./typeschema/utils";
export type { TypeSchemaIndex } from "./typeschema/utils";

// Helper utilities:
export { uppercaseFirstLetter, pascalCase, snakeCase, camelCase } from "./api/writer-generator/utils";

Registration API

A way to register a custom writer with APIBuilder:

// Option A: pass a writer class
builder.custom(MyWriter, { /* options */ })

// Option B: pass a factory function
builder.custom((tsIndex, options) => {
  // generate files using tsIndex.collectResources(), etc.
})

Example usage

import { Writer, APIBuilder } from "@atomic-ehr/codegen";
import type { TypeSchemaIndex, SpecializationTypeSchema } from "@atomic-ehr/codegen";

class ProcTsWriter extends Writer {
  async generate(tsIndex: TypeSchemaIndex) {
    for (const schema of tsIndex.collectResources()) {
      this.cd("/", () => {
        this.cat(`${schema.identifier.name}.ts`, () => {
          // generate interface...
          // generate export default type guard
        });
      });
    }
  }
}

const builder = new APIBuilder()
  .fromPackage("hl7.fhir.r4.core", "4.0.1")
  .custom(ProcTsWriter, { /* options */ })
  .outputTo("./fhir")
  .generate();

Context

The Python and TypeScript writers in the repo (src/api/writer-generator/typescript/writer.ts, src/api/writer-generator/python.ts) demonstrate the pattern perfectly — they extend Writer and use TypeSchemaIndex to iterate schemas. But since these base types aren't exported, external users can't do the same.

The .mustache() API is a partial solution but doesn't cover cases requiring programmatic logic (conditional generation, custom naming, different file-per-type strategies).

Current workaround

We use .typescript() + post-process: flatten hl7-fhir-r4-core/ subfolder, regex replace export const isXexport default function isX. It works but is fragile and doesn't allow deeper customization.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions