Skip to Content
📖 Guide DocumentsTTSCProgrammatic API

Programmatic API (ttsc JS class)

For tool authors that need to drive ttsc from JavaScript or TypeScript.

This page is for embedders, anyone wrapping ttsc inside a Node program (test harnesses, dev-server middleware, playground hosts, code-generation pipelines). Day-to-day users should keep using the ttsc, ttsx, and ttscserver CLIs instead.

For browser embedding (running ttsc inside a Web Worker / WebAssembly), see the WASM guide and the @ttsc/playground package built on top of it.

When to use

Reach for the programmatic API when you need to:

  • Compile or type-check a project from inside another Node tool and consume the result as structured data, not parsed terminal output.
  • Capture every emitted text artifact in memory without writing JS or .d.ts files into the user’s project tree.
  • Pay the lazy plugin-build cost ahead of time in CI warmup steps.
  • Manage the ttsc cache directory from your tool’s own lifecycle.

If you only need to run the same command a user would type, shell out to npx ttsc instead. The CLI is the public, stable surface for that.

Install

npm install ttsc typescript

Both packages are runtime dependencies. typescript ships the TypeScript-Go binary that ttsc drives.

Minimal example

import { TtscCompiler } from "ttsc"; const compiler = new TtscCompiler({ cwd: "/abs/path/to/project", // tsconfig: "tsconfig.build.json", // optional; auto-discovered when omitted }); const result = compiler.compile(); if (result.type === "success") { for (const [path, text] of Object.entries(result.output)) { // path is project-relative ("dist/index.js", "dist/index.d.ts", ...) console.log(path, text.length); } } else if (result.type === "failure") { for (const diag of result.diagnostics) { console.error(`${diag.file}:${diag.line}:${diag.character} ${diag.messageText}`); } } else { // type === "exception" console.error("ttsc host failed:", result.error); }

Lifecycle

TtscCompiler is construct-once, reuse. The constructor captures one project context (working directory, tsconfig, plugin list, cache root, env) and every operation runs against that context. The context is intentionally not replaceable per call, create another instance to compile a different project.

// One compiler per project, kept around across calls. const compiler = new TtscCompiler({ cwd: projectDir }); await ensurePluginsReady(compiler); // optional warmup const first = compiler.compile(); const second = compiler.compile(); // safe to call again

The context is defensively copied at construction, so mutating the original options object after the fact has no effect on the instance.

Operations

compile()

Runs the configured project through the TypeScript-Go pipeline and returns the structured result as a discriminated union.

For projects with no plugins, ttsc uses its in-process TypeScript-Go host and captures every WriteFile callback into the output map. For projects with plugins, ttsc runs the plugin pipeline against a temporary output directory under the configured cache root, reads the generated text back into memory, and removes the temp directory before returning.

Result shape (ITtscCompilerResult):

type ITtscCompilerResult = | { type: "success"; diagnostics?: ITtscCompilerDiagnostic[]; output: Record<string, string> } | { type: "failure"; diagnostics: ITtscCompilerDiagnostic[]; output: Record<string, string> } | { type: "exception"; error: unknown };
  • success: TypeScript-Go completed with no error-class diagnostics. The output map contains every text artifact (JS, .d.ts, source maps, declaration maps). diagnostics is omitted when empty; warnings appear here when present.
  • failure: Compilation produced error-class diagnostics or a plugin failed after ttsc could establish a build result. Before returning a plugin-backed failure, ttsc runs an independent no-emit TypeScript check so pure TypeScript errors remain in diagnostics; output may still be partial.
  • exception: The host or plugin setup failed before ttsc could establish a build result (cache lockup, missing binary, invalid project setup, etc.). Treat the error field as opaque diagnostic material; do not assume it’s an Error instance.

transform()

Source-to-source. Returns a typescript map (project-relative path → TypeScript text), never JS or .d.ts.

const t = compiler.transform(); if (t.type === "success") { // t.typescript["src/index.ts"] holds the post-transform source }

When no transform-stage plugin is configured, the map contains the unmodified files as TypeScript-Go loaded them. With a transform plugin (e.g. typia’s), the map contains the rewritten text.

prepare()

Builds every configured Go source plugin into the cache up front. Use this in CI warmup steps so the first user-triggered compile doesn’t pay the plugin build cost.

compiler.prepare(); // returns compiled binary paths (debug aid)

prepare() does not run diagnostics, does not create a Program, and does not emit files. It is purely a cache warmer.

clean()

Removes the cache directories owned by this compiler’s context. Returns the list of paths that were actually removed (existing directories only).

compiler.clean();

When the context sets an explicit cacheDir, clean() removes that directory wholesale (plus the legacy node_modules/.ttsc and .ttsc caches) — the same contract as ttsc clean --cache-dir <dir> on the CLI. An explicit cacheDir names a directory dedicated to ttsc, so nothing is left behind.

Otherwise — the default workspace-local node_modules/.cache/ttsc, or a root resolved from TTSC_CACHE_DIR — it removes only the ttsc-owned pieces:

  1. the compiled plugin binaries (<cache-root>/plugins);
  2. the ttsc-owned Go object cache (<cache-root>/go-build, plus an explicit TTSC_GO_CACHE_DIR when it points elsewhere);
  3. the legacy project-local caches (node_modules/.ttsc, .ttsc).

In that case the parent cache root itself is never removed (it may be shared with other tools), and a user-provided GOCACHE is treated as external and left untouched.

Resident transform service (TtscService)

TtscService is the resident, incremental counterpart to transform(). Where transform() reloads and recompiles the whole project on every call, TtscService compiles the project once into a warm host and then answers per-file requests from it. A single process that transforms many files (a watch server, an editor, a long-running codegen tool) pays the project compile once.

import { TtscService } from "ttsc"; const service = new TtscService({ cwd: "/abs/path/to/project" }); try { // The file's post-plugin TypeScript, or undefined when it is not in the program. const code = await service.transformFile("src/index.ts"); // Apply an unsaved edit and re-transform; a later transformFile reflects it. const ok = await service.updateFile("src/index.ts", "export const x: number = 2;\n"); const next = await service.transformFile("src/index.ts"); } finally { service.dispose(); }

The constructor takes the same ITtscCompilerContext and launches the host synchronously; the host compiles in the background, so the first transformFile resolves once that compile lands.

  • transformFile(name)Promise<string | undefined>. The file’s post-plugin TypeScript source. A relative name resolves against the project root. Resolves undefined when the file is outside the compiled program. Rejects when the project failed to compile, carrying the host’s diagnostics in the rejection.
  • updateFile(name, content)Promise<boolean>. Apply new in-memory content for a file and re-transform. Returns whether the rebuild succeeded; false means the edit did not compile and the previous transform is still in effect.
  • dispose(). Terminate the resident host and reject any in-flight requests. Always call it (use try/finally) so the host process does not leak.

TtscService runs through the linked-plugin host, the only host that serves resident requests, so the project must declare at least one transform-stage plugin. It is source-to-source: it returns TypeScript, never JS or .d.ts (use compile() for emit).

The service is resident within one process. It does not share a host across separate worker processes (for example a Metro worker pool); each process that constructs a TtscService runs its own host. A shared cross-process daemon is tracked in #255.

Context options

ITtscCompilerContext:

FieldPurpose
cwdWorking directory. Defaults to process.cwd().
tsconfigPath to a project config file. Relative paths resolve against cwd. Omit to auto-discover the nearest tsconfig.
projectRootOverride the project root used for plugin resolution and cache anchoring. Most callers leave this unset.
binaryPin a specific TypeScript-Go binary. For test rigs and embedded toolchains; normal callers omit it.
envExtra environment variables merged over process.env for child compiler processes.
cacheDirRoot for compiled ttsc artifacts. Relative paths resolve against cwd. Defaults to TTSC_CACHE_DIR, else the workspace-local node_modules/.cache/ttsc.
pluginsundefined = read project plugins. false = ignore project plugins for this instance. Array = explicit entries.

Diagnostics

ITtscCompilerDiagnostic is a flat, JSON-friendly shape independent of TypeScript-Go’s internal Go types.

interface ITtscCompilerDiagnostic { file: string | null; // null for global diagnostics category: "error" | "warning" | "suggestion" | "message"; code: number | string; // numeric for TS, string for plugin codes start?: number; // 0-based char offset length?: number; line?: number; // 1-based, display convention character?: number; // 1-based, display convention messageText: string; // chains are pre-flattened }

When the host process exits non-zero but produced no parsable diagnostics, ttsc synthesizes a { code: "TTSC_PROCESS", category: "error" } entry that carries the captured stderr/stdout. The same entry is seeded when a plugin fails without reporting structured diagnostics and ttsc recovers pure TypeScript errors through its independent no-emit check, so the recovered type errors never displace the plugin failure. Embedders can branch on code to detect this synthesized case.

Plugins

Project plugins are taken from compilerOptions.plugins in the project’s tsconfig and from installed package markers. The constructor’s plugins field lets embedders override that resolution for one compiler instance:

// Run the project without any plugins at all. const bare = new TtscCompiler({ cwd, plugins: false }); // Run the project with a different plugin set than the tsconfig requests. const lintOnly = new TtscCompiler({ cwd, plugins: [{ name: "@ttsc/lint" }], });

See ITtscProjectPluginConfig for the entry shape and Plugin authoring for how to ship a plugin package.

Patterns

Long-lived service

Build one TtscCompiler per watched project at startup. Call prepare() once, then compile() whenever you need a fresh build; each compile() reloads the project, so the construction cost is just descriptor wiring, not the compile itself.

When the long-lived tool transforms the same project’s files repeatedly rather than emitting full builds, use TtscService instead: it keeps the host warm and answers per-file transform requests without recompiling per call.

CI warmup

const compiler = new TtscCompiler({ cwd }); compiler.prepare(); // pre-build native plugins into cache

Pair this with caching TTSC_CACHE_DIR between CI runs to skip the rebuild on subsequent invocations. That root contains both source-plugin binaries and, unless TTSC_GO_CACHE_DIR or GOCACHE is set, the Go object cache used while building them.

Driving a Web Worker / Browser

TtscCompiler is a Node API, it spawns native processes. For browser embedding, use @ttsc/wasm directly or the higher-level @ttsc/playground package.

Stability

TtscCompiler, TtscService, the result types, and ITtscCompilerContext are the public programmatic surface. CLI launchers, binary resolution helpers, project config parsing utilities, and native build helpers stay internal, depending on them will break. The package exports field enforces this.

Last updated on