Nix modules in TypeScript, typed by your own configurations.
Typeflake reads the option graph of your NixOS, nix-darwin and Home Manager
configurations with
nix eval and generates TypeScript types from it. You write modules in
Effect TypeScript against those types, and Typeflake renders each one to an ordinary
.nix file next to it. That file builds with Nix alone.
Early alpha
The probe, the type generator, module rendering and the CLI work. A first port of
infra's app-stores module builds the same drvPath as the original on
srv-2 and m1.
import { mkIf, nix, NixOS, Options, path } from "typeflake"
export default NixOS.module({
options: {
appStores: {
enable: Options.enable("asc and gplay with the Urbs UG store credentials"),
},
},
config: ({ config, lib, pkgs }) => {
const secrets = config.sops.secrets
const asc = pkgs.writeShellApplication({
name: "asc",
text: nix`
ASC_PRIVATE_KEY=$(cat ${secrets["app-store-connect/api-key"].path})
exec ${lib.getExe(pkgs.callPackage(path("./asc-package.nix"), {}))} "$@"
`,
})
return mkIf(config.appStores.enable, {
sops: {
secrets: {
"app-store-connect/api-key": {
owner: config.identity.primaryUser,
mode: "0400",
},
},
},
environment: { systemPackages: [asc] },
})
},
})
{ config, lib, pkgs, ... }:
{
options.appStores.enable = lib.mkEnableOption "asc and gplay with the Urbs UG store credentials";
config = lib.mkIf config.appStores.enable {
sops.secrets."app-store-connect/api-key" = {
owner = config.identity.primaryUser;
mode = "0400";
};
environment.systemPackages = [
(pkgs.writeShellApplication {
name = "asc";
text = ''
ASC_PRIVATE_KEY=$(cat ${config.sops.secrets."app-store-connect/api-key".path})
exec ${lib.getExe (pkgs.callPackage ./asc-package.nix { })} "$@"
'';
})
];
};
}
infra's app-stores module, condensed as in the design notes. It typechecks against srv-2's generated types, and the Nix is the renderer's own output. Point at a highlighted part on either side to find its counterpart. Tap a highlighted part on either side to find its counterpart.
Typeflake calls config once, with proxies for config,
lib and pkgs. Property access builds Nix syntax, and nothing
is evaluated in Node. The rendered function takes the arguments the body uses.
Options.enable declares the option, like lib.mkEnableOption.
The types of a module's own options are inferred locally, so changing one needs no new
probe.
config.sops.secrets has the type sops-nix's options give it. A TypeScript
variable holds an expression, so the Nix has the expression where the module used the
variable.
pkgs.writeShellApplication renders as the same call. asc holds
that call, so it lands where asc is used, in systemPackages.
nix`…` dedents exactly like a Nix '' string, so a script moves
between the two unchanged.
path("./asc-package.nix") is a Nix path literal, relative to the module's
file.
TypeScript can't branch on a Nix<boolean>, so conditions go through
mkIf or ifThenElse. A typo inside mkIf fails like
one outside it.
sops.secrets is an attribute set of submodules. Each entry is typed with
sops-nix's secret options.
identity.primaryUser is an option infra declares itself. It is typed like
any option from nixpkgs.
sops-nix wants the mode as a string. Written as mode: 400, this line fails
with TS2322.
systemPackages is a list of packages. Adding the string
"asc" to it fails with TS2322.
One host, 41,038 options
srv-2 is a NixOS server in infra, the repository Typeflake is first built for. Its
configuration has 41,038 options once Home Manager's are counted, and 35,962 of them
are visible in the manual. They come from nixpkgs, Home Manager, disko, sops-nix and
infra's own modules, at the versions
flake.lock pins.
Typeflake turns them into 5.9 MB of TypeScript, leaving out only the hidden ones. A typo in an nginx location, a sops-nix mode written as a number or a Home Manager state version that doesn't exist then fails in the editor, before Nix evaluates anything.
| srv-2, NixOS | m1, nix-darwin | |
|---|---|---|
| Probe | 11 s, 940 MB | 3.4 s, 445 MB |
| Options, Home Manager included | 41,038 (35,962 visible) | Not measured |
| Probe output | 32 MB of JSON | 11 MB of JSON |
| Generated types | 5.9 MB, 6,400 interfaces | 1.4 MB |
| Typecheck of a sample module | 0.48 s, 59 MB | Not measured |
- nixpkgs 29,027
- Home Manager 6,061
- Other flake inputs 5,792 (disko 5,473, nix-infra-modules 267, sops-nix 46, vscode-server 6)
- infra itself 158
Every block is a namespace in srv-2's option graph.
Point at one to read its path and size. Tap one to read its path and size. With the map focused, the arrow keys step through the namespaces.
Show the counts as a table
| Namespace | Options | Largest inside |
|---|---|---|
services | 23,683 | services.prometheus 2,160, services.xserver 382, services.tor 373 |
home-manager | 6,519 | home-manager.users.<name>.programs 3,862, home-manager.users.<name>.services 1,211, home-manager.users.<name>.accounts 322 |
disko | 5,472 | disko.devices.disk 1,913, disko.devices.mdadm 1,912, disko.devices.zpool 810 |
programs | 971 | programs.zsh 45 |
boot | 840 | boot.initrd 540, boot.loader 149, boot 38 |
systemd | 667 | systemd.user 197, systemd.network 140, systemd.services 49 |
security | 479 | security.pam 156, security.acme 72 |
networking | 434 | networking 49, networking.networkmanager 42, networking.interfaces 39 |
hardware | 406 | hardware.nvidia 27, hardware.cpu 25, hardware.facter 25 |
vps | 309 | vps.services 275 |
virtualisation | 277 | virtualisation.xen 56, virtualisation.libvirtd 42, virtualisation.oci-containers 33 |
59 smaller namespaces | 981 | system 142, users 140, nix 85 |
| All | 41,038 | 35,962 visible |
Ten mistakes that don't compile
These come from the spike that measured the generated types before modules existed: a
configuration passed to a function that takes Shape<NixosOptions>.
It sets sshd's freeform settings, nginx virtual hosts, sops-nix secrets, Home Manager
and options infra declares itself. Modules check against the same types.
Pick a mistake to make it in the file and read what tsc reports.
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})$ tsc --noEmit No errors.
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: pkgs.git,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: ["22"],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUsr: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "12.34" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: 400 },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPas: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName: port,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "maybe",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStores: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, "ripgrep"] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
import type { NixosOptions } from "./options.ts"
import type { Nix, Package, Shape } from "./prelude.ts"
declare const pkgs: { readonly git: Package; readonly ripgrep: Package }
declare const hostName: Nix<string>
declare const enabled: Nix<boolean>
declare const port: Nix<number>
const nixos = (config: Shape<NixosOptions>) => config
nixos({
appStore: { enable: true },
identity: { primaryUser: "hauke" },
networking: {
hostName,
firewall: { allowedTCPPorts: [22, port, 443] },
},
environment: { systemPackages: [pkgs.git, pkgs.ripgrep] },
services: {
openssh: {
enable: enabled,
ports: [22],
settings: {
PermitRootLogin: "no",
PasswordAuthentication: false,
ClientAliveInterval: 60,
},
},
nginx: {
enable: true,
virtualHosts: {
"files.schnau.dev": {
forceSSL: true,
locations: { "/": { proxyPass: "http://127.0.0.1:8080" } },
},
},
},
},
sops: {
secrets: {
"app-store-connect/api-key": { owner: "hauke", mode: "0400" },
},
},
users: { users: { hauke: { isNormalUser: true, extraGroups: ["wheel"] } } },
"home-manager": {
users: {
hauke: {
home: { stateVersion: "25.11" },
programs: { git: { enable: true } },
},
},
},
})
sample.ts:20:7 error TS2322: Type 'Package' is not assignable to type 'Definition<boolean> | undefined'.
sample.ts:21:15 error TS2322: Type 'string' is not assignable to type 'Definition<number>'.
sample.ts:43:30 error TS2353: Object literal may only specify known properties, and 'isNormalUsr' does not exist in type 'Definition<UsersExtraUsers>'.
sample.ts:47:17 error TS2322: Type '"12.34"' is not assignable to type 'Definition<"18.09" | "19.03" | "19.09" | "20.03" | "20.09" | "21.03" | "21.05" | "21.11" | "22.05" | "22.11" | "23.05" | "23.11" | "24.05" | "24.11" | "25.05" | "25.11" | "26.05" | "26.11"> | undefined'.
sample.ts:40:54 error TS2322: Type 'number' is not assignable to type 'Definition<string> | undefined'.
sample.ts:33:31 error TS2353: Object literal may only specify known properties, and 'proxyPas' does not exist in type 'Definition<ServicesAgorakitNginxLocations>'.
sample.ts:14:5 error TS2322: Type 'Nix<number>' is not assignable to type 'Definition<string> | undefined'.
Type 'Nix<number>' is not assignable to type 'Nix<string>'.
Type 'number' is not assignable to type 'string'.
sample.ts:23:9 error TS2322: Type '"maybe"' is not assignable to type 'Definition<ServicesOpensshSettingsPermitRootLoginValue> | undefined'.
sample.ts:17:45 error TS2322: Type 'string' is not assignable to type 'Definition<Package>'.
sample.ts:11:3 error TS2561: Object literal may only specify known properties, but 'appStore' does not exist in type '{ readonly agents?: Definition<Agents>; readonly appStores?: Definition<AppStores>; readonly appstream?: Definition<Appstream>; ... 61 more ...; readonly zramSwap?: Definition<...>; }'. Did you mean to write 'appStores'?
Each error above is what tsc printed for that exact file, checked against the
5.9 MB of types generated from srv-2. Identical interfaces are shared and named after the
first option path that has them, which is why nginx's locations carry agorakit's name.
From option graph to plain Nix
-
Probe the option graph
Works today
src/probe.nixwalks a configuration'soptionswith builtins only and prints the tree as JSON. It reads the module system's type metadata, such as nested types, enum values and submodule options, and never parses descriptions. srv-2 takes 11 seconds.Shellnix eval --impure --json .#nixosConfigurations.srv-2.options \ --apply 'import ./src/probe.nix { class = "nixos"; }'srv-2.json One option, as the probe prints it{ "PermitRootLogin": { "id": "/nix/store/fjr02205wq70r3vghhfbfzv5g97nr4ya-source/nixos/modules/services/networking/ssh/sshd.nix:554:15#PermitRootLogin", "description": "Whether the root user can login using ssh.\n", "type": { "description": "null or one of \"yes\", \"without-password\", \"prohibit-password\", \"forced-commands-only\", \"no\"", "name": "nullOr", "nested": { "elemType": { "description": "one of \"yes\", \"without-password\", \"prohibit-password\", \"forced-commands-only\", \"no\"", "name": "enum", "values": ["yes", "without-password", "prohibit-password", "forced-commands-only", "no"] } } }, "visible": true } } -
Generate types
Works today
src/Typegen.tsturns the tree into interfaces that describe evaluated values. Groups and submodules become interfaces named after their first option path, and identical ones are shared. An option's description becomes its JSDoc, so the editor shows it on hover.typeflake types nixosConfigurations.srv-2runs the probe and the generator and writes the result into.typeflake/.options.ts An excerpt of what the generator wroteexport type ServicesOpensshSettingsPermitRootLoginValue = "yes" | "without-password" | "prohibit-password" | "forced-commands-only" | "no" | null export interface ServicesOpensshSettings { /** * Specifies whether password authentication is allowed. */ readonly PasswordAuthentication: boolean | null /** * Whether the root user can login using ssh. */ readonly PermitRootLogin: ServicesOpensshSettingsPermitRootLoginValue /** * Whether to enable printing /etc/motd when a user logs in interactively. */ readonly PrintMotd: boolean | null } -
Write a module
Works today
A module is a
*.nix.tsfile like the one at the top. Generated declarations augment aRegisterinterface, soNixOS.module(…)is typed without annotations. The first port typechecks against srv-2's types in 0.77 seconds, and four mistakes injected into it all fail. -
Render it to Nix
Works today
typeflake renderwrites each module's.nixfile next to it, and--checkfails when one is out of date. Yourflake.niximports the result like any other module. Delete the.tsfiles and the repository still builds. -
Check the port
Done for app-stores
A port must not change what any host builds. For app-stores, each host swapped the module once for a copy of the original and once for the rendered port, and both printed the same
drvPath, on srv-2 and on m1.Shellnix eval --raw .#nixosConfigurations.srv-2.config.system.build.toplevel.drvPath
Rules it keeps
-
Nix stays the build
Generated Nix builds with Nix alone, without Node, Bun or a Typeflake Nix library. Typeflake is a way to write
.nixfiles, not a runtime. -
Types come from your configurations
They are probed from the flake you already have. There is no hand-written schema to fall behind nixpkgs.
-
No
anyEvery Nix expression has the type of the value it evaluates to,
Nix<T>. Option types Typeflake can't map yet stay visible asUnsupported<"name">. About 240 option positions do today, across 25 custom types, and 152 of them are nixpkgs'systemd option. -
A port changes nothing
Porting a module leaves every host's
system.build.toplevel.drvPathas it was. That is the test for every port.
Where it stands
Works today
- The option probe on NixOS and nix-darwin hosts, with Home Manager's options from the same NixOS probe.
- The type generator, and
typeflake typesto run both for a host. -
Modules:
NixOS.module,Options,nix`…`,mkIfand the proxies forconfig,libandpkgs. typeflake render, with--checkfor drift.-
A first port of infra's app-stores module, with an unchanged
drvPath.
Next
pnpm add -D typeflake
pnpm exec typeflake types nixosConfigurations.srv-2
pnpm exec typeflake render
Early alpha releases of this rebuild go to npm from 0.1.0 on, and
typeflake is the CLI they install. The 0.0.1 versions there are from an
earlier prototype.