Skip to content
QueryWeave

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 stays the same everywhere; only the adapter changes with the environment.

When a parser and a serializer are configured separately, they drift: the parser accepts values the serializer cannot produce, and a value no longer survives the trip from state to URL and back.

In QueryWeave, each parameter has one codec that handles both directions. There is no createParser or separate serializer to keep in step with it.

If a default lives in the component that reads the query, every other reader has to repeat it. The URL also stops being canonical: ?page=1 and an absent page mean the same thing but are different strings.

Declaring the default on the parameter fixes both. Every reader gets the same default when decoding, and encoding leaves page out when it equals 1, so both spellings collapse into one URL.

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/core has no framework dependency, and a repository check keeps it that way. Vue and Nuxt support are integrations built on the same runtime a plain TypeScript application uses.

Parsing the string is the smallest part of the library. Most of it is the model around the string: issues with stable codes, the canonical form, and the rules for when and how a transition writes the URL.

You declare a model, then change it through named operations such as update and reset. Hook-shaped APIs, per-key hooks, tuple setters, and parseAs*-style constructors are ruled out in ADR 0001.

  • 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.
  • No debounce. A runtime can throttle its writes so a search field does not write on every keystroke, but a quiet-period delay, where nothing is written until typing pauses, stays yours to write.

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