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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Vue 3 context storage system with URL query synchronization support.
4
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
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen)](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 = ref<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: Ref<T>` - Reactive reference to sync
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 vue4 from "vue";
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: vue4.DefineComponent<{}, () => vue4.VNode<vue4.RendererNode, vue4.RendererElement, {
7
+ declare const __VLS_export$3: vue9.DefineComponent<{}, () => vue9.VNode<vue9.RendererNode, vue9.RendererElement, {
8
8
  [key: string]: any;
9
- }>, {}, {}, {}, vue4.ComponentOptionsMixin, vue4.ComponentOptionsMixin, {}, string, vue4.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue4.ComponentProvideOptions, true, {}, any>;
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: vue4.DefineComponent<vue4.ExtractPropTypes<{
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
- }>, () => vue4.VNode<vue4.RendererNode, vue4.RendererElement, {
34
+ }>, () => vue9.VNode<vue9.RendererNode, vue9.RendererElement, {
35
35
  [key: string]: any;
36
- }>[] | undefined, {}, {}, {}, vue4.ComponentOptionsMixin, vue4.ComponentOptionsMixin, {}, string, vue4.PublicProps, Readonly<vue4.ExtractPropTypes<{
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, vue4.ComponentProvideOptions, true, {}, any>;
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: vue4.DefineComponent<vue4.ExtractPropTypes<{
47
+ declare const __VLS_export$1: vue9.DefineComponent<vue9.ExtractPropTypes<{
48
48
  itemKey: {
49
49
  type: StringConstructor;
50
50
  required: true;
51
51
  };
52
- }>, () => vue4.VNode<vue4.RendererNode, vue4.RendererElement, {
52
+ }>, () => vue9.VNode<vue9.RendererNode, vue9.RendererElement, {
53
53
  [key: string]: any;
54
- }>[] | undefined, {}, {}, {}, vue4.ComponentOptionsMixin, vue4.ComponentOptionsMixin, {}, string, vue4.PublicProps, Readonly<vue4.ExtractPropTypes<{
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, vue4.ComponentProvideOptions, true, {}, any>;
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: vue4.DefineComponent<Props, {}, {}, {}, {}, vue4.ComponentOptionsMixin, vue4.ComponentOptionsMixin, {}, string, vue4.PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, vue4.ComponentProvideOptions, false, {}, any>;
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("[ContextStorage] Got error while routing", e);
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?.transform) deserialized = item.options.transform(deserialized, item.initialData);
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(`[ContextStorage] Key ${key} is already present, overriding ` + (item.options?.causer || ""));
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.12",
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"