Why Nola
Nola adds notation for LLM inference to TypeScript. Its name comes from NOtation LAnguage. The aim is to let you write a request where you need its result, using TypeScript types to describe what should come back.
Every new kind of computation gets a notation
Section titled “Every new kind of computation gets a notation”Syntax can make a recurring task easier to read. With async/await, asynchronous work fits into ordinary control flow. JSX gives UI trees a notation that can live beside the code that uses them. Nola applies a similar idea to inference.
An inference request needs an instruction, an expected result type, context and a model. Nola gives each of those a place: an extractor describes the request and type, contextual parameters supply data, and configuration chooses the model.
The compiler derives a schema from the TypeScript type and connects each ask to its source location. The runtime validates the reply before returning it. This lets you work from your existing types while keeping requests close to the code that uses their results.
The notation
Section titled “The notation”Here is a function with two asks:
export infer function analyzeUserRequest(userId: string, .message: string) { const ticketId = ask `ticket id mentioned in the message`: string; const isFraud = ask `does the message look fraudulent`: boolean; return { userId, ticketId, isFraud };}infermarks a function that returns a lazyIntent<T>. Its body runs when you resolve that intent..messageis a contextual parameter. Its value is available to every ask in this invocation.askresolves an intent. An extractor such asask `instruction`: Tasks the model for a value of typeT. Use..`instruction`: Tto create an extractor when it does not appear directly afterask.
Plain TypeScript imports the file directly and awaits the result:
import { analyzeUserRequest } from "./analyze.tsi";
const result = await analyzeUserRequest("user-1", "Ticket TCK-4711: customer reports suspicious activity.");// → { userId: "user-1", ticketId: "TCK-4711", isFraud: true }The function still uses TypeScript for its arguments, local variables and return value. Nola lowers the added syntax to plain TypeScript for the rest of the toolchain. The mental model explains how intents, context and asks fit together.
What you get because it is syntax
Section titled “What you get because it is syntax”Because Nola parses the inference syntax, it can connect the request to your types, context and source locations:
| Because the compiler sees… | You get |
|---|---|
| the type argument on an extractor | A response schema derived from the TypeScript type. The runtime validates replies against it and converts supported wire values, such as ISO date strings, into their TypeScript representation. Changing an interface changes the derived schema; invalid model replies are caught at runtime. |
| which parameters carry a dot | Shared context within an invocation. A second ask still sees .message, and an infer function invoked with ask inherits the caller’s context. |
every ask site |
Editor support tied to the original source: completion inside ${…} holes, go-to-definition from .ts, diagnostics at .tsi positions and breakpoints inside infer functions. |
| the boundary where inference happens | Receipts, telemetry, retries, timeouts, model routing (ask with fast …) and record/replay. These can be configured for the application or adjusted for individual asks. |
how .tsi lowers to plain TS |
Integration with TypeScript, Node and supported bundlers through Nola’s tooling. Built applications run as JavaScript with the runtime and configured providers. |
The comparison pages show the same task in Nola, BAML, the Vercel AI SDK and LangGraph. They are a useful next step if you want to compare how each approach expresses types, prompts and control flow.
What Nola is not
Section titled “What Nola is not”Nola focuses on typed inference. A few boundaries help explain where it fits:
- Workflow orchestration stays in your application. Use
if,for,Promise.alland functions, or combine Nola with a workflow framework when you need durable state and checkpointing. See Nola vs LangGraph. .tsimodules are imported directly. Nola tooling handles their compilation. You do not generate a separate client to call an infer function. See Nola vs BAML.- Prompt wording is yours to maintain. Nola composes prompts from your code and includes the result in receipts; it does not search for better wording.
- The runtime runs in your server process. Configure a provider in
nola.config.ts, either through Nola’s hosted inference service or with your own provider key. The Node loader requires Node ≥ 22.18.
The bet, and what it costs
Section titled “The bet, and what it costs”.tsi is not valid TypeScript, so adopting Nola means adding a compilation step and editor support. Nola uses a vendored Babel parser with a Nola plugin, provides nola check for type-checking, and supplies a loader and bundler plugins. A project that uses only tsc will need these tools to work with .tsi source.
The tradeoff is whether keeping inference beside your TypeScript types and control flow is worth that tooling dependency for your project. The quick start gives you a small example to try before making that decision.
Next: Quick start — scaffold a project and run your first .tsi file offline.