never-rest

Type performance budget

never-rest publishes a per-route TypeScript instantiation budget and enforces it in CI. The goal is to keep contract-first ergonomics without the DSL tax of builder chains like @ts-rest/core's c.router().

Budget: under 1,800 instantiations per route (marginal slope on synthetic fixtures).

Measured results (in-repo, real src types)

ItemValue
TypeScript5.9.3
@ark/attest0.56.3
Fixture route counts1, 5, 20, 40
MethodIsolated per-file bench(...).types(); routes defined inside each bench arrow

Per-route slope (linear regression on 1 / 5 / 20 / 40)

SeriesIntercept (fixed)Per-route slopeVerdict
Contract as const satisfies ContractDef74,915584PASS
Client<TContract>74,921584PASS
Combined (contract + client)74,921584PASS
Handlers<TContract> (serve surface)74,925584PASS
Plain object control74,870308

Marginal deltas vs contract-only at 40 routes:

  • Client<T>: +6 total (not per route)
  • Handlers<T>: +10 total

The gated series is combined (hand-written contract + one-level Client<T> consumption).

Absolute instantiation counts (bench body contribution)

Each cell is @ark/attest contributed instantiations for that isolated bench file.

RoutesContractClientCombinedPlain controlHandlers
175,49975,50575,50575,17875,509
577,83577,84177,84176,41077,845
2086,59586,60186,60181,03086,605
4098,27598,28198,28187,19098,285

Absolute totals include a large fixed overhead (~75k) from importing never-rest's type graph once per bench file. The budget is on marginal per-route slope, not total file cost.

Comparison to research anchors

AnchorPer-routeRatio vs never-rest (combined)
Plain object literals (primary-source research)~1,1930.49× (fixtures differ; see note)
@ts-rest/core c.router() (research)~5,984~10× cheaper
Published budget1,800PASS (584 < 1,800)

Research anchors used lighter fixtures (minimal literals, no Zod per route). This harness uses Zod + Standard Schema stubs on every route so slopes are comparable across contract / client / serve surfaces in one repo. The spike on stub types (.tmp/spike/MEASUREMENT.md) measured ~1,346/route with lower fixed overhead; in-repo measurement against real src types is ~584/route — both well under budget.

Budget verdict

CheckResult
Combined contract + client ≤ 1,800 / routePASS (~584)
Within ~1.5× plain literals (~1,193)PASS
≥ 3× cheaper than ts-rest (~5,984)PASS (~10×)
Safe to ship v0.1 type surfaceYES

Which construct dominates cost?

Not Client (+6 fixed). Not Handlers (+10). Per-route cost is dominated by per-route schema inference (StandardSchemaV1.InferOutput via HandlerArgsOf / OutputOf) and the Zod object schemas in each route fixture — the same work plain object literals do, plus a small premium for as const satisfies ContractDef vs the plain control (~276/route marginal delta: 584 − 308).

Reproduce

# Regenerate isolated bench files (after editing perf/generate-benches.mjs)
node perf/generate-benches.mjs

# Establish or refresh inline attest snapshots (after fixture or src type changes)
cd perf && ATTEST_updateSnapshots=1 node --experimental-strip-types benches/combined-20.bench.ts

# CI gate (also validates slope from perf/baseline.json)
node scripts/typeperf.mjs

# Verify the gate catches runaway instantiations
node scripts/typeperf.mjs --self-test

After changing fixtures or src types, re-run all benches with ATTEST_updateSnapshots=1, recompute slopes, and update perf/baseline.json.

On-demand trace (not CI)

node scripts/typeperf-trace.mjs
node scripts/typeperf-trace.mjs perf/benches/combined-20.bench.ts

Layout

perf/
  baseline.json           # committed slopes, counts, TypeScript version
  fixtures/
    schema.ts             # Zod → Standard Schema helper
    plain-object.ts       # plain-object control types
  generate-benches.mjs    # one isolated bench file per measurement
  benches/*.bench.ts      # inline .types([N, "instantiations"]) snapshots
scripts/
  typeperf.mjs            # CI gate
  typeperf-trace.mjs      # optional tsc --generateTrace diagnostic

Updating the baseline

  1. Bump TypeScript only after re-measuring all benches.
  2. Edit perf/baseline.json with new counts, intercepts, and slopes.
  3. Do not raise budgetPerRoute quietly — if the budget cannot be met, escalate as a design finding.

On this page