Skip to content

Doctor Build

GitHub Actions edited this page Aug 18, 2026 · 3 revisions

build

Dev-time codegen that scans @Vial-decorated classes and writes a VialRegistry type augmentation, so inject('Token') is typed without importing the class.

Deno

Imported from the ./build subpath — Deno-only (declared in deno.json only, not package.json). It uses Deno.readDir / writeTextFile, so run it under Deno as a dev/CI step; the file it emits is plain type declarations consumed unchanged on every runtime.

import { build } from '@tundralibs/doctor/build';

Signature

build(options: BuildOptions): Promise<VialSite[]>;

interface BuildOptions {
  roots: string[]; // directories/files scanned recursively for @Vial
  out: string; // registry file to (over)write
  module?: string; // module to augment — defaults to '@tundralibs/doctor'
}

build returns the discovered sites ({ token, className, file }), the same data it wrote, so callers can log or assert on what was found.

What it does

For every @Vial(...) class X it finds, build records X as the token (the class name) and emits an augmentation keyed by it:

// AUTO-GENERATED by @tundralibs/doctor/build — do not edit by hand.
import type { Config } from './Config.ts';
import type { Logger } from './Logger.ts';

declare module '@tundralibs/doctor' {
  interface VialRegistry {
    Config: Config;
    Logger: Logger;
  }
}

Entries are sorted and deduped (last registration of a name wins). The output file is never scanned, so re-running is idempotent.

Workflow

// scripts/build-registry.ts
import { build } from '@tundralibs/doctor/build';

await build({
  roots: ['./src'],
  out: './src/vial-registry.ts',
});
deno run -A scripts/build-registry.ts

Then import the generated file once (e.g. at your entry point) so the augmentation is in scope, and resolve by token:

import './vial-registry.ts';
import { inject } from '@tundralibs/doctor';

const config = inject('Config'); // typed as Config

Use the module option when the registry references the package by a relative path instead of its name — for example module: '../mod.ts' inside this repo.

Exposed helpers

scan(roots, out?), render(sites, out, module), and relativeImport(from, to) are exported individually for testing or custom pipelines; build is scan + render + write.

Caveats

  • The token is the class name — names must be unique and survive minification. See inject caveats.
  • Only @Vial-decorated classes are scanned. Vials registered via Doctor.prescribe(...) / a factory are not found; add those to the registry by hand.
  • *.test.ts, *.bench.ts, and *.d.ts files are skipped, so test-only vials never enter the registry.
  • Scanning is textual, not a full parse — but comments and string contents are blanked before matching, and only decorators / export / default / abstract may sit between @Vial(...) and its class, so a @Vial( inside a comment, string, or JSDoc example does not register. Pathological sources (e.g. regex literals containing an unpaired quote) can still confuse the blanking pass on that line.

See also

  • inject — consumes the generated VialRegistry
  • @Vial — the decorator build scans

← Back to Doctor

Clone this wiki locally