nola-lang/nola

Nola - an AI-notation TypeScript superset (.tsi): infer functions and typed ask extractors, lowered to plain TS

TypeScript

4

16 commits

updated Sep 17, 2026

See the code
ai
compiler
language
llm
nextjs
structured-output
typescript
vite

README

Nola

ask the LLM in TypeScript

A TypeScript superset where inference is a language feature — ask for typed values, or let the model call your code.

npm version VS Code Marketplace Node >= 22.18 Apache-2.0 Documentation


Nola is a TypeScript superset. Files end in .tsi, and everything you know about TypeScript applies inside them — plus two constructs that make talking to a language model part of the language rather than a library call. The toolchain lowers .tsi to plain TypeScript before tsc, bundlers, Node, or your editor ever see it, the same way JSX is compiled away.

// analyze.tsi
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 };
}
// main.ts — plain TS imports the .tsi directly
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 }

.message is a contextual parameter — its value is shown to the model in every ask of that invocation; userId is a plain argument the model never sees. The TypeScript type is the schema: <boolean> is validated, and a Date comes back as a real Date.

Hosted inference, no account

npm create nola offers a free trial key: 25 hosted runs, no sign-up, no provider account. When they are used up, npx nola-lang billing opens a single page to add prepaid balance to the same key. Hosted inference is never required — point nola.config.ts at any supported provider instead (providers).

Quick start

Requires Node ≥ 22.18. The starter needs no API key — it runs offline from a committed replay ledger.

npm create nola          # prompts for a name, a template, editor + agent setup
cd my-app
npm install
npm start                # extracts typed data from prose, offline

Non-interactive, and for a project you already have:

npm create nola my-app -- --template extract-resume   # pick a template up front
npm create nola -- --add                              # retrofit the current project

--add writes nola.config.ts and merges the packages into your existing package.json; a bare interactive run offers it automatically when it finds one. Templates are starter (the default), empty, and the curated examples. npm create nola-lang is the same command under its full name, and nola init runs the same flow from inside a project.

Prefer the CLI on your PATH?

npm i -g nola-lang       # gives you `nola` anywhere
nola init my-app

In a project, nola-lang belongs in devDependencies — it never ships to production. Your app depends on @nola-lang/runtime and @nola-lang/providers.

Quick start → · Project anatomy → · Add to an existing project →

Editor support

Nola for VS Code gives .tsi files syntax highlighting, diagnostics, hover, completion and go-to-definition through a Volar language server — and a bundled tsserver plugin means plain .ts files that import a .tsi see full types. Breakpoints bind in .tsi source: stepping into an infer function lands on its first statement.

code --install-extension nola.nola-vscode

Scaffolding writes .vscode/launch.json and .vscode/extensions.json for you if you accept the editor step (or pass --ide vscode). Keep .tsi files inside a directory-style tsconfig include (["src"], never ["src/**/*.ts"]) so the editor can admit them.

Editor setup → · VS Code extension →

Agent skill

Nola ships a skill that teaches coding agents to write it — syntax, configuration, patterns and pitfalls, versioned with the release you installed.

nola skill install --agents claude,universal,agents-md
TargetWhat it writes
claude.claude/skills/nola/ — the full skill directory + references (Claude Code)
universal.agents/skills/nola/ — the same, at the open Agent Skills location (Cursor, Copilot, Codex, Gemini CLI, …)
agents-mdAGENTS.md — the skill body inline, for agents that read only that file

The same layout comes out of the community CLI: npx skills add nola-lang/nola. Each copy carries a version stamp, so a later run reports what has gone stale and --force refreshes it. The source is packages/create-nola-lang/skills/nola/; the scaffolder offers the same step as --agents.

The core constructs

ConstructSyntaxMeaning
Nola functioninfer function name(…) { … } — optionally name`instruction`(…)The mounting point: importable from plain TS/JS. Calling it runs nothing; it returns a lazy, thenable Intent<T>. await is legal in the body for ordinary promises.
Contextual parameter.name: TThe argument's value joins the prompt of every ask in the invocation. Plain parameters contribute name and type only. One dot in, two dots out.
Extractor..`instruction`<T>A request to pull a T from context. Supports ${} interpolation; may be constructed anywhere; resolved with ask.
ask operatorask <intent>Resolves an intent the way await resolves a promise. Legal only directly inside an infer function body. ask is a reserved word in .tsi.
Provider routingask with <name> <intent>Resolves one ask through a named provider from nola.config.ts (static identifier; .withProvider() is the dynamic form).
Call intentfn`hint`(…) or a plain call with an extractor argument, fn(..`x`<T>, …)The model fills the extractor-shaped arguments, then the function is called; async results are awaited. Only the hint form carries instruction text.
Prompt template${.member} inside any instruction literalReads the intent's prompt scope (.default, .next, .type, .args, …); the literal then replaces that intent's built-in prompt block.
Intent methods.withRetry(n) · .withProvider() · .withParams() · .withTimeout() · .detached()Per-intent knobs; each clones the intent. The last two exist only on the Intent an infer function returns.

Typed extractors derive a JSON Schema at compile time from string, number, boolean, Date, arrays, inline object literals, string-literal unions and string enums, and named aliases/interfaces from the same file or imported from another (recursive types included); JSDoc comments on members become schema descriptions.

The mental model → · Syntax cheatsheet →

Documentation

Full documentation lives at nola.sh/docs. Its source is in this repo under docs-site/ — the pages are Starlight .mdx, so read them on the site rather than here, but edit them there.

The CLI at a glance

nola init [dir]        # scaffold or retrofit (--template · --add · --ide · --agents)
nola build [dir]       # .tsi → .js + .map + .d.ts into --out
nola run <entry>       # run a .tsi/.ts entry with the loader + nola.config.ts
nola check [dir]       # type-check lowered .tsi and your .ts, mapped back to source
nola declarations      # adjacent <name>.d.tsi.ts so plain tsc resolves .tsi imports
nola skill install     # write the agent skill (.claude/skills, .agents/skills) and/or AGENTS.md

The loader is --import-able (the tsx model), so any entry runs — and debugs — under plain node: node --import nola-lang/register src/main.tsi. That's the development path; build with nola build for deployment.

How it works

source.tsi
  → @nola-lang/parser  (vendored @babel/parser + `nola` plugin) → Nola AST (lossless locations)
  → @nola-lang/compiler (magic-string span replacement)         → plain TS + source map
      ├─ nola build   → esbuild type-strip → dist/*.js + .map + .d.ts (+ a self-configuring nola.config.js)
      ├─ nola run     → same transform in-memory via a module.register loader
      ├─ nola check   → tsc API over lowered TS + your plain .ts → diagnostics remapped to .tsi
      ├─ bundlers     → @nola-lang/vite | webpack | rollup | rolldown | esbuild | rspack | next
      └─ editor       → Volar virtual code → VS Code extension (LSP + tsserver plugin)

Nola is not valid TypeScript, so tsserver/tsc can't parse it directly. Nola owns the parse (a vendored Babel 8 fork with a nola internal plugin) and only ever hands lowered plain TS to tsc — no tsc fork, no tsc plugin. This is the same approach Vue and Svelte take. Nola is server-only in v0: a client bundle that imports .tsi fails at build time.

Packages

All packages are versioned in lockstep.

PackageRole
nola-langThe dev tool (devDependency): nola init / build / run / check / declarations / skill + the nola-lang/register loader hook
create-nola-langnpm create nola-lang — interactive scaffolding (templates and the prompt flow; nola init reuses both)
create-nolanpm create nola — short alias; its bin forwards to create-nola-lang
@nola-lang/runtimeThe app dependency: intent resolution, validator, defineConfig, hooks, receipts, logger
@nola-lang/providersEverything provider-shaped: openai, anthropic, google, mockProvider, resilience combinators, record/replay
@nola-lang/coreIntent<T> / Askable<T> types, provider/config/hook contracts, errors, redaction, fingerprints (dependency-free)
@nola-lang/astNola AST node types, visitors, diagnostic codes
@nola-lang/parser.tsi source → Nola AST with structured diagnostics
@nola-lang/compilerAST → plain TS + source map; schema derivation; companion modules
@nola-lang/node-loadermodule.register hooks + nola.config.ts loading/bundling
@nola-lang/language-coreVolar virtual-code plugin over the lowering (editor-agnostic)
@nola-lang/language-serverThe LSP server (diagnostics, hover, completion, definition)
@nola-lang/typescript-plugintsserver plugin: .ts files importing .tsi get full types and go-to-definition
nola-vscodeThe VS Code extension: highlighting, language server, debug launch snippet
@nola-lang/unplugin + @nola-lang/vite / webpack / rollup / rolldown / esbuild / rspackBundler plugins — one unplugin core, thin named wrappers
@nola-lang/nextwithNola for Next.js (webpack + Turbopack, server-only)
@nola-lang/babel-parserVendored @babel/parser (v8.0.0-rc.6) with the nola plugin — private

Examples

examples/ holds standalone projects covering the canonical LLM-programming tasks — typed extraction, classification over closed label sets, multi-step reasoning, contextual parameters, prompt templates, cross-file and recursive types, and TS control flow orchestrating nola functions. All run on the mock provider, so no API key is needed. Several are also scaffoldable: npm create nola my-app -- --template extract-resume.

npm run build
cd examples/extract-person
node ../../packages/nola-lang/dist/main.js run src/main.ts
# → {"name":"Alice Smith","age":32,"employer":"Acme Corp","job":"staff engineer"}

Examples on the docs site →

Contributing

Node ≥ 22.18, npm workspaces.

npm install
npm run build      # builds the vendored parser first, then tsc -b across packages
npm test           # vitest — whole suite
npm run lint       # biome

Documentation changes are made in docs-site/, not in the site repo — so a syntax change and the docs describing it land in the same commit. The agent-facing language reference lives in packages/create-nola-lang/skills/nola/ and moves with the surface it describes.

License

Apache-2.0

Contributors

mykhailen

16 commits

nola-lang/nola

Nola - an AI-notation TypeScript superset (.tsi): infer functions and typed ask extractors, lowered to plain TS

TypeScript

4

16 commits

updated Sep 17, 2026

See the code
ai
compiler
language
llm
nextjs
structured-output
typescript
vite

README

Nola

ask the LLM in TypeScript

A TypeScript superset where inference is a language feature — ask for typed values, or let the model call your code.

npm version VS Code Marketplace Node >= 22.18 Apache-2.0 Documentation


Nola is a TypeScript superset. Files end in .tsi, and everything you know about TypeScript applies inside them — plus two constructs that make talking to a language model part of the language rather than a library call. The toolchain lowers .tsi to plain TypeScript before tsc, bundlers, Node, or your editor ever see it, the same way JSX is compiled away.

// analyze.tsi
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 };
}
// main.ts — plain TS imports the .tsi directly
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 }

.message is a contextual parameter — its value is shown to the model in every ask of that invocation; userId is a plain argument the model never sees. The TypeScript type is the schema: <boolean> is validated, and a Date comes back as a real Date.

Hosted inference, no account

npm create nola offers a free trial key: 25 hosted runs, no sign-up, no provider account. When they are used up, npx nola-lang billing opens a single page to add prepaid balance to the same key. Hosted inference is never required — point nola.config.ts at any supported provider instead (providers).

Quick start

Requires Node ≥ 22.18. The starter needs no API key — it runs offline from a committed replay ledger.

npm create nola          # prompts for a name, a template, editor + agent setup
cd my-app
npm install
npm start                # extracts typed data from prose, offline

Non-interactive, and for a project you already have:

npm create nola my-app -- --template extract-resume   # pick a template up front
npm create nola -- --add                              # retrofit the current project

--add writes nola.config.ts and merges the packages into your existing package.json; a bare interactive run offers it automatically when it finds one. Templates are starter (the default), empty, and the curated examples. npm create nola-lang is the same command under its full name, and nola init runs the same flow from inside a project.

Prefer the CLI on your PATH?

npm i -g nola-lang       # gives you `nola` anywhere
nola init my-app

In a project, nola-lang belongs in devDependencies — it never ships to production. Your app depends on @nola-lang/runtime and @nola-lang/providers.

Quick start → · Project anatomy → · Add to an existing project →

Editor support

Nola for VS Code gives .tsi files syntax highlighting, diagnostics, hover, completion and go-to-definition through a Volar language server — and a bundled tsserver plugin means plain .ts files that import a .tsi see full types. Breakpoints bind in .tsi source: stepping into an infer function lands on its first statement.

code --install-extension nola.nola-vscode

Scaffolding writes .vscode/launch.json and .vscode/extensions.json for you if you accept the editor step (or pass --ide vscode). Keep .tsi files inside a directory-style tsconfig include (["src"], never ["src/**/*.ts"]) so the editor can admit them.

Editor setup → · VS Code extension →

Agent skill

Nola ships a skill that teaches coding agents to write it — syntax, configuration, patterns and pitfalls, versioned with the release you installed.

nola skill install --agents claude,universal,agents-md
TargetWhat it writes
claude.claude/skills/nola/ — the full skill directory + references (Claude Code)
universal.agents/skills/nola/ — the same, at the open Agent Skills location (Cursor, Copilot, Codex, Gemini CLI, …)
agents-mdAGENTS.md — the skill body inline, for agents that read only that file

The same layout comes out of the community CLI: npx skills add nola-lang/nola. Each copy carries a version stamp, so a later run reports what has gone stale and --force refreshes it. The source is packages/create-nola-lang/skills/nola/; the scaffolder offers the same step as --agents.

The core constructs

ConstructSyntaxMeaning
Nola functioninfer function name(…) { … } — optionally name`instruction`(…)The mounting point: importable from plain TS/JS. Calling it runs nothing; it returns a lazy, thenable Intent<T>. await is legal in the body for ordinary promises.
Contextual parameter.name: TThe argument's value joins the prompt of every ask in the invocation. Plain parameters contribute name and type only. One dot in, two dots out.
Extractor..`instruction`<T>A request to pull a T from context. Supports ${} interpolation; may be constructed anywhere; resolved with ask.
ask operatorask <intent>Resolves an intent the way await resolves a promise. Legal only directly inside an infer function body. ask is a reserved word in .tsi.
Provider routingask with <name> <intent>Resolves one ask through a named provider from nola.config.ts (static identifier; .withProvider() is the dynamic form).
Call intentfn`hint`(…) or a plain call with an extractor argument, fn(..`x`<T>, …)The model fills the extractor-shaped arguments, then the function is called; async results are awaited. Only the hint form carries instruction text.
Prompt template${.member} inside any instruction literalReads the intent's prompt scope (.default, .next, .type, .args, …); the literal then replaces that intent's built-in prompt block.
Intent methods.withRetry(n) · .withProvider() · .withParams() · .withTimeout() · .detached()Per-intent knobs; each clones the intent. The last two exist only on the Intent an infer function returns.

Typed extractors derive a JSON Schema at compile time from string, number, boolean, Date, arrays, inline object literals, string-literal unions and string enums, and named aliases/interfaces from the same file or imported from another (recursive types included); JSDoc comments on members become schema descriptions.

The mental model → · Syntax cheatsheet →

Documentation

Full documentation lives at nola.sh/docs. Its source is in this repo under docs-site/ — the pages are Starlight .mdx, so read them on the site rather than here, but edit them there.

The CLI at a glance

nola init [dir]        # scaffold or retrofit (--template · --add · --ide · --agents)
nola build [dir]       # .tsi → .js + .map + .d.ts into --out
nola run <entry>       # run a .tsi/.ts entry with the loader + nola.config.ts
nola check [dir]       # type-check lowered .tsi and your .ts, mapped back to source
nola declarations      # adjacent <name>.d.tsi.ts so plain tsc resolves .tsi imports
nola skill install     # write the agent skill (.claude/skills, .agents/skills) and/or AGENTS.md

The loader is --import-able (the tsx model), so any entry runs — and debugs — under plain node: node --import nola-lang/register src/main.tsi. That's the development path; build with nola build for deployment.

How it works

source.tsi
  → @nola-lang/parser  (vendored @babel/parser + `nola` plugin) → Nola AST (lossless locations)
  → @nola-lang/compiler (magic-string span replacement)         → plain TS + source map
      ├─ nola build   → esbuild type-strip → dist/*.js + .map + .d.ts (+ a self-configuring nola.config.js)
      ├─ nola run     → same transform in-memory via a module.register loader
      ├─ nola check   → tsc API over lowered TS + your plain .ts → diagnostics remapped to .tsi
      ├─ bundlers     → @nola-lang/vite | webpack | rollup | rolldown | esbuild | rspack | next
      └─ editor       → Volar virtual code → VS Code extension (LSP + tsserver plugin)

Nola is not valid TypeScript, so tsserver/tsc can't parse it directly. Nola owns the parse (a vendored Babel 8 fork with a nola internal plugin) and only ever hands lowered plain TS to tsc — no tsc fork, no tsc plugin. This is the same approach Vue and Svelte take. Nola is server-only in v0: a client bundle that imports .tsi fails at build time.

Packages

All packages are versioned in lockstep.

PackageRole
nola-langThe dev tool (devDependency): nola init / build / run / check / declarations / skill + the nola-lang/register loader hook
create-nola-langnpm create nola-lang — interactive scaffolding (templates and the prompt flow; nola init reuses both)
create-nolanpm create nola — short alias; its bin forwards to create-nola-lang
@nola-lang/runtimeThe app dependency: intent resolution, validator, defineConfig, hooks, receipts, logger
@nola-lang/providersEverything provider-shaped: openai, anthropic, google, mockProvider, resilience combinators, record/replay
@nola-lang/coreIntent<T> / Askable<T> types, provider/config/hook contracts, errors, redaction, fingerprints (dependency-free)
@nola-lang/astNola AST node types, visitors, diagnostic codes
@nola-lang/parser.tsi source → Nola AST with structured diagnostics
@nola-lang/compilerAST → plain TS + source map; schema derivation; companion modules
@nola-lang/node-loadermodule.register hooks + nola.config.ts loading/bundling
@nola-lang/language-coreVolar virtual-code plugin over the lowering (editor-agnostic)
@nola-lang/language-serverThe LSP server (diagnostics, hover, completion, definition)
@nola-lang/typescript-plugintsserver plugin: .ts files importing .tsi get full types and go-to-definition
nola-vscodeThe VS Code extension: highlighting, language server, debug launch snippet
@nola-lang/unplugin + @nola-lang/vite / webpack / rollup / rolldown / esbuild / rspackBundler plugins — one unplugin core, thin named wrappers
@nola-lang/nextwithNola for Next.js (webpack + Turbopack, server-only)
@nola-lang/babel-parserVendored @babel/parser (v8.0.0-rc.6) with the nola plugin — private

Examples

examples/ holds standalone projects covering the canonical LLM-programming tasks — typed extraction, classification over closed label sets, multi-step reasoning, contextual parameters, prompt templates, cross-file and recursive types, and TS control flow orchestrating nola functions. All run on the mock provider, so no API key is needed. Several are also scaffoldable: npm create nola my-app -- --template extract-resume.

npm run build
cd examples/extract-person
node ../../packages/nola-lang/dist/main.js run src/main.ts
# → {"name":"Alice Smith","age":32,"employer":"Acme Corp","job":"staff engineer"}

Examples on the docs site →

Contributing

Node ≥ 22.18, npm workspaces.

npm install
npm run build      # builds the vendored parser first, then tsc -b across packages
npm test           # vitest — whole suite
npm run lint       # biome

Documentation changes are made in docs-site/, not in the site repo — so a syntax change and the docs describing it land in the same commit. The agent-facing language reference lives in packages/create-nola-lang/skills/nola/ and moves with the surface it describes.

License

Apache-2.0

Contributors

mykhailen

16 commits

Languages

TypeScript

90.3%

MDX

8.6%