Luq

Project status

What has shipped and is measured, what to establish before expanding integrations, and why the earlier .luq roadmap was withdrawn rather than rescheduled.

Next priority: one complete adoption path

Focus on projects that already own their TypeScript types. Introduce validation at one boundary, connect it to a form through Standard Schema, and verify that changing the upstream type exposes incompatible rules. The repository's form example is a starting point; it is not evidence of production adoption.

  1. Keep measured claims and known limits consistent across the package and documentation.
  2. Evaluate one existing project through setup, invalid input, type changes, and rule maintenance.
  3. Use that evidence to decide which integration to finish. Code generation remains checkout-only. Before publication, unsupported constraints must be handled explicitly and generated validators must be checked for semantic agreement on supported schemas.

These are acceptance criteria for future work, with no release date promised. Arbitrary-depth recursive JSON Schema validation and transform output inference throughuseField() remain limitations. For type-changing transforms, use direct.v() declarations to retain output inference.

What is in the box

78 plugins across 77 subpaths

Every rule you can call is a plugin you imported by name.

Browse the catalogue

100.00% of the Draft-07 suite

929 of 929 required cases, with skipped cases counted as failures. A validator that always said yes would score 59.31%; 1.x scored 57.70%.

See the breakdown

A 8,799 B gzipped floor

That is Builder with zero plugins. 1.x charged 17,423 B before you used anything.

See the measurements

CSP-safe, and checked

Zero occurrences of eval and new Function across all 874 emitted modules, verified by a script and by the public-API smoke test.

How it is checked

89 export keys, every one resolving

A scratch consumer typechecks all 89 against the published declarations under both node16 and bundler resolution, and proves an unpublished subpath fails.

Get started

Standard Schema v1

toStandardSchema() on the @maroonedog/luq/standard-schema subpath, so anything that accepts a Standard Schema — tRPC, TanStack Form, Hono, t3-env — accepts a Luq validator. A subpath rather than part of build(), because carrying it on every validator costs 312 B gzipped.

How to use it

Paths that name the real index

Declare items[*].productId and the issue you get back says items[1].productId.

The path grammar

What this page used to list as missing

Nine rows, and all nine are closed — the Draft-07 corpus now passes 929 of 929.

What was missingSuite casesWhat closed it
External and remote $ref57Resolved against a map of documents the caller already has. Luq does not fetch: a URI written in a schema can never make the process open a socket, and conversion stays synchronous.
__proto__ in a declared path14Declarable, and prototype pollution still unreachable — the writer uses Object.defineProperty, so __proto__ is a key rather than a setter. Conformance and safety turned out not to be a trade.
JSON pointer escaping9The whole fragment is percent-decoded before it is split, which is the order RFC 6901 §6 specifies.
Tuple items with additionalItems6A branch no longer re-follows the $ref that created it, so a $ref inside a tuple position produces rules instead of silently producing none.
null reaching a rule5Inside a sub-schema, null is a VALUE rather than an absence presence settles first. false, {"not":{}} and an enum without null all forbid null while saying nothing about type.
additionalProperties with patternProperties3The plugin takes the patterns as well as the declared keys, so it no longer over-rejects a key a pattern already matched.
Base URIs from $id, and anchors3A $ref is a URI reference, so resolution carries the base in force and an index of what a URI can name — not just the root document.
Code-point string length2minLength / maxLength count code points, not UTF-16 code units.
Second-hop $ref, and boolean sub-schemas2The same one-line fix as tuple items: the recursion guard was stopping itself.

Where this release is weak

Slower than 1.x on flat and nested shapes

Measured in one process: ×0.21 on one field, ×0.79 on three fields, ×0.83 nested. Arrays (×4.3) and JSON Schema (×2.1) are the two wins. Recovering that ground is the largest single piece of unfinished engineering in the project.

See the numbers

A recursive $ref is unrolled three times, and then stops

A mutually recursive schema has infinitely many declared paths, so the expansion is bounded. Unrolling further is exponential in the number of definitions in the cycle: a three-way cycle builds in 17 ms at one unroll, 55 ms at two, 538 ms at three and 9.3 s at four. Three is what the official corpus needs. Past that depth a document is not checked — the same silence as before, moved further out rather than removed.

See the numbers

The performance gate has a resolution

CI gates on the ratio between Luq and a hand-written validator: it catches a 35% regression and misses a 15% one.

See the numbers

The “universal platform” goal, withdrawn

1.x advertised a .luq DSL that would generate validators for other languages: database and cache adapters, endpoint definitions, a luq generate --lang=java command, OpenAPI emission. This page carried it with dated milestones. Those dates have since passed, and none of it shipped.

No part of it ships in this package and this release makes no claim about when it will. What is in the box is the TypeScript validation library described above.

The milestones are gone rather than moved. When something is built, it appears in the list above with a measurement beside it.