zustand-querystring 0.0.14 → 0.0.16

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.
@@ -0,0 +1,187 @@
1
+ import {
2
+ parse,
3
+ stringify
4
+ } from "./chunk-QPGVZKLE.mjs";
5
+
6
+ // src/middleware.ts
7
+ import { mergeWith, isEqual, cloneDeep } from "lodash-es";
8
+ var compact = (newState, initialState) => {
9
+ const output = {};
10
+ Object.keys(newState).forEach((key) => {
11
+ if (newState[key] !== null && newState[key] !== void 0 && typeof newState[key] !== "function" && !isEqual(newState[key], initialState[key])) {
12
+ if (typeof newState[key] === "object" && !Array.isArray(newState[key])) {
13
+ const value = compact(newState[key], initialState[key]);
14
+ if (value && Object.keys(value).length > 0) {
15
+ output[key] = value;
16
+ }
17
+ } else {
18
+ output[key] = newState[key];
19
+ }
20
+ }
21
+ });
22
+ return output;
23
+ };
24
+ var translateSelectionToState = (selection, state) => {
25
+ if (typeof state !== "object" || !state) {
26
+ return {};
27
+ }
28
+ return Object.keys(selection).reduce((acc, key) => {
29
+ if (!(key in state)) {
30
+ return acc;
31
+ }
32
+ const value = selection[key];
33
+ if (typeof value === "boolean") {
34
+ if (value) {
35
+ acc[key] = state[key];
36
+ }
37
+ } else {
38
+ acc[key] = translateSelectionToState(value, state[key]);
39
+ }
40
+ return acc;
41
+ }, {});
42
+ };
43
+ var escapeStringRegexp = (string) => {
44
+ if (typeof string !== "string") {
45
+ throw new TypeError("Expected a string");
46
+ }
47
+ return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
48
+ };
49
+ var queryStringImpl = (fn, options) => (set, get, api) => {
50
+ const defaultedOptions = {
51
+ key: "$",
52
+ ...options
53
+ };
54
+ const escapedKey = escapeStringRegexp(defaultedOptions.key);
55
+ const stateMatcher = new RegExp(`${escapedKey}=(.*);;|${escapedKey}=(.*)$`);
56
+ const splitMatcher = new RegExp(`${escapedKey}=.*;;|${escapedKey}=.*$`);
57
+ const parseQueryString = (querystring2) => {
58
+ var _a;
59
+ const match = querystring2.match(stateMatcher);
60
+ if (match) {
61
+ let m = (_a = match[1]) != null ? _a : match[2];
62
+ if (!m.startsWith("$")) {
63
+ m = "$" + m;
64
+ }
65
+ return parse(m);
66
+ }
67
+ return null;
68
+ };
69
+ const url = defaultedOptions.url;
70
+ const getSelectedState = (state, pathname) => {
71
+ if (defaultedOptions.select) {
72
+ const selection = defaultedOptions.select(pathname);
73
+ const selectedState = translateSelectionToState(selection, state);
74
+ return selectedState;
75
+ }
76
+ return state != null ? state : {};
77
+ };
78
+ const initialize = (url2, initialState) => {
79
+ try {
80
+ const queryString = url2.search.substring(1);
81
+ const pathname = url2.pathname;
82
+ if (!queryString) {
83
+ return initialState;
84
+ }
85
+ const parsed = parseQueryString(queryString);
86
+ if (!parsed) {
87
+ return initialState;
88
+ }
89
+ const merged = mergeWith(
90
+ cloneDeep(initialState),
91
+ getSelectedState(parsed, pathname)
92
+ );
93
+ set(merged, true);
94
+ return merged;
95
+ } catch (error) {
96
+ console.error(error);
97
+ return initialState;
98
+ }
99
+ };
100
+ if (typeof window !== "undefined") {
101
+ const initialState = cloneDeep(
102
+ fn(
103
+ (...args) => {
104
+ set(...args);
105
+ setQuery();
106
+ },
107
+ get,
108
+ api
109
+ )
110
+ );
111
+ const setQuery = () => {
112
+ const selectedState = getSelectedState(get(), location.pathname);
113
+ const currentQueryString = location.search;
114
+ const currentParsed = parseQueryString(currentQueryString);
115
+ const newMerged = {
116
+ ...currentParsed,
117
+ ...selectedState
118
+ };
119
+ const splitIgnored = currentQueryString.split(splitMatcher);
120
+ let ignored = "";
121
+ for (let str of splitIgnored) {
122
+ if (!str || str === "?" || str === "&") {
123
+ continue;
124
+ }
125
+ if (str.startsWith("&") || str.startsWith("?")) {
126
+ str = str.substring(1);
127
+ }
128
+ if (str.endsWith("&")) {
129
+ str = str.substring(0, str.length - 1);
130
+ }
131
+ ignored += (ignored ? "&" : "?") + str;
132
+ }
133
+ const newCompacted = compact(newMerged, initialState);
134
+ if (Object.keys(newCompacted).length) {
135
+ const stringified = stringify(newCompacted).substring(1);
136
+ const newQueryState = `${defaultedOptions.key}=${stringified};;`;
137
+ let newQueryString = "";
138
+ if (currentParsed) {
139
+ newQueryString = currentQueryString.replace(
140
+ splitMatcher,
141
+ newQueryState
142
+ );
143
+ } else if (ignored) {
144
+ newQueryString = ignored + "&" + newQueryState;
145
+ } else {
146
+ newQueryString = "?" + newQueryState;
147
+ }
148
+ history.replaceState(
149
+ history.state,
150
+ "",
151
+ location.pathname + newQueryString
152
+ );
153
+ } else {
154
+ history.replaceState(history.state, "", location.pathname + ignored);
155
+ }
156
+ };
157
+ if (!api.__ZUSTAND_QUERYSTRING_INIT__) {
158
+ api.__ZUSTAND_QUERYSTRING_INIT__ = true;
159
+ let previousPathname = "";
160
+ const cb = () => {
161
+ if (location.pathname !== previousPathname) {
162
+ previousPathname = location.pathname;
163
+ setQuery();
164
+ }
165
+ requestAnimationFrame(cb);
166
+ };
167
+ requestAnimationFrame(cb);
168
+ }
169
+ const originalSetState = api.setState;
170
+ api.setState = (...args) => {
171
+ originalSetState(...args);
172
+ setQuery();
173
+ };
174
+ return initialize(new URL(location.href), initialState);
175
+ } else if (url) {
176
+ return initialize(
177
+ new URL(decodeURIComponent(url), "http://localhost"),
178
+ fn(set, get, api)
179
+ );
180
+ }
181
+ return fn(set, get, api);
182
+ };
183
+ var querystring = queryStringImpl;
184
+
185
+ export {
186
+ querystring
187
+ };
@@ -0,0 +1,121 @@
1
+ // src/parser.ts
2
+ var keyStringifyRegexp = /([=:@$/])/g;
3
+ var valueStringifyRegexp = /([&;/])/g;
4
+ var keyParseRegexp = /[=:@$]/;
5
+ var valueParseRegexp = /[&;]/;
6
+ function encodeString(str, regexp) {
7
+ return encodeURI(str.replace(regexp, "/$1"));
8
+ }
9
+ function trim(res) {
10
+ return typeof res === "string" ? res.replace(/;+$/g, "") : res;
11
+ }
12
+ function stringify(input, recursive) {
13
+ if (!recursive) {
14
+ return trim(stringify(input, true));
15
+ }
16
+ if (typeof input === "function") {
17
+ return;
18
+ }
19
+ if (typeof input === "number" || input === true || input === false || input === null) {
20
+ return ":" + input;
21
+ }
22
+ const res = [];
23
+ if (Array.isArray(input)) {
24
+ for (const elem of input) {
25
+ typeof elem === "undefined" ? res.push(":null") : res.push(stringify(elem, true));
26
+ }
27
+ return "@" + res.join("&") + ";";
28
+ }
29
+ if (typeof input === "object") {
30
+ for (const [key, value] of Object.entries(input)) {
31
+ const stringifiedValue = stringify(value, true);
32
+ if (stringifiedValue) {
33
+ res.push(encodeString(key, keyStringifyRegexp) + stringifiedValue);
34
+ }
35
+ }
36
+ return "$" + res.join("&") + ";";
37
+ }
38
+ if (typeof input === "undefined") {
39
+ return;
40
+ }
41
+ return "=" + encodeString(input.toString(), valueStringifyRegexp);
42
+ }
43
+ function parse(str) {
44
+ let pos = 0;
45
+ str = decodeURI(str);
46
+ function readToken(regexp) {
47
+ let token = "";
48
+ for (; pos !== str.length; ++pos) {
49
+ if (str.charAt(pos) === "/") {
50
+ pos += 1;
51
+ if (pos === str.length) {
52
+ token += ";";
53
+ break;
54
+ }
55
+ } else if (str.charAt(pos).match(regexp)) {
56
+ break;
57
+ }
58
+ token += str.charAt(pos);
59
+ }
60
+ return token;
61
+ }
62
+ function parseToken() {
63
+ const type = str.charAt(pos++);
64
+ if (type === "=") {
65
+ return readToken(valueParseRegexp);
66
+ }
67
+ if (type === ":") {
68
+ const value = readToken(valueParseRegexp);
69
+ if (value === "true") {
70
+ return true;
71
+ }
72
+ if (value === "false") {
73
+ return false;
74
+ }
75
+ const parsedValue = parseFloat(value);
76
+ return isNaN(parsedValue) ? null : parsedValue;
77
+ }
78
+ if (type === "@") {
79
+ const res = [];
80
+ loop: {
81
+ if (pos >= str.length || str.charAt(pos) === ";") {
82
+ break loop;
83
+ }
84
+ while (1) {
85
+ res.push(parseToken());
86
+ if (pos >= str.length || str.charAt(pos) === ";") {
87
+ break loop;
88
+ }
89
+ pos += 1;
90
+ }
91
+ }
92
+ pos += 1;
93
+ return res;
94
+ }
95
+ if (type === "$") {
96
+ const res = {};
97
+ loop: {
98
+ if (pos >= str.length || str.charAt(pos) === ";") {
99
+ break loop;
100
+ }
101
+ while (1) {
102
+ var name = readToken(keyParseRegexp);
103
+ res[name] = parseToken();
104
+ if (pos >= str.length || str.charAt(pos) === ";") {
105
+ break loop;
106
+ }
107
+ pos += 1;
108
+ }
109
+ }
110
+ pos += 1;
111
+ return res;
112
+ }
113
+ throw new Error("Unexpected char " + type);
114
+ }
115
+ return parseToken();
116
+ }
117
+
118
+ export {
119
+ stringify,
120
+ parse
121
+ };
@@ -0,0 +1,2 @@
1
+ export { QueryStringOptions, querystring } from './middleware.js';
2
+ import 'zustand/vanilla';
package/dist/index.js ADDED
@@ -0,0 +1,324 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.ts
20
+ var src_exports = {};
21
+ __export(src_exports, {
22
+ querystring: () => querystring
23
+ });
24
+ module.exports = __toCommonJS(src_exports);
25
+
26
+ // src/parser.ts
27
+ var keyStringifyRegexp = /([=:@$/])/g;
28
+ var valueStringifyRegexp = /([&;/])/g;
29
+ var keyParseRegexp = /[=:@$]/;
30
+ var valueParseRegexp = /[&;]/;
31
+ function encodeString(str, regexp) {
32
+ return encodeURI(str.replace(regexp, "/$1"));
33
+ }
34
+ function trim(res) {
35
+ return typeof res === "string" ? res.replace(/;+$/g, "") : res;
36
+ }
37
+ function stringify(input, recursive) {
38
+ if (!recursive) {
39
+ return trim(stringify(input, true));
40
+ }
41
+ if (typeof input === "function") {
42
+ return;
43
+ }
44
+ if (typeof input === "number" || input === true || input === false || input === null) {
45
+ return ":" + input;
46
+ }
47
+ const res = [];
48
+ if (Array.isArray(input)) {
49
+ for (const elem of input) {
50
+ typeof elem === "undefined" ? res.push(":null") : res.push(stringify(elem, true));
51
+ }
52
+ return "@" + res.join("&") + ";";
53
+ }
54
+ if (typeof input === "object") {
55
+ for (const [key, value] of Object.entries(input)) {
56
+ const stringifiedValue = stringify(value, true);
57
+ if (stringifiedValue) {
58
+ res.push(encodeString(key, keyStringifyRegexp) + stringifiedValue);
59
+ }
60
+ }
61
+ return "$" + res.join("&") + ";";
62
+ }
63
+ if (typeof input === "undefined") {
64
+ return;
65
+ }
66
+ return "=" + encodeString(input.toString(), valueStringifyRegexp);
67
+ }
68
+ function parse(str) {
69
+ let pos = 0;
70
+ str = decodeURI(str);
71
+ function readToken(regexp) {
72
+ let token = "";
73
+ for (; pos !== str.length; ++pos) {
74
+ if (str.charAt(pos) === "/") {
75
+ pos += 1;
76
+ if (pos === str.length) {
77
+ token += ";";
78
+ break;
79
+ }
80
+ } else if (str.charAt(pos).match(regexp)) {
81
+ break;
82
+ }
83
+ token += str.charAt(pos);
84
+ }
85
+ return token;
86
+ }
87
+ function parseToken() {
88
+ const type = str.charAt(pos++);
89
+ if (type === "=") {
90
+ return readToken(valueParseRegexp);
91
+ }
92
+ if (type === ":") {
93
+ const value = readToken(valueParseRegexp);
94
+ if (value === "true") {
95
+ return true;
96
+ }
97
+ if (value === "false") {
98
+ return false;
99
+ }
100
+ const parsedValue = parseFloat(value);
101
+ return isNaN(parsedValue) ? null : parsedValue;
102
+ }
103
+ if (type === "@") {
104
+ const res = [];
105
+ loop: {
106
+ if (pos >= str.length || str.charAt(pos) === ";") {
107
+ break loop;
108
+ }
109
+ while (1) {
110
+ res.push(parseToken());
111
+ if (pos >= str.length || str.charAt(pos) === ";") {
112
+ break loop;
113
+ }
114
+ pos += 1;
115
+ }
116
+ }
117
+ pos += 1;
118
+ return res;
119
+ }
120
+ if (type === "$") {
121
+ const res = {};
122
+ loop: {
123
+ if (pos >= str.length || str.charAt(pos) === ";") {
124
+ break loop;
125
+ }
126
+ while (1) {
127
+ var name = readToken(keyParseRegexp);
128
+ res[name] = parseToken();
129
+ if (pos >= str.length || str.charAt(pos) === ";") {
130
+ break loop;
131
+ }
132
+ pos += 1;
133
+ }
134
+ }
135
+ pos += 1;
136
+ return res;
137
+ }
138
+ throw new Error("Unexpected char " + type);
139
+ }
140
+ return parseToken();
141
+ }
142
+
143
+ // src/middleware.ts
144
+ var import_lodash_es = require("lodash-es");
145
+ var compact = (newState, initialState) => {
146
+ const output = {};
147
+ Object.keys(newState).forEach((key) => {
148
+ if (newState[key] !== null && newState[key] !== void 0 && typeof newState[key] !== "function" && !(0, import_lodash_es.isEqual)(newState[key], initialState[key])) {
149
+ if (typeof newState[key] === "object" && !Array.isArray(newState[key])) {
150
+ const value = compact(newState[key], initialState[key]);
151
+ if (value && Object.keys(value).length > 0) {
152
+ output[key] = value;
153
+ }
154
+ } else {
155
+ output[key] = newState[key];
156
+ }
157
+ }
158
+ });
159
+ return output;
160
+ };
161
+ var translateSelectionToState = (selection, state) => {
162
+ if (typeof state !== "object" || !state) {
163
+ return {};
164
+ }
165
+ return Object.keys(selection).reduce((acc, key) => {
166
+ if (!(key in state)) {
167
+ return acc;
168
+ }
169
+ const value = selection[key];
170
+ if (typeof value === "boolean") {
171
+ if (value) {
172
+ acc[key] = state[key];
173
+ }
174
+ } else {
175
+ acc[key] = translateSelectionToState(value, state[key]);
176
+ }
177
+ return acc;
178
+ }, {});
179
+ };
180
+ var escapeStringRegexp = (string) => {
181
+ if (typeof string !== "string") {
182
+ throw new TypeError("Expected a string");
183
+ }
184
+ return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
185
+ };
186
+ var queryStringImpl = (fn, options) => (set, get, api) => {
187
+ const defaultedOptions = {
188
+ key: "$",
189
+ ...options
190
+ };
191
+ const escapedKey = escapeStringRegexp(defaultedOptions.key);
192
+ const stateMatcher = new RegExp(`${escapedKey}=(.*);;|${escapedKey}=(.*)$`);
193
+ const splitMatcher = new RegExp(`${escapedKey}=.*;;|${escapedKey}=.*$`);
194
+ const parseQueryString = (querystring2) => {
195
+ var _a;
196
+ const match = querystring2.match(stateMatcher);
197
+ if (match) {
198
+ let m = (_a = match[1]) != null ? _a : match[2];
199
+ if (!m.startsWith("$")) {
200
+ m = "$" + m;
201
+ }
202
+ return parse(m);
203
+ }
204
+ return null;
205
+ };
206
+ const url = defaultedOptions.url;
207
+ const getSelectedState = (state, pathname) => {
208
+ if (defaultedOptions.select) {
209
+ const selection = defaultedOptions.select(pathname);
210
+ const selectedState = translateSelectionToState(selection, state);
211
+ return selectedState;
212
+ }
213
+ return state != null ? state : {};
214
+ };
215
+ const initialize = (url2, initialState) => {
216
+ try {
217
+ const queryString = url2.search.substring(1);
218
+ const pathname = url2.pathname;
219
+ if (!queryString) {
220
+ return initialState;
221
+ }
222
+ const parsed = parseQueryString(queryString);
223
+ if (!parsed) {
224
+ return initialState;
225
+ }
226
+ const merged = (0, import_lodash_es.mergeWith)(
227
+ (0, import_lodash_es.cloneDeep)(initialState),
228
+ getSelectedState(parsed, pathname)
229
+ );
230
+ set(merged, true);
231
+ return merged;
232
+ } catch (error) {
233
+ console.error(error);
234
+ return initialState;
235
+ }
236
+ };
237
+ if (typeof window !== "undefined") {
238
+ const initialState = (0, import_lodash_es.cloneDeep)(
239
+ fn(
240
+ (...args) => {
241
+ set(...args);
242
+ setQuery();
243
+ },
244
+ get,
245
+ api
246
+ )
247
+ );
248
+ const setQuery = () => {
249
+ const selectedState = getSelectedState(get(), location.pathname);
250
+ const currentQueryString = location.search;
251
+ const currentParsed = parseQueryString(currentQueryString);
252
+ const newMerged = {
253
+ ...currentParsed,
254
+ ...selectedState
255
+ };
256
+ const splitIgnored = currentQueryString.split(splitMatcher);
257
+ let ignored = "";
258
+ for (let str of splitIgnored) {
259
+ if (!str || str === "?" || str === "&") {
260
+ continue;
261
+ }
262
+ if (str.startsWith("&") || str.startsWith("?")) {
263
+ str = str.substring(1);
264
+ }
265
+ if (str.endsWith("&")) {
266
+ str = str.substring(0, str.length - 1);
267
+ }
268
+ ignored += (ignored ? "&" : "?") + str;
269
+ }
270
+ const newCompacted = compact(newMerged, initialState);
271
+ if (Object.keys(newCompacted).length) {
272
+ const stringified = stringify(newCompacted).substring(1);
273
+ const newQueryState = `${defaultedOptions.key}=${stringified};;`;
274
+ let newQueryString = "";
275
+ if (currentParsed) {
276
+ newQueryString = currentQueryString.replace(
277
+ splitMatcher,
278
+ newQueryState
279
+ );
280
+ } else if (ignored) {
281
+ newQueryString = ignored + "&" + newQueryState;
282
+ } else {
283
+ newQueryString = "?" + newQueryState;
284
+ }
285
+ history.replaceState(
286
+ history.state,
287
+ "",
288
+ location.pathname + newQueryString
289
+ );
290
+ } else {
291
+ history.replaceState(history.state, "", location.pathname + ignored);
292
+ }
293
+ };
294
+ if (!api.__ZUSTAND_QUERYSTRING_INIT__) {
295
+ api.__ZUSTAND_QUERYSTRING_INIT__ = true;
296
+ let previousPathname = "";
297
+ const cb = () => {
298
+ if (location.pathname !== previousPathname) {
299
+ previousPathname = location.pathname;
300
+ setQuery();
301
+ }
302
+ requestAnimationFrame(cb);
303
+ };
304
+ requestAnimationFrame(cb);
305
+ }
306
+ const originalSetState = api.setState;
307
+ api.setState = (...args) => {
308
+ originalSetState(...args);
309
+ setQuery();
310
+ };
311
+ return initialize(new URL(location.href), initialState);
312
+ } else if (url) {
313
+ return initialize(
314
+ new URL(decodeURIComponent(url), "http://localhost"),
315
+ fn(set, get, api)
316
+ );
317
+ }
318
+ return fn(set, get, api);
319
+ };
320
+ var querystring = queryStringImpl;
321
+ // Annotate the CommonJS export names for ESM import in node:
322
+ 0 && (module.exports = {
323
+ querystring
324
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ querystring
3
+ } from "./chunk-QIA7KKX4.mjs";
4
+ import "./chunk-QPGVZKLE.mjs";
5
+ export {
6
+ querystring
7
+ };
@@ -1,12 +1,14 @@
1
- import { StateCreator, StoreMutatorIdentifier } from 'zustand/vanilla';
1
+ import { StoreMutatorIdentifier, StateCreator } from 'zustand/vanilla';
2
+
2
3
  type DeepSelect<T> = T extends object ? {
3
4
  [P in keyof T]?: DeepSelect<T[P]> | boolean;
4
5
  } : boolean;
5
- export interface QueryStringOptions<T> {
6
+ interface QueryStringOptions<T> {
6
7
  url?: string;
7
8
  select?: (pathname: string) => DeepSelect<T>;
8
9
  key?: string;
9
10
  }
10
11
  type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T>) => StateCreator<T, Mps, Mcs>;
11
- export declare const querystring: QueryString;
12
- export {};
12
+ declare const querystring: QueryString;
13
+
14
+ export { QueryStringOptions, querystring };