Skip to content
Runtime
Framework

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.

Twelve Vitest projects, one per boundary: core, runtime, testing, browser, server, node, standard-schema, vue, vue-router, nuxt, types, and repository. The browser project runs in real Chromium 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, and seven consumer fixtures install packed archives into clean projects outside the workspace — including a Nuxt fixture that builds, server-renders two concurrent requests with different queries to prove they do not share state, and hydrates.

These are open, and their absence is deliberate:

  • Transition scheduling, throttling, coalescing, and concurrency control.
  • Codec composition for date, JSON, object, tuple, and nested representations.
  • 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