Xantham.TypeScript.Wire 0.2.0

dotnet add package Xantham.TypeScript.Wire --version 0.2.0
                    
NuGet\Install-Package Xantham.TypeScript.Wire -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Xantham.TypeScript.Wire" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Xantham.TypeScript.Wire" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Xantham.TypeScript.Wire" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Xantham.TypeScript.Wire --version 0.2.0
                    
#r "nuget: Xantham.TypeScript.Wire, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Xantham.TypeScript.Wire@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Xantham.TypeScript.Wire&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Xantham.TypeScript.Wire&version=0.2.0
                    
Install as a Cake Tool

Xantham.TypeScript.Wire

An F# client for the TypeScript 7 compiler's API server — the Go tsc binary run as tsc --api. It speaks the compiler's own msgpack protocol over stdio, and reads the binary AST it returns without going through JSON.

  • The full API surface, generated from the compiler's shipped schema: 142 synchronous calls, the same set again as Async, and typed records for every parameter and response.
  • The binary AST, read in place. A node is a struct over the blob and an index, not an object graph, and the typed layer gives each one a tag — Node<FunctionDeclaration> — so narrowing is a compile-time question. Generated from ast.json, so the 351 kinds and their child slots come from the compiler rather than from hand-written constants.
  • Session<'T>, which binds the snapshot and project that 126 of those 142 calls repeat, so a call site carries only what varies.
  • A batching mailbox that collects overlapping calls into one batchRequests round trip. Under load it runs 2.1–2.3× the serial path; a lone caller pays nothing for it.
  • A virtual filesystem, so the compiler can be pointed at sources that exist only in memory.

Targets net10.0, net8.0 and netstandard2.1.

Getting started

The compiler is not bundled — install the typescript npm package whose version matches this one, since the protocol is unversioned and both sides must come from the same tree.

open Xantham.TypeScript.Wire
open Xantham.TypeScript.Wire.Proto

// Finds the platform's tsc executable under a directory containing node_modules.
let exe = (Tsc.locate "./my-project").Value

use channel = new TscChannel(exe, "./my-project")
Api.initialize channel |> ignore

// Records whose fields are all optional carry a `Default` - copy-update the ones you mean.
let program =
    Api.createProgram channel
        { CreateProgramParams.Default with
            RootFiles = ValueSome [| DocumentIdentifier.FileName "./my-project/index.d.ts" |] }

let project = program.Project.Value.Id

match Api.getSourceFile channel { Snapshot = program.Snapshot; Project = project; File = DocumentIdentifier.FileName "./my-project/index.d.ts" } with
| ValueSome ast -> printfn $"%d{ast.NodeCount} nodes"
| ValueNone -> ()

Every method is also an extension member on the channel, with a second overload taking the parameter record's fields directly, so the channel need not be threaded through by hand:

let ast =
    channel.getSourceFile(
        snapshot = program.Snapshot,
        project = project,
        file = DocumentIdentifier.FileName "./my-project/index.d.ts")

Sessions

Most of the API resolves everything against a snapshot and a project, and restating that pair at every call is most of what a call site says. Session<'T> binds it once and re-exposes the methods without it:

let session = channel.Session program   // the createProgram response already is the pair

let ast = session.getSourceFile(DocumentIdentifier.FileName "./my-project/index.d.ts")
let symbol = session.getSymbolAtPosition(DocumentIdentifier.FileName "./my-project/index.d.ts", 42)

The pair is data rather than identity, so a session moves: session.WithSnapshot updated.Snapshot after a file changed, session.WithProject "tsconfig.build", and session.ForSymbol symbol for the project a symbol was first observed in. The calls that precede a snapshot and so cannot take one — updateSnapshot, createProgram, the transpile* family — live on session.Sessionless.

Session<TscMailbox> is the same members returning Async, so the transport is chosen where the session is built and nowhere else.

TscMailbox is the same surface asynchronously, and batches whatever overlaps:

use mailbox = new TscMailbox(exe, "./my-project")
Async.RunSynchronously(AsyncApi.initialize mailbox) |> ignore

let! names = mailbox.getSourceFileNames(snapshot, project)

Reading the AST

Navigate through the typed layer rather than raw indexes — see docs/wire-navigation.md:

open Xantham.TypeScript.Wire.Patterns

for statement in SourceFile.statements (Node.root ast) do
    match statement with
    | FunctionDeclaration declaration ->
        // `name` is a Node<Identifier> voption - a function declaration need not have one.
        match FunctionDeclaration.name declaration with
        | ValueSome name -> printfn $"function %A{Identifier.text name}"
        | ValueNone -> ()
    | _ -> ()

A Node<'Tag> is a struct over the blob and an index. The tags inherit each other exactly when one's kinds are a subset of the other's, so 'Tag :> Expression is the compile-time form of isExpression; narrowing is a [<return: Struct>] active pattern that allocates nothing.

Serving files from memory

let fs =
    { VirtualFileSystem.Default with
        ReadFile = ValueSome(fun path -> if path = "/virtual.ts" then Content "export const a = 1" else FallBack)
        FileExists = ValueSome(fun path -> if path = "/virtual.ts" then ValueSome true else ValueNone) }

use channel = new TscChannel(exe, cwd, VirtualFileSystem.callbacks fs)

FallBack and ValueNone mean "not answered, read the real filesystem"; Missing means the file does not exist and resolution stops there. The distinction changes module resolution, so it is in the types rather than in a comment.

Documentation

  • docs/wire-navigation.md — the typed layer, tags, views and the escape hatches.
  • docs/wire-hand-written.md — every fact in the pipeline transcribed from upstream rather than derived from its schema, with how to update each one.
  • docs/plans/tsgo-protocol.md — the wire protocol itself, verified against live byte traces.

Licensed under Apache-2.0.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 39 9/1/2026