Skip to content
Runtime
Framework

Why QueryWeave

A server renders the first response from it. The client reads it again after hydration. The user shares it as a link. A test asserts the result. A helper tied to one framework’s component lifecycle can only cover one of those reads.

QueryWeave defines the query as a model and syncs it through environment-specific adapters. The model is portable; the adapter changes with the environment.

Reading and writing a URL are one problem. When a parser and a serializer are configured separately, they drift: the parser accepts values the serializer cannot produce, and round-tripping stops being stable.

In QueryWeave, each parameter has one codec for both directions. That is why there is no createParser, no separate serializer, and no way to configure decode and encode independently.

Defaults are part of the meaning, not of the reader

Section titled “Defaults are part of the meaning, not of the reader”

If a default lives in the component that reads the query, every other reader must repeat it, and the URL cannot be canonical — ?page=1 and an absent page mean the same thing but produce different strings.

Declaring the default on the parameter fixes both problems at once. The default participates in decoding and in encoding, which is what makes omission possible.

Query strings arrive from links, bookmarks, crawlers, and typos. Treating bad input as an exception forces every call site into a try/catch that usually swallows the cause.

QueryWeave returns a result. A failed decode still carries partial values and a list of issues with stable codes, so an application can render something useful and report what went wrong.

QueryWeave is not a port, a wrapper, or a rename of another library.

  • It is not a framework utility. @queryweave/core has no framework dependency, and a repository check enforces that — not convention alone.
  • It is not a query-string helper. Parsing a string is the smallest part; the model, the issues, the canonical form, and the transition semantics are the substance.
  • It is not a collection of composables. Vue and Nuxt are integrations built on the same runtime a plain TypeScript application uses.

The public API is deliberately model-first. Hook-shaped identities, per-key hooks, tuple setters, and parseAs*-style constructors are rejected by name in ADR 0001, and a test in the repository fails the build if one ever appears.

Model-first is not free, and the trade-offs are real:

  • More to write up front. A model must be declared before anything can read a single value. For an application with one ?tab= parameter, that is more ceremony than it is worth.
  • No per-key ergonomics. There is no useTab(). You read binding.values.tab and call an explicit operation to change it.
  • Transitions are immediate. Without scheduling, a fast-changing input such as a search field writes on every keystroke unless you debounce it yourself. Scheduling is planned, not present.

URL state as a domain walks through the concepts in order.