Nix, transpiled to TypeScript.
jinx reads Nix and writes TypeScript that evaluates to the same thing. The output is a program you can read, type-check, debug, profile and run in a browser.
$ jinx compile pkgs/by-name/he/hello/package.nix// Generated by jinx from .../pkgs/by-name/he/hello/package.nix. Do not edit.
import * as nix from "../../../../.jinx/runtime/index.ts";
const here = nix.here(import.meta.url);
const formals$1 = { args: { callPackage: false, fetchurl: false, hello: false, lib: false, nixos: false, stdenv: false, testers: false }, ellipsis: false };
export default nix.lambda((args) => {
const $ = nix.formals(args, formals$1);
const callPackage = nix.arg($, "callPackage");
const fetchurl = nix.arg($, "fetchurl");
...
return nix.app(nix.sel(stdenv, "mkDerivation"), (finalAttrs) => ({
pname: "hello",
version: "2.12.2",
src: nix.thunk(() => nix.app(fetchurl, {
url: nix.thunk(() => nix.str`mirror://gnu/hello/hello-${nix.sel(finalAttrs, "version")}.tar.gz`),
hash: "sha256-...",
})),
...
}));
}, formals$1);$ jinx eval hello.nix
{ drv = "/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv"; out = "/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2"; }
$ nix-instantiate --eval --strict hello.nix
{ drv = "/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv"; out = "/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2"; }Documentation: Usage · Why · Design · Semantics · Verification · Performance · Constraints · Comparisons · The compiled nixpkgs
- nixpkgs
6510408d,
every attribute of
ci/eval/outpaths.nixon x86_64-linux, aarch64-linux and aarch64-darwin, jinx against nix-eval-jobs: 231,777 attributes, 229,063 identical drvPaths, 2,714 failing on both sides, 0 differing, 0 failing on one side only. 14 minutes on 32 cores; nix-eval-jobs takes 26. - nix-conformance: 1,108 of 1,305 cases, every
.drvand NAR case among them (verification). - The compiled nixpkgs is published at fzakaria/jinx-nixpkgs, one commit per tracked revision.
- jinx evaluates nixpkgs in a browser tab from that tree, fetching the modules it needs: hello in 3 s over the network, Nix's drvPaths.
- Per attribute, jinx is 2.5x to 5.1x slower than Nix (performance).
- All 41,313
.nixfiles in nixpkgs parse in 8 s and compile in 28 s.
Nix evaluates nixpkgs with an interpreter, and an interpreter gives you
a value. A compiler gives you a program, and once nixpkgs is a
TypeScript program the tools built for TypeScript work on it: step
through stdenv.mkDerivation in a debugger, profile an evaluation to
see which package the time goes to, bundle the modules one
configuration reaches and nothing else, evaluate a package set in a
browser with no server behind it. A derivation computed by jinx has the
same drvPath as one computed by Nix, so everything built on top of it
stays compatible with the Nix store and with nix-store --realise.
Each .nix file becomes one .ts module of the same name in a tree
that mirrors the source. A lambda is an arrow function, a let is a
block of consts, an attribute set is an object literal, import is
require. Nix is lazy, so the emitted code wraps an expression in
nix.thunk(() => ...) wherever Nix would allocate a thunk and calls
nix.force wherever Nix would force one, by the same rules as Nix's
evaluator, so sharing and evaluation order match.
The runtime (src/runtime/) is the value model, the builtins, string
contexts, and the derivation code that turns an attribute set into an
ATerm, hashes it, and computes output paths. Evaluation writes nothing
to /nix/store; jinx instantiate writes the .drv closure
afterwards for nix-store --realise.
The compiled form is faithful to Nix down to every thunk, which makes
it exact and makes it tedious to write by hand. src/lib is the
hand-written form: a view over a Nix value where attribute reads force,
calls apply, and plain objects, arrays, strings and numbers pass as
arguments. It interoperates with nixpkgs, so a package written in
TypeScript can call stdenv.mkDerivation and depend on anything in
pkgs. examples/hello.ts:
import { loadNixpkgs } from "jinx";
const pkgs = loadNixpkgs("/path/to/nixpkgs", { system: "x86_64-linux" });
const hello = pkgs.stdenv.mkDerivation({
pname: "hello",
version: "2.12.2",
src: pkgs.fetchurl({
url: "mirror://gnu/hello/hello-2.12.2.tar.gz",
hash: "sha256-WpqZbcKSzCTc9BHO6H6S9qrluNE72caBm0x6nc4IGKs=",
}),
meta: { license: pkgs.lib.licenses.gpl3Plus },
});
console.log(hello.drvPath);The same runtime evaluates nixpkgs underneath, so the drvPath is the
one Nix would compute, and jinx instantiate writes it to the store.
$ nix develop # node, tsc, nix, nix-eval-jobs
$ node src/cli/main.ts eval file.nix
$ node src/cli/main.ts compile <nixpkgs> --out work/nixpkgs
$ npm test # unit tests; JINX_NIX_DIFF=1 adds the nix comparisons
$ npm run corpus # tests/corpus against nix-instantiate
$ nix flake check # all of the above, offline, plus nix-conformance
$ nix run .#serve # the site, with nixpkgs in the browser served from the store
$ nix fmt # before committingNo npm dependencies. Node 24 runs the TypeScript directly and tsc
only checks; @types/node is pinned by hash in
nix/types-node.nix.
- psyclyx/fix for the whole-nixpkgs drvPath differential and the clearest written account of derivation hashing outside the Nix source.
- snix (formerly tvix) for a decade of findings on what is hard about reimplementing Nix.
- numtide/nix-conformance for turning Nix's test suite into something any implementation can be held to.
MIT, see LICENSE. The transpiled nixpkgs trees are derived from nixpkgs, which is MIT.