vue-context-storage 0.1.22 → 0.1.23

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/README.md CHANGED
@@ -1,357 +1,642 @@
1
- # vue-context-storage
2
-
3
- Vue 3 context storage system with URL query synchronization support.
4
-
5
- [![npm downloads](https://img.shields.io/npm/dm/vue-context-storage.svg)](https://www.npmjs.com/package/vue-context-storage)
6
- [![TypeScript](https://badgen.net/badge/icon/TypeScript?icon=typescript&label)](https://www.typescriptlang.org/)
7
- [![Vue 3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/)
8
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/vue-context-storage)](https://bundlephobia.com/package/vue-context-storage)
9
- [![GitHub issues](https://img.shields.io/github/issues/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage/issues)
10
- [![GitHub License](https://img.shields.io/github/license/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage)
11
- ![CI](https://github.com/lviobio/vue-context-storage/actions/workflows/ci.yml/badge.svg)
12
- ![Coverage](https://github.com/lviobio/vue-context-storage/actions/workflows/coverage.yml/badge.svg)
13
- [![codecov](https://codecov.io/gh/lviobio/vue-context-storage/branch/main/graph/badge.svg)](https://codecov.io/gh/lviobio/vue-context-storage)
14
- [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen)](https://lviobio.github.io/vue-context-storage/)
15
-
16
- A powerful state management solution for Vue 3 applications that provides:
17
- - **Context-based storage** using Vue's provide/inject API
18
- - **Automatic URL query synchronization** for preserving state across page reloads
19
- - **Multiple storage contexts** with activation management
20
- - **Type-safe** TypeScript support
21
- - **Tree-shakeable** and lightweight
22
-
23
- ## Live Demo
24
-
25
- 🚀 **[Try the interactive playground](https://lviobio.github.io/vue-context-storage)**
26
-
27
- ## Installation
28
-
29
- ```bash
30
- npm install vue-context-storage
31
- ```
32
-
33
- ## Features
34
-
35
- - ✅ **Vue 3 Composition API** - Built with modern Vue patterns
36
- - ✅ **URL Query Sync** - Automatically sync state with URL parameters
37
- - ✅ **Multiple Contexts** - Support multiple independent storage contexts
38
- - ✅ **TypeScript** - Full type safety and IntelliSense support
39
- - ✅ **Flexible** - Works with vue-router 4+
40
- - ✅ **Transform Helpers** - Built-in utilities for type conversion
41
-
42
- ## Basic Usage
43
-
44
- ### Option 1: Using Vue Plugin (Recommended)
45
-
46
- Register the plugin in your main app file:
47
-
48
- ```typescript
49
- import { createApp } from 'vue'
50
- import { VueContextStoragePlugin } from 'vue-context-storage/plugin'
51
- import App from './App.vue'
52
-
53
- const app = createApp(App)
54
-
55
- // Register components globally
56
- app.use(VueContextStoragePlugin)
57
-
58
- app.mount('#app')
59
- ```
60
-
61
- Then use components without importing in your `App.vue`:
62
-
63
- ```vue
64
- <template>
65
- <ContextStorage>
66
- <router-view />
67
- </ContextStorage>
68
- </template>
69
- ```
70
-
71
- ### Option 2: Manual Component Import
72
-
73
- Import components individually when needed in your `App.vue`:
74
-
75
- ```vue
76
- <template>
77
- <ContextStorage>
78
- <router-view />
79
- </ContextStorage>
80
- </template>
81
-
82
- <script setup lang="ts">
83
- import { ContextStorage } from 'vue-context-storage/components'
84
- </script>
85
- ```
86
-
87
- ## Use Query Handler in Components
88
-
89
- Sync reactive state with URL query parameters:
90
-
91
- ```vue
92
- <script setup lang="ts">
93
- import { ref } from 'vue'
94
- import { useContextStorageQueryHandler } from 'vue-context-storage'
95
-
96
- interface Filters {
97
- search: string
98
- status: string
99
- page: number
100
- }
101
-
102
- const filters = reactive<Filters>({
103
- search: '',
104
- status: 'active',
105
- page: 1,
106
- })
107
-
108
- // Automatically syncs filters with URL query
109
- useContextStorageQueryHandler(filters, {
110
- prefix: 'filters', // URL will be: ?filters[search]=...&filters[status]=...
111
- })
112
- </script>
113
- ```
114
-
115
- ## Advanced Usage
116
-
117
- ### Using Transform Helpers
118
-
119
- Convert URL query string values to proper types:
120
-
121
- ```typescript
122
- import { ref } from 'vue'
123
- import { useContextStorageQueryHandler, transform } from 'vue-context-storage'
124
-
125
- interface TableState {
126
- page: number
127
- search: string
128
- perPage: number
129
- }
130
-
131
- const state = ref<TableState>({
132
- page: 1,
133
- search: '',
134
- perPage: 25,
135
- })
136
-
137
- useContextStorageQueryHandler(state, {
138
- prefix: 'table',
139
- transform: (deserialized, initial) => ({
140
- page: transform.asNumber(deserialized.page, { fallback: 1 }),
141
- search: transform.asString(deserialized.search, { fallback: '' }),
142
- perPage: transform.asNumber(deserialized.perPage, { fallback: 25 }),
143
- }),
144
- })
145
- ```
146
-
147
- ### Available Transform Helpers
148
-
149
- - `asNumber(value, options)` - Convert to number
150
- - `asString(value, options)` - Convert to string
151
- - `asBoolean(value, options)` - Convert to boolean
152
- - `asArray(value, options)` - Convert to array
153
- - `asNumberArray(value, options)` - Convert to number array
154
-
155
- ### Using Zod Schemas
156
-
157
- Alternatively, you can use [Zod](https://zod.dev/) schemas for automatic validation and type inference:
158
-
159
- ```typescript
160
- import { z } from 'zod'
161
- import { useContextStorageQueryHandler } from 'vue-context-storage'
162
-
163
- // Define schema with automatic coercion
164
- const FiltersSchema = z.object({
165
- search: z.string().default(''),
166
- page: z.coerce.number().int().positive().default(1),
167
- status: z.enum(['active', 'inactive']).default('active'),
168
- })
169
-
170
- const filters = ref(FiltersSchema.parse({}))
171
-
172
- // Use schema for automatic validation
173
- useContextStorageQueryHandler(filters, {
174
- prefix: 'filters',
175
- schema: FiltersSchema,
176
- })
177
- ```
178
-
179
- **Benefits:**
180
- - Automatic type coercion (strings numbers, etc.)
181
- - Runtime validation with detailed errors
182
- - Automatic TypeScript type inference
183
- - Less boilerplate code
184
- - Single source of truth for structure and validation
185
-
186
- ### Preserve Empty State
187
-
188
- Keep empty state in URL to prevent resetting on reload:
189
-
190
- ```typescript
191
- useContextStorageQueryHandler(filters, {
192
- prefix: 'filters',
193
- preserveEmptyState: true,
194
- // Empty filters will show as: ?filters
195
- // Without this option, empty filters would clear the URL completely
196
- })
197
- ```
198
-
199
- ### Configure Query Handler
200
-
201
- Customize global behavior:
202
-
203
- ```typescript
204
- import { ContextStorageQueryHandler } from 'vue-context-storage'
205
-
206
- ContextStorageQueryHandler.configure({
207
- mode: 'push', // 'replace' (default) or 'push' for history
208
- preserveUnusedKeys: true, // Keep other query params
209
- preserveEmptyState: false,
210
- })
211
- ```
212
-
213
- ## API Reference
214
-
215
- ### Composables
216
-
217
- #### `useContextStorageQueryHandler<T>(data, options)`
218
-
219
- Registers reactive data for URL query synchronization.
220
-
221
- **Parameters:**
222
- - `data: MaybeRefOrGetter<T>` - Reactive reference to sync
223
- - `options?: RegisterQueryHandlerOptions<T>`
224
- - `prefix?: string` - Query parameter prefix
225
- - `transform?: (deserialized, initial) => T` - Transform function
226
- - `preserveEmptyState?: boolean` - Keep empty state in URL
227
- - `mergeOnlyExistingKeysWithoutTransform?: boolean` - Only merge existing keys (default: true)
228
-
229
- ### Classes
230
-
231
- #### `ContextStorageQueryHandler`
232
-
233
- Main handler for URL query synchronization.
234
-
235
- **Static Methods:**
236
- - `configure(options): ContextStorageHandlerConstructor` - Configure global options
237
- - `getInitialStateResolver(): () => LocationQuery` - Get initial state resolver
238
-
239
- **Methods:**
240
- - `register<T>(data, options): () => void` - Register data for sync
241
- - `setEnabled(state, initial): void` - Enable/disable handler
242
- - `setInitialState(state): void` - Set initial state
243
-
244
- ### Transform Helpers
245
-
246
- All transform helpers support nullable and missable options:
247
-
248
- ```typescript
249
- transform.asNumber(value, {
250
- fallback: 0, // Default value
251
- nullable: false, // Allow null return
252
- missable: false, // Allow undefined return
253
- })
254
- ```
255
-
256
- ## TypeScript Support
257
-
258
- Full TypeScript support with type inference:
259
-
260
- ```typescript
261
- import type {
262
- ContextStorageHandler,
263
- ContextStorageHandlerConstructor,
264
- IContextStorageQueryHandler,
265
- QueryValue,
266
- SerializeOptions,
267
- } from 'vue-context-storage'
268
- ```
269
-
270
- When using Zod schemas, TypeScript will automatically infer types:
271
-
272
- ```typescript
273
- const FiltersSchema = z.object({
274
- search: z.string().default(''),
275
- page: z.coerce.number().default(1),
276
- })
277
-
278
- type Filters = z.infer<typeof FiltersSchema>
279
- // Result: { search: string; page: number }
280
- ```
281
-
282
- ## Examples
283
-
284
- ### Pagination with URL Sync
285
-
286
- ```typescript
287
- import { ref } from 'vue'
288
- import { useContextStorageQueryHandler, transform } from 'vue-context-storage'
289
-
290
- const pagination = ref({
291
- page: 1,
292
- perPage: 25,
293
- total: 0,
294
- })
295
-
296
- useContextStorageQueryHandler(pagination, {
297
- prefix: 'page',
298
- transform: (data, initial) => ({
299
- page: transform.asNumber(data.page, { fallback: 1 }),
300
- perPage: transform.asNumber(data.perPage, { fallback: 25 }),
301
- total: initial.total, // Don't sync total from URL
302
- })
303
- })
304
- ```
305
-
306
- ## Peer Dependencies
307
-
308
- - `vue`: ^3.5.0
309
- - `vue-router`: ^4.0.0
310
- - `zod`: ^4.0.0 (optional - only if using schema validation)
311
-
312
- ## License
313
-
314
- MIT
315
-
316
- ## Development
317
-
318
- ### Running Playground Locally
319
-
320
- ```bash
321
- # Development mode (hot reload)
322
- npm run play
323
-
324
- # Production preview
325
- npm run build:playground
326
- npm run preview:playground
327
- ```
328
-
329
- ### Building
330
-
331
- ```bash
332
- # Build library
333
- npm run build
334
-
335
- # Build playground for deployment
336
- npm run build:playground
337
- ```
338
-
339
- ### Testing & Quality
340
-
341
- ```bash
342
- # Run all checks
343
- npm run check
344
-
345
- # Type checking
346
- npm run ts:check
347
-
348
- # Linting
349
- npm run lint
350
-
351
- # Formatting
352
- npm run format
353
- ```
354
-
355
- ## Contributing
356
-
357
- Contributions are welcome! Please feel free to submit a Pull Request.
1
+ # vue-context-storage
2
+
3
+ Vue 3 context storage system with URL query, localStorage, and sessionStorage synchronization support.
4
+
5
+ [![npm downloads](https://img.shields.io/npm/dm/vue-context-storage.svg)](https://www.npmjs.com/package/vue-context-storage)
6
+ [![TypeScript](https://badgen.net/badge/icon/TypeScript?icon=typescript&label)](https://www.typescriptlang.org/)
7
+ [![Vue 3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/)
8
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/vue-context-storage)](https://bundlephobia.com/package/vue-context-storage)
9
+ [![GitHub issues](https://img.shields.io/github/issues/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage/issues)
10
+ [![GitHub License](https://img.shields.io/github/license/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage)
11
+ ![CI](https://github.com/lviobio/vue-context-storage/actions/workflows/ci.yml/badge.svg)
12
+ ![Coverage](https://github.com/lviobio/vue-context-storage/actions/workflows/coverage.yml/badge.svg)
13
+ [![codecov](https://codecov.io/gh/lviobio/vue-context-storage/branch/main/graph/badge.svg)](https://codecov.io/gh/lviobio/vue-context-storage)
14
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen)](https://lviobio.github.io/vue-context-storage/)
15
+
16
+ A powerful state management solution for Vue 3 applications that provides:
17
+
18
+ - **Context-based storage** using Vue's provide/inject API
19
+ - **Automatic URL query synchronization** for preserving state across page reloads
20
+ - **localStorage & sessionStorage handlers** for persistent and session-scoped state
21
+ - **Multiple storage contexts** with activation management
22
+ - **Type-safe** TypeScript support
23
+ - **Tree-shakeable** and lightweight
24
+
25
+ ## Live Demo
26
+
27
+ 🚀 **[Try the interactive playground](https://lviobio.github.io/vue-context-storage)**
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install vue-context-storage
33
+ ```
34
+
35
+ ## Features
36
+
37
+ - ✅ **Vue 3 Composition API** - Built with modern Vue patterns
38
+ - ✅ **URL Query Sync** - Automatically sync state with URL parameters
39
+ - ✅ **localStorage Handler** - Persist state to localStorage with cross-tab sync
40
+ - ✅ **sessionStorage Handler** - Session-scoped state that survives page refreshes
41
+ - ✅ **Multiple Contexts** - Support multiple independent storage contexts
42
+ - **TypeScript** - Full type safety and IntelliSense support
43
+ - ✅ **Flexible** - Works with vue-router 4+
44
+ - **Transform Helpers** - Built-in utilities for type conversion
45
+
46
+ ## Basic Usage
47
+
48
+ ### Option 1: Manual Component Import (Recommended)
49
+
50
+ Import ContextStorage component in your `App.vue`:
51
+
52
+ ```vue
53
+ <template>
54
+ <ContextStorage>
55
+ <router-view />
56
+ </ContextStorage>
57
+ </template>
58
+
59
+ <script setup lang="ts">
60
+ import { ContextStorage } from 'vue-context-storage/components'
61
+ </script>
62
+ ```
63
+
64
+ ### Option 2: Using Vue Plugin
65
+
66
+ Register the plugin in your main app file:
67
+
68
+ ```typescript
69
+ import { createApp } from 'vue'
70
+ import { VueContextStoragePlugin } from 'vue-context-storage/plugin'
71
+ import App from './App.vue'
72
+
73
+ const app = createApp(App)
74
+
75
+ // Register components globally
76
+ app.use(VueContextStoragePlugin)
77
+
78
+ app.mount('#app')
79
+ ```
80
+
81
+ Then use components without importing in your `App.vue`:
82
+
83
+ ```vue
84
+ <template>
85
+ <ContextStorage>
86
+ <router-view />
87
+ </ContextStorage>
88
+ </template>
89
+ ```
90
+
91
+ ## Unified Composable
92
+
93
+ `useContextStorage()` provides a single entry point for all handler types:
94
+
95
+ ```vue
96
+ <script setup lang="ts">
97
+ import { reactive } from 'vue'
98
+ import { useContextStorage } from 'vue-context-storage'
99
+
100
+ const filters = reactive({
101
+ search: '',
102
+ status: 'active',
103
+ page: 1,
104
+ })
105
+
106
+ // Sync with URL query
107
+ useContextStorage('query', filters, {
108
+ prefix: 'filters',
109
+ })
110
+
111
+ // Sync with localStorage
112
+ useContextStorage('localStorage', filters, {
113
+ key: 'saved-filters',
114
+ })
115
+
116
+ // Sync with sessionStorage
117
+ useContextStorage('sessionStorage', filters, {
118
+ key: 'temp-filters',
119
+ })
120
+ </script>
121
+ ```
122
+
123
+ Options are type-checked per handler `'query'` accepts query options, `'localStorage'` and `'sessionStorage'` require a `key`, etc.
124
+
125
+ You can also pass an injection key directly instead of a string:
126
+
127
+ ```typescript
128
+ import { contextStorageQueryHandlerInjectKey } from 'vue-context-storage'
129
+
130
+ useContextStorage(contextStorageQueryHandlerInjectKey, filters, {
131
+ prefix: 'filters',
132
+ })
133
+ ```
134
+
135
+ ### Registering Custom Handlers
136
+
137
+ Register your own handlers at runtime and extend the type map for full type safety:
138
+
139
+ ```typescript
140
+ import { defineContextStorageHandler } from 'vue-context-storage'
141
+ import { myHandlerInjectionKey } from './my-handler'
142
+
143
+ // Runtime registration
144
+ defineContextStorageHandler('myHandler', myHandlerInjectionKey)
145
+
146
+ // TypeScript augmentation (e.g. in a .d.ts or at module level)
147
+ declare module 'vue-context-storage' {
148
+ interface ContextStorageHandlerMap {
149
+ myHandler: { key: string }
150
+ }
151
+ }
152
+
153
+ // Now fully type-checked
154
+ useContextStorage('myHandler', data, { key: 'example' })
155
+ ```
156
+
157
+ ## Use Query Handler in Components
158
+
159
+ Sync reactive state with URL query parameters:
160
+
161
+ ```vue
162
+ <script setup lang="ts">
163
+ import { reactive } from 'vue'
164
+ import { useContextStorage } from 'vue-context-storage'
165
+
166
+ interface Filters {
167
+ search: string
168
+ status: string
169
+ page: number
170
+ }
171
+
172
+ const filters = reactive<Filters>({
173
+ search: '',
174
+ status: 'active',
175
+ page: 1,
176
+ })
177
+
178
+ // Automatically syncs filters with URL query
179
+ useContextStorage('query', filters, {
180
+ prefix: 'filters', // URL will be: ?filters[search]=...&filters[status]=...
181
+ })
182
+ </script>
183
+ ```
184
+
185
+ Also available as a dedicated composable:
186
+
187
+ ```typescript
188
+ import { useContextStorageQueryHandler } from 'vue-context-storage'
189
+
190
+ useContextStorageQueryHandler(filters, {
191
+ prefix: 'filters',
192
+ })
193
+ ```
194
+
195
+ ## Advanced Usage
196
+
197
+ ### Using Transform Helpers
198
+
199
+ Convert URL query string values to proper types:
200
+
201
+ ```typescript
202
+ import { ref } from 'vue'
203
+ import { useContextStorage, transform } from 'vue-context-storage'
204
+
205
+ interface TableState {
206
+ page: number
207
+ search: string
208
+ perPage: number
209
+ }
210
+
211
+ const state = ref<TableState>({
212
+ page: 1,
213
+ search: '',
214
+ perPage: 25,
215
+ })
216
+
217
+ useContextStorage('query', state, {
218
+ prefix: 'table',
219
+ transform: (deserialized, initial) => ({
220
+ page: transform.asNumber(deserialized.page, { fallback: 1 }),
221
+ search: transform.asString(deserialized.search, { fallback: '' }),
222
+ perPage: transform.asNumber(deserialized.perPage, { fallback: 25 }),
223
+ }),
224
+ })
225
+ ```
226
+
227
+ ### Available Transform Helpers
228
+
229
+ - `asNumber(value, options)` - Convert to number
230
+ - `asString(value, options)` - Convert to string
231
+ - `asBoolean(value, options)` - Convert to boolean
232
+ - `asArray(value, options)` - Convert to array
233
+ - `asNumberArray(value, options)` - Convert to number array
234
+
235
+ ### Using Zod Schemas
236
+
237
+ Alternatively, you can use [Zod](https://zod.dev/) schemas for automatic validation and type inference:
238
+
239
+ ```typescript
240
+ import { z } from 'zod'
241
+ import { useContextStorage } from 'vue-context-storage'
242
+
243
+ // Define schema with automatic coercion
244
+ const FiltersSchema = z.object({
245
+ search: z.string().default(''),
246
+ page: z.coerce.number().int().positive().default(1),
247
+ status: z.enum(['active', 'inactive']).default('active'),
248
+ })
249
+
250
+ const filters = ref(FiltersSchema.parse({}))
251
+
252
+ // Use schema for automatic validation
253
+ useContextStorage('query', filters, {
254
+ prefix: 'filters',
255
+ schema: FiltersSchema,
256
+ })
257
+ ```
258
+
259
+ **Benefits:**
260
+
261
+ - Automatic type coercion (strings → numbers, etc.)
262
+ - Runtime validation with detailed errors
263
+ - Automatic TypeScript type inference
264
+ - Less boilerplate code
265
+ - Single source of truth for structure and validation
266
+
267
+ ### Preserve Empty State
268
+
269
+ Keep empty state in URL to prevent resetting on reload:
270
+
271
+ ```typescript
272
+ useContextStorage('query', filters, {
273
+ prefix: 'filters',
274
+ preserveEmptyState: true,
275
+ // Empty filters will show as: ?filters
276
+ // Without this option, empty filters would clear the URL completely
277
+ })
278
+ ```
279
+
280
+ ### Configure Query Handler
281
+
282
+ Customize global behavior:
283
+
284
+ ```typescript
285
+ import { ContextStorageQueryHandler } from 'vue-context-storage'
286
+
287
+ ContextStorageQueryHandler.configure({
288
+ mode: 'push', // 'replace' (default) or 'push' for history
289
+ preserveUnusedKeys: true, // Keep other query params
290
+ preserveEmptyState: false,
291
+ })
292
+ ```
293
+
294
+ ## Use localStorage Handler in Components
295
+
296
+ Persist reactive state to `localStorage`. Data is automatically synced across browser tabs.
297
+
298
+ ```vue
299
+ <script setup lang="ts">
300
+ import { reactive } from 'vue'
301
+ import { useContextStorage } from 'vue-context-storage'
302
+
303
+ const settings = reactive({
304
+ theme: 'light',
305
+ fontSize: 14,
306
+ sidebarOpen: true,
307
+ })
308
+
309
+ // Automatically syncs settings with localStorage under the key "app-settings"
310
+ useContextStorage('localStorage', settings, {
311
+ key: 'app-settings',
312
+ })
313
+ </script>
314
+ ```
315
+
316
+ Also available as a dedicated composable:
317
+
318
+ ```typescript
319
+ import { useContextStorageLocalStorage } from 'vue-context-storage'
320
+
321
+ useContextStorageLocalStorage(settings, {
322
+ key: 'app-settings',
323
+ })
324
+ ```
325
+
326
+ ### Configure localStorage Handler
327
+
328
+ ```typescript
329
+ import { ContextStorageLocalStorageHandler } from 'vue-context-storage'
330
+
331
+ ContextStorageLocalStorageHandler.configure({
332
+ listenToStorageEvents: true, // Cross-tab sync (default: true)
333
+ })
334
+ ```
335
+
336
+ ## Use sessionStorage Handler in Components
337
+
338
+ Persist reactive state to `sessionStorage`. Data survives page refreshes but is cleared when the tab is closed.
339
+
340
+ ```vue
341
+ <script setup lang="ts">
342
+ import { reactive } from 'vue'
343
+ import { useContextStorage } from 'vue-context-storage'
344
+
345
+ const formDraft = reactive({
346
+ email: '',
347
+ message: '',
348
+ step: 1,
349
+ })
350
+
351
+ // Automatically syncs form draft with sessionStorage
352
+ useContextStorage('sessionStorage', formDraft, {
353
+ key: 'contact-form-draft',
354
+ })
355
+ </script>
356
+ ```
357
+
358
+ Also available as a dedicated composable:
359
+
360
+ ```typescript
361
+ import { useContextStorageSessionStorage } from 'vue-context-storage'
362
+
363
+ useContextStorageSessionStorage(formDraft, {
364
+ key: 'contact-form-draft',
365
+ })
366
+ ```
367
+
368
+ ### Using Prefix
369
+
370
+ Store multiple data objects under a single storage key using prefixes:
371
+
372
+ ```typescript
373
+ const filters = reactive({ search: '', status: 'active' })
374
+
375
+ useContextStorage('sessionStorage', filters, {
376
+ key: 'app-state',
377
+ prefix: 'filters', // Stored as { filters: { search: '', status: 'active' } }
378
+ })
379
+
380
+ const pagination = reactive({ page: 1, perPage: 25 })
381
+
382
+ useContextStorage('sessionStorage', pagination, {
383
+ key: 'app-state',
384
+ prefix: 'pagination', // Stored as { filters: {...}, pagination: { page: 1, perPage: 25 } }
385
+ })
386
+ ```
387
+
388
+ ### Using Transform with Storage Handlers
389
+
390
+ Convert stored values to proper types when reading from storage:
391
+
392
+ ```typescript
393
+ import { useContextStorage, transform } from 'vue-context-storage'
394
+
395
+ const settings = reactive({
396
+ theme: 'light',
397
+ fontSize: 14,
398
+ })
399
+
400
+ useContextStorage('localStorage', settings, {
401
+ key: 'app-settings',
402
+ transform: (deserialized, initial) => ({
403
+ theme: transform.asString(deserialized.theme, { fallback: 'light' }),
404
+ fontSize: transform.asNumber(deserialized.fontSize, { fallback: 14 }),
405
+ }),
406
+ })
407
+ ```
408
+
409
+ ### Using Zod Schemas with Storage Handlers
410
+
411
+ ```typescript
412
+ import { z } from 'zod'
413
+ import { useContextStorageLocalStorage } from 'vue-context-storage'
414
+
415
+ const SettingsSchema = z.object({
416
+ theme: z.enum(['light', 'dark']).default('light'),
417
+ fontSize: z.number().int().positive().default(14),
418
+ sidebarOpen: z.boolean().default(true),
419
+ })
420
+
421
+ const settings = reactive(SettingsSchema.parse({}))
422
+
423
+ useContextStorage('localStorage', settings, {
424
+ key: 'app-settings',
425
+ schema: SettingsSchema,
426
+ })
427
+ ```
428
+
429
+ ### Custom Serialization
430
+
431
+ Provide custom serializer/deserializer functions:
432
+
433
+ ```typescript
434
+ useContextStorage('localStorage', settings, {
435
+ key: 'app-settings',
436
+ serializer: (data) => btoa(JSON.stringify(data)),
437
+ deserializer: (str) => JSON.parse(atob(str)),
438
+ })
439
+ ```
440
+
441
+ ## API Reference
442
+
443
+ ### Composables
444
+
445
+ #### `useContextStorage(type, data, options)`
446
+
447
+ Unified composable that delegates to the correct handler based on `type`.
448
+
449
+ **Parameters:**
450
+
451
+ - `type: 'query' | 'localStorage' | 'sessionStorage' | InjectionKey` - Handler type or injection key
452
+ - `data: MaybeRefOrGetter<T>` - Reactive reference to sync
453
+ - `options` - Handler-specific options (type-checked per handler)
454
+
455
+ **Custom handler registration:**
456
+
457
+ - `defineContextStorageHandler(name, injectionKey)` - Register a custom handler
458
+ - `resolveHandlerInjectionKey(type)` - Look up an injection key by name
459
+
460
+ #### `useContextStorageQueryHandler<T>(data, options)`
461
+
462
+ Registers reactive data for URL query synchronization.
463
+
464
+ **Parameters:**
465
+
466
+ - `data: MaybeRefOrGetter<T>` - Reactive reference to sync
467
+ - `options?: RegisterQueryHandlerOptions<T>`
468
+ - `prefix?: string` - Query parameter prefix
469
+ - `transform?: (deserialized, initial) => T` - Transform function
470
+ - `preserveEmptyState?: boolean` - Keep empty state in URL
471
+ - `mergeOnlyExistingKeysWithoutTransform?: boolean` - Only merge existing keys (default: true)
472
+
473
+ ### Classes
474
+
475
+ #### `ContextStorageQueryHandler`
476
+
477
+ Main handler for URL query synchronization.
478
+
479
+ **Static Methods:**
480
+
481
+ - `configure(options): ContextStorageHandlerConstructor` - Configure global options
482
+ - `getInitialStateResolver(): () => LocationQuery` - Get initial state resolver
483
+
484
+ **Methods:**
485
+
486
+ - `register<T>(data, options): () => void` - Register data for sync
487
+ - `setEnabled(state, initial): void` - Enable/disable handler
488
+ - `setInitialState(state): void` - Set initial state
489
+
490
+ #### `useContextStorageLocalStorage<T>(data, options)`
491
+
492
+ Registers reactive data for localStorage synchronization.
493
+
494
+ **Parameters:**
495
+
496
+ - `data: MaybeRefOrGetter<T>` - Reactive reference to sync
497
+ - `options: RegisterWebStorageHandlerBaseOptions<T>`
498
+ - `key: string` - Storage key (required)
499
+ - `prefix?: string` - Namespace within the storage key
500
+ - `transform?: (deserialized, initial) => T` - Transform function
501
+ - `schema?: ZodSchema` - Zod schema for validation
502
+ - `serializer?: (data: T) => string` - Custom serializer (default: `JSON.stringify`)
503
+ - `deserializer?: (str: string) => unknown` - Custom deserializer (default: `JSON.parse`)
504
+
505
+ #### `useContextStorageSessionStorage<T>(data, options)`
506
+
507
+ Registers reactive data for sessionStorage synchronization. Same options as `useContextStorageLocalStorage`.
508
+
509
+ ### Classes
510
+
511
+ #### `ContextStorageLocalStorageHandler`
512
+
513
+ Handler for localStorage synchronization. Supports cross-tab sync via `storage` events.
514
+
515
+ **Static Methods:**
516
+
517
+ - `configure(options): ContextStorageHandlerConstructor` - Configure global options
518
+ - `listenToStorageEvents?: boolean` - Enable cross-tab sync (default: `true`)
519
+
520
+ #### `ContextStorageSessionStorageHandler`
521
+
522
+ Handler for sessionStorage synchronization. Data is scoped to the current tab.
523
+
524
+ **Static Methods:**
525
+
526
+ - `configure(options): ContextStorageHandlerConstructor` - Configure global options
527
+ - `listenToStorageEvents?: boolean` - Listen to storage events (default: `false`)
528
+
529
+ ### Transform Helpers
530
+
531
+ All transform helpers support nullable and missable options:
532
+
533
+ ```typescript
534
+ transform.asNumber(value, {
535
+ fallback: 0, // Default value
536
+ nullable: false, // Allow null return
537
+ missable: false, // Allow undefined return
538
+ })
539
+ ```
540
+
541
+ ## TypeScript Support
542
+
543
+ Full TypeScript support with type inference:
544
+
545
+ ```typescript
546
+ import type {
547
+ ContextStorageHandler,
548
+ ContextStorageHandlerConstructor,
549
+ IContextStorageQueryHandler,
550
+ QueryValue,
551
+ SerializeOptions,
552
+ } from 'vue-context-storage'
553
+ ```
554
+
555
+ When using Zod schemas, TypeScript will automatically infer types:
556
+
557
+ ```typescript
558
+ const FiltersSchema = z.object({
559
+ search: z.string().default(''),
560
+ page: z.coerce.number().default(1),
561
+ })
562
+
563
+ type Filters = z.infer<typeof FiltersSchema>
564
+ // Result: { search: string; page: number }
565
+ ```
566
+
567
+ ## Examples
568
+
569
+ ### Pagination with URL Sync
570
+
571
+ ```typescript
572
+ import { ref } from 'vue'
573
+ import { useContextStorageQueryHandler, transform } from 'vue-context-storage'
574
+
575
+ const pagination = ref({
576
+ page: 1,
577
+ perPage: 25,
578
+ total: 0,
579
+ })
580
+
581
+ useContextStorageQueryHandler(pagination, {
582
+ prefix: 'page',
583
+ transform: (data, initial) => ({
584
+ page: transform.asNumber(data.page, { fallback: 1 }),
585
+ perPage: transform.asNumber(data.perPage, { fallback: 25 }),
586
+ total: initial.total, // Don't sync total from URL
587
+ }),
588
+ })
589
+ ```
590
+
591
+ ## Peer Dependencies
592
+
593
+ - `vue`: ^3.5.0
594
+ - `vue-router`: ^4.0.0
595
+ - `zod`: ^4.0.0 (optional - only if using schema validation)
596
+
597
+ ## License
598
+
599
+ MIT
600
+
601
+ ## Development
602
+
603
+ ### Running Playground Locally
604
+
605
+ ```bash
606
+ # Development mode (hot reload)
607
+ npm run play
608
+
609
+ # Production preview
610
+ npm run build:playground
611
+ npm run preview:playground
612
+ ```
613
+
614
+ ### Building
615
+
616
+ ```bash
617
+ # Build library
618
+ npm run build
619
+
620
+ # Build playground for deployment
621
+ npm run build:playground
622
+ ```
623
+
624
+ ### Testing & Quality
625
+
626
+ ```bash
627
+ # Run all checks
628
+ npm run check
629
+
630
+ # Type checking
631
+ npm run ts:check
632
+
633
+ # Linting
634
+ npm run lint
635
+
636
+ # Formatting
637
+ npm run format
638
+ ```
639
+
640
+ ## Contributing
641
+
642
+ Contributions are welcome! Please feel free to submit a Pull Request.