npm.io
0.1.0 • Published 3d ago

@systemfsoftware/effect-schema-law

Licence
MIT
Version
0.1.0
Deps
0
Size
8 kB
Vulns
0
Weekly
0

@systemfsoftware/effect-schema-law

Codec-law property tests for Effect Schema, in one call.

A schema is a two-way codec. ruleOfSchemas asserts the two laws every well-formed codec must obey, generating its inputs from the schema itself (via @effect/vitest's it.prop + fast-check):

  • Round-trip identitydecode(encode(x)) equals x (by the schema's type equivalence).
  • Encode stability — re-encoding the decoded value reproduces the original encoded form (by the encoded-side equivalence).
import { ruleOfSchemas } from '@systemfsoftware/effect-schema-law'
import { Schema as S } from 'effect'

const Email = S.String.pipe(S.brand('Email'))

// inside a Vitest file — registers two property tests
ruleOfSchemas('Email', Email)

Recursive schemas

ruleOfSchemas generates its inputs from the schema. Effect bounds that generation at array seams, but a union that recurses through a non-array fieldBinary.left: Expression, Member.object: Expression — is generated as an unbounded fc.oneof(...members). A single sample can then recurse until the call stack overflows, and the test crashes before it ever checks a law.

boundedUnion builds the same union — decode, encode, and equivalence are identical to S.Union(...) — but caps generation depth. Past maxDepth (default 2), generation collapses to the base case, so every variant stays reachable while the recursion always terminates. Split the members into the non-recursive base (the leaves, used as the base case) and the self-referential recur:

import { boundedUnion, ruleOfSchemas } from '@systemfsoftware/effect-schema-law'
import { Schema as S } from 'effect'

interface Lit {
  readonly _tag: 'Lit'
  readonly value: number
}
interface Add {
  readonly _tag: 'Add'
  readonly left: Expr
  readonly right: Expr
}
type Expr = Lit | Add

const Lit = S.Struct({ _tag: S.Literal('Lit'), value: S.JsonNumber })
const Add: S.Schema<Add> = S.suspend((): S.Schema<Add> => S.Struct({ _tag: S.Literal('Add'), left: Expr, right: Expr }))

const Expr: S.Schema<Expr> = boundedUnion('Expr', {
  base: [Lit],
  recur: [Add],
})

ruleOfSchemas('Expr', Expr) // generates and law-tests, no stack overflow

The first argument is both the schema's identifier and the depthIdentifier fast-check counts depth against — keep it unique per recursive cycle.

Install

pnpm add -D @systemfsoftware/effect-schema-law

Install it as a devDependency — it's a test helper. effect, vitest, and @effect/vitest are peer dependencies: you bring your own (you already have them to run your tests), so the helper shares your single test-runner instance. Call ruleOfSchemas(name, schema) at the top level of a Vitest test file; it registers the two it.prop cases for you.

Keywords