@voila.dev/biome-plugins
Biome GritQL lint rules for Effect codebases: schema-backed errors, seedable randomness, injected identifiers, validated construction, runMain entrypoints.
An Effect codebase keeps its promises only if nobody reaches around them: one
Math.random() and a test can no longer be seeded, one crypto.randomUUID()
and identifiers stop being injectable, one Data.TaggedError and an endpoint
cannot encode its failure. Code review catches these some of the time; these
Biome GritQL plugins catch them every time.
bun add -d @voila.dev/biome-plugins @biomejs/biomeRequires Biome 2.5 or later. The package ships .grit files only, no code.
Enabling a rule
Biome loads a plugin from a path, so point at the file inside node_modules.
Give it includes to scope it to the folders where the rule holds:
// biome.json
{
"plugins": [
"./node_modules/@voila.dev/biome-plugins/rules/no-math-random.grit",
{
"path": "./node_modules/@voila.dev/biome-plugins/rules/use-run-main.grit",
"includes": ["**/scripts/**", "!**/*.test.ts"]
}
]
}Start each includes glob with **/: Biome matches plugin globs against the
full path of the file.
A deliberate exception is suppressed in the file, with its reason:
// biome-ignore lint/plugin: the identifier service itself
const id = Bun.randomUUIDv7();Rules
no-data-tagged-error
Forbids Data.TaggedError(...). A Data.TaggedError has no schema, so it
cannot be declared on an HttpApi endpoint or an RPC, nor decoded by the
client. Schema.TaggedError is yieldable the same way and also encodes.
Enable it where errors are declared (domain and application code).
no-math-random
Forbids Math.random(). It cannot be seeded, so code that uses it cannot be
tested deterministically. Draw from Effect’s Random service
(Random.next, Random.nextIntBetween, Random.shuffle), which a test
replaces with Random.withSeed; use crypto.getRandomValues for secrets.
Usually excluded from *.test.ts.
no-random-uuid
Forbids crypto.randomUUID(), globalThis.crypto.randomUUID() and a bare
randomUUID(). Identifiers should come from a service provided as a Layer:
tests pin them, and the format (a time-ordered UUIDv7 keeps database indexes
compact) is chosen once. Exclude the file that implements that service.
no-schema-disable-checks
Forbids disableChecks: true, which constructs a schema class or value
without its checks and lets a value that breaks its invariants into the
domain. Construct with make (throws) or makeEffect (fails in the error
channel). Exclude tests and fixtures, where an invalid value is the point.
use-run-main
Forbids process.exit(...) and Effect.runPromise, runPromiseExit,
runSync, runSyncExit, runFork. A program’s entrypoint ends with
runMain from @effect/platform-bun or @effect/platform-node, which sets
the exit code, interrupts the fiber on SIGINT so finalizers run, and reports
the failure. Enable it on entrypoints and scripts only: libraries and tests
legitimately run effects at their edge.
Not included
A rule forbidding vitest imports: which test runner a repository uses is not
an Effect concern, and Biome’s own noRestrictedImports already does it.