vuerl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/index.cjs +738 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +696 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/rate-limiter.d.ts +25 -0
- package/dist/internal/rate-limiter.d.ts.map +1 -0
- package/dist/internal/utils.d.ts +5 -0
- package/dist/internal/utils.d.ts.map +1 -0
- package/dist/parsers.d.ts +154 -0
- package/dist/parsers.d.ts.map +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useQueryState.d.ts +25 -0
- package/dist/useQueryState.d.ts.map +1 -0
- package/dist/useQueryStates.d.ts +35 -0
- package/dist/useQueryStates.d.ts.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Marius Oseth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# vuerl
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/vuerl)
|
|
4
|
+
[](https://github.com/mariusoseth/vuerls/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
> Type-safe URL query state management for Vue 3 + Vue Router.
|
|
8
|
+
|
|
9
|
+
This package gives you typed composables that keep Vue state and the URL query string in sync. You get the same ergonomics as `const [value, setValue] = useQueryState(...)` in React/nuqs, including parser-based type safety, automatic batching, and browser-aware rate limiting.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add vuerl
|
|
15
|
+
# or
|
|
16
|
+
npm install vuerl
|
|
17
|
+
# or
|
|
18
|
+
yarn add vuerl
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peer dependencies:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add vue@^3.3 vue-router@^4.0
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { useQueryState, parseAsString, parseAsInteger } from 'vuerl'
|
|
32
|
+
|
|
33
|
+
const [search, setSearch] = useQueryState('q', parseAsString.withDefault(''))
|
|
34
|
+
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1), {
|
|
35
|
+
debounce: 200,
|
|
36
|
+
history: 'replace'
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<input v-model="search" placeholder="Search" />
|
|
42
|
+
<button @click="setPage(page.value + 1)">Next Page</button>
|
|
43
|
+
</template>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- State updates immediately when you type.
|
|
47
|
+
- URL updates are debounced to keep browsers happy.
|
|
48
|
+
- Defaults never show up in the URL (unless you turn off `clearOnDefault`).
|
|
49
|
+
- TypeScript knows `search.value` is `string` and `page.value` is `number`.
|
|
50
|
+
|
|
51
|
+
## Multi-parameter State
|
|
52
|
+
|
|
53
|
+
Use `useQueryStates` when several params should update together.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import {
|
|
57
|
+
useQueryStates,
|
|
58
|
+
parseAsString,
|
|
59
|
+
parseAsInteger,
|
|
60
|
+
parseAsStringLiteral
|
|
61
|
+
} from 'vuerl'
|
|
62
|
+
|
|
63
|
+
const [filters, setFilters] = useQueryStates({
|
|
64
|
+
search: parseAsString.withDefault(''),
|
|
65
|
+
status: parseAsStringLiteral(['active', 'inactive']).withDefault('active'),
|
|
66
|
+
limit: parseAsInteger.withDefault(20)
|
|
67
|
+
}, {
|
|
68
|
+
debounce: 120,
|
|
69
|
+
history: 'push'
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
setFilters({ search: 'vue' }) // single field
|
|
73
|
+
setFilters({ status: 'inactive', limit: 50 }) // batched update → one router push
|
|
74
|
+
setFilters({ status: null }) // drop from URL + reset to parser default
|
|
75
|
+
setFilters(null) // reset every field to defaults
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Updates inside the same tick (`setFilters({...}); setFilters({...})`) merge before touching the router, so you only pay for one navigation.
|
|
79
|
+
|
|
80
|
+
## Parser Cheatsheet
|
|
81
|
+
|
|
82
|
+
Parsers define how a query string turns into a typed value (and back). Every parser supports `.withDefault(value)` and `.withOptions({ ...queryStateOptions })`.
|
|
83
|
+
|
|
84
|
+
| Parser | Example | URL → Value |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `parseAsString` | `"hello"` | `'hello'`
|
|
87
|
+
| `parseAsInteger` | `"42"` | `42`
|
|
88
|
+
| `parseAsFloat` | `"3.14"` | `3.14`
|
|
89
|
+
| `parseAsBoolean` | `"true"` | `true`
|
|
90
|
+
| `parseAsStringLiteral(['asc','desc'])` | `"asc"` | `'asc'`
|
|
91
|
+
| `parseAsStringEnum(MyEnum)` | `"VALUE"` | `MyEnum.VALUE`
|
|
92
|
+
| `parseAsArrayOf(parseAsInteger)` | `"1,2,3"` | `[1,2,3]`
|
|
93
|
+
| `parseAsNativeArrayOf(parseAsString)` | `?tag=a&tag=b` | `['a','b']`
|
|
94
|
+
| `parseAsIsoDate` | `"2025-11-22"` | `Date`
|
|
95
|
+
| `parseAsIsoDateTime` | `"2025-11-22T10:30:00Z"` | `Date`
|
|
96
|
+
| `parseAsJson()` | `"%7B%5C"id%5C":1%7D"` | `{ id: 1 }`
|
|
97
|
+
| `parseAsHex` | `"ff00ff"` | `'ff00ff'`
|
|
98
|
+
| `withDefault(customParser, defaultValue)` | – | Keeps custom parser logic but adds defaults |
|
|
99
|
+
|
|
100
|
+
Need something custom? Implement the `Parser<T>` interface or wrap an existing parser with `.withDefault`/`.withOptions`.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const parseAsSlug = withDefault({
|
|
104
|
+
parse: (value) => (value && /[a-z0-9-]+/.test(value) ? value : null),
|
|
105
|
+
serialize: (value) => value ?? null
|
|
106
|
+
}, 'home')
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Options Reference
|
|
110
|
+
|
|
111
|
+
Both hooks accept the same `QueryStateOptions` either directly or via parser `.withOptions`.
|
|
112
|
+
|
|
113
|
+
| Option | Default | What it does |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `history` | `'replace'` | `'push'` to record every change in browser history. |
|
|
116
|
+
| `debounce` | browser-safe (50ms / 120ms Safari) | Delay before writing to the URL. |
|
|
117
|
+
| `throttle` | `null` | Alternate rate limiter if you prefer throttling. |
|
|
118
|
+
| `clearOnDefault` | `true` | Drop params from the URL when they match parser default. |
|
|
119
|
+
| `shallow` | `true` | Skip full router navigation (like `router.replace({ query })`). Set `false` when SSR needs to know about query changes. |
|
|
120
|
+
| `scroll` | `false` | Force scroll to top on navigation. |
|
|
121
|
+
|
|
122
|
+
Parser-level options merge with hook options, so you can keep most defaults global and override only the odd field:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const parser = parseAsInteger
|
|
126
|
+
.withDefault(20)
|
|
127
|
+
.withOptions({ clearOnDefault: false })
|
|
128
|
+
|
|
129
|
+
const [, setLimit] = useQueryState('limit', parser)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Watching External Changes
|
|
133
|
+
|
|
134
|
+
Both hooks watch `route.query` so back/forward navigation, shared URLs, or manual `router.push` calls stay in sync automatically. Pending debounced updates are cancelled when the user navigates elsewhere.
|
|
135
|
+
|
|
136
|
+
## Working With Arrays
|
|
137
|
+
|
|
138
|
+
`parseAsArrayOf` (comma separated) and `parseAsNativeArrayOf` (repeated params) always return concrete arrays:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const [tags] = useQueryState('tags', parseAsArrayOf(parseAsString))
|
|
142
|
+
tags.value // always string[]
|
|
143
|
+
|
|
144
|
+
const [, setIds] = useQueryState('id', parseAsNativeArrayOf(parseAsInteger))
|
|
145
|
+
setIds([1, 2, 3]) // → ?id=1&id=2&id=3
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Custom Equality
|
|
149
|
+
|
|
150
|
+
If your parser returns objects/arrays that shouldn't trigger updates when reference changes, provide `eq(a, b)`:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const parseAsJsonFilters = withDefault({
|
|
154
|
+
parse: (value) => value ? JSON.parse(value) : null,
|
|
155
|
+
serialize: (value) => value ? JSON.stringify(value) : null,
|
|
156
|
+
eq: (a, b) => JSON.stringify(a) === JSON.stringify(b)
|
|
157
|
+
}, {})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Testing Helpers
|
|
161
|
+
|
|
162
|
+
The test suite uses Vue Test Utils + Vue Router in memory. If you need to write your own tests, copy the helper from `tests/helpers.ts` to mount a composable inside a dummy component.
|
|
163
|
+
|
|
164
|
+
## FAQ
|
|
165
|
+
|
|
166
|
+
**Does this work with SSR?**
|
|
167
|
+
|
|
168
|
+
Yes. Hooks guard against `window` access and fall back to safe defaults in SSR environments. Set `{ shallow: false }` if your framework needs full navigations for query changes.
|
|
169
|
+
|
|
170
|
+
**What about browser rate limits?**
|
|
171
|
+
|
|
172
|
+
We auto-detect Safari vs other browsers and ensure the debounce/throttle never drops below 120ms/50ms respectively. You can still pass your own values—they're clamped to the safe floor.
|
|
173
|
+
|
|
174
|
+
**Can I mix `useQueryState` and `useQueryStates`?**
|
|
175
|
+
|
|
176
|
+
Absolutely. They both watch the same router instance and will stay in sync. When both write the same key the last writer wins, so prefer one hook per param to avoid confusion.
|
|
177
|
+
|
|
178
|
+
## Contributing
|
|
179
|
+
|
|
180
|
+
PRs welcome! If you add new parsers, remember to extend `src/parsers.ts`, export from `src/index.ts`, and cover them in `tests/parsers.test.ts`.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|