Vue Router
Framework
A router-backed adapter that keeps path and hash intact.
- Package
- @queryweave/vue-router
- Runtime
- Vue Router application
- Depends on
- @queryweave/core
- Requires
- vue, vue-router
This package produces an adapter and nothing else. It depends on @queryweave/core alone, not on
@queryweave/vue, so you can sync the router without the Vue binding.
The adapter reads route.query as entries, keeping repeated keys, and navigates with
router.push or router.replace while keeping the current path and hash. It watches
route.fullPath in a scope it owns and reports changes. A refused or redirected navigation comes
back as the transition’s outcome and does not throw. Decoding, validation, and defaults stay in the
model; reactive bindings come from @queryweave/vue.
Mutable environment — reads, navigates, and notifies
Mutable environment — reads, navigates, and notifies
- QueryModel (meaning)
- QueryRuntime (transitions)
- QueryAdapter (synchronization)
- Vue Router (environment)
QueryModel → QueryRuntime. QueryRuntime → QueryAdapter. QueryAdapter → Vue Router.
Install
Section titled “Install”pnpm add @queryweave/core @queryweave/vue-routerVue Router 4.4 and newer, and Vue Router 5, are supported; both are run in the consumer fixtures.
Example
Section titled “Example”import { createVueRouterAdapter } from "@queryweave/vue-router";import { provideQueryAdapter, useQueryModel } from "@queryweave/vue";import { useRouter } from "vue-router";
import { products } from "./products";
const router = useRouter();const adapter = createVueRouterAdapter(router);
provideQueryAdapter(adapter);const filters = useQueryModel(products);
async function showNextPage(): Promise<void> { // current route: /products#results, page defaults to 1 await filters.update({ page: filters.values.page + 1 }); // /products?page=2#results — the current path and hash are preserved}Without the Vue binding, drive it directly:
import { createQueryRuntime } from "@queryweave/core";
const runtime = createQueryRuntime({ model: products, adapter: createVueRouterAdapter(router),});Handling refused navigation
Section titled “Handling refused navigation”Router navigation can be declined by a guard or redirected. That is an environment condition, not a programming error, so the transition resolves with the outcome instead of rejecting:
const result = await filters.update({ page: 9 });
if (result.outcome === "refused") { console.warn("Query navigation was refused", result.reason); // Vue Router's NavigationFailure}if (result.outcome === "redirected") { // the router ended on another route; result.snapshot reflects it}result.snapshot is the settled state after the transition whatever its outcome. A guard that
throws is an error rather than a refusal, and it rejects the transition.
Which adapter should you use?
Section titled “Which adapter should you use?”If your application already has Vue Router, use this one rather than
@queryweave/browser. Two writers on one history stack — the router and the
History API — will fight, and the router will not know about writes it did not make.
| Situation | Adapter |
|---|---|
| Vue Router present | @queryweave/vue-router |
| Vue without a router | @queryweave/browser |
| Nuxt | Nuxt wires this one for you |
| Tests, or no environment at all | @queryweave/testing |
Edge cases
Section titled “Edge cases”- Path and hash survive every transition; only the query is rewritten.
- The router owns the URL’s text. Values are handed over as entries and the router writes them
in its own encoding and key order, so the bytes can differ from
formatQueryString; the entries do not. - A
nullquery value — Vue Router’s representation of?flag— becomes an empty string entry. - Repeated keys round-trip through the router’s array form.
- A key named after an
Object.prototypemember (?constructor=1) is read and written correctly; the router’s own parse of it is worked around. - Writes wait for
router.isReady(), so a transition issued during the initial navigation lands on the real route rather than on the placeholder. - Notification comes from the route watcher, not from
push, because the router already reports its own changes. This is why one transition still produces exactly one notification. The watcher lives in a detached effect scope owned by the adapter, so it survives the unmounting of the component that subscribed first and stops when the last subscriber leaves. - After
dispose, the watcher stops and later navigation and subscription throw.