valve_

Development

Build valve, run its test suites and benchmarks, and work on these docs.

Set up

Use Node.js 24 and pnpm 11.

pnpm install
pnpm build

Run checks

pnpm check

pnpm check runs lint, typecheck, unit and property tests with coverage, type inference tests, builds, source conventions, package validation, and bundle size limits. Run them individually while working:

CommandRuns
pnpm testUnit and property tests
pnpm test:coverageUnit tests with coverage
pnpm test:typesType inference tests
pnpm lintESLint and Prettier
pnpm check:sizeBundle size limits

Test against Redis and Valkey

docker compose -f compose.test.yml up -d
REDIS_URL=redis://127.0.0.1:6379 pnpm test:integration

The integration suite covers Lua parity with the memory store, ioredis and node-redis clients, script-cache recovery, shared pauses, timeouts, server restarts, post-timeout offline replay, and bounds across eight child processes. The replay suite uses a local TCP proxy and a separate healthy observer. It holds reconnection until after the store timeout, compares enabled/disabled offline queues with ioredis 5 and node-redis 4/5, and checks all mutating operations and wait/throw/open policies with ioredis. It checks server rate state, lease timestamps and expiry, single-client pause/resume order, and callback counts. These probes do not cover every network partition, cluster failover, or cross-client control race. CI runs it against Redis 6.2, 7.4, and 8, and Valkey 7.2 and 8. Set REDIS_CLUSTER_URL to include Redis Cluster checks.

Fault injection for state loss

The integration suite deletes valve's keys to check that a lost state starts a fresh budget. Server faults are manual probes. Run them only against a disposable instance:

docker run -d --rm --name valve-fault -p 16400:6379 redis:7.4 --save '' --appendonly no

Connect the test client to redis://127.0.0.1:16400. Run Redis commands against that instance with redis-cli -p 16400. Use a fixed-window rule { limit: 1, interval: '1m' }. Call store.acquire once, confirm a second call returns granted: 0, inject one fault, then acquire again with the same prefix and id. The no-eviction and failover probes use fresh ids as described below.

FaultInjectExpected after the fault
Flushredis-cli -p 16400 FLUSHDBGranted 1
Restartdocker restart valve-faultGranted 1
EvictionCONFIG SET maxmemory-policy volatile-ttl, lower maxmemory below used_memory, write until evicted_keys rises, then set maxmemory back to 0Granted 1
No evictionCONFIG SET maxmemory-policy noeviction, lower maxmemory below used_memory, then acquire with a fresh id; inspect that id's rule keystore.acquire rejects with OOM; the new rule key stays absent
FailoverStart a replica and wait for it to sync, cut its replication link, acquire on the primary with a fresh id and confirm granted: 1, then run REPLICAOF NO ONE on the replica and acquire there with that idGranted 1

The no-eviction row calls the store directly. Through valve, the OOM follows the configured onStoreError behavior: throw rejects with STORE_UNAVAILABLE, wait retries, and open admits jobs without shared rate rules. Restore maxmemory to 0 before running another probe. For failover, target the replica explicitly when promoting it and when making the final acquire.

Every row except "No eviction" shows silent loss. See Durability.

Benchmarks

pnpm bench          # Local scheduler workloads
pnpm bench:compare  # Repeated comparisons. Add REDIS_URL and VALKEY_URL for distributed runs.

Recorded results and methodology live in packages/valve/bench/COMPARISON.md.

Work on the docs

The site is built with Next.js and Fumadocs. Pages are MDX files in docs/content/docs/.

pnpm docs:dev    # http://127.0.0.1:4321
pnpm docs:build  # Static export to docs/out, including TypeDoc

Browser tests run against the built site:

pnpm --filter @valve/web test:browser

See CONTRIBUTING.md for commit conventions and changesets.

On this page