Example Usage of typescript-go with a Simple Go API
You can integrate the TypeScript compiler into a Go application by launching tsgo --api to start a pipe-based server, then connecting via api.NewPipeTransport and api.NewSession to programmatically query symbols, types, and diagnostics.
The microsoft/typescript-go repository is a native Go port of the TypeScript compiler that exposes a programmatic API for embedding type-checking capabilities directly into Go programs. Unlike the traditional Node.js-based tsc, this implementation allows you to spin up a language service and communicate with it via a named pipe transport. This article demonstrates example usage of typescript-go with a simple Go API, covering the complete workflow from server initialization to symbol resolution.
Understanding the Three Operation Modes
The compiler supports three distinct entry points defined in cmd/tsgo/main.go:
- CLI Mode (default) – Compiles
.tsfiles directly viaexecute.CommandLine(), mirroring the traditionaltscexperience. - LSP Mode (
--lsp) – Starts a Language Server Protocol server for editor integration, implemented ininternal/lsp/server.go. - API Mode (
--api) – Starts a pipe-based server that exposes the programmatic API defined ininternal/api/session.go.
When you invoke tsgo --api, the runAPI() function creates a new api.Session and begins listening on a uniquely named pipe generated by generatePipePath().
Starting the API Server
To expose the TypeScript compiler as a service, run the binary with the --api flag:
tsgo --api
This command blocks and prints a pipe path (e.g., \\.\pipe\tsgo-api-abcdef1234567890) to stdout. Internally, cmd/tsgo/api.go initializes the server using the api.Session type, which manages project sessions and snapshots—isolated type-checking contexts that preserve stable handles for symbols and types across wire requests.
Connecting from a Go Client
Once the server is running, any Go process can connect to the named pipe and issue requests. The client workflow involves three main steps: establishing transport, creating a session, and executing API calls.
Establishing the Pipe Transport
Use api.NewPipeTransport() to open the connection using the pipe path printed by the server:
pipePath := `\\.\pipe\tsgo-api-abcdef1234567890`
transport, err := api.NewPipeTransport(pipePath)
if err != nil {
log.Fatalf("cannot open pipe: %v", err)
}
This transport implements the connection layer that speaks the protocol defined in internal/api/protocol.go.
Initializing the Session
Create an API session and wrap it in an async connection handler. The api.NewSession() function accepts project options (or nil for defaults) and returns a *Session that implements the api.Handler interface:
session := api.NewSession(nil, nil) // nil uses default project session
client := conn.NewAsyncConn(transport, session)
go func() {
if err := client.Run(context.Background()); err != nil {
log.Fatalf("API connection error: %v", err)
}
}()
The conn.NewAsyncConn constructor, defined in internal/api/conn.go, runs the request loop in a background goroutine.
Querying Symbols and Types
All API requests are method calls on the session. The typical workflow involves updating the snapshot, retrieving a symbol handle, and resolving its type:
- UpdateSnapshot – Opens a project or syncs files via
handleUpdateSnapshot, returning a snapshot handle. - GetSymbolAtPosition – Returns a stable handle to the symbol at a specific UTF-16 offset.
- GetTypeOfSymbol – Resolves the type associated with the symbol handle.
- TypeToString – Generates a human-readable string representation of the type.
Request and response structs like UpdateSnapshotParams and SymbolResponse are defined in internal/api/protocol.go.
Complete Working Example
The following program demonstrates a minimal client that connects to a running tsgo --api server, opens a project, and prints the type of a symbol at a specific position:
package main
import (
"context"
"log"
"time"
"github.com/microsoft/typescript-go/internal/api"
"github.com/microsoft/typescript-go/internal/api/conn"
)
func main() {
// Replace with the actual pipe printed by `tsgo --api`
pipePath := `\\.\pipe\tsgo-api-abcdef1234567890`
// Connect to the API server
transport, err := api.NewPipeTransport(pipePath)
if err != nil {
log.Fatalf("cannot open pipe: %v", err)
}
// Create a session (no custom options needed for a simple demo)
session := api.NewSession(nil, nil)
// Async connection runs in its own goroutine
client := conn.NewAsyncConn(transport, session)
go func() {
if err := client.Run(context.Background()); err != nil {
log.Fatalf("API connection error: %v", err)
}
}()
// Give the connection a moment to start
time.Sleep(100 * time.Millisecond)
// 1️⃣ Update snapshot – open a project (tsconfig.json) and sync files
upd := &api.UpdateSnapshotParams{
OpenProject: "example/tsconfig.json",
}
updResp, err := session.HandleRequest(context.Background(),
string(api.MethodUpdateSnapshot), upd)
if err != nil {
log.Fatalf("UpdateSnapshot failed: %v", err)
}
snapshotHandle := updResp.(*api.UpdateSnapshotResponse).Snapshot
// 2️⃣ Ask for the symbol at line 5, column 10 of a source file
symReq := &api.GetSymbolAtPositionParams{
Snapshot: snapshotHandle,
Project: updResp.(*api.UpdateSnapshotResponse).Projects[0].Handle,
File: api.NewFileReference("example/src/main.ts"),
Position: 120, // UTF‑16 offset (adjust for your file)
}
symResp, err := session.HandleRequest(context.Background(),
string(api.MethodGetSymbolAtPosition), symReq)
if err != nil {
log.Fatalf("GetSymbolAtPosition failed: %v", err)
}
symHandle := symResp.(*api.SymbolResponse).Id
// 3️⃣ Resolve the symbol’s type
typReq := &api.GetTypeOfSymbolParams{
Snapshot: snapshotHandle,
Project: updResp.(*api.UpdateSnapshotResponse).Projects[0].Handle,
Symbol: symHandle,
}
typResp, err := session.HandleRequest(context.Background(),
string(api.MethodGetTypeOfSymbol), typReq)
if err != nil {
log.Fatalf("GetTypeOfSymbol failed: %v", err)
}
// Print a human‑readable representation of the type
tStrReq := &api.TypeToStringParams{
Snapshot: snapshotHandle,
Project: updResp.(*api.UpdateSnapshotResponse).Projects[0].Handle,
Type: typResp.(*api.TypeResponse).Id,
}
str, err := session.HandleRequest(context.Background(),
string(api.MethodTypeToString), tStrReq)
if err != nil {
log.Fatalf("TypeToString failed: %v", err)
}
log.Printf("Resolved type: %s", str.(string))
}
This example demonstrates how the Session type routes requests through HandleRequest(), using typed handles (Handle[project.Snapshot], symbol IDs) that maintain identity across the pipe boundary.
Key Source Files and Architecture
Understanding the following files helps when extending the API client:
cmd/tsgo/main.go– Entry point that dispatches torunLSP(),runAPI(), or standard compilation.cmd/tsgo/api.go– ImplementsrunAPI(), which instantiates the server session and pipe listener.internal/api/session.go– Core implementation ofapi.Handler, includinghandleUpdateSnapshotandhandleGetSymbolAtPosition.internal/api/protocol.go– Defines all request/response structs (e.g.,GetSymbolAtPositionParams,TypeToStringParams).internal/api/conn.go– Defines theHandlerinterface used by both LSP and API connections.
The architecture uses snapshots (instances of project.Snapshot) to isolate type-checking state. Each snapshot owns its own symbol and type registries, ensuring that handles returned by snapshotHandle() in internal/api/session.go remain valid for subsequent queries.
Summary
tsgo --apistarts a pipe-based server that exposes the TypeScript compiler's language service.api.NewPipeTransport()andapi.NewSession()establish the client connection to the server.- Request structs in
internal/api/protocol.godefine the protocol for updating snapshots, querying symbols, and resolving types. - Handles provide stable references to snapshots, symbols, and types across the wire, managed by registries in the API session.
- The LSP server in
internal/lsp/server.goshares the same snapshot infrastructure, ensuring consistency between programmatic API usage and editor integrations.
Frequently Asked Questions
What is the difference between typescript-go and the standard TypeScript compiler?
typescript-go is a complete rewrite of the TypeScript compiler in Go, providing equivalent type-checking and emit capabilities to the Node.js-based tsc but with significantly faster performance. While tsc runs as a Node.js process, typescript-go compiles to a native binary and exposes additional entry points for programmatic use via the API mode, which is not available in the standard distribution.
How does the pipe transport work in typescript-go?
The pipe transport uses named pipes (Windows) or Unix domain sockets to communicate between the tsgo --api server and client processes. When the server starts, generatePipePath() in internal/api/session.go creates a unique path like \\.\pipe\tsgo-api-<timestamp>-<random>. The client connects via api.NewPipeTransport(), and both sides exchange MessagePack-encoded requests defined in internal/api/protocol.go.
Can I use typescript-go as a drop-in replacement for tsc?
Yes, for standard compilation workflows.** Running tsgo without flags invokes execute.CommandLine() in internal/execute/execute.go, which accepts the same command-line arguments as tsc and produces identical JavaScript output. However, some legacy configuration options and plugin APIs may differ given the port is currently in preview status.
What are snapshots and handles in the API?
Snapshots (project.Snapshot) are immutable type-checking contexts that cache parsed source files and their associated symbol tables. Handles (e.g., Handle[project.Snapshot]) are string-encoded references generated by snapshotHandle() that allow the API to refer to complex objects over the wire without serializing the entire AST. When you call UpdateSnapshot, the server creates a new snapshot and returns its handle, which you then supply to subsequent requests like GetSymbolAtPosition.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →