vue-context-storage 0.1.45 → 0.1.46
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 +88 -2
- package/dist/index.d.ts +161 -47
- package/dist/index.js +146 -36
- package/package.json +94 -98
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Vue 3 reactive state management — sync state with the URL query, localStorage,
|
|
|
11
11
|

|
|
12
12
|

|
|
13
13
|
[](https://codecov.io/gh/lviobio/vue-context-storage)
|
|
14
|
-
[](https://lviobio.github.io/vue-context-storage
|
|
14
|
+
[](https://lviobio.github.io/vue-context-storage)
|
|
15
15
|
|
|
16
16
|
Key features:
|
|
17
17
|
|
|
@@ -66,7 +66,6 @@ In Vue applications, reactive state often needs to live beyond a single componen
|
|
|
66
66
|
- **URL query parameters** stay in sync with your data automatically - users can bookmark or share a page and get the exact same state back.
|
|
67
67
|
- **localStorage and sessionStorage** are kept up to date without manual `getItem`/`setItem` calls, including cross-tab synchronization.
|
|
68
68
|
- **Type safety** is preserved end-to-end: URL strings are coerced back to numbers, booleans, and arrays via transform helpers or Zod schemas.
|
|
69
|
-
- **Multiple independent contexts** (e.g. two data tables on the same page) are supported out of the box through the key pattern, so query parameters never collide.
|
|
70
69
|
|
|
71
70
|
The goal is a single, declarative API - `useContextStorage('query', data, options)` - that replaces scattered watchers, router guards, and storage listeners with one composable call per piece of state.
|
|
72
71
|
|
|
@@ -462,6 +461,93 @@ const customHandlers = [
|
|
|
462
461
|
// <ContextStorage :handlers="customHandlers">
|
|
463
462
|
```
|
|
464
463
|
|
|
464
|
+
### Comma-separated Arrays in the Query String
|
|
465
|
+
|
|
466
|
+
By default, flat arrays are written as repeated query keys (`?ids=1&ids=2&ids=3`). Set `serialize: { arrayFormat: 'comma' }` to store them as a single comma-joined value instead:
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
createQueryHandler({ serialize: { arrayFormat: 'comma' } })
|
|
470
|
+
|
|
471
|
+
// { ids: [1, 2, 3] } → ?ids=1,2,3
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Values that themselves contain the separator are safely backslash-escaped, so they survive the round-trip without breaking into extra elements:
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// { tags: ['Some value', 'with, comma', 'last'] }
|
|
478
|
+
// → ?tags=Some value,with\,comma,last
|
|
479
|
+
// → { tags: ['Some value', 'with,comma', 'last'] } (escaped comma restored)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
The option can be set on the factory (applies to every registered context) or per `useContextStorage('query', ...)` call (the per-call value wins). A custom separator is supported via `arraySeparator`:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
createQueryHandler({ serialize: { arrayFormat: 'comma', arraySeparator: '|' } })
|
|
486
|
+
|
|
487
|
+
// { ids: [1, 2, 3] } → ?ids=1|2|3
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Restoring a comma-joined array back into an array requires a `schema` or `transform` (a comma-joined string is indistinguishable from a plain string on the URL — the same requirement as any non-string query value):
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import { z } from 'zod'
|
|
494
|
+
|
|
495
|
+
const data = reactive({ ids: [] as number[] })
|
|
496
|
+
|
|
497
|
+
useContextStorage('query', data, {
|
|
498
|
+
schema: z.object({ ids: z.array(z.number()) }),
|
|
499
|
+
serialize: { arrayFormat: 'comma' },
|
|
500
|
+
})
|
|
501
|
+
// ?ids=1,2,3 → { ids: [1, 2, 3] } (split + coerced automatically)
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
With the manual `transform` approach, pass the matching `separator` to the array helpers:
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
useContextStorage('query', data, {
|
|
508
|
+
transform: (deserialized) => ({
|
|
509
|
+
ids: transform.asNumberArray(deserialized.ids, { separator: ',' }),
|
|
510
|
+
}),
|
|
511
|
+
serialize: { arrayFormat: 'comma' },
|
|
512
|
+
})
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Fully Custom Serializer
|
|
516
|
+
|
|
517
|
+
When the built-in serializer options aren't enough, pass a `serializer` (a `serialize` / `deserialize` pair) to `createQueryHandler` to take **full control** of how state is encoded to and decoded from the URL — e.g. a `qs`-style format, base64, or JSON. This is a **factory-level** option.
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
import { createQueryHandler, type QuerySerializer } from 'vue-context-storage'
|
|
521
|
+
|
|
522
|
+
// Example: JSON-encode each registration's data under its key
|
|
523
|
+
const jsonSerializer: QuerySerializer = {
|
|
524
|
+
serialize: (data, options) => ({ [options?.key ?? '_']: JSON.stringify(data) }),
|
|
525
|
+
deserialize: (query) => {
|
|
526
|
+
const out: Record<string, unknown> = {}
|
|
527
|
+
for (const key of Object.keys(query)) {
|
|
528
|
+
try {
|
|
529
|
+
out[key] = JSON.parse(query[key] as string)
|
|
530
|
+
} catch {
|
|
531
|
+
out[key] = query[key]
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return out
|
|
535
|
+
},
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// <ContextStorage :additional-handlers="[createQueryHandler({ serializer: jsonSerializer })]">
|
|
539
|
+
// useContextStorage('query', data, { key: 'f', schema: ... })
|
|
540
|
+
// → ?f={"ids":[1,2,3],"q":"hello"}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**Contract** (`QuerySerializer`):
|
|
544
|
+
|
|
545
|
+
- `serialize` and `deserialize` **must be mutual inverses**.
|
|
546
|
+
- They must preserve the same per-`key` nesting the built-in pair uses: `deserialize(serialize(data, { key }))` reproduces `{ [key]: data }` (and `{ ...data }` when no `key` is given). The handler relies on this to extract each registration's subtree, diff against baselines, and drive `onlyChanges`.
|
|
547
|
+
- `serialize` always receives the **fully resolved options** (`ResolvedSerializeOptions`): `key`, plus `arrayFormat` and `arraySeparator` (the factory-level and per-`useContextStorage` `serialize` options merged, with defaults applied). A custom serializer can honour them — e.g. respect `arrayFormat: 'comma'` — or own the encoding entirely and ignore them.
|
|
548
|
+
- `deserialize` runs on the **whole** route query before per-registration extraction, so it does NOT receive options — fix the decoding format in the function (mirroring whatever `serialize` produced).
|
|
549
|
+
- `deserialize` output still flows through `schema` / `transform` coercion, so return already-typed values (numbers, arrays — like the JSON example above) or URL-style strings the standard coercion understands.
|
|
550
|
+
|
|
465
551
|
## Use localStorage Handler in Components
|
|
466
552
|
|
|
467
553
|
Persist reactive state to `localStorage`. Data is automatically synced across browser tabs.
|
package/dist/index.d.ts
CHANGED
|
@@ -220,8 +220,119 @@ declare function createCollectionManager(handlerFactories: ContextStorageHandler
|
|
|
220
220
|
};
|
|
221
221
|
type CollectionManager = ReturnType<typeof createCollectionManager>;
|
|
222
222
|
//#endregion
|
|
223
|
+
//#region src/handlers/query/helpers.d.ts
|
|
224
|
+
/**
|
|
225
|
+
* Controls how flat (scalar) arrays are written to the URL query.
|
|
226
|
+
*
|
|
227
|
+
* - `'repeat'` (default): one query entry per value — `?ids=1&ids=2&ids=3`.
|
|
228
|
+
* Round-trips natively because vue-router restores repeated keys as arrays.
|
|
229
|
+
* - `'comma'`: a single comma-joined value — `?ids=1,2,3`. Produces shorter,
|
|
230
|
+
* more readable URLs, but the array-ness is lost on the URL (a comma-joined
|
|
231
|
+
* string is indistinguishable from a plain string), so restoring an array
|
|
232
|
+
* requires a `schema` or `transform` to know the field is an array.
|
|
233
|
+
*/
|
|
234
|
+
type QueryArrayFormat = 'repeat' | 'comma';
|
|
235
|
+
interface QuerySerializeOptions {
|
|
236
|
+
/**
|
|
237
|
+
* How flat (scalar) arrays are written to the URL. Default: `'repeat'`.
|
|
238
|
+
*/
|
|
239
|
+
arrayFormat?: QueryArrayFormat;
|
|
240
|
+
/**
|
|
241
|
+
* Separator used when `arrayFormat` is `'comma'`. Default: `','`.
|
|
242
|
+
*/
|
|
243
|
+
arraySeparator?: string;
|
|
244
|
+
}
|
|
245
|
+
interface SerializeOptions extends QuerySerializeOptions {
|
|
246
|
+
/**
|
|
247
|
+
* Custom key prefix for serialized keys.
|
|
248
|
+
* @example
|
|
249
|
+
* - key: 'filters' => 'filters[field]'
|
|
250
|
+
* - key: 'search' => 'search[field]'
|
|
251
|
+
* - key: '' => 'field' (no prefix)
|
|
252
|
+
*/
|
|
253
|
+
key?: string;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The serialize options after the handler has resolved them — the factory-level
|
|
257
|
+
* and per-register `serialize` options merged, with defaults applied
|
|
258
|
+
* (`arrayFormat: 'repeat'`, `arraySeparator: ','`). This is exactly what the
|
|
259
|
+
* query handler passes to {@link serializeParams} (and to a custom
|
|
260
|
+
* `serializer.serialize`), so `arrayFormat` and `arraySeparator` are always
|
|
261
|
+
* present — `key` is present only when the registration sets one.
|
|
262
|
+
*/
|
|
263
|
+
interface ResolvedSerializeOptions {
|
|
264
|
+
key?: string;
|
|
265
|
+
arrayFormat: QueryArrayFormat;
|
|
266
|
+
arraySeparator: string;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Serializes filter parameters into a URL-friendly format.
|
|
270
|
+
*
|
|
271
|
+
* @param params - Raw parameters object to serialize
|
|
272
|
+
* @param options - Serialization options
|
|
273
|
+
* @returns Serialized parameters with prefixed keys
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* // With default prefix 'filters'
|
|
277
|
+
* serializeFiltersParams({ status: 'active', tags: ['a', 'b'] })
|
|
278
|
+
* // => { 'filters[status]': 'active', 'filters[tags]': 'a,b' }
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* // With custom prefix
|
|
282
|
+
* serializeFiltersParams({ name: 'John', all: true }, { prefix: 'search' })
|
|
283
|
+
* // => { 'search[name]': 'John', 'search[all]': '1' }
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* // Without prefix
|
|
287
|
+
* serializeFiltersParams({ page: 1, all: false }, { prefix: '' })
|
|
288
|
+
* // => { 'page': '1', 'all': '0' }
|
|
289
|
+
*/
|
|
290
|
+
declare function serializeParams(params: Record<string, unknown>, options?: SerializeOptions): LocationQuery;
|
|
291
|
+
/**
|
|
292
|
+
* Deserializes query parameters from a URL-friendly format back to an object.
|
|
293
|
+
*
|
|
294
|
+
* @param params - Serialized parameters object
|
|
295
|
+
* @returns Deserialized parameters object
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* deserializeParams({ 'filters[status]': 'active', search: 'test' })
|
|
299
|
+
* // => { filters: {status: 'active'}, search: 'test' }
|
|
300
|
+
*/
|
|
301
|
+
declare function deserializeParams(params: Record<string, any>): Record<string, unknown>;
|
|
302
|
+
//#endregion
|
|
223
303
|
//#region src/handlers/query/types.d.ts
|
|
224
304
|
type QueryValue = LocationQueryValue | LocationQueryValue[];
|
|
305
|
+
/**
|
|
306
|
+
* A fully custom serializer pair that replaces the built-in
|
|
307
|
+
* `serializeParams` / `deserializeParams` for the query handler.
|
|
308
|
+
*
|
|
309
|
+
* Set it on the handler factory (`createQueryHandler({ serializer })`) to take
|
|
310
|
+
* full control of how reactive state is encoded to / decoded from the URL query
|
|
311
|
+
* (e.g. a `qs`-style format, base64, JSON, or custom escaping).
|
|
312
|
+
*
|
|
313
|
+
* Contract:
|
|
314
|
+
* - `serialize` and `deserialize` MUST be mutual inverses.
|
|
315
|
+
* - They must preserve the same per-`key` nesting the standard pair uses, i.e.
|
|
316
|
+
* `deserialize(serialize(data, { key }))` reproduces `{ [key]: data }` (and
|
|
317
|
+
* `{ ...data }` when no `key` is given). The handler relies on this to extract
|
|
318
|
+
* each registration's subtree, diff against baselines, and drive `onlyChanges`.
|
|
319
|
+
* - `serialize` receives the fully {@link ResolvedSerializeOptions resolved}
|
|
320
|
+
* options — the factory-level and per-register `serialize` options merged with
|
|
321
|
+
* defaults — so `key`, `arrayFormat` and `arraySeparator` are all available.
|
|
322
|
+
* A custom implementation may honour the array options (e.g. respect
|
|
323
|
+
* `arrayFormat: 'comma'`) or own the encoding entirely and ignore them.
|
|
324
|
+
* - `deserialize` runs on the whole route query before per-registration
|
|
325
|
+
* extraction, so it does NOT receive options — the author fixes the decoding
|
|
326
|
+
* format (and must mirror whatever `serialize` produced).
|
|
327
|
+
*
|
|
328
|
+
* `deserialize` output still flows through `schema` / `transform` coercion, so
|
|
329
|
+
* return already-typed values (numbers, arrays) or URL-style strings the
|
|
330
|
+
* standard coercion understands.
|
|
331
|
+
*/
|
|
332
|
+
interface QuerySerializer {
|
|
333
|
+
serialize: (params: Record<string, unknown>, options: ResolvedSerializeOptions) => LocationQuery;
|
|
334
|
+
deserialize: (query: Record<string, any>) => Record<string, any>;
|
|
335
|
+
}
|
|
225
336
|
type DeepTransformValuesToLocationQueryValue<T> = { [K in keyof T]?: T[K] extends object ? T[K] extends Array<any> ? QueryValue : DeepTransformValuesToLocationQueryValue<T[K]> : QueryValue };
|
|
226
337
|
interface QueryHandlerSharedOptions {
|
|
227
338
|
/**
|
|
@@ -290,6 +401,28 @@ interface QueryHandlerSharedOptions {
|
|
|
290
401
|
* If transform option is not passed, ref will be merged with query only by keys that exists in ref.
|
|
291
402
|
*/
|
|
292
403
|
mergeOnlyExistingKeysWithoutTransform?: boolean;
|
|
404
|
+
/**
|
|
405
|
+
* Options for the standard query serializer.
|
|
406
|
+
*
|
|
407
|
+
* Use this to store flat arrays as a single comma-joined value instead of
|
|
408
|
+
* repeated keys:
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```ts
|
|
412
|
+
* createQueryHandler({ serialize: { arrayFormat: 'comma' } })
|
|
413
|
+
*
|
|
414
|
+
* // { ids: [1, 2, 3] } → ?ids=1,2,3 (instead of ?ids=1&ids=2&ids=3)
|
|
415
|
+
* ```
|
|
416
|
+
*
|
|
417
|
+
* Can be set on the handler factory (applies to every registered context) or
|
|
418
|
+
* per `useContextStorage('query', ...)` call (overrides the factory value).
|
|
419
|
+
*
|
|
420
|
+
* Restoring a comma-joined array back into an array requires a `schema` or a
|
|
421
|
+
* `transform` (the comma string is indistinguishable from a plain string on
|
|
422
|
+
* the URL). The built-in `schema` coercion and the `asArray` / `asNumberArray`
|
|
423
|
+
* transform helpers split on `arraySeparator` automatically.
|
|
424
|
+
*/
|
|
425
|
+
serialize?: QuerySerializeOptions;
|
|
293
426
|
}
|
|
294
427
|
interface QueryHandlerBaseOptions extends QueryHandlerSharedOptions {
|
|
295
428
|
/**
|
|
@@ -317,6 +450,27 @@ interface QueryHandlerBaseOptions extends QueryHandlerSharedOptions {
|
|
|
317
450
|
* to have exclusive ownership of all query parameters.
|
|
318
451
|
*/
|
|
319
452
|
preserveUnusedKeys?: boolean;
|
|
453
|
+
/**
|
|
454
|
+
* A fully custom serializer pair replacing the built-in `serializeParams` /
|
|
455
|
+
* `deserializeParams`. Factory-level only — owns the entire URL encoding.
|
|
456
|
+
*
|
|
457
|
+
* When set, the built-in `serialize` options (`arrayFormat` / `arraySeparator`)
|
|
458
|
+
* no longer apply. See {@link QuerySerializer} for the contract.
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```ts
|
|
462
|
+
* import qs from 'qs'
|
|
463
|
+
*
|
|
464
|
+
* createQueryHandler({
|
|
465
|
+
* serializer: {
|
|
466
|
+
* serialize: (data, { key } = {}) =>
|
|
467
|
+
* qs.parse(qs.stringify(key ? { [key]: data } : data, { encode: false })),
|
|
468
|
+
* deserialize: (query) => query,
|
|
469
|
+
* },
|
|
470
|
+
* })
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
serializer?: QuerySerializer;
|
|
320
474
|
}
|
|
321
475
|
interface RegisterQueryHandlerBaseOptions<T> extends QueryHandlerSharedOptions {
|
|
322
476
|
/**
|
|
@@ -557,30 +711,36 @@ declare function asString(value: QueryValue | undefined, options: {
|
|
|
557
711
|
declare function asNumberArray(value: QueryValue | undefined): number[];
|
|
558
712
|
declare function asNumberArray(value: QueryValue | undefined, options: {
|
|
559
713
|
nullable: true;
|
|
714
|
+
separator?: string;
|
|
560
715
|
}): number[] | null;
|
|
561
716
|
declare function asNumberArray(value: QueryValue | undefined, options: {
|
|
562
717
|
nullable?: false;
|
|
718
|
+
separator?: string;
|
|
563
719
|
}): number[];
|
|
564
720
|
declare function asArray<T>(value: QueryValue | undefined): T[];
|
|
565
721
|
declare function asArray<T>(value: QueryValue | undefined, options: {
|
|
566
722
|
nullable: true;
|
|
567
723
|
missable: true;
|
|
568
724
|
transform?: (value: QueryValue) => T;
|
|
725
|
+
separator?: string;
|
|
569
726
|
}): T[] | null | undefined;
|
|
570
727
|
declare function asArray<T>(value: QueryValue | undefined, options: {
|
|
571
728
|
nullable: true;
|
|
572
729
|
missable?: false;
|
|
573
730
|
transform?: (value: QueryValue) => T;
|
|
731
|
+
separator?: string;
|
|
574
732
|
}): T[] | null;
|
|
575
733
|
declare function asArray<T>(value: QueryValue | undefined, options: {
|
|
576
734
|
nullable?: false;
|
|
577
735
|
missable: true;
|
|
578
736
|
transform?: (value: QueryValue) => T;
|
|
737
|
+
separator?: string;
|
|
579
738
|
}): T[] | undefined;
|
|
580
739
|
declare function asArray<T>(value: QueryValue | undefined, options: {
|
|
581
740
|
nullable?: false;
|
|
582
741
|
missable?: false;
|
|
583
742
|
transform?: (value: QueryValue) => T;
|
|
743
|
+
separator?: string;
|
|
584
744
|
}): T[];
|
|
585
745
|
declare function asBoolean(value: QueryValue | undefined): boolean;
|
|
586
746
|
declare function asBoolean(value: QueryValue | undefined, options: {
|
|
@@ -634,52 +794,6 @@ declare const transform: {
|
|
|
634
794
|
asObjectArray: typeof asObjectArray;
|
|
635
795
|
};
|
|
636
796
|
//#endregion
|
|
637
|
-
//#region src/handlers/query/helpers.d.ts
|
|
638
|
-
interface SerializeOptions {
|
|
639
|
-
/**
|
|
640
|
-
* Custom key prefix for serialized keys.
|
|
641
|
-
* @example
|
|
642
|
-
* - key: 'filters' => 'filters[field]'
|
|
643
|
-
* - key: 'search' => 'search[field]'
|
|
644
|
-
* - key: '' => 'field' (no prefix)
|
|
645
|
-
*/
|
|
646
|
-
key?: string;
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Serializes filter parameters into a URL-friendly format.
|
|
650
|
-
*
|
|
651
|
-
* @param params - Raw parameters object to serialize
|
|
652
|
-
* @param options - Serialization options
|
|
653
|
-
* @returns Serialized parameters with prefixed keys
|
|
654
|
-
*
|
|
655
|
-
* @example
|
|
656
|
-
* // With default prefix 'filters'
|
|
657
|
-
* serializeFiltersParams({ status: 'active', tags: ['a', 'b'] })
|
|
658
|
-
* // => { 'filters[status]': 'active', 'filters[tags]': 'a,b' }
|
|
659
|
-
*
|
|
660
|
-
* @example
|
|
661
|
-
* // With custom prefix
|
|
662
|
-
* serializeFiltersParams({ name: 'John', all: true }, { prefix: 'search' })
|
|
663
|
-
* // => { 'search[name]': 'John', 'search[all]': '1' }
|
|
664
|
-
*
|
|
665
|
-
* @example
|
|
666
|
-
* // Without prefix
|
|
667
|
-
* serializeFiltersParams({ page: 1, all: false }, { prefix: '' })
|
|
668
|
-
* // => { 'page': '1', 'all': '0' }
|
|
669
|
-
*/
|
|
670
|
-
declare function serializeParams(params: Record<string, unknown>, options?: SerializeOptions): LocationQuery;
|
|
671
|
-
/**
|
|
672
|
-
* Deserializes query parameters from a URL-friendly format back to an object.
|
|
673
|
-
*
|
|
674
|
-
* @param params - Serialized parameters object
|
|
675
|
-
* @returns Deserialized parameters object
|
|
676
|
-
*
|
|
677
|
-
* @example
|
|
678
|
-
* deserializeParams({ 'filters[status]': 'active', search: 'test' })
|
|
679
|
-
* // => { filters: {status: 'active'}, search: 'test' }
|
|
680
|
-
*/
|
|
681
|
-
declare function deserializeParams(params: Record<string, any>): Record<string, unknown>;
|
|
682
|
-
//#endregion
|
|
683
797
|
//#region src/injectionSymbols.d.ts
|
|
684
798
|
declare const contextStorageCollectionInjectKey: InjectionKey<CollectionManager>;
|
|
685
799
|
declare const contextStorageCollectionItemInjectKey: InjectionKey<CollectionManagerItem>;
|
|
@@ -691,4 +805,4 @@ declare const contextStorageSessionStorageHandlerInjectKey: InjectionKey<Context
|
|
|
691
805
|
//#region src/constants.d.ts
|
|
692
806
|
declare const defaultHandlers: ContextStorageHandlerFactory[];
|
|
693
807
|
//#endregion
|
|
694
|
-
export { type CollectionManager, type CollectionManagerItem, _default as ContextStorage, _default$1 as ContextStorageActivator, _default$2 as ContextStorageCollection, type ContextStorageHandler, type ContextStorageHandlerFactory, type ContextStorageHandlerMap, _default$3 as ContextStoragePrefix, type ContextStoragePrefixSegment, _default$4 as ContextStorageProvider, type WebStorageHandlerBaseOptions as LocalStorageHandlerBaseOptions, type MaybeWithSchema, type QueryValue, type RegisterBaseOptions, type RegisterWebStorageHandlerBaseOptions as RegisterLocalStorageHandlerBaseOptions, SCHEMA_SYMBOL, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asObjectArray, asString, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageLocalStorageHandlerInjectKey, contextStoragePrefixSegmentsInjectKey, contextStorageQueryHandlerInjectKey, contextStorageSessionStorageHandlerInjectKey, createCollectionManager, createEmptyZodObject, createLocalStorageHandler, createQueryHandler, createSessionStorageHandler, defaultHandlers, defineContextStorageHandler, deserializeParams as deserializeQueryParams, resolveHandlerInjectionKey, serializeParams as serializeQueryParams, transform, useContextStorage, useContextStorageActivator, useContextStorageCollection, useContextStorageProvider };
|
|
808
|
+
export { type CollectionManager, type CollectionManagerItem, _default as ContextStorage, _default$1 as ContextStorageActivator, _default$2 as ContextStorageCollection, type ContextStorageHandler, type ContextStorageHandlerFactory, type ContextStorageHandlerMap, _default$3 as ContextStoragePrefix, type ContextStoragePrefixSegment, _default$4 as ContextStorageProvider, type WebStorageHandlerBaseOptions as LocalStorageHandlerBaseOptions, type MaybeWithSchema, type QueryArrayFormat, type QueryHandlerBaseOptions, type QuerySerializeOptions, type QuerySerializer, type QueryValue, type RegisterBaseOptions, type RegisterWebStorageHandlerBaseOptions as RegisterLocalStorageHandlerBaseOptions, type ResolvedSerializeOptions, SCHEMA_SYMBOL, type SerializeOptions, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asObjectArray, asString, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageLocalStorageHandlerInjectKey, contextStoragePrefixSegmentsInjectKey, contextStorageQueryHandlerInjectKey, contextStorageSessionStorageHandlerInjectKey, createCollectionManager, createEmptyZodObject, createLocalStorageHandler, createQueryHandler, createSessionStorageHandler, defaultHandlers, defineContextStorageHandler, deserializeParams as deserializeQueryParams, resolveHandlerInjectionKey, serializeParams as serializeQueryParams, transform, useContextStorage, useContextStorageActivator, useContextStorageCollection, useContextStorageProvider };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, onUnmounted, provide, toValue, watch } from "vue";
|
|
1
|
+
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, onUnmounted, provide, toRaw, toValue, watch } from "vue";
|
|
2
2
|
import { useRoute, useRouter } from "vue-router";
|
|
3
|
-
import { cloneDeep, isEqual, merge, omit, pick } from "lodash";
|
|
4
3
|
|
|
5
4
|
//#region src/symbols.ts
|
|
6
5
|
const collection = /* @__PURE__ */ Symbol("context-storage-collection");
|
|
@@ -60,8 +59,39 @@ var ContextStorageActivator_default = ContextStorageActivator_vue_vue_type_scrip
|
|
|
60
59
|
|
|
61
60
|
//#endregion
|
|
62
61
|
//#region src/handlers/query/helpers.ts
|
|
62
|
+
function escapeArrayValue(value, separator) {
|
|
63
|
+
if (!value.includes(separator) && !value.includes("\\")) return value;
|
|
64
|
+
return value.split("\\").join("\\\\").split(separator).join("\\" + separator);
|
|
65
|
+
}
|
|
66
|
+
function joinArrayValues(values, separator) {
|
|
67
|
+
return values.map((value) => escapeArrayValue(String(value), separator)).join(separator);
|
|
68
|
+
}
|
|
69
|
+
function splitArrayValue(value, separator) {
|
|
70
|
+
if (separator.length === 0) return [value];
|
|
71
|
+
if (!value.includes("\\")) return value.split(separator);
|
|
72
|
+
const result = [];
|
|
73
|
+
let current = "";
|
|
74
|
+
let i = 0;
|
|
75
|
+
while (i < value.length) {
|
|
76
|
+
if (value[i] === "\\" && i + 1 < value.length) {
|
|
77
|
+
current += value[i + 1];
|
|
78
|
+
i += 2;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (value.startsWith(separator, i)) {
|
|
82
|
+
result.push(current);
|
|
83
|
+
current = "";
|
|
84
|
+
i += separator.length;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
current += value[i];
|
|
88
|
+
i += 1;
|
|
89
|
+
}
|
|
90
|
+
result.push(current);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
63
93
|
function serializeParams(params, options = {}) {
|
|
64
|
-
const { key: prefix = "" } = options;
|
|
94
|
+
const { key: prefix = "", arrayFormat = "repeat", arraySeparator = "," } = options;
|
|
65
95
|
const result = {};
|
|
66
96
|
Object.keys(params).forEach((key) => {
|
|
67
97
|
const value = params[key];
|
|
@@ -81,7 +111,8 @@ function serializeParams(params, options = {}) {
|
|
|
81
111
|
...options,
|
|
82
112
|
key: formattedKey
|
|
83
113
|
}));
|
|
84
|
-
} else result[formattedKey] = value
|
|
114
|
+
} else if (arrayFormat === "comma") result[formattedKey] = joinArrayValues(value, arraySeparator);
|
|
115
|
+
else result[formattedKey] = value.map(String);
|
|
85
116
|
else Object.assign(result, serializeParams(value, {
|
|
86
117
|
...options,
|
|
87
118
|
key: formattedKey
|
|
@@ -112,6 +143,52 @@ function deserializeParams(params) {
|
|
|
112
143
|
}, {});
|
|
113
144
|
}
|
|
114
145
|
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/handlers/deep-utils.ts
|
|
148
|
+
function isPlainObject(value) {
|
|
149
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
150
|
+
const proto = Object.getPrototypeOf(value);
|
|
151
|
+
return proto === Object.prototype || proto === null;
|
|
152
|
+
}
|
|
153
|
+
function isEqual(a, b) {
|
|
154
|
+
if (a === b) return true;
|
|
155
|
+
if (a !== a && b !== b) return true;
|
|
156
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
157
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
158
|
+
return a.every((item, i) => isEqual(item, b[i]));
|
|
159
|
+
}
|
|
160
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
161
|
+
const aKeys = Object.keys(a);
|
|
162
|
+
const bKeys = Object.keys(b);
|
|
163
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
164
|
+
return aKeys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && isEqual(a[key], b[key]));
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
function mergeDeep(target, ...sources) {
|
|
169
|
+
for (const source of sources) {
|
|
170
|
+
if (!source) continue;
|
|
171
|
+
for (const key of Object.keys(source)) {
|
|
172
|
+
const sourceValue = source[key];
|
|
173
|
+
if (sourceValue === void 0) continue;
|
|
174
|
+
const targetValue = target[key];
|
|
175
|
+
target[key] = isPlainObject(targetValue) && isPlainObject(sourceValue) ? mergeDeep({ ...targetValue }, sourceValue) : sourceValue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return target;
|
|
179
|
+
}
|
|
180
|
+
function pick(obj, keys) {
|
|
181
|
+
const result = {};
|
|
182
|
+
for (const key of keys) if (key in obj) result[key] = obj[key];
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function omit(obj, keys) {
|
|
186
|
+
const keySet = new Set(keys);
|
|
187
|
+
const result = {};
|
|
188
|
+
for (const key of Object.keys(obj)) if (!keySet.has(key)) result[key] = obj[key];
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
115
192
|
//#endregion
|
|
116
193
|
//#region src/handlers/helpers.ts
|
|
117
194
|
function syncReactive(target, source) {
|
|
@@ -185,7 +262,7 @@ function extractDefaultsFromSchema(schema) {
|
|
|
185
262
|
const { hasDefault: hasDefault2, value: objectDefault } = readZodFieldDefault(shape[key]);
|
|
186
263
|
const nested = extractDefaultsFromSchema(base);
|
|
187
264
|
let combined;
|
|
188
|
-
if (hasDefault2 && nested) combined =
|
|
265
|
+
if (hasDefault2 && nested) combined = mergeDeep({}, objectDefault, nested);
|
|
189
266
|
else if (hasDefault2) combined = objectDefault;
|
|
190
267
|
else combined = nested;
|
|
191
268
|
if (combined !== void 0) {
|
|
@@ -217,7 +294,7 @@ function coerceScalar(value, baseType) {
|
|
|
217
294
|
}
|
|
218
295
|
return value;
|
|
219
296
|
}
|
|
220
|
-
function coerceDataForSchema(data, schema) {
|
|
297
|
+
function coerceDataForSchema(data, schema, arraySeparator) {
|
|
221
298
|
const shape = schema?.shape;
|
|
222
299
|
if (!shape || typeof shape !== "object") return data;
|
|
223
300
|
const result = { ...data };
|
|
@@ -232,16 +309,17 @@ function coerceDataForSchema(data, schema) {
|
|
|
232
309
|
let arr;
|
|
233
310
|
if (Array.isArray(value)) arr = value;
|
|
234
311
|
else if (typeof value === "object") arr = Object.entries(value).sort(([a], [b]) => Number(a) - Number(b)).map(([, v]) => v);
|
|
312
|
+
else if (arraySeparator !== void 0 && typeof value === "string") arr = splitArrayValue(value, arraySeparator);
|
|
235
313
|
else arr = [value];
|
|
236
314
|
const elementBase = unwrapZodField(base.element ?? base._zod?.def?.element);
|
|
237
315
|
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);
|
|
316
|
+
if (elementType === "object" && elementBase.shape) arr = arr.map((v) => v && typeof v === "object" && !Array.isArray(v) ? coerceDataForSchema(v, elementBase, arraySeparator) : v);
|
|
239
317
|
else if (elementType === "number" || elementType === "boolean") arr = arr.map((v) => coerceScalar(v, elementType));
|
|
240
318
|
result[key] = arr;
|
|
241
319
|
}
|
|
242
320
|
} else if (baseType === "object" && base.shape) {
|
|
243
321
|
const nested = result[key];
|
|
244
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) result[key] = coerceDataForSchema(nested, base);
|
|
322
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) result[key] = coerceDataForSchema(nested, base, arraySeparator);
|
|
245
323
|
}
|
|
246
324
|
}
|
|
247
325
|
return result;
|
|
@@ -250,7 +328,7 @@ function applyTransform(input) {
|
|
|
250
328
|
const warnings = [];
|
|
251
329
|
let data = input.state;
|
|
252
330
|
if (input.schema) {
|
|
253
|
-
const coerced = coerceDataForSchema(
|
|
331
|
+
const coerced = coerceDataForSchema(mergeDeep({}, input.initialData, data), input.schema, input.arraySeparator);
|
|
254
332
|
const result = input.schema.safeParse(coerced);
|
|
255
333
|
if (result.success) data = result.data;
|
|
256
334
|
else {
|
|
@@ -339,11 +417,16 @@ function sortQueryByReference(query, ...references) {
|
|
|
339
417
|
function buildQuery(input) {
|
|
340
418
|
const warnings = [];
|
|
341
419
|
const newQueryRaw = {};
|
|
420
|
+
const serialize = input.serialize ?? serializeParams;
|
|
342
421
|
const ownedKeys = /* @__PURE__ */ new Set();
|
|
343
422
|
input.items.forEach((item) => {
|
|
344
423
|
const { key, onlyChanges = input.onlyChanges } = item;
|
|
345
424
|
let preserveEmptyState = item.preserveEmptyState ?? input.preserveEmptyState;
|
|
346
|
-
const patch =
|
|
425
|
+
const patch = serialize(item.data, {
|
|
426
|
+
key,
|
|
427
|
+
arrayFormat: item.arrayFormat ?? "repeat",
|
|
428
|
+
arraySeparator: item.arraySeparator ?? ","
|
|
429
|
+
});
|
|
347
430
|
Object.keys(patch).forEach((k) => ownedKeys.add(k));
|
|
348
431
|
Object.keys(item.initialQueryData).forEach((k) => ownedKeys.add(k));
|
|
349
432
|
if (onlyChanges) {
|
|
@@ -433,8 +516,21 @@ function createQueryHandler(baseOptions) {
|
|
|
433
516
|
preserveUnusedKeys: true,
|
|
434
517
|
preserveEmptyState: false,
|
|
435
518
|
onlyChanges: true,
|
|
519
|
+
serialize: {},
|
|
436
520
|
...baseOptions
|
|
437
521
|
};
|
|
522
|
+
const serialize = baseOptions?.serializer?.serialize ?? serializeParams;
|
|
523
|
+
const deserialize = baseOptions?.serializer?.deserialize ?? deserializeParams;
|
|
524
|
+
function resolveSerializeOptions(itemOptions) {
|
|
525
|
+
const merged = {
|
|
526
|
+
...options.serialize,
|
|
527
|
+
...itemOptions?.serialize
|
|
528
|
+
};
|
|
529
|
+
return {
|
|
530
|
+
arrayFormat: merged.arrayFormat ?? "repeat",
|
|
531
|
+
arraySeparator: merged.arraySeparator ?? ","
|
|
532
|
+
};
|
|
533
|
+
}
|
|
438
534
|
const stopAfterEach = router.afterEach(() => {
|
|
439
535
|
afterEachRoute();
|
|
440
536
|
});
|
|
@@ -508,7 +604,7 @@ function createQueryHandler(baseOptions) {
|
|
|
508
604
|
const { mergeOnlyExistingKeysWithoutTransform = options.mergeOnlyExistingKeysWithoutTransform } = item.options || {};
|
|
509
605
|
const itemState = toValue(item.data);
|
|
510
606
|
const result = computeSyncState({
|
|
511
|
-
deserializedState:
|
|
607
|
+
deserializedState: deserialize(route.query),
|
|
512
608
|
initialData: item.initialData,
|
|
513
609
|
key,
|
|
514
610
|
emptyPlaceholder: options.emptyPlaceholder
|
|
@@ -516,16 +612,18 @@ function createQueryHandler(baseOptions) {
|
|
|
516
612
|
if (result.type === "none") return;
|
|
517
613
|
if (result.type === "reset") {
|
|
518
614
|
result.data;
|
|
519
|
-
syncReactive(itemState,
|
|
615
|
+
syncReactive(itemState, structuredClone(result.data));
|
|
520
616
|
return;
|
|
521
617
|
}
|
|
522
618
|
const urlKeys = new Set(Object.keys(result.data));
|
|
619
|
+
const serializeOptions = resolveSerializeOptions(item.options);
|
|
523
620
|
const transformed = applyTransform({
|
|
524
621
|
state: result.data,
|
|
525
622
|
initialData: item.initialData,
|
|
526
623
|
schema: item.options?.schema,
|
|
527
624
|
transform: item.options?.transform,
|
|
528
|
-
mergeOnlyExistingKeysWithoutTransform
|
|
625
|
+
mergeOnlyExistingKeysWithoutTransform,
|
|
626
|
+
arraySeparator: serializeOptions.arrayFormat === "comma" ? serializeOptions.arraySeparator : void 0
|
|
529
627
|
});
|
|
530
628
|
transformed.warnings.forEach((w) => console.warn(w.message, ...w.args));
|
|
531
629
|
const finalData = { ...transformed.data };
|
|
@@ -550,11 +648,16 @@ function createQueryHandler(baseOptions) {
|
|
|
550
648
|
registerOptions.key;
|
|
551
649
|
scheduleSyncToQuery();
|
|
552
650
|
}, { deep: true });
|
|
553
|
-
const
|
|
554
|
-
const
|
|
555
|
-
|
|
651
|
+
const serializeOptions = resolveSerializeOptions(registerOptions);
|
|
652
|
+
const serializeBaselineOptions = {
|
|
653
|
+
key: registerOptions.key,
|
|
654
|
+
...serializeOptions
|
|
655
|
+
};
|
|
656
|
+
const initialData = structuredClone(toRaw(resolvedData));
|
|
657
|
+
const initialQueryData = serialize(initialData, serializeBaselineOptions);
|
|
658
|
+
const additionalDefaultQueryData = registerOptions.additionalDefaultData ? serialize(registerOptions.additionalDefaultData, serializeBaselineOptions) : void 0;
|
|
556
659
|
const schemaMetaDefaults = registerOptions.schema ? extractAdditionalDefaultDataFromSchema(registerOptions.schema) : void 0;
|
|
557
|
-
const schemaMetaDefaultQueryData = schemaMetaDefaults ?
|
|
660
|
+
const schemaMetaDefaultQueryData = schemaMetaDefaults ? serialize(schemaMetaDefaults, serializeBaselineOptions) : void 0;
|
|
558
661
|
const schemaDefaults = registerOptions.schema ? extractDefaultsFromSchema(registerOptions.schema) : void 0;
|
|
559
662
|
const item = {
|
|
560
663
|
data,
|
|
@@ -562,7 +665,7 @@ function createQueryHandler(baseOptions) {
|
|
|
562
665
|
initialQueryData,
|
|
563
666
|
additionalDefaultQueryData,
|
|
564
667
|
schemaMetaDefaultQueryData,
|
|
565
|
-
schemaDefaultQueryData: schemaDefaults ?
|
|
668
|
+
schemaDefaultQueryData: schemaDefaults ? serialize(schemaDefaults, serializeBaselineOptions) : void 0,
|
|
566
669
|
options: registerOptions,
|
|
567
670
|
watchHandle
|
|
568
671
|
};
|
|
@@ -591,30 +694,36 @@ function createQueryHandler(baseOptions) {
|
|
|
591
694
|
},
|
|
592
695
|
reset: () => {
|
|
593
696
|
registerOptions.key;
|
|
594
|
-
syncReactive(toValue(data),
|
|
697
|
+
syncReactive(toValue(data), structuredClone(initialData));
|
|
595
698
|
},
|
|
596
699
|
wasChanged: computed(() => !isEqual(toValue(data), initialData))
|
|
597
700
|
};
|
|
598
701
|
}
|
|
599
702
|
function buildQueryFromRegistered() {
|
|
600
703
|
const result = buildQuery({
|
|
601
|
-
items: registered.map((item) =>
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
704
|
+
items: registered.map((item) => {
|
|
705
|
+
const serializeOptions = resolveSerializeOptions(item.options);
|
|
706
|
+
return {
|
|
707
|
+
data: toValue(item.data),
|
|
708
|
+
initialQueryData: item.initialQueryData,
|
|
709
|
+
additionalDefaultQueryData: item.additionalDefaultQueryData,
|
|
710
|
+
schemaMetaDefaultQueryData: item.schemaMetaDefaultQueryData,
|
|
711
|
+
schemaDefaultQueryData: item.schemaDefaultQueryData,
|
|
712
|
+
key: item.options?.key,
|
|
713
|
+
onlyChanges: item.options?.onlyChanges,
|
|
714
|
+
preserveEmptyState: item.options?.preserveEmptyState,
|
|
715
|
+
causer: item.options?.causer,
|
|
716
|
+
arrayFormat: serializeOptions.arrayFormat,
|
|
717
|
+
arraySeparator: serializeOptions.arraySeparator
|
|
718
|
+
};
|
|
719
|
+
}),
|
|
612
720
|
currentQuery,
|
|
613
721
|
routeQuery: route.query,
|
|
614
722
|
preserveUnusedKeys: options.preserveUnusedKeys,
|
|
615
723
|
preserveEmptyState: options.preserveEmptyState,
|
|
616
724
|
onlyChanges: options.onlyChanges,
|
|
617
|
-
emptyPlaceholder: options.emptyPlaceholder
|
|
725
|
+
emptyPlaceholder: options.emptyPlaceholder,
|
|
726
|
+
serialize
|
|
618
727
|
});
|
|
619
728
|
result.warnings.forEach((w) => console.warn(w));
|
|
620
729
|
return {
|
|
@@ -722,7 +831,7 @@ function createWebStorageHandlerInstance(config) {
|
|
|
722
831
|
const watchHandle = watch(data, () => syncRegisteredToStorage(), { deep: true });
|
|
723
832
|
const item = {
|
|
724
833
|
data,
|
|
725
|
-
initialData:
|
|
834
|
+
initialData: structuredClone(toRaw(resolvedData)),
|
|
726
835
|
options,
|
|
727
836
|
watchHandle
|
|
728
837
|
};
|
|
@@ -737,7 +846,7 @@ function createWebStorageHandlerInstance(config) {
|
|
|
737
846
|
registeredDataObjects.delete(resolvedData);
|
|
738
847
|
},
|
|
739
848
|
reset: () => {
|
|
740
|
-
syncReactive(toValue(data),
|
|
849
|
+
syncReactive(toValue(data), structuredClone(item.initialData));
|
|
741
850
|
},
|
|
742
851
|
wasChanged: computed(() => !isEqual(toValue(data), item.initialData))
|
|
743
852
|
};
|
|
@@ -1104,12 +1213,12 @@ function asString(value, options) {
|
|
|
1104
1213
|
return stringValue;
|
|
1105
1214
|
}
|
|
1106
1215
|
function asNumberArray(value, options) {
|
|
1107
|
-
const { nullable = false } = options || {};
|
|
1216
|
+
const { nullable = false, separator } = options || {};
|
|
1108
1217
|
if (value === null && nullable) return null;
|
|
1109
1218
|
if (value === void 0) return nullable ? null : [];
|
|
1110
1219
|
let arrayValue;
|
|
1111
1220
|
if (Array.isArray(value)) arrayValue = value;
|
|
1112
|
-
else if (typeof value === "string") arrayValue = [value];
|
|
1221
|
+
else if (typeof value === "string") arrayValue = separator !== void 0 ? splitArrayValue(value, separator) : [value];
|
|
1113
1222
|
else arrayValue = [];
|
|
1114
1223
|
return arrayValue.map((item) => {
|
|
1115
1224
|
if (item === null) return 0;
|
|
@@ -1118,12 +1227,13 @@ function asNumberArray(value, options) {
|
|
|
1118
1227
|
});
|
|
1119
1228
|
}
|
|
1120
1229
|
function asArray(value, options) {
|
|
1121
|
-
const { nullable = false, missable = false, transform: transform2 } = options || {};
|
|
1230
|
+
const { nullable = false, missable = false, transform: transform2, separator } = options || {};
|
|
1122
1231
|
if (value === null && nullable) return null;
|
|
1123
1232
|
if (value === void 0 && missable) return;
|
|
1124
1233
|
if (value === void 0) return nullable ? null : [];
|
|
1125
1234
|
let arrayValue;
|
|
1126
1235
|
if (Array.isArray(value)) arrayValue = value;
|
|
1236
|
+
else if (separator !== void 0 && typeof value === "string") arrayValue = splitArrayValue(value, separator);
|
|
1127
1237
|
else arrayValue = [value];
|
|
1128
1238
|
if (transform2) return arrayValue.map((item) => transform2(item));
|
|
1129
1239
|
return arrayValue;
|
package/package.json
CHANGED
|
@@ -1,98 +1,94 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vue-context-storage",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "Vue 3 reactive state management — sync state with the URL query, localStorage, and sessionStorage",
|
|
6
|
-
"author": "",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"homepage": "https://github.com/lviobio/vue-context-storage#readme",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/lviobio/vue-context-storage.git"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/lviobio/vue-context-storage/issues"
|
|
15
|
-
},
|
|
16
|
-
"exports": {
|
|
17
|
-
".": "./dist/index.js",
|
|
18
|
-
"./package.json": "./package.json"
|
|
19
|
-
},
|
|
20
|
-
"main": "./dist/index.js",
|
|
21
|
-
"module": "./dist/index.js",
|
|
22
|
-
"types": "./dist/index.d.ts",
|
|
23
|
-
"files": [
|
|
24
|
-
"dist"
|
|
25
|
-
],
|
|
26
|
-
"publishConfig": {
|
|
27
|
-
"access": "public"
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "tsdown",
|
|
31
|
-
"dev": "tsdown --watch",
|
|
32
|
-
"check": "npm run ts:check && npm run lint:check && npm run format:check && npm run dependency-cruiser:check",
|
|
33
|
-
"ts:check": "vue-tsc --noEmit",
|
|
34
|
-
"format": "prettier --write src/ playground/src playground-e2e/src",
|
|
35
|
-
"format:check": "prettier --check src/ playground/src playground-e2e/src",
|
|
36
|
-
"lint": "eslint . --fix",
|
|
37
|
-
"lint:check": "eslint .",
|
|
38
|
-
"dependency-cruiser:check": "depcruise --config .dependency-cruiser.cjs src playground/src",
|
|
39
|
-
"play": "vite --port 5173",
|
|
40
|
-
"play:e2e": "vite --port 5174 --config vite.e2e.config.ts",
|
|
41
|
-
"build:playground": "vite build",
|
|
42
|
-
"preview:playground": "vite preview --outDir playground/dist",
|
|
43
|
-
"test": "npm run test:unit && npm run test:e2e",
|
|
44
|
-
"test:unit": "vitest run",
|
|
45
|
-
"test:e2e": "playwright test",
|
|
46
|
-
"test:e2e:ui": "playwright test --ui",
|
|
47
|
-
"test:e2e:headed": "playwright test --headed",
|
|
48
|
-
"test:e2e:debug": "playwright test --debug",
|
|
49
|
-
"test:e2e:report": "playwright show-report",
|
|
50
|
-
"release": "npm run check && npm run test && bumpp && npm publish",
|
|
51
|
-
"prepublishOnly": "npm run check && npm run test && npm run build"
|
|
52
|
-
},
|
|
53
|
-
"peerDependencies": {
|
|
54
|
-
"vue": "^3.0.0",
|
|
55
|
-
"vue-router": "^4.0.0 || ^5.0.0",
|
|
56
|
-
"zod": "^4.0.0"
|
|
57
|
-
},
|
|
58
|
-
"peerDependenciesMeta": {
|
|
59
|
-
"zod": {
|
|
60
|
-
"optional": true
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
"devDependencies": {
|
|
64
|
-
"@playwright/test": "^1.60.0",
|
|
65
|
-
"@tailwindcss/vite": "^4.1.18",
|
|
66
|
-
"@types/
|
|
67
|
-
"@
|
|
68
|
-
"@
|
|
69
|
-
"@vitest/
|
|
70
|
-
"@
|
|
71
|
-
"@vue/
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"eslint": "^
|
|
76
|
-
"eslint-
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"typescript": "^
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"vitest": "^
|
|
90
|
-
"
|
|
91
|
-
"vue": "^3.
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"dependencies": {
|
|
96
|
-
"lodash": "^4.17.23"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-context-storage",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.46",
|
|
5
|
+
"description": "Vue 3 reactive state management — sync state with the URL query, localStorage, and sessionStorage",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/lviobio/vue-context-storage#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/lviobio/vue-context-storage.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/lviobio/vue-context-storage/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsdown",
|
|
31
|
+
"dev": "tsdown --watch",
|
|
32
|
+
"check": "npm run ts:check && npm run lint:check && npm run format:check && npm run dependency-cruiser:check",
|
|
33
|
+
"ts:check": "vue-tsc --noEmit",
|
|
34
|
+
"format": "prettier --write src/ playground/src playground-e2e/src",
|
|
35
|
+
"format:check": "prettier --check src/ playground/src playground-e2e/src",
|
|
36
|
+
"lint": "eslint . --fix",
|
|
37
|
+
"lint:check": "eslint .",
|
|
38
|
+
"dependency-cruiser:check": "depcruise --config .dependency-cruiser.cjs src playground/src",
|
|
39
|
+
"play": "vite --port 5173",
|
|
40
|
+
"play:e2e": "vite --port 5174 --config vite.e2e.config.ts",
|
|
41
|
+
"build:playground": "vite build",
|
|
42
|
+
"preview:playground": "vite preview --outDir playground/dist",
|
|
43
|
+
"test": "npm run test:unit && npm run test:e2e",
|
|
44
|
+
"test:unit": "vitest run",
|
|
45
|
+
"test:e2e": "playwright test",
|
|
46
|
+
"test:e2e:ui": "playwright test --ui",
|
|
47
|
+
"test:e2e:headed": "playwright test --headed",
|
|
48
|
+
"test:e2e:debug": "playwright test --debug",
|
|
49
|
+
"test:e2e:report": "playwright show-report",
|
|
50
|
+
"release": "npm run check && npm run test && bumpp && npm publish",
|
|
51
|
+
"prepublishOnly": "npm run check && npm run test && npm run build"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"vue": "^3.0.0",
|
|
55
|
+
"vue-router": "^4.0.0 || ^5.0.0",
|
|
56
|
+
"zod": "^4.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"zod": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@playwright/test": "^1.60.0",
|
|
65
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
66
|
+
"@types/node": "^25.0.3",
|
|
67
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
68
|
+
"@vitest/browser-playwright": "^4.0.16",
|
|
69
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
70
|
+
"@vue/eslint-config-typescript": "^14.6.0",
|
|
71
|
+
"@vue/test-utils": "^2.4.6",
|
|
72
|
+
"bumpp": "^10.3.2",
|
|
73
|
+
"dependency-cruiser": "^17.3.6",
|
|
74
|
+
"eslint": "^9.39.2",
|
|
75
|
+
"eslint-config-prettier": "^10.1.8",
|
|
76
|
+
"eslint-plugin-vue": "^10.6.2",
|
|
77
|
+
"happy-dom": "^20.1.0",
|
|
78
|
+
"highlight.js": "^11.11.1",
|
|
79
|
+
"jsdom": "^27.4.0",
|
|
80
|
+
"naive-ui": "^2.43.2",
|
|
81
|
+
"playwright": "^1.57.0",
|
|
82
|
+
"prettier": "^3.7.4",
|
|
83
|
+
"tailwindcss": "^4.1.18",
|
|
84
|
+
"tsdown": "^0.18.4",
|
|
85
|
+
"typescript": "^5.9.3",
|
|
86
|
+
"typescript-eslint": "^8.51.0",
|
|
87
|
+
"vite": "^7.3.0",
|
|
88
|
+
"vitest": "^4.0.16",
|
|
89
|
+
"vitest-browser-vue": "^2.0.1",
|
|
90
|
+
"vue": "^3.5.26",
|
|
91
|
+
"vue-tsc": "^3.2.1",
|
|
92
|
+
"zod": "^4.3.5"
|
|
93
|
+
}
|
|
94
|
+
}
|