Skip to content

Commit f627aa1

Browse files
committed
merge beta for stable release
2 parents 03a7742 + 74efbf2 commit f627aa1

File tree

76 files changed

+3969
-2922
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+3969
-2922
lines changed

README.md

Lines changed: 159 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,92 +4,92 @@
44

55
[![CI](https://github.com/iamsomraj/vue-qs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/iamsomraj/vue-qs/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/vue-qs.svg)](https://www.npmjs.com/package/vue-qs) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
66

7-
> Active Development Notice
8-
> This library is currently in active development. APIs may change significantly between versions. Please use with caution and expect breaking changes.
7+
> **Note**: This library is currently in active development. APIs may change significantly between versions. Please use with caution and expect breaking changes.
98
10-
Docs: https://iamsomraj.github.io/vue-qs/ · 中文文档: https://iamsomraj.github.io/vue-qs/zh/
9+
📖 **Documentation**: [https://iamsomraj.github.io/vue-qs/](https://iamsomraj.github.io/vue-qs/)
10+
🌏 **中文文档**: [https://iamsomraj.github.io/vue-qs/zh/](https://iamsomraj.github.io/vue-qs/zh/)
1111

12-
Type‑safe, reactive URL query params for Vue 3. Inspired by nuqs (React) but built for the Vue Composition API.
12+
Type‑safe, reactive URL query parameters for Vue 3. Inspired by [nuqs](https://nuqs.47ng.com/) (React) but built for the Vue Composition API.
1313

14-
## Why
14+
## ✨ Features
15+
16+
- 🔄 **Bidirectional Sync**: URL parameters stay in sync with your reactive state
17+
- 🎯 **Type Safety**: Full TypeScript support with type inference
18+
- 🚀 **Vue 3 Ready**: Built for Vue 3 Composition API
19+
- 🔧 **Flexible**: Works with or without Vue Router
20+
- 🛡️ **SSR Safe**: Server-side rendering compatible
21+
- 📦 **Tree Shakeable**: Only import what you need
22+
- 🎨 **Customizable**: Built-in codecs + custom serialization support
23+
24+
## 🎯 Why vue-qs?
1525

1626
Keep UI state (page, filters, search text, sort, tabs) in the URL so users can:
1727

18-
- Refresh and keep state
19-
- Share links
28+
- 🔄 **Refresh and keep state**
29+
- 🔗 **Share links with specific state**
30+
- ⬅️➡️ **Use browser back/forward buttons**
2031

21-
vue-qs gives you small composables that feel like normal refs / reactive objects, but they stay in sync with the query string. You pick the type (string, number, boolean, Date, enum, arrays, custom) through codecs.
32+
vue-qs gives you composables that feel like normal refs/reactive objects, but they automatically stay in sync with the URL query string.
2233

23-
## Install
34+
## 📦 Installation
2435

25-
```sh
26-
npm i vue-qs
36+
```bash
37+
npm install vue-qs
2738
# or
2839
pnpm add vue-qs
2940
# or
3041
bun add vue-qs
3142
```
3243

33-
Peer dependency: vue ^3.3. Vue Router ^4.2 is optional (only if you want to integrate with router navigation).
44+
**Peer Dependencies:**
45+
46+
- `vue` ^3.3.0 (required)
47+
- `vue-router` ^4.2.0 (optional, for router integration)
48+
49+
## 🚀 Quick Start
3450

35-
## Quick start (no router)
51+
### Basic Usage (No Router)
3652

3753
```vue
3854
<script setup lang="ts">
3955
import { queryRef } from 'vue-qs';
4056
4157
// Create a ref bound to ?name=...
42-
// If the param is missing we fall back to the default ('').
43-
const name = queryRef('name', { defaultValue: '', parse: String });
58+
// Falls back to default value if param is missing
59+
const name = queryRef('name', {
60+
defaultValue: '',
61+
});
4462
</script>
4563
4664
<template>
4765
<input v-model="name" placeholder="Your name" />
4866
</template>
4967
```
5068

51-
Multiple params in one reactive object:
69+
### Multiple Parameters
5270

5371
```vue
5472
<script setup lang="ts">
5573
import { queryReactive } from 'vue-qs';
5674
57-
// Each field config controls parsing, defaults, omission rules
58-
const { queryState } = queryReactive({
75+
// Each field config controls parsing, defaults, and omission rules
76+
const queryState = queryReactive({
5977
q: { defaultValue: '' },
60-
page: { defaultValue: 1, parse: Number },
78+
page: { defaultValue: 1, codec: numberCodec },
79+
showDetails: { defaultValue: false, codec: booleanCodec },
6180
});
6281
</script>
6382
6483
<template>
65-
<input v-model="queryState.q" />
66-
<button @click="queryState.page++">Next</button>
84+
<input v-model="queryState.q" placeholder="Search..." />
85+
<button @click="queryState.page++">Next Page</button>
86+
<button @click="queryState.showDetails = !queryState.showDetails">Toggle Details</button>
6787
</template>
6888
```
6989

70-
## Using Vue Router
71-
72-
Two ways:
73-
74-
1. Pass an adapter directly
75-
76-
```vue
77-
<script setup lang="ts">
78-
import { queryRef, queryReactive, createVueRouterAdapter } from 'vue-qs';
79-
import { useRouter } from 'vue-router';
80-
81-
const adapter = createVueRouterAdapter(useRouter());
82-
83-
const page = queryRef<number>('page', {
84-
defaultValue: 1,
85-
parse: Number,
86-
queryAdapter: adapter,
87-
});
88-
const { queryState } = queryReactive({ q: { defaultValue: '' } }, { queryAdapter: adapter });
89-
</script>
90-
```
90+
## 🔗 Vue Router Integration
9191

92-
2. Provide a global adapter once (recommended)
92+
### Option 1: Global Plugin (Recommended)
9393

9494
```ts
9595
// main.ts
@@ -98,133 +98,165 @@ import { createVueQsPlugin, createVueRouterAdapter } from 'vue-qs';
9898
import { router } from './router';
9999
import App from './App.vue';
100100

101-
createApp(App)
102-
.use(createVueQsPlugin({ queryAdapter: createVueRouterAdapter(router) }))
103-
.use(router)
104-
.mount('#app');
105-
```
106-
107-
## Omitting defaults
108-
109-
By default if a value equals its `defaultValue`, the param is removed from the URL for cleanliness. Want it always there? Set `shouldOmitDefault: false`.
101+
const app = createApp(App);
110102

111-
Call `.syncToUrl()` on a ref or the reactive group to force a write right now.
103+
app.use(
104+
createVueQsPlugin({
105+
queryAdapter: createVueRouterAdapter(router),
106+
})
107+
);
108+
app.use(router);
109+
app.mount('#app');
110+
```
112111

113-
## Codecs (parse + serialize)
112+
### Option 2: Per-Component Adapter
114113

115-
Import ready‑made codecs:
114+
```vue
115+
<script setup lang="ts">
116+
import { queryRef, createVueRouterAdapter } from 'vue-qs';
117+
import { useRouter } from 'vue-router';
116118
117-
```ts
118-
import { serializers } from 'vue-qs';
119+
const router = useRouter();
120+
const adapter = createVueRouterAdapter(router);
119121
120-
const n = queryRef('n', { defaultValue: 0, parse: serializers.numberCodec.parse });
121-
const b = queryRef('b', { defaultValue: false, parse: serializers.booleanCodec.parse });
122-
const tags = queryRef('tags', {
123-
defaultValue: [] as string[],
124-
codec: serializers.createArrayCodec(serializers.stringCodec),
122+
const page = queryRef('page', {
123+
defaultValue: 1,
124+
codec: numberCodec,
125+
queryAdapter: adapter,
125126
});
127+
</script>
126128
```
127129

128-
Use `codec` instead of separate `parse` / `serializeFunction` for brevity.
129-
130-
Available built‑ins: `stringCodec`, `numberCodec`, `booleanCodec`, `dateISOCodec`, `createJsonCodec<T>()`, `createArrayCodec(codec)`, `createEnumCodec([...])`.
130+
## 🔧 Codecs (Type Conversion)
131131

132-
## Batch updates
133-
134-
Update several params in one history entry:
132+
Import ready‑made codecs for common types:
135133

136134
```ts
137-
const { queryState, updateBatch } = queryReactive({
138-
q: { defaultValue: '' },
139-
page: { defaultValue: 1 },
135+
import {
136+
stringCodec,
137+
numberCodec,
138+
booleanCodec,
139+
dateISOCodec,
140+
createArrayCodec,
141+
createJsonCodec,
142+
} from 'vue-qs';
143+
144+
// Basic types
145+
const name = queryRef('name', {
146+
defaultValue: '',
147+
codec: stringCodec,
140148
});
141-
updateBatch({ q: 'hello', page: 2 }, { historyStrategy: 'push' });
142-
```
143149

144-
## Custom equality
150+
const page = queryRef('page', {
151+
defaultValue: 1,
152+
codec: numberCodec,
153+
});
145154

146-
For objects/arrays supply `isEqual(a,b)` to decide if current value equals the default (so it can be omitted).
155+
const isActive = queryRef('active', {
156+
defaultValue: false,
157+
codec: booleanCodec,
158+
});
147159

148-
```ts
149-
const jsonCodec = serializers.createJsonCodec<{ a: number }>();
150-
const item = queryRef('obj', {
151-
defaultValue: { a: 1 },
152-
codec: jsonCodec,
153-
isEqual: (x, y) => x?.a === y?.a,
160+
// Complex types
161+
const tags = queryRef('tags', {
162+
defaultValue: [] as string[],
163+
codec: createArrayCodec(stringCodec),
154164
});
155-
```
156165

157-
## SSR safety
166+
const filters = queryRef('filters', {
167+
defaultValue: { category: 'all', sort: 'name' },
168+
codec: createJsonCodec<{ category: string; sort: string }>(),
169+
});
170+
```
158171

159-
On the server the hooks never touch `window`. They use an internal cache until hydration, so you can render initial HTML safely.
172+
## ⚙️ Configuration Options
160173

161-
## Mini API reference
174+
### Shared Options
162175

163-
queryRef(name, options)
176+
| Option | Type | Default | Description |
177+
| ------------------- | ------------------------- | ------------- | -------------------------------------------- |
178+
| `defaultValue` | `T` | - | Initial value if parameter is missing |
179+
| `codec` | `QueryCodec<T>` | `stringCodec` | Parser and serializer for the type |
180+
| `parse` | `QueryParser<T>` | - | Custom parser function (overrides codec) |
181+
| `serializeFunction` | `QuerySerializer<T>` | - | Custom serializer function (overrides codec) |
182+
| `shouldOmitDefault` | `boolean` | `true` | Remove from URL when equal to default |
183+
| `isEqual` | `(a: T, b: T) => boolean` | `Object.is` | Custom equality function |
184+
| `historyStrategy` | `'replace' \| 'push'` | `'replace'` | Browser history update strategy |
185+
| `queryAdapter` | `QueryAdapter` | - | Override default query adapter |
164186

165-
- Returns a ref with extra method `.syncToUrl()`.
187+
### Custom Equality Example
166188

167-
queryReactive(schema, options)
189+
```ts
190+
const filters = queryRef('filters', {
191+
defaultValue: { category: 'all', sort: 'name' },
192+
codec: createJsonCodec<{ category: string; sort: string }>(),
193+
isEqual: (a, b) => a.category === b.category && a.sort === b.sort,
194+
});
195+
```
168196

169-
- Returns `{ queryState, updateBatch, syncAllToUrl }`.
197+
## 🛡️ SSR Safety
170198

171-
createHistoryAdapter(options)
199+
vue-qs is SSR-safe. On the server, the composables use an internal cache until hydration, so you can render initial HTML safely without touching `window`.
172200

173-
- Creates adapter for browser History API (default choice).
201+
## 📚 API Reference
174202

175-
createVueRouterAdapter(router)
203+
### `queryRef(name, options)`
176204

177-
- Creates adapter for Vue Router (enables reacting to navigations).
205+
Creates a reactive ref that syncs with a URL query parameter.
178206

179-
createVueQsPlugin({ queryAdapter }) / provideQueryAdapter(adapter)
207+
```ts
208+
function queryRef<T>(parameterName: string, options?: QueryRefOptions<T>): Ref<T>;
209+
```
180210

181-
- Provide an adapter globally or locally.
211+
### `queryReactive(schema, options)`
182212

183-
## Options snapshot
213+
Creates a reactive object that syncs multiple URL query parameters.
184214

185-
Shared options:
215+
```ts
216+
function queryReactive<TSchema extends QueryParameterSchema>(
217+
parameterSchema: TSchema,
218+
options?: QueryReactiveOptions
219+
): ReactiveQueryState<TSchema>;
220+
```
186221

187-
- defaultValue: initial value if param is missing
188-
- parse / serializeFunction OR codec: convert string <-> type
189-
- shouldOmitDefault (default true): remove from URL when equal to default
190-
- isEqual: custom compare (deep equality, etc.)
191-
- historyStrategy: 'replace' (default) or 'push'
192-
- queryAdapter: override which query adapter to use
222+
### `createHistoryAdapter()`
193223

194-
Extra on reactive version:
224+
Creates an adapter for browser History API (default).
195225

196-
- updateBatch(update, { historyStrategy }): multi‑field update
226+
```ts
227+
function createHistoryAdapter(): QueryAdapter;
228+
```
197229

198-
## Contributing
230+
### `createVueRouterAdapter(router)`
199231

200-
Clone and install:
232+
Creates an adapter for Vue Router integration.
201233

202-
```sh
203-
bun install
234+
```ts
235+
function createVueRouterAdapter(router: Router): QueryAdapter;
204236
```
205237

206-
Dev build (watch):
207-
208-
```sh
209-
bun run dev
210-
```
238+
### Development Setup
211239

212-
Run tests / typecheck / lint:
240+
```bash
241+
# Clone and install
242+
git clone https://github.com/iamsomraj/vue-qs.git
243+
cd vue-qs
244+
bun install
213245

214-
```sh
215-
bun run test
216-
bun run typecheck
217-
bun run lint
246+
# Development
247+
bun run dev # Watch mode
248+
bun run test # Run tests
249+
bun run typecheck # Type checking
250+
bun run lint # Linting
251+
bun run docs:dev # Documentation dev server
218252
```
219253

220-
Docs local dev:
221-
222-
```sh
223-
bun run docs:dev
224-
```
254+
## 📄 License
225255

226-
PRs and issues welcome (see CONTRIBUTING.md).
256+
MIT License - see [LICENSE](LICENSE) for details.
227257

228-
## License
258+
## 🙏 Acknowledgments
229259

230-
MIT
260+
- Inspired by [nuqs](https://nuqs.47ng.com/) for React
261+
- Built with [Vue 3](https://vuejs.org/) Composition API
262+
- TypeScript support powered by [TypeScript](https://www.typescriptlang.org/)

0 commit comments

Comments
 (0)