usewagen 0.1.0 → 0.2.0

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,6 +1,6 @@
1
1
  # usewagen
2
2
 
3
- Reactive state for URL and storage in Vue. Type-safe, with the value in exactly one place.
3
+ Reactive state for URL and storage in Vue. Type-safe, with the source always in charge.
4
4
 
5
5
  Documentation lives at [usewagen.dev](https://usewagen.dev).
6
6
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as parseAsStringLiteral, S as parseAsStringEnum, T as unwrapDefault, _ as parseAsIndex, a as InferParserValue, b as parseAsNumberLiteral, c as DefaultValue, d as ParserWithDefault, f as defineParser, g as parseAsFloat, h as parseAsDate, i as InferInputWritable, l as Parser, m as parseAsBoolean, n as CustomParsers, o as KnownParsers, p as parseAsArrayOf, r as InferInputValue, s as ParserInput, t as BuiltinParsers, u as ParserOptions, v as parseAsInteger, w as tryParse, x as parseAsString, y as parseAsJson } from "./types-CNt2lcP1.mjs";
2
- import { i as RouteStateSource, t as HistoryMode } from "./types-CuwASQTi.mjs";
1
+ import { A as unwrapDefault, C as parseAsInteger, D as parseAsStringEnum, E as parseAsString, O as parseAsStringLiteral, S as parseAsIndex, T as parseAsNumberLiteral, _ as defineParser, a as CustomParsers, b as parseAsDate, c as InferParserValue, d as UnwrapParser, f as WithParser, g as ParserWithDefault, h as ParserOptions, i as BuiltinParsers, k as tryParse, l as KnownParsers, m as Parser, o as InferInputValue, p as DefaultValue, r as StateMode, s as InferInputWritable, u as ParserInput, v as parseAsArrayOf, w as parseAsJson, x as parseAsFloat, y as parseAsBoolean } from "./types-JtRmT3qo.mjs";
2
+ import { i as RouteStateSource, t as HistoryMode } from "./types-fVW6rBJ3.mjs";
3
3
  import { a as StorageInstance, t as ErrorHandler } from "./create-storage-DESmB4bY.mjs";
4
4
  import { App } from "vue";
5
5
  //#region src/wagen.d.ts
@@ -10,11 +10,13 @@ interface WagenStorageOptions {
10
10
  local?: StorageInstance;
11
11
  session?: StorageInstance;
12
12
  default?: 'local' | 'session';
13
+ mode?: StateMode;
13
14
  }
14
15
  interface WagenRouterOptions {
15
16
  history?: HistoryMode;
16
17
  source?: RouteStateSource;
17
18
  clearOnDefault?: boolean;
19
+ mode?: StateMode;
18
20
  }
19
21
  type WagenParsers = Record<string, Parser<any>>;
20
22
  interface WagenConfig {
@@ -26,6 +28,7 @@ interface WagenStorage {
26
28
  readonly local: StorageInstance;
27
29
  readonly session: StorageInstance;
28
30
  readonly default: StorageInstance;
31
+ readonly mode: StateMode;
29
32
  }
30
33
  type ResolvedWagenRouterOptions = Required<WagenRouterOptions>;
31
34
  interface Wagen {
@@ -35,9 +38,9 @@ interface Wagen {
35
38
  install: (app: App) => void;
36
39
  destroy: () => void;
37
40
  }
38
- declare function defineWagenConfig(config: WagenConfig): WagenConfig;
39
- declare function createWagen(config?: WagenConfig): Wagen;
40
- declare function getActiveWagen(): Wagen;
41
- declare function useWagen(): Wagen;
41
+ export declare function defineWagenConfig(config: WagenConfig): WagenConfig;
42
+ export declare function createWagen(config?: WagenConfig): Wagen;
43
+ export declare function getActiveWagen(): Wagen;
44
+ export declare function useWagen(): Wagen;
42
45
  //#endregion
43
- export { type BuiltinParsers, type CustomParsers, DefaultValue, type InferInputValue, type InferInputWritable, type InferParserValue, type KnownParsers, Parser, type ParserInput, ParserOptions, ParserWithDefault, type ResolvedWagenRouterOptions, type Wagen, type WagenConfig, type WagenParsers, type WagenRouterOptions, type WagenStorage, type WagenStorageOptions, createWagen, defineParser, defineWagenConfig, getActiveWagen, parseAsArrayOf, parseAsBoolean, parseAsDate, parseAsFloat, parseAsIndex, parseAsInteger, parseAsJson, parseAsNumberLiteral, parseAsString, parseAsStringEnum, parseAsStringLiteral, tryParse, unwrapDefault, useWagen };
46
+ export { type BuiltinParsers, type CustomParsers, DefaultValue, type InferInputValue, type InferInputWritable, type InferParserValue, type KnownParsers, Parser, type ParserInput, ParserOptions, ParserWithDefault, type ResolvedWagenRouterOptions, type StateMode, type UnwrapParser, type Wagen, type WagenConfig, type WagenParsers, type WagenRouterOptions, type WagenStorage, type WagenStorageOptions, type WithParser, defineParser, parseAsArrayOf, parseAsBoolean, parseAsDate, parseAsFloat, parseAsIndex, parseAsInteger, parseAsJson, parseAsNumberLiteral, parseAsString, parseAsStringEnum, parseAsStringLiteral, tryParse, unwrapDefault };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  import { a as parseAsFloat, c as parseAsJson, d as parseAsStringEnum, f as parseAsStringLiteral, i as parseAsDate, l as parseAsNumberLiteral, m as unwrapDefault, n as parseAsArrayOf, o as parseAsIndex, p as tryParse, r as parseAsBoolean, s as parseAsInteger, t as defineParser, u as parseAsString } from "./parsers-CA8aZuF-.mjs";
2
2
  import "./parser-B6hfbAwA.mjs";
3
- import { i as useWagen, n as defineWagenConfig, r as getActiveWagen, t as createWagen } from "./wagen-C9_CPUdF.mjs";
3
+ import { i as useWagen, n as defineWagenConfig, r as getActiveWagen, t as createWagen } from "./wagen-C2p8oQgu.mjs";
4
4
  export { createWagen, defineParser, defineWagenConfig, getActiveWagen, parseAsArrayOf, parseAsBoolean, parseAsDate, parseAsFloat, parseAsIndex, parseAsInteger, parseAsJson, parseAsNumberLiteral, parseAsString, parseAsStringEnum, parseAsStringLiteral, tryParse, unwrapDefault, useWagen };
@@ -1,28 +1,19 @@
1
- import { i as InferInputWritable, r as InferInputValue, s as ParserInput } from "../types-CNt2lcP1.mjs";
2
- import { i as RouteStateSource, n as ResolvedRouteStateOptions, r as RouteStateOptions, t as HistoryMode } from "../types-CuwASQTi.mjs";
3
- import { n as ReactiveOptions, t as ReactiveFields } from "../options-BKybciek.mjs";
1
+ import { f as WithParser, n as ReactiveOptions, o as InferInputValue, r as StateMode, s as InferInputWritable, t as ReactiveFields, u as ParserInput } from "../types-JtRmT3qo.mjs";
2
+ import { i as RouteStateSource, n as ResolvedRouteStateOptions, r as RouteStateOptions, t as HistoryMode } from "../types-fVW6rBJ3.mjs";
4
3
  import { Ref } from "vue";
5
4
  //#region src/router/use-route-hash.d.ts
6
5
  interface RouteHashOptions {
7
6
  parser?: ParserInput;
8
7
  history?: HistoryMode;
9
8
  clearOnDefault?: boolean;
9
+ mode?: StateMode;
10
10
  }
11
- type UseRouteHashOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<Omit<RouteHashOptions, 'parser'> & {
12
- parser?: P;
13
- }, 'parser'>;
14
- declare function useRouteHash<P extends ParserInput | undefined = undefined>(options?: UseRouteHashOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
15
- //#endregion
16
- //#region src/router/use-route-state.d.ts
17
- type UseRouteStateOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<Omit<RouteStateOptions, 'parser'> & {
18
- parser?: P;
19
- }, 'parser'>;
20
- declare function useRouteState<P extends ParserInput | undefined = undefined>(options: UseRouteStateOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
11
+ type UseRouteHashOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<WithParser<RouteHashOptions, P>>;
12
+ type RouteHashConfig = ReactiveFields<RouteHashOptions>;
13
+ export declare function useRouteHash<P extends ParserInput | undefined = undefined>(options?: UseRouteHashOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
21
14
  //#endregion
22
15
  //#region src/router/use-route-states.d.ts
23
- type RouteStateConfig<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveFields<Omit<RouteStateOptions, 'parser'> & {
24
- parser?: P;
25
- }, 'key' | 'parser'>;
16
+ type RouteStateConfig<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveFields<WithParser<RouteStateOptions, P>, 'key'>;
26
17
  interface BatchOptions {
27
18
  history?: HistoryMode;
28
19
  }
@@ -35,6 +26,17 @@ interface UseRouteStatesApi<TOptions extends readonly RouteStateConfig[]> {
35
26
  toObject: () => StateSnapshot<TOptions>;
36
27
  }
37
28
  type UseRouteStatesReturn<TOptions extends readonly RouteStateConfig[]> = Refs<TOptions> & UseRouteStatesApi<TOptions>;
38
- declare function useRouteStates<const TOptions extends readonly RouteStateConfig[]>(configs: TOptions): UseRouteStatesReturn<TOptions>;
29
+ export declare function useRouteStates<const TOptions extends readonly RouteStateConfig[]>(configs: TOptions): UseRouteStatesReturn<TOptions>;
30
+ //#endregion
31
+ //#region src/router/options.d.ts
32
+ type Exact<T, TShape> = T & Record<Exclude<keyof T, keyof TShape>, never>;
33
+ export declare function routeStateOptions<const T extends RouteStateConfig>(options: Exact<T, RouteStateConfig>): T;
34
+ export declare function routeStateOptions<const T extends RouteStateConfig>(options: () => Exact<T, RouteStateConfig>): () => T;
35
+ export declare function routeHashOptions<const T extends RouteHashConfig>(options: Exact<T, RouteHashConfig>): T;
36
+ export declare function routeHashOptions<const T extends RouteHashConfig>(options: () => Exact<T, RouteHashConfig>): () => T;
37
+ //#endregion
38
+ //#region src/router/use-route-state.d.ts
39
+ type UseRouteStateOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<WithParser<RouteStateOptions, P>>;
40
+ export declare function useRouteState<P extends ParserInput | undefined = undefined>(options: UseRouteStateOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
39
41
  //#endregion
40
- export { type BatchOptions, type HistoryMode, type ResolvedRouteStateOptions, type RouteHashOptions, type RouteStateConfig, type RouteStateOptions, type RouteStateSource, type UseRouteHashOptions, type UseRouteStateOptions, type UseRouteStatesApi, type UseRouteStatesReturn, useRouteHash, useRouteState, useRouteStates };
42
+ export type { BatchOptions, HistoryMode, ResolvedRouteStateOptions, RouteHashConfig, RouteHashOptions, RouteStateConfig, RouteStateOptions, RouteStateSource, StateMode, UseRouteHashOptions, UseRouteStateOptions, UseRouteStatesApi, UseRouteStatesReturn };
@@ -1,15 +1,72 @@
1
1
  import { _ as warnDev, h as ErrorCodes, m as unwrapDefault } from "../parsers-CA8aZuF-.mjs";
2
2
  import "../parser-B6hfbAwA.mjs";
3
- import { r as getActiveWagen } from "../wagen-C9_CPUdF.mjs";
4
- import { i as toValueDeep, n as serializeValue, r as resolveParser, t as parseValue } from "../utils-B-885gDp.mjs";
3
+ import { r as getActiveWagen } from "../wagen-C2p8oQgu.mjs";
4
+ import { a as toValueDeep, i as resolveParser, n as parseValue, r as serializeValue, t as normalizeRaw } from "../utils-EL2EiRpH.mjs";
5
5
  import { computed, customRef, getCurrentScope, nextTick, toValue } from "vue";
6
6
  import { useRoute, useRouter } from "vue-router";
7
+ //#region src/router/options.ts
8
+ function routeStateOptions(options) {
9
+ return options;
10
+ }
11
+ function routeHashOptions(options) {
12
+ return options;
13
+ }
14
+ //#endregion
15
+ //#region src/router/queue.ts
16
+ const batches = /* @__PURE__ */ new WeakMap();
17
+ function resolveBatchHistory(candidates, override) {
18
+ if (override) return override;
19
+ return candidates.includes("push") ? "push" : "replace";
20
+ }
21
+ function enqueue(router, write) {
22
+ let batch = batches.get(router);
23
+ if (!batch) {
24
+ const created = {
25
+ changes: [],
26
+ histories: [],
27
+ hash: void 0,
28
+ settled: Promise.resolve()
29
+ };
30
+ created.settled = nextTick().then(() => flush(router, created));
31
+ batches.set(router, created);
32
+ batch = created;
33
+ }
34
+ if (write.changes) batch.changes.push(...write.changes);
35
+ if (write.hash !== void 0) batch.hash = write.hash;
36
+ batch.histories.push(write.history);
37
+ return batch.settled;
38
+ }
39
+ function flush(router, batch) {
40
+ if (batches.get(router) === batch) batches.delete(router);
41
+ const route = router.currentRoute.value;
42
+ const params = { ...route.params };
43
+ const query = { ...route.query };
44
+ let changed = false;
45
+ for (const change of batch.changes) {
46
+ const target = change.source === "params" ? params : query;
47
+ const current = target[change.urlKey];
48
+ if (Array.isArray(current)) changed = true;
49
+ else if (change.serialized === null) changed ||= current !== void 0;
50
+ else changed ||= current !== change.serialized;
51
+ target[change.urlKey] = change.serialized === null ? void 0 : change.serialized;
52
+ }
53
+ const hash = batch.hash === void 0 ? route.hash : batch.hash ?? "";
54
+ changed ||= hash !== route.hash;
55
+ if (!changed) return Promise.resolve();
56
+ return router[resolveBatchHistory(batch.histories)]({
57
+ params,
58
+ query,
59
+ hash: hash === "" ? void 0 : hash
60
+ }).then(() => {}, () => {});
61
+ }
62
+ //#endregion
7
63
  //#region src/router/use-route-hash.ts
8
64
  function toResolvedHashOptions(options, defaults) {
9
65
  return {
10
66
  parser: resolveParser(options.parser),
11
67
  history: options.history ?? defaults.history,
12
- clearOnDefault: options.clearOnDefault ?? defaults.clearOnDefault
68
+ clearOnDefault: options.clearOnDefault ?? defaults.clearOnDefault,
69
+ mode: options.mode ?? defaults.mode
13
70
  };
14
71
  }
15
72
  function useRouteHash(options = {}) {
@@ -18,77 +75,120 @@ function useRouteHash(options = {}) {
18
75
  const router = useRouter();
19
76
  const defaults = getActiveWagen().router;
20
77
  const resolvedOptions = computed(() => toResolvedHashOptions(toValueDeep(options), defaults));
21
- return customRef((track) => ({
22
- get: () => {
23
- track();
24
- const raw = route.hash === "" ? void 0 : route.hash;
25
- return parseValue(resolvedOptions.value.parser, raw);
26
- },
27
- set: (next) => {
28
- const { parser, history, clearOnDefault } = resolvedOptions.value;
29
- const serialized = serializeValue(parser, clearOnDefault, next);
30
- nextTick(() => {
31
- if ((route.hash === "" ? null : route.hash) === serialized) return;
32
- router[history]({
33
- query: route.query,
34
- params: route.params,
35
- hash: serialized === null ? void 0 : serialized
78
+ let cache = null;
79
+ let generation = 0;
80
+ let trigger;
81
+ function currentHash() {
82
+ return route.hash === "" ? null : route.hash;
83
+ }
84
+ return customRef((track, triggerRef) => {
85
+ trigger = triggerRef;
86
+ return {
87
+ get: () => {
88
+ track();
89
+ const { parser, mode } = resolvedOptions.value;
90
+ const current = currentHash();
91
+ if (mode === "source") {
92
+ cache = null;
93
+ return parseValue(parser, current);
94
+ }
95
+ if (cache && cache.seen === current) return parseValue(parser, cache.raw);
96
+ cache = {
97
+ seen: current,
98
+ raw: current
99
+ };
100
+ return parseValue(parser, current);
101
+ },
102
+ set: (next) => {
103
+ const { parser, clearOnDefault, history, mode } = resolvedOptions.value;
104
+ const serialized = serializeValue(parser, clearOnDefault, next);
105
+ const optimistic = mode === "optimistic";
106
+ const own = ++generation;
107
+ if (optimistic) {
108
+ cache = {
109
+ seen: currentHash(),
110
+ raw: serialized
111
+ };
112
+ trigger();
113
+ }
114
+ enqueue(router, {
115
+ history,
116
+ hash: serialized
117
+ }).then(() => {
118
+ if (!optimistic || generation !== own) return;
119
+ cache = null;
120
+ trigger();
36
121
  });
37
- });
38
- }
39
- }));
122
+ }
123
+ };
124
+ });
40
125
  }
41
126
  //#endregion
42
127
  //#region src/router/use-base-route-state.ts
43
128
  function useBaseRouteState() {
44
129
  const route = useRoute();
45
130
  const router = useRouter();
46
- function hasChanged(change) {
47
- const current = change.source === "params" ? route.params[change.urlKey] : route.query[change.urlKey];
48
- if (Array.isArray(current)) return true;
49
- if (change.serialized === null) return current !== void 0;
50
- return current !== change.serialized;
51
- }
52
- function navigate(changes, history) {
53
- const changed = changes.filter(hasChanged);
54
- if (changed.length === 0) return;
55
- const params = { ...route.params };
56
- const query = { ...route.query };
57
- for (const change of changed) {
58
- const target = change.source === "params" ? params : query;
59
- target[change.urlKey] = change.serialized === null ? void 0 : change.serialized;
131
+ function createRouteStateRef(options) {
132
+ let cache = null;
133
+ let generation = 0;
134
+ let trigger;
135
+ function rawValue(urlKey, source) {
136
+ return source === "params" ? route.params[urlKey] : route.query[urlKey];
60
137
  }
61
- router[history]({
62
- params,
63
- query,
64
- hash: route.hash
138
+ const state = customRef((track, triggerRef) => {
139
+ trigger = triggerRef;
140
+ return {
141
+ get: () => {
142
+ track();
143
+ const { urlKey, source, parser, mode } = toValue(options);
144
+ const current = normalizeRaw(rawValue(urlKey, source));
145
+ if (mode === "source") {
146
+ cache = null;
147
+ return parseValue(parser, current);
148
+ }
149
+ if (cache && cache.seen === current) return parseValue(parser, cache.raw);
150
+ cache = {
151
+ seen: current,
152
+ raw: current
153
+ };
154
+ return parseValue(parser, current);
155
+ },
156
+ set: (next) => {
157
+ const { parser, clearOnDefault } = toValue(options);
158
+ write(serializeValue(parser, clearOnDefault, next));
159
+ }
160
+ };
65
161
  });
66
- }
67
- function createRouteStateRef(options) {
68
- return customRef((track) => ({
69
- get: () => {
70
- track();
71
- const { urlKey, source, parser } = toValue(options);
72
- const raw = source === "params" ? route.params[urlKey] : route.query[urlKey];
73
- return parseValue(parser, raw);
74
- },
75
- set: (next) => {
76
- const { urlKey, source, parser, history, clearOnDefault } = toValue(options);
77
- const serialized = serializeValue(parser, clearOnDefault, next);
78
- nextTick(() => {
79
- navigate([{
80
- urlKey,
81
- source,
82
- serialized
83
- }], history);
84
- });
162
+ function write(serialized, history) {
163
+ const resolved = toValue(options);
164
+ const optimistic = resolved.mode === "optimistic";
165
+ const current = ++generation;
166
+ if (optimistic) {
167
+ cache = {
168
+ seen: normalizeRaw(rawValue(resolved.urlKey, resolved.source)),
169
+ raw: serialized
170
+ };
171
+ trigger();
85
172
  }
86
- }));
173
+ return enqueue(router, {
174
+ history: history ?? resolved.history,
175
+ changes: [{
176
+ urlKey: resolved.urlKey,
177
+ source: resolved.source,
178
+ serialized
179
+ }]
180
+ }).then(() => {
181
+ if (!optimistic || generation !== current) return;
182
+ cache = null;
183
+ trigger();
184
+ });
185
+ }
186
+ return {
187
+ state,
188
+ write
189
+ };
87
190
  }
88
- return {
89
- createRouteStateRef,
90
- navigate
91
- };
191
+ return { createRouteStateRef };
92
192
  }
93
193
  //#endregion
94
194
  //#region src/router/utils.ts
@@ -99,7 +199,8 @@ function toResolvedOptions(input, defaults) {
99
199
  urlKey: input.urlKey ?? input.key,
100
200
  source: input.source ?? defaults.source,
101
201
  history: input.history ?? defaults.history,
102
- clearOnDefault: input.clearOnDefault ?? defaults.clearOnDefault
202
+ clearOnDefault: input.clearOnDefault ?? defaults.clearOnDefault,
203
+ mode: input.mode ?? defaults.mode
103
204
  };
104
205
  }
105
206
  //#endregion
@@ -108,58 +209,51 @@ function useRouteState(options) {
108
209
  if (!getCurrentScope()) warnDev(ErrorCodes.NO_EFFECT_SCOPE, "useRouteState");
109
210
  const { createRouteStateRef } = useBaseRouteState();
110
211
  const defaults = getActiveWagen().router;
111
- return createRouteStateRef(computed(() => toResolvedOptions(toValueDeep(options), defaults)));
212
+ return createRouteStateRef(computed(() => toResolvedOptions(toValueDeep(options), defaults))).state;
112
213
  }
113
214
  //#endregion
114
215
  //#region src/router/use-route-states.ts
115
- function resolveBatchHistory(candidates, override) {
116
- if (override) return override;
117
- return candidates.includes("push") ? "push" : "replace";
118
- }
119
216
  function useRouteStates(configs) {
120
217
  if (!getCurrentScope()) warnDev(ErrorCodes.NO_EFFECT_SCOPE, "useRouteStates");
121
- const { createRouteStateRef, navigate } = useBaseRouteState();
218
+ const { createRouteStateRef } = useBaseRouteState();
122
219
  const defaults = getActiveWagen().router;
123
220
  const resolvedList = configs.map((config) => computed(() => toResolvedOptions(toValueDeep(config), defaults)));
124
- const refs = Object.fromEntries(resolvedList.map((resolved) => [resolved.value.key, createRouteStateRef(resolved)]));
221
+ const handles = resolvedList.map((resolved) => createRouteStateRef(resolved));
222
+ const refs = Object.fromEntries(resolvedList.map((resolved, index) => [resolved.value.key, handles[index].state]));
223
+ function commit(writes, candidates, override) {
224
+ if (writes.length === 0) return;
225
+ const history = resolveBatchHistory(candidates, override);
226
+ for (const { handle, serialized } of writes) handle.write(serialized, history);
227
+ }
125
228
  const api = {
126
229
  set(patch, options) {
127
- const changes = [];
128
- const historyCandidates = [];
129
- for (const resolved of resolvedList) {
130
- const { key, urlKey, source, parser, clearOnDefault, history } = resolved.value;
131
- if (!(key in patch)) continue;
230
+ const writes = [];
231
+ const candidates = [];
232
+ resolvedList.forEach((resolved, index) => {
233
+ const { key, parser, clearOnDefault, history } = resolved.value;
234
+ if (!(key in patch)) return;
132
235
  const next = patch[key];
133
- changes.push({
134
- urlKey,
135
- source,
236
+ writes.push({
237
+ handle: handles[index],
136
238
  serialized: serializeValue(parser, clearOnDefault, next)
137
239
  });
138
- historyCandidates.push(history);
139
- }
140
- if (changes.length === 0) return;
141
- const history = resolveBatchHistory(historyCandidates, options?.history);
142
- nextTick(() => {
143
- navigate(changes, history);
240
+ candidates.push(history);
144
241
  });
242
+ commit(writes, candidates, options?.history);
145
243
  },
146
244
  reset(options) {
147
- const changes = [];
148
- const historyCandidates = [];
149
- for (const resolved of resolvedList) {
150
- const { urlKey, source, parser, clearOnDefault, history } = resolved.value;
245
+ const writes = [];
246
+ const candidates = [];
247
+ resolvedList.forEach((resolved, index) => {
248
+ const { parser, clearOnDefault, history } = resolved.value;
151
249
  const next = parser.defaultValue !== void 0 ? unwrapDefault(parser.defaultValue) : null;
152
- changes.push({
153
- urlKey,
154
- source,
250
+ writes.push({
251
+ handle: handles[index],
155
252
  serialized: serializeValue(parser, clearOnDefault, next)
156
253
  });
157
- historyCandidates.push(history);
158
- }
159
- const history = resolveBatchHistory(historyCandidates, options?.history);
160
- nextTick(() => {
161
- navigate(changes, history);
254
+ candidates.push(history);
162
255
  });
256
+ commit(writes, candidates, options?.history);
163
257
  },
164
258
  toObject() {
165
259
  const snapshot = {};
@@ -176,4 +270,4 @@ function useRouteStates(configs) {
176
270
  };
177
271
  }
178
272
  //#endregion
179
- export { useRouteHash, useRouteState, useRouteStates };
273
+ export { routeHashOptions, routeStateOptions, useRouteHash, useRouteState, useRouteStates };
@@ -1,6 +1,5 @@
1
- import { i as InferInputWritable, r as InferInputValue, s as ParserInput } from "../types-CNt2lcP1.mjs";
1
+ import { f as WithParser, n as ReactiveOptions, o as InferInputValue, r as StateMode, s as InferInputWritable, u as ParserInput } from "../types-JtRmT3qo.mjs";
2
2
  import { a as StorageInstance, c as Unsubscribe, i as StorageConfig, l as createStorage, n as KeyListener, o as StorageListener, r as StorageAdapter, s as StorageOptions, t as ErrorHandler } from "../create-storage-DESmB4bY.mjs";
3
- import { n as ReactiveOptions } from "../options-BKybciek.mjs";
4
3
  import { Ref } from "vue";
5
4
  //#region src/storage/define-storage-state.d.ts
6
5
  type StorageSource = StorageInstance | 'local' | 'session';
@@ -9,6 +8,7 @@ interface StorageStateOptions {
9
8
  storage?: StorageSource;
10
9
  parser?: ParserInput;
11
10
  clearOnDefault?: boolean;
11
+ mode?: StateMode;
12
12
  }
13
13
  interface StorageState<T = string | null, W = T | null | undefined> {
14
14
  readonly key: string;
@@ -18,26 +18,20 @@ interface StorageState<T = string | null, W = T | null | undefined> {
18
18
  remove: () => void;
19
19
  subscribe: (listener: () => void) => Unsubscribe;
20
20
  }
21
- declare function defineStorageState<P extends ParserInput | undefined = undefined>(options: Omit<StorageStateOptions, 'parser'> & {
22
- parser?: P;
23
- }): StorageState<InferInputValue<P>, InferInputWritable<P>>;
21
+ export declare function defineStorageState<P extends ParserInput | undefined = undefined>(options: WithParser<StorageStateOptions, P>): StorageState<InferInputValue<P>, InferInputWritable<P>>;
24
22
  //#endregion
25
23
  //#region src/storage/presets.d.ts
26
- declare function createMemoryStorage(config?: StorageConfig): StorageInstance;
27
- declare function createLocalStorage(config?: StorageConfig): StorageInstance;
28
- declare function createSessionStorage(config?: StorageConfig): StorageInstance;
24
+ export declare function createMemoryStorage(config?: StorageConfig): StorageInstance;
25
+ export declare function createLocalStorage(config?: StorageConfig): StorageInstance;
26
+ export declare function createSessionStorage(config?: StorageConfig): StorageInstance;
29
27
  //#endregion
30
28
  //#region src/storage/use-storage.d.ts
31
- type UseStorageOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<Omit<StorageStateOptions, 'parser'> & {
32
- parser?: P;
33
- }, 'parser'>;
34
- type UseLocalStorageOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<Omit<StorageStateOptions, 'parser' | 'storage'> & {
35
- parser?: P;
36
- }, 'parser'>;
29
+ type UseStorageOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<WithParser<StorageStateOptions, P>>;
30
+ type UseLocalStorageOptions<P extends ParserInput | undefined = ParserInput | undefined> = ReactiveOptions<WithParser<Omit<StorageStateOptions, 'storage'>, P>>;
37
31
  type UseSessionStorageOptions<P extends ParserInput | undefined = ParserInput | undefined> = UseLocalStorageOptions<P>;
38
- declare function useStorage<T, W>(state: StorageState<T, W>): Ref<T, W>;
39
- declare function useStorage<P extends ParserInput | undefined = undefined>(options: UseStorageOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
40
- declare function useLocalStorage<P extends ParserInput | undefined = undefined>(options: UseLocalStorageOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
41
- declare function useSessionStorage<P extends ParserInput | undefined = undefined>(options: UseSessionStorageOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
32
+ export declare function useStorage<T, W>(state: StorageState<T, W>): Ref<T, W>;
33
+ export declare function useStorage<P extends ParserInput | undefined = undefined>(options: UseStorageOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
34
+ export declare function useLocalStorage<P extends ParserInput | undefined = undefined>(options: UseLocalStorageOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
35
+ export declare function useSessionStorage<P extends ParserInput | undefined = undefined>(options: UseSessionStorageOptions<P>): Ref<InferInputValue<P>, InferInputWritable<P>>;
42
36
  //#endregion
43
- export { type ErrorHandler, type KeyListener, type StorageAdapter, type StorageConfig, type StorageInstance, type StorageListener, type StorageOptions, type StorageSource, type StorageState, type StorageStateOptions, type Unsubscribe, type UseLocalStorageOptions, type UseSessionStorageOptions, type UseStorageOptions, createLocalStorage, createMemoryStorage, createSessionStorage, createStorage, defineStorageState, useLocalStorage, useSessionStorage, useStorage };
37
+ export { type ErrorHandler, type KeyListener, type StateMode, type StorageAdapter, type StorageConfig, type StorageInstance, type StorageListener, type StorageOptions, type StorageSource, type StorageState, type StorageStateOptions, type Unsubscribe, type UseLocalStorageOptions, type UseSessionStorageOptions, type UseStorageOptions, createStorage };
@@ -1,31 +1,61 @@
1
1
  import { _ as warnDev, h as ErrorCodes } from "../parsers-CA8aZuF-.mjs";
2
- import { a as createLocalStorage, c as createStorage, o as createMemoryStorage, r as getActiveWagen, s as createSessionStorage } from "../wagen-C9_CPUdF.mjs";
3
- import { i as toValueDeep, n as serializeValue, r as resolveParser, t as parseValue } from "../utils-B-885gDp.mjs";
2
+ import { a as createLocalStorage, c as createStorage, o as createMemoryStorage, r as getActiveWagen, s as createSessionStorage } from "../wagen-C2p8oQgu.mjs";
3
+ import { a as toValueDeep, i as resolveParser, n as parseValue, r as serializeValue } from "../utils-EL2EiRpH.mjs";
4
4
  import { computed, customRef, getCurrentScope, onWatcherCleanup, toValue, watch } from "vue";
5
5
  //#region src/storage/define-storage-state.ts
6
6
  function defineStorageState(options) {
7
7
  const { key } = options;
8
8
  const clearOnDefault = options.clearOnDefault ?? true;
9
9
  const parser = resolveParser(options.parser);
10
- function storage() {
10
+ let cache = null;
11
+ function resolved() {
11
12
  const source = options.storage;
12
- if (source && typeof source !== "string") return source;
13
- const { storage } = getActiveWagen();
14
- return source ? storage[source] : storage.default;
13
+ const mode = options.mode;
14
+ if (mode !== void 0 && source !== void 0 && typeof source !== "string") return {
15
+ instance: source,
16
+ optimistic: mode === "optimistic"
17
+ };
18
+ const wagen = getActiveWagen();
19
+ return {
20
+ instance: source === void 0 ? wagen.storage.default : typeof source === "string" ? wagen.storage[source] : source,
21
+ optimistic: (mode ?? wagen.storage.mode) === "optimistic"
22
+ };
23
+ }
24
+ function write(serialized) {
25
+ const { instance, optimistic } = resolved();
26
+ if (optimistic) cache = {
27
+ seen: instance.getItem(key),
28
+ raw: serialized
29
+ };
30
+ if (serialized === null) instance.removeItem(key);
31
+ else instance.setItem(key, serialized);
32
+ if (optimistic) cache = {
33
+ seen: instance.getItem(key),
34
+ raw: serialized
35
+ };
15
36
  }
16
37
  return {
17
38
  key,
18
39
  get storage() {
19
- return storage();
40
+ return resolved().instance;
20
41
  },
21
- get: () => parseValue(parser, storage().getItem(key)),
22
- set: (next) => {
23
- const serialized = serializeValue(parser, clearOnDefault, next);
24
- if (serialized === null) storage().removeItem(key);
25
- else storage().setItem(key, serialized);
42
+ get: () => {
43
+ const { instance, optimistic } = resolved();
44
+ const current = instance.getItem(key);
45
+ if (!optimistic) {
46
+ cache = null;
47
+ return parseValue(parser, current);
48
+ }
49
+ if (cache && cache.seen === current) return parseValue(parser, cache.raw);
50
+ cache = {
51
+ seen: current,
52
+ raw: current
53
+ };
54
+ return parseValue(parser, current);
26
55
  },
27
- remove: () => storage().removeItem(key),
28
- subscribe: (listener) => storage().subscribe(key, listener)
56
+ set: (next) => write(serializeValue(parser, clearOnDefault, next)),
57
+ remove: () => write(null),
58
+ subscribe: (listener) => resolved().instance.subscribe(key, listener)
29
59
  };
30
60
  }
31
61
  //#endregion
@@ -33,18 +63,19 @@ function defineStorageState(options) {
33
63
  function isStorageState(input) {
34
64
  return typeof input === "object" && input !== null && typeof input.get === "function";
35
65
  }
36
- function withResolvedStorage(options, wagen) {
66
+ function withWagenDefaults(options, wagen) {
37
67
  const source = options.storage;
38
- if (source && typeof source !== "string") return options;
68
+ const storage = source === void 0 ? wagen.storage.default : typeof source === "string" ? wagen.storage[source] : source;
39
69
  return {
40
70
  ...options,
41
- storage: source ? wagen.storage[source] : wagen.storage.default
71
+ storage,
72
+ mode: options.mode ?? wagen.storage.mode
42
73
  };
43
74
  }
44
75
  function useStorageState(input) {
45
76
  if (isStorageState(input)) return computed(() => input);
46
77
  const wagen = getActiveWagen();
47
- return computed(() => defineStorageState(withResolvedStorage(toValueDeep(input), wagen)));
78
+ return computed(() => defineStorageState(withWagenDefaults(toValueDeep(input), wagen)));
48
79
  }
49
80
  function useStorage(input) {
50
81
  if (!getCurrentScope()) warnDev(ErrorCodes.NO_EFFECT_SCOPE, "useStorage");
@@ -1,3 +1,4 @@
1
+ import { MaybeRefOrGetter, Ref } from "vue";
1
2
  //#region src/parser/parsers.d.ts
2
3
  type ParserOptions<T> = {
3
4
  parse: (raw: string) => T | null;
@@ -49,7 +50,12 @@ type NamedParserRef = { [K in keyof KnownParsers]: {
49
50
  type ParserInput = (Parser<any> & {
50
51
  name?: never;
51
52
  }) | NamedParserRef;
52
- type InferInputValue<P> = [P] extends [undefined] ? string | null : P extends ParserWithDefault<infer T> ? T : P extends Parser<infer T> ? T | null : P extends {
53
+ type UnwrapParser<P> = P extends Ref<infer U> ? U : P extends (() => infer U) ? U : P;
54
+ type WithParser<T, P extends ParserInput | undefined> = Omit<T, 'parser'> & {
55
+ parser?: P;
56
+ };
57
+ type InferInputValue<P> = ResolveInputValue<UnwrapParser<P>>;
58
+ type ResolveInputValue<P> = [P] extends [undefined] ? string | null : P extends ParserWithDefault<infer T> ? T : P extends Parser<infer T> ? T | null : P extends {
53
59
  name: infer K;
54
60
  defaultValue: any;
55
61
  } ? K extends keyof KnownParsers ? InferParserValue<KnownParsers[K]> : string : P extends {
@@ -57,4 +63,10 @@ type InferInputValue<P> = [P] extends [undefined] ? string | null : P extends Pa
57
63
  } ? K extends keyof KnownParsers ? InferParserValue<KnownParsers[K]> | null : string | null : string | null;
58
64
  type InferInputWritable<P> = InferInputValue<P> | null | undefined;
59
65
  //#endregion
60
- export { parseAsStringLiteral as C, parseAsStringEnum as S, unwrapDefault as T, parseAsIndex as _, InferParserValue as a, parseAsNumberLiteral as b, DefaultValue as c, ParserWithDefault as d, defineParser as f, parseAsFloat as g, parseAsDate as h, InferInputWritable as i, Parser as l, parseAsBoolean as m, CustomParsers as n, KnownParsers as o, parseAsArrayOf as p, InferInputValue as r, ParserInput as s, BuiltinParsers as t, ParserOptions as u, parseAsInteger as v, tryParse as w, parseAsString as x, parseAsJson as y };
66
+ //#region src/types.d.ts
67
+ type StateMode = 'optimistic' | 'source';
68
+ type MaybeRefsOrGetters<T> = { [K in keyof T]: MaybeRefOrGetter<T[K]>; };
69
+ type ReactiveFields<T, TStatic extends keyof T = never> = MaybeRefsOrGetters<Omit<T, TStatic>> & Pick<T, TStatic>;
70
+ type ReactiveOptions<T, TStatic extends keyof T = never> = MaybeRefOrGetter<ReactiveFields<T, TStatic>>;
71
+ //#endregion
72
+ export { unwrapDefault as A, parseAsInteger as C, parseAsStringEnum as D, parseAsString as E, parseAsStringLiteral as O, parseAsIndex as S, parseAsNumberLiteral as T, defineParser as _, CustomParsers as a, parseAsDate as b, InferParserValue as c, UnwrapParser as d, WithParser as f, ParserWithDefault as g, ParserOptions as h, BuiltinParsers as i, tryParse as k, KnownParsers as l, Parser as m, ReactiveOptions as n, InferInputValue as o, DefaultValue as p, StateMode as r, InferInputWritable as s, ReactiveFields as t, ParserInput as u, parseAsArrayOf as v, parseAsJson as w, parseAsFloat as x, parseAsBoolean as y };
@@ -1,4 +1,4 @@
1
- import { c as DefaultValue, s as ParserInput } from "./types-CNt2lcP1.mjs";
1
+ import { p as DefaultValue, r as StateMode, u as ParserInput } from "./types-JtRmT3qo.mjs";
2
2
  //#region src/parser/resolve.d.ts
3
3
  type ResolvedParser<T> = {
4
4
  parse: (raw: string) => T | null;
@@ -16,6 +16,7 @@ interface RouteStateOptions {
16
16
  source?: RouteStateSource;
17
17
  history?: HistoryMode;
18
18
  clearOnDefault?: boolean;
19
+ mode?: StateMode;
19
20
  }
20
21
  type ResolvedRouteStateOptions = Omit<Required<RouteStateOptions>, 'parser'> & {
21
22
  parser: ResolvedParser<any>;
@@ -1,5 +1,5 @@
1
1
  import { g as warn, h as ErrorCodes, m as unwrapDefault, u as parseAsString } from "./parsers-CA8aZuF-.mjs";
2
- import { r as getActiveWagen } from "./wagen-C9_CPUdF.mjs";
2
+ import { r as getActiveWagen } from "./wagen-C2p8oQgu.mjs";
3
3
  import { n as isBuiltinParserName, t as builtinParsers } from "./builtins-DiCjLgsG.mjs";
4
4
  import { toValue } from "vue";
5
5
  //#region src/options.ts
@@ -57,9 +57,12 @@ function resolveParser(input = { name: "parseAsString" }) {
57
57
  }
58
58
  //#endregion
59
59
  //#region src/parser/utils.ts
60
+ function normalizeRaw(raw) {
61
+ return (Array.isArray(raw) ? raw.find((v) => v != null) : raw) ?? null;
62
+ }
60
63
  function parseValue(parser, raw) {
61
- const value = Array.isArray(raw) ? raw.find((v) => v != null) : raw;
62
- if (value == null) return parser.defaultValue !== void 0 ? unwrapDefault(parser.defaultValue) : null;
64
+ const value = normalizeRaw(raw);
65
+ if (value === null) return parser.defaultValue !== void 0 ? unwrapDefault(parser.defaultValue) : null;
63
66
  const parsed = parser.parse(value);
64
67
  if (parsed !== null) return parsed;
65
68
  return parser.defaultValue !== void 0 ? unwrapDefault(parser.defaultValue) : null;
@@ -73,4 +76,4 @@ function serializeValue(parser, clearOnDefault, next) {
73
76
  return serialized;
74
77
  }
75
78
  //#endregion
76
- export { toValueDeep as i, serializeValue as n, resolveParser as r, parseValue as t };
79
+ export { toValueDeep as a, resolveParser as i, parseValue as n, serializeValue as r, normalizeRaw as t };
@@ -1,8 +1,8 @@
1
1
  import { Plugin } from "vite";
2
2
  //#region src/vite/index.d.ts
3
- declare const PARSERS_ID = "virtual:usewagen/parsers";
4
- declare const RESOLVED_PARSERS_ID = "\0virtual:usewagen/parsers";
5
- interface WagenPluginOptions {
3
+ export declare const PARSERS_ID = "virtual:usewagen/parsers";
4
+ export declare const RESOLVED_PARSERS_ID = "\0virtual:usewagen/parsers";
5
+ export interface WagenPluginOptions {
6
6
  /**
7
7
  * Directory or directories to scan for parser files.
8
8
  * @default 'src/parsers'
@@ -14,6 +14,6 @@ interface WagenPluginOptions {
14
14
  */
15
15
  dts?: string | false;
16
16
  }
17
- declare function usewagen(options?: WagenPluginOptions): Plugin;
17
+ export declare function usewagen(options?: WagenPluginOptions): Plugin;
18
18
  //#endregion
19
- export { PARSERS_ID, RESOLVED_PARSERS_ID, WagenPluginOptions, usewagen as default, usewagen };
19
+ export { usewagen as default };
@@ -197,7 +197,8 @@ const WAGEN_KEY = Symbol.for("usewagen");
197
197
  const ROUTER_DEFAULTS = {
198
198
  history: "replace",
199
199
  source: "query",
200
- clearOnDefault: true
200
+ clearOnDefault: true,
201
+ mode: "optimistic"
201
202
  };
202
203
  let activeWagen = null;
203
204
  function createStorage(options) {
@@ -228,7 +229,8 @@ function createStorage(options) {
228
229
  },
229
230
  get default() {
230
231
  return options.default === "session" ? storage.session : storage.local;
231
- }
232
+ },
233
+ mode: options.mode ?? "optimistic"
232
234
  };
233
235
  function destroy() {
234
236
  for (const instance of created) instance.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usewagen",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Reactive state for URL and storage in Vue.",
5
5
  "keywords": [
6
6
  "composables",
@@ -64,7 +64,7 @@
64
64
  "devEngines": {
65
65
  "packageManager": {
66
66
  "name": "pnpm",
67
- "version": "11.24.0",
67
+ "version": "11.25.0",
68
68
  "onFail": "download"
69
69
  }
70
70
  },
@@ -1,7 +0,0 @@
1
- import { MaybeRefOrGetter } from "vue";
2
- //#region src/options.d.ts
3
- type MaybeRefsOrGetters<T> = { [K in keyof T]: MaybeRefOrGetter<T[K]>; };
4
- type ReactiveFields<T, TStatic extends keyof T = never> = MaybeRefsOrGetters<Omit<T, TStatic>> & Pick<T, TStatic>;
5
- type ReactiveOptions<T, TStatic extends keyof T = never> = MaybeRefOrGetter<ReactiveFields<T, TStatic>>;
6
- //#endregion
7
- export { ReactiveOptions as n, ReactiveFields as t };