vue-context-storage 0.1.12 → 0.1.13
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 +101 -4
- package/dist/index.d.ts +49 -12
- package/dist/index.js +10 -3
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Vue 3 context storage system with URL query synchronization support.
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/vue-context-storage)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://vuejs.org/)
|
|
8
|
+
[](https://bundlephobia.com/package/vue-context-storage)
|
|
9
|
+
[](https://github.com/lviobio/vue-context-storage/issues)
|
|
10
|
+
[](https://github.com/lviobio/vue-context-storage)
|
|
11
|
+
[](https://lviobio.github.io/vue-context-storage/)
|
|
12
|
+
|
|
5
13
|
A powerful state management solution for Vue 3 applications that provides:
|
|
6
14
|
- **Context-based storage** using Vue's provide/inject API
|
|
7
15
|
- **Automatic URL query synchronization** for preserving state across page reloads
|
|
@@ -9,6 +17,10 @@ A powerful state management solution for Vue 3 applications that provides:
|
|
|
9
17
|
- **Type-safe** TypeScript support
|
|
10
18
|
- **Tree-shakeable** and lightweight
|
|
11
19
|
|
|
20
|
+
## Live Demo
|
|
21
|
+
|
|
22
|
+
🚀 **[Try the interactive playground](https://lviobio.github.io/vue-context-storage)**
|
|
23
|
+
|
|
12
24
|
## Installation
|
|
13
25
|
|
|
14
26
|
```bash
|
|
@@ -43,7 +55,7 @@ app.use(VueContextStoragePlugin)
|
|
|
43
55
|
app.mount('#app')
|
|
44
56
|
```
|
|
45
57
|
|
|
46
|
-
Then use components without importing
|
|
58
|
+
Then use components without importing in your `App.vue`:
|
|
47
59
|
|
|
48
60
|
```vue
|
|
49
61
|
<template>
|
|
@@ -55,7 +67,7 @@ Then use components without importing:
|
|
|
55
67
|
|
|
56
68
|
### Option 2: Manual Component Import
|
|
57
69
|
|
|
58
|
-
Import components individually when needed
|
|
70
|
+
Import components individually when needed in your `App.vue`:
|
|
59
71
|
|
|
60
72
|
```vue
|
|
61
73
|
<template>
|
|
@@ -84,7 +96,7 @@ interface Filters {
|
|
|
84
96
|
page: number
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
const filters =
|
|
99
|
+
const filters = reactive<Filters>({
|
|
88
100
|
search: '',
|
|
89
101
|
status: 'active',
|
|
90
102
|
page: 1,
|
|
@@ -137,6 +149,39 @@ useContextStorageQueryHandler(state, {
|
|
|
137
149
|
- `asArray(value, options)` - Convert to array
|
|
138
150
|
- `asNumberArray(value, options)` - Convert to number array
|
|
139
151
|
|
|
152
|
+
### Using Zod Schemas
|
|
153
|
+
|
|
154
|
+
Alternatively, you can use [Zod](https://zod.dev/) schemas for automatic validation and type inference:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { z } from 'zod'
|
|
158
|
+
import { useContextStorageQueryHandler } from 'vue-context-storage'
|
|
159
|
+
|
|
160
|
+
// Define schema with automatic coercion
|
|
161
|
+
const FiltersSchema = z.object({
|
|
162
|
+
search: z.string().default(''),
|
|
163
|
+
page: z.coerce.number().int().positive().default(1),
|
|
164
|
+
status: z.enum(['active', 'inactive']).default('active'),
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const filters = ref(FiltersSchema.parse({}))
|
|
168
|
+
|
|
169
|
+
// Use schema for automatic validation
|
|
170
|
+
useContextStorageQueryHandler(filters, {
|
|
171
|
+
prefix: 'filters',
|
|
172
|
+
schema: FiltersSchema,
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Benefits:**
|
|
177
|
+
- Automatic type coercion (strings → numbers, etc.)
|
|
178
|
+
- Runtime validation with detailed errors
|
|
179
|
+
- Automatic TypeScript type inference
|
|
180
|
+
- Less boilerplate code
|
|
181
|
+
- Single source of truth for structure and validation
|
|
182
|
+
|
|
183
|
+
See [ZOD_INTEGRATION.md](./ZOD_INTEGRATION.md) for detailed examples and comparison with manual transforms.
|
|
184
|
+
|
|
140
185
|
### Preserve Empty State
|
|
141
186
|
|
|
142
187
|
Keep empty state in URL to prevent resetting on reload:
|
|
@@ -173,7 +218,7 @@ ContextStorageQueryHandler.configure({
|
|
|
173
218
|
Registers reactive data for URL query synchronization.
|
|
174
219
|
|
|
175
220
|
**Parameters:**
|
|
176
|
-
- `data:
|
|
221
|
+
- `data: MaybeRefOrGetter<T>` - Reactive reference to sync
|
|
177
222
|
- `options?: RegisterQueryHandlerOptions<T>`
|
|
178
223
|
- `prefix?: string` - Query parameter prefix
|
|
179
224
|
- `transform?: (deserialized, initial) => T` - Transform function
|
|
@@ -221,6 +266,18 @@ import type {
|
|
|
221
266
|
} from 'vue-context-storage'
|
|
222
267
|
```
|
|
223
268
|
|
|
269
|
+
When using Zod schemas, TypeScript will automatically infer types:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const FiltersSchema = z.object({
|
|
273
|
+
search: z.string().default(''),
|
|
274
|
+
page: z.coerce.number().default(1),
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
type Filters = z.infer<typeof FiltersSchema>
|
|
278
|
+
// Result: { search: string; page: number }
|
|
279
|
+
```
|
|
280
|
+
|
|
224
281
|
## Examples
|
|
225
282
|
|
|
226
283
|
### Pagination with URL Sync
|
|
@@ -249,11 +306,51 @@ useContextStorageQueryHandler(pagination, {
|
|
|
249
306
|
|
|
250
307
|
- `vue`: ^3.5.0
|
|
251
308
|
- `vue-router`: ^4.0.0
|
|
309
|
+
- `zod`: ^4.0.0 (optional - only if using schema validation)
|
|
252
310
|
|
|
253
311
|
## License
|
|
254
312
|
|
|
255
313
|
MIT
|
|
256
314
|
|
|
315
|
+
## Development
|
|
316
|
+
|
|
317
|
+
### Running Playground Locally
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# Development mode (hot reload)
|
|
321
|
+
npm run play
|
|
322
|
+
|
|
323
|
+
# Production preview
|
|
324
|
+
npm run build:playground
|
|
325
|
+
npm run preview:playground
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Building
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Build library
|
|
332
|
+
npm run build
|
|
333
|
+
|
|
334
|
+
# Build playground for deployment
|
|
335
|
+
npm run build:playground
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Testing & Quality
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Run all checks
|
|
342
|
+
npm run check
|
|
343
|
+
|
|
344
|
+
# Type checking
|
|
345
|
+
npm run ts:check
|
|
346
|
+
|
|
347
|
+
# Linting
|
|
348
|
+
npm run lint
|
|
349
|
+
|
|
350
|
+
# Formatting
|
|
351
|
+
npm run format
|
|
352
|
+
```
|
|
353
|
+
|
|
257
354
|
## Contributing
|
|
258
355
|
|
|
259
356
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as vue9 from "vue";
|
|
2
2
|
import { InjectionKey, MaybeRefOrGetter, Plugin, PropType, UnwrapNestedRefs, WatchHandle } from "vue";
|
|
3
3
|
import { LocationQuery, LocationQueryValue } from "vue-router";
|
|
4
4
|
|
|
5
5
|
//#region src/components/ContextStorageActivator.vue.d.ts
|
|
6
6
|
declare const _default$1: typeof __VLS_export$3;
|
|
7
|
-
declare const __VLS_export$3:
|
|
7
|
+
declare const __VLS_export$3: vue9.DefineComponent<{}, () => vue9.VNode<vue9.RendererNode, vue9.RendererElement, {
|
|
8
8
|
[key: string]: any;
|
|
9
|
-
}>, {}, {}, {},
|
|
9
|
+
}>, {}, {}, {}, vue9.ComponentOptionsMixin, vue9.ComponentOptionsMixin, {}, string, vue9.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue9.ComponentProvideOptions, true, {}, any>;
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/handlers.d.ts
|
|
12
12
|
interface ContextStorageHandlerConstructor {
|
|
@@ -26,37 +26,37 @@ interface ContextStorageHandler {
|
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/components/ContextStorageCollection.vue.d.ts
|
|
28
28
|
declare const _default$2: typeof __VLS_export$2;
|
|
29
|
-
declare const __VLS_export$2:
|
|
29
|
+
declare const __VLS_export$2: vue9.DefineComponent<vue9.ExtractPropTypes<{
|
|
30
30
|
handlers: {
|
|
31
31
|
type: PropType<ContextStorageHandlerConstructor[]>;
|
|
32
32
|
default: () => ContextStorageHandlerConstructor[];
|
|
33
33
|
};
|
|
34
|
-
}>, () =>
|
|
34
|
+
}>, () => vue9.VNode<vue9.RendererNode, vue9.RendererElement, {
|
|
35
35
|
[key: string]: any;
|
|
36
|
-
}>[] | undefined, {}, {}, {},
|
|
36
|
+
}>[] | undefined, {}, {}, {}, vue9.ComponentOptionsMixin, vue9.ComponentOptionsMixin, {}, string, vue9.PublicProps, Readonly<vue9.ExtractPropTypes<{
|
|
37
37
|
handlers: {
|
|
38
38
|
type: PropType<ContextStorageHandlerConstructor[]>;
|
|
39
39
|
default: () => ContextStorageHandlerConstructor[];
|
|
40
40
|
};
|
|
41
41
|
}>> & Readonly<{}>, {
|
|
42
42
|
handlers: ContextStorageHandlerConstructor[];
|
|
43
|
-
}, {}, {}, {}, string,
|
|
43
|
+
}, {}, {}, {}, string, vue9.ComponentProvideOptions, true, {}, any>;
|
|
44
44
|
//#endregion
|
|
45
45
|
//#region src/components/ContextStorageProvider.vue.d.ts
|
|
46
46
|
declare const _default$3: typeof __VLS_export$1;
|
|
47
|
-
declare const __VLS_export$1:
|
|
47
|
+
declare const __VLS_export$1: vue9.DefineComponent<vue9.ExtractPropTypes<{
|
|
48
48
|
itemKey: {
|
|
49
49
|
type: StringConstructor;
|
|
50
50
|
required: true;
|
|
51
51
|
};
|
|
52
|
-
}>, () =>
|
|
52
|
+
}>, () => vue9.VNode<vue9.RendererNode, vue9.RendererElement, {
|
|
53
53
|
[key: string]: any;
|
|
54
|
-
}>[] | undefined, {}, {}, {},
|
|
54
|
+
}>[] | undefined, {}, {}, {}, vue9.ComponentOptionsMixin, vue9.ComponentOptionsMixin, {}, string, vue9.PublicProps, Readonly<vue9.ExtractPropTypes<{
|
|
55
55
|
itemKey: {
|
|
56
56
|
type: StringConstructor;
|
|
57
57
|
required: true;
|
|
58
58
|
};
|
|
59
|
-
}>> & Readonly<{}>, {}, {}, {}, {}, string,
|
|
59
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue9.ComponentProvideOptions, true, {}, any>;
|
|
60
60
|
//#endregion
|
|
61
61
|
//#region src/components/ContextStorage.vue.d.ts
|
|
62
62
|
interface Props {
|
|
@@ -66,7 +66,7 @@ declare var __VLS_20: {};
|
|
|
66
66
|
type __VLS_Slots = {} & {
|
|
67
67
|
default?: (props: typeof __VLS_20) => any;
|
|
68
68
|
};
|
|
69
|
-
declare const __VLS_base:
|
|
69
|
+
declare const __VLS_base: vue9.DefineComponent<Props, {}, {}, {}, {}, vue9.ComponentOptionsMixin, vue9.ComponentOptionsMixin, {}, string, vue9.PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, vue9.ComponentProvideOptions, false, {}, any>;
|
|
70
70
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
71
71
|
declare const _default: typeof __VLS_export;
|
|
72
72
|
type __VLS_WithSlots<T, S> = T & {
|
|
@@ -169,7 +169,44 @@ interface RegisterQueryHandlerBaseOptions<T> extends QueryHandlerSharedOptions {
|
|
|
169
169
|
* ```
|
|
170
170
|
*/
|
|
171
171
|
prefix?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Transform function to convert deserialized query parameters to the expected type.
|
|
174
|
+
*
|
|
175
|
+
* Note: If `schema` is provided, it takes priority over `transform`.
|
|
176
|
+
*/
|
|
172
177
|
transform?: (deserialized: DeepTransformValuesToLocationQueryValue<UnwrapNestedRefs<T>>, initialData: T) => UnwrapNestedRefs<T>;
|
|
178
|
+
/**
|
|
179
|
+
* Zod schema for automatic validation and type coercion.
|
|
180
|
+
*
|
|
181
|
+
* When provided, the schema will be used to parse and validate query parameters.
|
|
182
|
+
* This option takes priority over the `transform` option.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* import { z } from 'zod'
|
|
187
|
+
*
|
|
188
|
+
* const FiltersSchema = z.object({
|
|
189
|
+
* search: z.string().default(''),
|
|
190
|
+
* page: z.coerce.number().int().positive().default(1),
|
|
191
|
+
* status: z.enum(['active', 'inactive']).default('active'),
|
|
192
|
+
* })
|
|
193
|
+
*
|
|
194
|
+
* useContextStorageQueryHandler(filters, {
|
|
195
|
+
* prefix: 'filters',
|
|
196
|
+
* schema: FiltersSchema,
|
|
197
|
+
* })
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
schema?: {
|
|
201
|
+
safeParse: (data: unknown) => {
|
|
202
|
+
success: true;
|
|
203
|
+
data: T;
|
|
204
|
+
} | {
|
|
205
|
+
success: false;
|
|
206
|
+
error: any;
|
|
207
|
+
};
|
|
208
|
+
parse: (data: unknown) => T;
|
|
209
|
+
};
|
|
173
210
|
}
|
|
174
211
|
interface RegisterQueryHandlerOptions<T> extends RegisterBaseOptions, RegisterQueryHandlerBaseOptions<T> {}
|
|
175
212
|
interface IContextStorageQueryHandler extends ContextStorageHandler {
|
package/dist/index.js
CHANGED
|
@@ -229,7 +229,7 @@ var ContextStorageQueryHandler = class ContextStorageQueryHandler {
|
|
|
229
229
|
query: newQuery
|
|
230
230
|
});
|
|
231
231
|
} catch (e) {
|
|
232
|
-
console.error("[
|
|
232
|
+
console.error("[vue-context-storage] Got error while routing", e);
|
|
233
233
|
}
|
|
234
234
|
this.preventAfterEachRouteCallsWhileCallingRouter = false;
|
|
235
235
|
}
|
|
@@ -271,7 +271,14 @@ var ContextStorageQueryHandler = class ContextStorageQueryHandler {
|
|
|
271
271
|
}
|
|
272
272
|
if (deserializedKeys.length === 1 && deserialized[this.options.emptyPlaceholder] === null) delete deserialized[this.options.emptyPlaceholder];
|
|
273
273
|
}
|
|
274
|
-
if (item.options?.
|
|
274
|
+
if (item.options?.schema) {
|
|
275
|
+
console.log("[vue-context-storage] 248", deserialized);
|
|
276
|
+
const result = item.options.schema.safeParse(deserialized);
|
|
277
|
+
console.log("[vue-context-storage] 251", result);
|
|
278
|
+
if (result.success) deserialized = result.data;
|
|
279
|
+
else console.warn("[vue-context-storage] schema parse failed", result.error);
|
|
280
|
+
if (item.options?.transform) console.warn("[vue-context-storage] transform is not supported with schema");
|
|
281
|
+
} else if (item.options?.transform) deserialized = item.options.transform(deserialized, item.initialData);
|
|
275
282
|
else if (mergeOnlyExistingKeysWithoutTransform) deserialized = pick(deserialized, Object.keys(item.initialData));
|
|
276
283
|
if (isEqual(itemData, deserialized)) return;
|
|
277
284
|
merge(itemData, deserialized);
|
|
@@ -311,7 +318,7 @@ var ContextStorageQueryHandler = class ContextStorageQueryHandler {
|
|
|
311
318
|
const patch = serializeParams(toValue(item.data), { prefix });
|
|
312
319
|
const patchKeys = Object.keys(patch);
|
|
313
320
|
patchKeys.forEach((key) => {
|
|
314
|
-
if (newQueryRaw.hasOwnProperty(key)) console.warn(`[
|
|
321
|
+
if (newQueryRaw.hasOwnProperty(key)) console.warn(`[vue-context-storage] Key ${key} is already present, overriding ` + (item.options?.causer || ""));
|
|
315
322
|
});
|
|
316
323
|
if (!patchKeys.length && preserveEmptyState) patch[prefix || this.options.emptyPlaceholder] = null;
|
|
317
324
|
Object.assign(newQueryRaw, patch);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue-context-storage",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.13",
|
|
5
5
|
"description": "Vue 3 context storage system with URL query synchronization support",
|
|
6
6
|
"author": "",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,13 +37,21 @@
|
|
|
37
37
|
"lint:check": "eslint .",
|
|
38
38
|
"dependency-cruiser:check": "depcruise --config .dependency-cruiser.cjs src",
|
|
39
39
|
"play": "vite",
|
|
40
|
+
"build:playground": "vite build",
|
|
41
|
+
"preview:playground": "vite preview --outDir playground/dist",
|
|
40
42
|
"test": "vitest",
|
|
41
43
|
"release": "bumpp && npm publish",
|
|
42
44
|
"prepublishOnly": "npm run check && npm run build"
|
|
43
45
|
},
|
|
44
46
|
"peerDependencies": {
|
|
45
47
|
"vue": "^3.0.0",
|
|
46
|
-
"vue-router": "^4.0.0"
|
|
48
|
+
"vue-router": "^4.0.0",
|
|
49
|
+
"zod": "^4.0.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"zod": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
47
55
|
},
|
|
48
56
|
"devDependencies": {
|
|
49
57
|
"@types/lodash": "^4.17.21",
|
|
@@ -65,7 +73,8 @@
|
|
|
65
73
|
"vitest": "^4.0.16",
|
|
66
74
|
"vitest-browser-vue": "^2.0.1",
|
|
67
75
|
"vue": "^3.5.26",
|
|
68
|
-
"vue-tsc": "^3.2.1"
|
|
76
|
+
"vue-tsc": "^3.2.1",
|
|
77
|
+
"zod": "^4.3.5"
|
|
69
78
|
},
|
|
70
79
|
"dependencies": {
|
|
71
80
|
"lodash": "^4.17.21"
|