Explicit state. Checked semantics. Ordinary JavaScript.
NiralScript is an independent, compiler-first language for .nrs files. Its own
parser, semantic analyzer, type checker and module linker compile reactive source
into standalone JavaScript ES modules. The compiler has zero dependencies;
generated modules include only the reactive primitives they need.
Documentation | Language Reference | npm Package | Issues
NiralScript is experimental. Version 0.1 is not a promise of language stability, full JavaScript/TypeScript compatibility, or a universal performance advantage.
Requires Node.js 22 or newer. Install the compiler as development tooling:
npm install --save-dev niralscript
npx nrs --helpWithout adding it to a project:
npx --package niralscript nrs --helpThe package is named niralscript; its executable is named nrs.
Using npx nrs without first installing NiralScript can resolve a different npm
package. Use the explicit --package niralscript form for one-off commands.
Create counter.nrs:
input initial: number
state count: number = initial
derive doubled: number = count * 2
export function increment(): number {
count += 1
return doubled
}
Check it and compile it:
npx nrs check counter.nrs --strict
npx nrs compile counter.nrs --strict --out counter.mjsCompilation produces counter.mjs and counter.mjs.map. Create consumer.mjs:
import { createInstance } from "./counter.mjs";
const counter = createInstance({ initial: 2 });
console.log(counter.values.count); // 2
console.log(counter.values.increment()); // 6
console.log(counter.values.count); // 3
const another = createInstance({ initial: 10 });
console.log(another.values.count); // 10: an independent instancenode --enable-source-maps consumer.mjsNo Niral application or separately installed reactive runtime is required.
nrs check <file.nrs>... [--root <directory>] [--global <name>] [--strict] [--json]
nrs compile <file.nrs> --out <file.mjs> [--root <directory>] [--global <name>] [--strict] [--json]
| Command / option | Behavior |
|---|---|
check |
Check one or more entries and their relative imports without executing or emitting code. |
compile |
Compile exactly one entry and its imported module graph into an ESM instance factory and source map. |
--root <directory> |
Existing project root; entries and output paths are relative to it. Defaults to the current directory. |
--out <file.mjs> |
Required for compile. Output parents must already exist inside the root. |
--strict |
Require narrowing of unknown and nullable values before typed use. |
--global <name> |
Explicitly allow a host identifier; repeat for multiple names. Does not provide its runtime value. |
--json |
Emit structured results with ok, entry count and diagnostics. |
--help, -h |
Show usage; also supported after check or compile. |
-- |
Treat the remaining arguments as entry paths. |
Exit codes: 0 success, 1 source/file errors, 2 invalid arguments.
The CLI rejects source paths outside the root and symlinked source paths. Imports
must be explicit relative .nrs paths. Existing output files are never
overwritten; use a new name or deliberately remove your previous generated
artifacts. There is no watch mode or nrs build command.
npx nrs check src/app.nrs src/worker.nrs --strict --json
npx nrs compile src/app.nrs --out dist/app.mjs --global printThe second example requires an existing dist directory and a host-provided
print function when the generated instance is created.
| Area | Implemented capabilities |
|---|---|
| Reactive declarations | Top-level input, state, derive; readonly inputs and derived bindings; immediate state reads. |
| Operators and literals | Arithmetic (+ - * / %), exponent ** (right-associative), bitwise/shift (& | ^ ~ << >> >>>), logical (&& || ??), strict equality/relational, and their compound assignments (**= &= |= ^= <<= >>= >>>=). Numeric literals include decimals, hexadecimal (0xFF), binary (0b1010), octal (0o17), digit separators (1_000_000) and bigint literals (123n). Loose equality (== !=), regex literals and legacy leading-zero numbers remain unsupported. |
| Functions and control flow | Named/anonymous functions, arrows, defaults/rest, spread, destructuring, conditionals, for/of/in, while, do...while, labeled loops with break/continue. |
| Async and errors | Async functions/arrows, await, throw, try/catch/finally; top-level await in standalone modules. |
| Structural type checking | A structural-subtyping checker over primitives, literals, unions, records, arrays, promises, aliases, interfaces, casts and signatures, with generics whose constraints are enforced (not just parsed) plus defaults and recursive types. Control-flow narrowing covers typeof, ===/!== against literal/null/undefined, truthiness, !, &&/` |
| Classes | Typed constructors, generic inheritance, private members, accessors, static blocks, class expressions and a documented decorator subset. |
| Modules | Named exports, explicit relative .nrs imports, type-only imports, isolated module scopes and original-source diagnostics. |
| Runtime contracts | Explicit contract<Type>(value) expressions emit validators for supported finite-data types. |
| Output | Standalone ESM, version-3 source maps and embedded reactive primitives. |
Ordinary type annotations are erased; they do not validate external data.
type Order = { quantity: number, note?: string }
export function readOrder(candidate: unknown): Order {
return contract<Order>(candidate)
}
An explicit contract copies valid data and throws NRS081 with a field path on invalid data. It does not coerce values or add business rules. Recursive, class, function, promise and unknown schemas cannot become runtime contracts.
tainted<T> marks untrusted input. It behaves as T everywhere, but the compiler
raises NRS090 if it reaches a dangerous sink without being laundered. The
enforced sinks are the SQL sink sql.query(...) and the raw-HTML (XSS) emitters
html(...), raw(...), unsafeHTML(...) and renderHTML(...). Launder with
contract<T>(), sanitize(), trust(), or escape() (HTML-escaping, for the
HTML sinks). Propagation is conservative (taint-on-contact): taint flows through
assignment, reassignment, aliasing, member access, array/object construction and
spread, + and coercion, template literals, and the arguments of any
non-laundering call — so injection prevention is checked at compile time rather
than left to convention, with no silent bypass through an unmodeled shape.
function search(term: tainted<string>): string {
return sql.query(contract<string>(term)) // laundered — compiles
}
Hosts can declare whole binding kinds as taint sources with taintSources, and
auto-taint the parameters of exported functions with taintExportedParams. The
latter treats every exported function as a remote-call entry point (an RPC) whose
arguments arrive from the network, so its parameters are untrusted by default and
must be laundered before reaching a sink. load (the SSR data loader) is exempt,
and non-exported helpers are unaffected.
// with { taintExportedParams: true }
export function run(q: string) { return sql.query(q) } // NRS090 — RPC arg is untrusted
export function run(q: string) { return sql.query(sanitize(q)) } // laundered — compiles
import { parseNiralScript } from "niralscript";
import { checkNiralScript } from "niralscript/check";
import { compileNiralScript } from "niralscript/compile";
const source = "state count: number = 1\nderive 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",
});These APIs do not read/write files or execute source. map contains original
source text; do not expose source maps publicly when your source is private.
The parser entry also exports analyzeNiralScript and NiralScriptError.
For imports, provide resolveModule(specifier, importer, location) returning
{ filename, source }. Hosts own filesystem resolution and policy. globals
explicitly permits host identifiers; supply their actual values at runtime.
See the language reference for the complete analyzer policy options.
const instance = createInstance({ initial: 2 }, { print: console.log });
instance.values.increment();
instance.updateInputs({ initial: 8 });- Each instance initializes its own state and imported module graph.
valuesexposes entry bindings; state is writable, while inputs and derives are readonly.updateInputsupdates known inputs without resetting state or rerunning ordinary initializers.- The default runtime is pull-based: derives are evaluated on reads, with instance-wide revision invalidation where caching is needed. Simple derives may be inlined by the compiler.
- Object/array reactivity is shallow. Replace a binding to invalidate dependent values; nested mutation is not automatically reactive.
- There are no default subscriptions or DOM rendering. Host globals are never injected implicitly.
- Top-level await makes
createInstanceasynchronous; await it before readingvalues.
A host can pass a compatible runtime as the third argument:
createInstance(inputs, globals, runtime). It must provide
signal(initial) -> { get, set } and derived(compute) -> { get } and preserve
immediate reads, instance isolation and cycle safety. This is the low-level
integration interface the official adapters are built on.
Niral is the first implemented adapter. It connects .nrs source to Niral's
component compiler, server/client boundaries and fine-grained runtime. .niral
remains the framework's component format; .nrs is the language's source format.
Niral vendors the compiler to retain its own zero-dependency guarantee.
Official adapters now carry the same compiled .nrs modules into other
ecosystems: @niral/react (use a module as a reactive React component),
@niral/vite (import .nrs files directly in any Vite app), and
@niral/next (Next.js App Router Client Components). Each is a thin,
zero-dependency layer over the standalone compiler output. Standalone
compilation still works independently of any framework.
The published Niral VS Code extension
supports .nrs highlighting, snippets, diagnostics, hover, completions and
definitions. Language-server features currently require niral lsp; the
standalone npm compiler does not include its own language server.
This repository is the standalone source of record. The Niral workspace keeps a vendored copy; compiler changes must be synchronized deliberately. The framework-hosted suite currently contains 87 language tests plus 272 framework tests, verified on 11 November 2026. A real npm tarball installation also passed API, CLI, generated runtime, isolation, input-update, source-map and error-path checks. These are verification results, not a stability guarantee.
node niralscript/cli.js --help
node --test tests/niralscript.jsThese source test commands are run from the Niral framework repository; the standalone checkout does not yet contain that framework-dependent suite.
The NiralScript compiler is Apache-2.0, with a Runtime Library Exception. The exception permits emitted runtime code and compiled output to be used and distributed under terms of your choosing without compiler-license attribution requirements for those emitted portions. It does not relicense the compiler.
See LICENSE for the full terms. The Niral framework is separately MIT-licensed; that does not make NiralScript MIT-licensed.
Built by ZyoraLabs.