Docs

NiralScript

Install NiralScript, check and compile .nrs files, use compiler APIs and standalone reactive modules, and understand runtime behavior and licensing.

On this page 7 sections

NiralScript is an independent, experimental language with its own zero-dependency compiler. Niral is its first framework integration. The language uses .nrs files; Niral components still use .niral.

Install the standalone compiler

Requires Node.js 22 or newer. In a standalone project:

npm install --save-dev niralscript
npx nrs --help

For a one-off command without installing into your project:

npx --package niralscript nrs --help

The package is named niralscript; the executable is nrs. Niral itself vendors the compiler and does not need an additional runtime dependency.

Check and compile a module

Save this as counter.nrs:

input initial: number
state count: number = initial
derive doubled: number = count * 2

export function increment(): number {
  count += 1
  return doubled
}
npx nrs check counter.nrs --strict
npx nrs compile counter.nrs --strict --out counter.mjs

Compilation emits counter.mjs and counter.mjs.map. Neither command executes your source. The compiled module embeds the reactive primitives it needs; there is no separate runtime package to install.

Save this consumer as consumer.mjs:

import { createInstance } from "./counter.mjs";

const counter = createInstance({ initial: 2 });
console.log(counter.values.count);       // 2
console.log(counter.values.increment()); // 6
counter.updateInputs({ initial: 8 });
console.log(counter.values.initial);     // 8
console.log(counter.values.count);       // 3
node --enable-source-maps consumer.mjs

Command reference

nrs check <file.nrs>... [--root <directory>] [--global <name>] [--strict] [--json]
nrs compile <file.nrs> --out <file.mjs> [--root <directory>] [--global <name>] [--strict] [--json]
  • check accepts one or more entry files and checks their imported module graphs.
  • compile accepts exactly one entry and requires --out ending in .mjs.
  • --root selects an existing project directory; input/output paths are relative

to it. The default is the current directory.

  • --strict requires narrowing of unknown and nullable values before typed use.
  • Repeat --global name to allow explicit host identifiers. Supply actual values

in the second argument of createInstance; the option does not inject them.

  • --json emits { ok, entries, diagnostics } with original-source locations.
  • --help or -h shows usage. -- treats subsequent arguments as entry paths.

Exit codes: 0 success, 1 source/file errors, 2 invalid arguments. Imports must use explicit relative .nrs paths and stay inside the root. Source symlinks are rejected. Output parent directories must already exist. Existing output files are never overwritten. There is no watch mode or nrs build command.

Compiler APIs

import { parseNiralScript } from "niralscript";
import { checkNiralScript } from "niralscript/check";
import { compileNiralScript } from "niralscript/compile";

const source = "state count: number = 1\n derive doubled = count * 2";
const program = parseNiralScript(source, "counter.nrs");
checkNiralScript(source, "counter.nrs", { strictTypes: true });
const { code, map, topLevelAwait } = compileNiralScript(source, "counter.nrs", {
  strictTypes: true, file: "counter.mjs",
});

APIs do not read/write files or execute source. For imports, hosts provide resolveModule(specifier, importer, location), returning { filename, source }. The host owns filesystem policy. Source maps include original source text; do not expose them publicly when source must remain private.

Runtime behavior

Each createInstance(inputs, globals, runtime?) initializes independent state and imported module scopes. values exposes entry bindings; state values are writable, while inputs and derives are readonly. updateInputs updates known inputs without resetting state or rerunning ordinary initializers.

The embedded runtime is pull-based. Derives are read on demand, using instance-wide revision invalidation where caching is needed; simple derives can be inlined. Object/array reactivity is shallow: replace a binding to invalidate dependent values. Nested mutations do not automatically trigger updates. There are no subscriptions or DOM rendering in the default runtime.

Top-level await makes the factory asynchronous; await createInstance before reading its values. Hosts can inject a runtime providing signal(initial) -> { get, set } and derived(compute) -> { get } as the third argument. Host integration must preserve immediate reads and instance isolation.

Types and external data

Gradual annotations check primitives, unions, records, arrays, aliases, interfaces, function signatures, generics and recursive types. Annotations are erased: they do not validate external data at runtime.

type Order = { quantity: number, note?: string }

export function readOrder(candidate: unknown): Order {
  return contract<Order>(candidate)
}

Explicit contracts copy valid data and throw NRS081 with a field path on invalid data. They do not coerce values. Recursive, class, function, promise and unknown schemas cannot become runtime contracts.

Integrations and license

Niral is the first-class framework integration, and you can also consume compiled .nrs modules in existing React, Vite and Next.js projects through the official adapters: Use in React, Use with Vite and Use in Next.js. Full JavaScript/TypeScript source compatibility is not a goal — .nrs files hold reactive logic. The published Niral VS Code extension supports standalone files; language-server features require niral lsp.

NiralScript is Apache-2.0 with a Runtime Library Exception. Emitted runtime code and compiled output may be distributed under your own terms; the compiler remains Apache-2.0. The Niral framework is separately MIT-licensed.

Full license | Language reference | Standalone source | NiralScript overview and playground