types

Generate the voidhash.gen.d.ts declaration file from the server schema and verify it stays in sync in CI.

voidhash-cli types keeps your project's generated TypeScript declaration file — voidhash.gen.d.ts — in sync with the schema on the server. It has two subcommands:

  • types generate — fetch the server schema and write the declaration file (optionally watching for changes)
  • types check — CI gate that fails when the committed file has fallen behind the server

Both require a logged-in session (auth login) and a voidhash.config.ts in the project root (init). You get a clear error pointing at the right command when either is missing.

types generate

npx voidhash-cli types generate

Fetches the schema from the server and writes the declaration file, then prints the embedded version:

Fetching remote schema...
✓ Types written to voidhash.gen.d.ts (version 3f6c1a9e2b74d0c8e5a...)

The output path defaults to voidhash.gen.d.ts at the project root; override it with typesOutput in voidhash.config.ts:

voidhash.config.ts
import { defineConfig } from "voidhash-cli";

export default defineConfig({
  team: "my-team",
  project: "my-app",
  typesOutput: "src/voidhash.gen.d.ts", // optional
});

Every generated file starts with a @voidhash:version header carrying the server-computed schema version hash:

voidhash.gen.d.ts
// voidhash.gen.d.ts — generated by voidhash-cli, do not edit
// @voidhash:version 3f6c1a9e2b74d0c8e5a1...
// @voidhash:fetched-at 2026-07-17T09:30:00.000Z

declare module "@voidhash/react-native" {
  interface VoidhashRegister {
    schema: {
      products: "monthly_sub" | "yearly_sub";
      locations: "onboarding" | "settings-upsell";
      perks: "all-access" | "premium-features";
    };
  }
}

export {};

The version is supplied by the server, not computed locally, so client and server can never disagree on the hash. It is what --watch and types check compare against.

Commit the generated file

voidhash.gen.d.ts belongs in version control — that is what makes types reproducible across your team and what gives types check something to verify in CI.

Flags

  • --watch — after the initial generate, keep running and regenerate whenever the server schema changes. Default false.
  • --poll-interval-ms <n> — polling interval for --watch mode, in milliseconds. Default 5000.

Watch mode

npx voidhash-cli types generate --watch --poll-interval-ms 2000

Watch mode polls the server's lightweight GET /schema/version endpoint on the configured interval and only re-downloads the full schema — and rewrites the file — when the version hash actually changes. Press Ctrl+C to stop.

The loop is deliberately resilient: poll errors and regeneration failures are logged as warnings and skipped, so a flaky network or a momentary logout never kills the watcher. You keep working against the last-known-good declaration file.

types check

npx voidhash-cli types check

The CI gate. Reads the @voidhash:version header from the local declaration file and compares it against the server's current schema version:

  • In sync — prints ✓ Types are up to date (version ...) and exits 0.
  • Stale — prints the divergence and exits non-zero, so a stale committed file cannot pass PR CI:
✗ Types are stale.
  Local:  3f6c1a9e2b74d0c8e5a1...
  Server: 9d2e7b41f0a3c6d8e2b5...

Run 'voidhash-cli types generate' to refresh, then commit the updated declaration file.

Two failure modes get distinct errors so they are easy to diagnose in CI logs:

  • File missingCould not read generated types at voidhash.gen.d.ts. Run 'voidhash-cli types generate' first.
  • Header missingvoidhash.gen.d.ts is missing the @voidhash:version header. Re-run 'voidhash-cli types generate' to regenerate. (usually the result of hand-editing the file)

CI workflow example

Authenticate the CLI in CI by setting the API key from a secret (the same api_key that auth login stores in ~/.voidhash on your machine — see config), then run the check:

.github/workflows/voidhash-types.yml
name: voidhash types
on: pull_request

jobs:
  types-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx voidhash-cli config set api_key "$VOIDHASH_API_KEY"
        env:
          VOIDHASH_API_KEY: ${{ secrets.VOIDHASH_API_KEY }}
      - run: npx voidhash-cli types check

When someone renames a product slug in the dashboard, the next PR fails types check until the declaration file is regenerated and committed — at which point TypeScript surfaces every call site still using the old slug.

What the generated file powers

The React Native SDK exports an intentionally empty VoidhashRegister interface; the generated file augments it via declaration merging. ProductSlug, LocationSlug, and PerkSlug resolve against the augmentation, so hooks like usePaywallByLocation("onboarding") autocomplete the literal union of valid slugs. Without the file (or with an empty schema), they degrade gracefully to string — the file is purely a compile-time aid; nothing at runtime depends on it.

During development you rarely run the watcher by hand: the SDK's Metro plugin spawns voidhash-cli types generate --watch alongside expo start and tears it down on shutdown:

metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withVoidhash } = require("@voidhash/react-native/metro");

module.exports = withVoidhash(getDefaultConfig(__dirname), {
  pollIntervalMs: 5000, // optional
});

The plugin's pollIntervalMs option is forwarded to --poll-interval-ms. See Schema & Generated Types for the full picture of how the server schema, the runtime fetch, and the generated types fit together.

  • Schema & Generated Types — how the SDK consumes the declaration file
  • init — creates voidhash.config.ts and the initial voidhash.gen.d.ts
  • auth — login session required by both subcommands
  • config — profiles and non-interactive api_key setup for CI