vue-context-storage 0.1.43 → 0.1.44

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
@@ -154,7 +154,7 @@ useContextStorage('sessionStorage', filters, {
154
154
  useContextStorage('query', filters, {
155
155
  key: 'filters',
156
156
  schema: z.object({
157
- page: z.coerce.number().default(1),
157
+ page: z.number().default(1),
158
158
  search: z.string().default(''),
159
159
  status: z.string().default('active'),
160
160
  }),
@@ -267,7 +267,7 @@ import { useContextStorage } from 'vue-context-storage'
267
267
  // Define schema with automatic coercion
268
268
  const FiltersSchema = z.object({
269
269
  search: z.string().default(''),
270
- page: z.coerce.number().int().positive().default(1),
270
+ page: z.number().int().positive().default(1),
271
271
  status: z.enum(['active', 'inactive']).default('active'),
272
272
  })
273
273
 
@@ -329,7 +329,7 @@ import { z } from 'zod'
329
329
 
330
330
  const ItemSchema = z.object({
331
331
  product: z.string().default(''),
332
- quantity: z.coerce.number().default(0),
332
+ quantity: z.number().default(0),
333
333
  })
334
334
 
335
335
  const DataSchema = z.object({
@@ -379,7 +379,7 @@ When using Zod schemas, you can also specify `additionalDefaultData` per-field v
379
379
 
380
380
  ```typescript
381
381
  const Schema = z.object({
382
- page: z.coerce.number().default(1).meta({ additionalDefaultData: 3 }),
382
+ page: z.number().default(1).meta({ additionalDefaultData: 3 }),
383
383
  search: z.string().default(''),
384
384
  })
385
385
 
@@ -405,7 +405,7 @@ Nested objects work the same way. Following the documented best practice of decl
405
405
  const Schema = z.object({
406
406
  filters: z
407
407
  .object({
408
- page: z.coerce.number(),
408
+ page: z.number(),
409
409
  sort: z.string(),
410
410
  })
411
411
  .default({ page: 1, sort: 'asc' }),
@@ -759,7 +759,7 @@ npm install zod
759
759
 
760
760
  ### Automatic Type Coercion
761
761
 
762
- When a `schema` is provided, the library automatically coerces URL query parameter values to match the expected Zod types before validation. This handles two common URL serialization quirks:
762
+ When a `schema` is provided, the library automatically coerces URL query parameter values to match the expected Zod types before validation. This handles the common URL serialization quirks below.
763
763
 
764
764
  #### Array Coercion
765
765
 
@@ -770,7 +770,7 @@ The library introspects the Zod schema before validation and wraps non-array val
770
770
  ```typescript
771
771
  const Schema = z.object({
772
772
  tags: z.string().array().default([]),
773
- ids: z.coerce.number().array().default([]),
773
+ ids: z.number().array().default([]),
774
774
  statuses: z.enum(['active', 'inactive']).array().default([]),
775
775
  filters: z
776
776
  .object({
@@ -790,7 +790,7 @@ Arrays of objects are also handled automatically. They deserialize from indexed
790
790
  ```typescript
791
791
  const ItemSchema = z.object({
792
792
  product: z.string().default(''),
793
- quantity: z.coerce.number().default(0),
793
+ quantity: z.number().default(0),
794
794
  })
795
795
 
796
796
  const Schema = z.object({
@@ -803,7 +803,7 @@ const Schema = z.object({
803
803
 
804
804
  #### Boolean Coercion
805
805
 
806
- The query handler serializes booleans as `'1'`/`'0'` strings in URL parameters (e.g. `?active=1`). Standard `z.coerce.boolean()` cannot be used because `Boolean('0')` is `true` in JavaScript. The library automatically converts `'1'` → `true` and `'0'` → `false` when the schema expects a boolean field. **No special helpers are needed** — plain `z.boolean()` works out of the box:
806
+ The query handler serializes booleans as `'1'`/`'0'` strings in URL parameters (e.g. `?active=1`). Standard `z.boolean()` cannot be used because `Boolean('0')` is `true` in JavaScript. The library automatically converts `'1'` → `true` and `'0'` → `false` when the schema expects a boolean field. **No special helpers are needed** — plain `z.boolean()` works out of the box:
807
807
 
808
808
  ```typescript
809
809
  const Schema = z.object({
@@ -815,6 +815,26 @@ const Schema = z.object({
815
815
  // ?active=0 → { active: false, enabled: true }
816
816
  ```
817
817
 
818
+ #### Number Coercion
819
+
820
+ URL query parameters are always strings (`?page=5` → `'5'`), so plain `z.number()` would reject them with `"expected number, received string"`. The library automatically converts numeric strings to numbers wherever the schema expects a number — including inside nested objects and `z.array(z.number())`. **`z` is not required** — plain `z.number()` works out of the box:
821
+
822
+ ```typescript
823
+ const Schema = z.object({
824
+ page: z.number().default(1),
825
+ score: z.number().nullable(),
826
+ ids: z.array(z.number()).default([]),
827
+ })
828
+
829
+ // ?page=5 → { page: 5, ... }
830
+ // ?score=42 → { score: 42, ... }
831
+ // ?ids=1&ids=2 → { ids: [1, 2], ... }
832
+ ```
833
+
834
+ Coercion is conservative: a non-numeric string (`?page=abc`) or an empty value is **not** forced to a number (`Number('')` would be `0`). Instead the schema validation fails and the field falls back to its initial data, so missing/garbage input never silently becomes `0`. `null` is preserved for `.nullable()` fields.
835
+
836
+ > `z.number()` still works and remains useful when you want Zod's own coercion semantics (e.g. coercing booleans or accepting empty strings as `0`).
837
+
818
838
  ### `createEmptyZodObject(schema, options?)`
819
839
 
820
840
  Creates a plain object with empty/default values based on a Zod schema. Useful for initializing reactive data from a schema definition.
@@ -825,7 +845,7 @@ import { createEmptyZodObject } from 'vue-context-storage'
825
845
 
826
846
  const FiltersSchema = z.object({
827
847
  search: z.string().default(''),
828
- page: z.coerce.number().default(1),
848
+ page: z.number().default(1),
829
849
  active: z.boolean().default(false),
830
850
  score: z.number().nullable(),
831
851
  })
@@ -876,7 +896,7 @@ When using Zod schemas, TypeScript will automatically infer types:
876
896
  ```typescript
877
897
  const FiltersSchema = z.object({
878
898
  search: z.string().default(''),
879
- page: z.coerce.number().default(1),
899
+ page: z.number().default(1),
880
900
  })
881
901
 
882
902
  type Filters = z.infer<typeof FiltersSchema>
package/dist/index.d.ts CHANGED
@@ -40,7 +40,7 @@ interface RegisterBaseOptions<T> {
40
40
  *
41
41
  * const FiltersSchema = z.object({
42
42
  * search: z.string().default(''),
43
- * page: z.coerce.number().int().positive().default(1),
43
+ * page: z.number().int().positive().default(1),
44
44
  * status: z.enum(['active', 'inactive']).default('active'),
45
45
  * })
46
46
  *
@@ -366,10 +366,12 @@ interface RegisterQueryHandlerOptions<T> extends RegisterBaseOptions<T>, Registe
366
366
  //#region src/handlers/web-storage-base/types.d.ts
367
367
  interface WebStorageHandlerBaseOptions {
368
368
  /**
369
- * Default: true for localStorage, false for sessionStorage
369
+ * Default: true
370
370
  *
371
- * If enabled - storage events will be listened to for cross-tab synchronization.
372
- * Only works with localStorage (sessionStorage is per-tab).
371
+ * If enabled - storage events will be listened to so external changes are synced
372
+ * back into the reactive data. For localStorage this covers other tabs; for
373
+ * sessionStorage it covers other contexts of the same session (iframes,
374
+ * `window.open` windows) and manual edits via DevTools.
373
375
  */
374
376
  listenToStorageEvents?: boolean;
375
377
  }
package/dist/index.js CHANGED
@@ -202,6 +202,21 @@ function extractDefaultsFromSchema(schema) {
202
202
  }
203
203
  return result;
204
204
  }
205
+ function coerceScalar(value, baseType) {
206
+ if (baseType === "boolean") {
207
+ if (value === "1") return true;
208
+ if (value === "0") return false;
209
+ return value;
210
+ }
211
+ if (baseType === "number") {
212
+ if (typeof value === "string" && value.trim() !== "") {
213
+ const n = Number(value);
214
+ if (!Number.isNaN(n)) return n;
215
+ }
216
+ return value;
217
+ }
218
+ return value;
219
+ }
205
220
  function coerceDataForSchema(data, schema) {
206
221
  const shape = schema?.shape;
207
222
  if (!shape || typeof shape !== "object") return data;
@@ -210,11 +225,8 @@ function coerceDataForSchema(data, schema) {
210
225
  if (!(key in result)) continue;
211
226
  const base = unwrapZodField(shape[key]);
212
227
  const baseType = zodDefType(base);
213
- if (baseType === "boolean") {
214
- const value = result[key];
215
- if (value === "1") result[key] = true;
216
- else if (value === "0") result[key] = false;
217
- } else if (baseType === "array") {
228
+ if (baseType === "boolean" || baseType === "number") result[key] = coerceScalar(result[key], baseType);
229
+ else if (baseType === "array") {
218
230
  const value = result[key];
219
231
  if (value !== void 0 && value !== null) {
220
232
  let arr;
@@ -222,7 +234,9 @@ function coerceDataForSchema(data, schema) {
222
234
  else if (typeof value === "object") arr = Object.entries(value).sort(([a], [b]) => Number(a) - Number(b)).map(([, v]) => v);
223
235
  else arr = [value];
224
236
  const elementBase = unwrapZodField(base.element ?? base._zod?.def?.element);
225
- if (zodDefType(elementBase) === "object" && elementBase.shape) arr = arr.map((v) => v && typeof v === "object" && !Array.isArray(v) ? coerceDataForSchema(v, elementBase) : v);
237
+ const elementType = zodDefType(elementBase);
238
+ if (elementType === "object" && elementBase.shape) arr = arr.map((v) => v && typeof v === "object" && !Array.isArray(v) ? coerceDataForSchema(v, elementBase) : v);
239
+ else if (elementType === "number" || elementType === "boolean") arr = arr.map((v) => coerceScalar(v, elementType));
226
240
  result[key] = arr;
227
241
  }
228
242
  } else if (baseType === "object" && base.shape) {
@@ -635,6 +649,7 @@ function createWebStorageHandlerInstance(config) {
635
649
  }
636
650
  function handleStorageEvent(event) {
637
651
  if (!enabled) return;
652
+ if (event.storageArea && event.storageArea !== config.storage) return;
638
653
  registered.forEach((item) => {
639
654
  if (event.key === item.options.key) syncStorageToRegisteredItem(item);
640
655
  });
@@ -757,7 +772,7 @@ function createSessionStorageHandler(customOptions) {
757
772
  injectionKey: contextStorageSessionStorageHandler,
758
773
  handlerName: "sessionStorage",
759
774
  options: {
760
- listenToStorageEvents: false,
775
+ listenToStorageEvents: true,
761
776
  ...customOptions
762
777
  }
763
778
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-context-storage",
3
3
  "type": "module",
4
- "version": "0.1.43",
4
+ "version": "0.1.44",
5
5
  "description": "Vue 3 reactive state management — sync state with the URL query, localStorage, and sessionStorage",
6
6
  "author": "",
7
7
  "license": "MIT",