Skip to content
QueryWeave

Architecture

QueryWeave separates query semantics from runtime integration. Everything else on this page follows from that sentence.

Definition, transformation, runtime transitions, and environment, top to bottom.

  1. Definition layer (param, QueryCodec, QueryModel)
  2. Transformation (decode, encode, normalize)
  3. Runtime transitions (QueryRuntime)
  4. Environment (QueryAdapter / QuerySource)

Definition layer → Transformation. Transformation → Runtime transitions. Runtime transitions → Environment.

Definition, transformation, transitions, environment. Each layer knows only the one below it.

Written as a dependency direction:

Definition layer param, QueryCodec, QueryModel
↓
Transformation decode, encode, normalize
↓
Runtime transitions QueryRuntime
↓
Environment QueryAdapter / QuerySource

A layer may depend on the one above it. Nothing depends downwards: the model cannot see the runtime, and the runtime cannot see which environment it is talking to beyond the four-method contract.

Mutable adapter chain on the left; read-only source chain on the right.

  1. QueryModel — Mutable environment (meaning)
  2. QueryRuntime (transitions)
  3. QueryAdapter (reads, navigates, notifies)
  4. Browser, router, memory (mutable environment)
  5. Request — Request-scoped source (incoming)
  6. QuerySource (read-only)
  7. QueryModel.decode() (typed result)

QueryModel → QueryRuntime. QueryRuntime → QueryAdapter. QueryAdapter → Browser, router, memory. Request → QuerySource. QuerySource → QueryModel.decode().

Left: a mutable environment implements QueryAdapter and can navigate. Right: a request-scoped source implements QuerySource and stops at decode.

A request-scoped environment implements QuerySource — one method. A mutable environment implements QueryAdapter, which extends it with push, replace, and subscribe.

This distinction is in the type system rather than in documentation, so server code physically cannot call push. A capability that does not exist is absent, not present and throwing.

@queryweave/core
↑
├── @queryweave/browser
├── @queryweave/server
│ ↑
│ └── @queryweave/node
├── @queryweave/standard-schema
├── @queryweave/testing
├── @queryweave/vue
│ ↑
│ └── @queryweave/nuxt
└── @queryweave/vue-router
↑
└── @queryweave/nuxt

Two consequences worth naming:

  • @queryweave/vue-router depends on core alone, not on @queryweave/vue. The adapter is useful without the binding, so coupling them would force an unnecessary dependency on anyone who wants router synchronization with their own binding.
  • @queryweave/node depends on @queryweave/server rather than duplicating it. Node’s contribution is resolving an absolute URL; decoding stays in one place.

@queryweave/core compiles with lib: ["ES2023"] and types: []. There is no DOM library and no Node library, so:

  • window, document, history, location, and process do not exist as names,
  • URLSearchParams does not exist either — it is accepted structurally as Iterable<QueryEntry>,
  • and the urlencoded codec is hand-written, because there is no URLSearchParams to delegate to.

That last point comes with an obligation: formatQueryString must stay byte-identical to URLSearchParams.prototype.toString, and a test compares them.

isolatedDeclarations is on for every publishable package except @queryweave/nuxt, whose module type cannot be expressed under it. Exported functions and constants therefore carry explicit type annotations.

Compiler settings cannot express “this package must never import Vue”. Three repository checks do, each reading something the previous one cannot see:

Check Reads Catches
pnpm boundary:check sources and manifests Dependency direction, forbidden imports, forbidden globals, workspace protocols, publishability
pnpm artifacts:check built output and archives Externalization, phantom dependencies, CommonJS emits, export-map targets, type resolution
pnpm consumers:check installed packed archives Anything only a real consumer sees

The globals check matches whole words in source text, comments included. That is blunt on purpose: a comment mentioning window in @queryweave/core is a signal that someone is thinking about the wrong layer.

tests/api-identity.test.ts fails the build if useQueryState, useQueryStates, parseAs*, createParser, createLoader, createSerializer, or withDefault appears in any package or application source, or if react appears in any manifest.

This is not hostility toward other libraries. It is a guard against a specific failure mode: a model-first API accreting hook-shaped conveniences one pull request at a time until it is a worse copy of something else.

Core codecs decode and encode without choosing a validation vendor. @queryweave/standard-schema is the only interoperability boundary, and it depends on the specification’s types alone. No published package may take a validator as a runtime dependency, and the boundary checker enforces that by name for Zod, Valibot, ArkType, Yup, Joi, Superstruct, and io-ts.

Thirteen Vitest projects, one per boundary: core, runtime, testing, browser, server, node, standard-schema, vue, vue-router, nuxt, docs, types, and repository. The browser project runs in Chromium, Firefox, and WebKit through Vitest Browser Mode. The types project asserts both what the inferred types accept and what they reject.

Beyond the unit suite: a Playwright suite drives the browser playground in the same three engines, and eight consumer fixtures install packed archives into clean projects outside the workspace — including a Nuxt fixture that builds and server-renders real pages/ routes, two of them concurrently with different queries to prove they do not share state, and a Vue Router 4 fixture beside the Vue Router 5 one. The support matrix lists what each fixture pins.

Area Suites and fixtures
Query models The core project covers definition, inference, and composition. tests/types asserts both what the inferred types accept and what they reject, so a widened type fails the build.
Parameters tests/core/semantics.test.ts covers presence, recovery, and the issue codes each family emits. tests/core/parameters.test.ts covers grammar, defaults, empty lists, transforms, and exceptions. tests/core/round-trip.test.ts asserts that decoding an encoded value returns the original for every family.
Decoding and encoding tests/core/query-input.test.ts covers parsing and formatting against URLSearchParams. tests/core/model.test.ts covers results, ordering, and omission. tests/core/round-trip.test.ts asserts decode-encode stability.
Issues tests/core/semantics.test.ts asserts the code emitted for each failure mode. tests/runtime/failures.test.ts asserts that a runtime snapshot stays usable while reporting them.
Runtime The runtime project drives a runtime against the memory adapter and asserts the single-notification rule, unmanaged-key preservation, and disposal. tests/runtime/failures.test.ts covers behavior when decoding fails. tests/runtime/serialization.test.ts covers overlapping transitions, outcomes, listener errors, and pending decodes.
Transitions tests/runtime/runtime.test.ts asserts one write and one notification per operation, patch merging, removal, reset, and transaction batching, against the memory adapter. tests/runtime/serialization.test.ts asserts ordering under an asynchronous adapter, every outcome, and the memory adapter’s guard option, which simulates a refusing environment. tests/runtime/scheduling.test.ts asserts throttled writes, the composition of held transitions, cancellation, and disposal.
Browser The browser project runs in Chromium, Firefox, and WebKit through Vitest Browser Mode. The Playwright suite drives the browser playground end to end in the same three engines, including back navigation.
Server tests/server/server.test.ts covers reading, encoding, and link building, including unmanaged-key preservation, malformed URL strings, and the relative-base fallback. The node fixture installs the packed archive together with @queryweave/node and runs the same helpers over a real request.
Node.js tests/node/node.test.ts covers URL resolution, header precedence, the forwarded-header opt-in, HTTP/2 pseudo-headers, TLS sockets, mounted routers, hostile paths and headers, and decoding. The node fixture runs the packed archive against a real node:http server.
Testing tests/testing/memory-adapter.test.ts covers the stack, truncation, notification, and disposal. This package has the highest coverage threshold in the repository, because everything else is tested through it.
Standard Schema tests/standard-schema/compatibility.test.ts runs one contract across Zod, Valibot, and ArkType, asserting normalized issues, inverses, throwing transforms, and inferred output types, and never a vendor’s own error shape. The standard-schema fixture repeats a Valibot slice of it against the packed archive.
Vue The vue project runs bindings inside effect scopes and server renders against the memory adapter, asserting reactivity, field bindings, issue propagation, pending decodes, and scope cleanup. The vue fixture type-checks the published archive against a Vue application.
Vue Router tests/vue-router/adapter.test.ts drives a real router through push, replace, refused and redirected navigation, a subscriber that unmounts before another subscribes, prototype-named keys, and repeated keys. The vue-router and vue-router-4 fixtures run the packed archive on both supported majors.
Nuxt tests/nuxt/module.test.ts and tests/nuxt/registration.test.ts cover module setup, plugin registration, and the server-side refusal. The nuxt fixture also renders a second route with its own model. It runs no browser, so hydration and client-side navigation are covered by the Vue Router and browser suites.

These are open, and their absence is deliberate:

  • Tuple, object, nested, and JSON families, whose URL spellings ADR 0011 already fixes.
  • Per-parameter configurability of default omission and recovery policy.
  • Model-level refinements that change the model’s output type.
  • Server response contribution, such as canonical-URL redirects.
  • Freezing the public contracts for 1.0.

See the roadmap for sequencing.

Each major API decision has an ADR in the repository:

ADR Decides
0001 Model-first public API, and the identities rejected
0002 Query input, duplicate keys, canonical encoding
0003 Parameter semantics and the issue taxonomy
0004 Runtime transitions and adapter contracts
0005 Validation interoperability
0006 Vue binding shape and Nuxt request scoping
0007 API consistency corrections and packaging uniformity
0008 Tree-shakable parameter constructors
0009 Transition outcomes, serialized transitions, pending decodes, refinement inverses
0010 Throttled writes, held transitions, cancellation
0011 Date and instant families; URL spellings for tuples, objects, and JSON