zustand-querystring 0.4.0 → 0.5.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/dist/index.mjs CHANGED
@@ -1,15 +1,9 @@
1
- // src/middleware.ts
2
- import { isEqual, mergeWith } from "lodash-es";
3
-
4
- // src/parser.ts
5
- function stringify(input) {
6
- return encodeURIComponent(JSON.stringify(input));
7
- }
8
- function parse(str) {
9
- return JSON.parse(decodeURIComponent(str));
10
- }
1
+ import {
2
+ marked
3
+ } from "./chunk-TQQUWVLF.mjs";
11
4
 
12
5
  // src/middleware.ts
6
+ import { isEqual, mergeWith } from "lodash-es";
13
7
  var compact = (newState, initialState, syncNull = false, syncUndefined = false) => {
14
8
  const output = {};
15
9
  const removed = [];
@@ -51,38 +45,60 @@ var translateSelectionToState = (selection, state) => {
51
45
  return acc;
52
46
  }, {});
53
47
  };
48
+ var parseSearchString = (search) => {
49
+ const result = {};
50
+ search.slice(search.startsWith("?") ? 1 : 0).split("&").filter(Boolean).forEach((param) => {
51
+ const eqIndex = param.indexOf("=");
52
+ if (eqIndex === -1) return;
53
+ const key = param.slice(0, eqIndex);
54
+ const value = param.slice(eqIndex + 1);
55
+ if (!result[key]) {
56
+ result[key] = [];
57
+ }
58
+ result[key].push(value);
59
+ });
60
+ return result;
61
+ };
54
62
  var queryStringImpl = (fn, options) => (set, get, api) => {
55
63
  const defaultedOptions = {
56
- key: "state",
57
- format: {
58
- stringify,
59
- parse
60
- },
64
+ key: false,
65
+ prefix: "",
66
+ format: marked,
61
67
  syncNull: false,
62
68
  syncUndefined: false,
63
69
  ...options
64
70
  };
65
- const standalone = !defaultedOptions.key;
66
- const getStateFromUrl = (url) => {
71
+ if (defaultedOptions.prefix === void 0) {
72
+ defaultedOptions.prefix = "";
73
+ }
74
+ const format = defaultedOptions.format;
75
+ const standalone = defaultedOptions.key === false;
76
+ const getStateFromUrl = (url, initialState) => {
77
+ const params = parseSearchString(url.search);
67
78
  if (standalone) {
68
- const params2 = new URLSearchParams(url.search);
69
- const state = {};
70
- params2.forEach((value, key) => {
71
- state[key] = defaultedOptions.format.parse(value, true);
72
- });
73
- return Object.keys(state).length > 0 ? state : null;
74
- }
75
- const params = url.search.slice(1).split("&");
76
- for (const param of params) {
77
- const eqIndex = param.indexOf("=");
78
- if (eqIndex === -1) continue;
79
- const key = param.slice(0, eqIndex);
80
- if (key === defaultedOptions.key) {
81
- const value = param.slice(eqIndex + 1);
82
- return value ? defaultedOptions.format.parse(value, false) : null;
79
+ let filteredParams = params;
80
+ if (defaultedOptions.prefix) {
81
+ filteredParams = {};
82
+ for (const [key, values] of Object.entries(params)) {
83
+ if (key.startsWith(defaultedOptions.prefix)) {
84
+ filteredParams[key.slice(defaultedOptions.prefix.length)] = values;
85
+ }
86
+ }
83
87
  }
88
+ const result = format.parseStandalone(filteredParams, { initialState });
89
+ return Object.keys(result).length > 0 ? result : null;
90
+ } else {
91
+ const fullKey = defaultedOptions.prefix + defaultedOptions.key;
92
+ const values = params[fullKey];
93
+ if (values && values.length > 0) {
94
+ try {
95
+ return format.parse(values[0], { initialState });
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ return null;
84
101
  }
85
- return null;
86
102
  };
87
103
  const getSelectedState = (state, pathname) => {
88
104
  if (defaultedOptions.select) {
@@ -94,18 +110,25 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
94
110
  };
95
111
  const initialize = (url, initialState) => {
96
112
  try {
97
- const stateFromURl = getStateFromUrl(url);
98
- if (!stateFromURl) {
113
+ const stateFromUrl = getStateFromUrl(url, initialState);
114
+ if (!stateFromUrl) {
99
115
  return initialState;
100
116
  }
117
+ const selected = getSelectedState(stateFromUrl, url.pathname);
101
118
  const merged = mergeWith(
102
119
  {},
103
120
  initialState,
104
- getSelectedState(stateFromURl, url.pathname)
121
+ selected,
122
+ (_objValue, srcValue) => {
123
+ if (Array.isArray(srcValue)) {
124
+ return srcValue;
125
+ }
126
+ return void 0;
127
+ }
105
128
  );
106
129
  return merged;
107
130
  } catch (error) {
108
- console.error(error);
131
+ console.error("[initialize] error:", error);
109
132
  return initialState;
110
133
  }
111
134
  };
@@ -128,31 +151,53 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
128
151
  defaultedOptions.syncUndefined
129
152
  );
130
153
  const previous = url.search;
154
+ let stateParams;
155
+ let managedKeys;
156
+ if (standalone) {
157
+ const allParams = format.stringifyStandalone(selectedState);
158
+ managedKeys = new Set(Object.keys(allParams).map((k) => defaultedOptions.prefix + k));
159
+ const compactedParams = format.stringifyStandalone(newCompacted);
160
+ stateParams = {};
161
+ for (const [key, values] of Object.entries(compactedParams)) {
162
+ stateParams[defaultedOptions.prefix + key] = values;
163
+ }
164
+ } else {
165
+ const fullKey = defaultedOptions.prefix + defaultedOptions.key;
166
+ if (Object.keys(newCompacted).length > 0) {
167
+ stateParams = { [fullKey]: [format.stringify(newCompacted)] };
168
+ } else {
169
+ stateParams = {};
170
+ }
171
+ managedKeys = /* @__PURE__ */ new Set([fullKey]);
172
+ }
173
+ const valuesToWrite = /* @__PURE__ */ new Map();
174
+ const keyOrder = [];
175
+ for (const [key, values] of Object.entries(stateParams)) {
176
+ valuesToWrite.set(key, [...values]);
177
+ keyOrder.push(key);
178
+ }
131
179
  const params = url.search.slice(1).split("&").filter(Boolean);
132
- const managedKeys = standalone ? new Set(Object.keys(selectedState).map(encodeURI)) : /* @__PURE__ */ new Set([defaultedOptions.key]);
133
- const valuesToWrite = standalone ? new Map(
134
- Object.entries(newCompacted).map(([k, v]) => [encodeURI(k), v])
135
- ) : Object.keys(newCompacted).length ? /* @__PURE__ */ new Map([[defaultedOptions.key, newCompacted]]) : /* @__PURE__ */ new Map();
136
180
  const result = [];
137
- params.forEach((p) => {
138
- const key = p.split("=")[0];
181
+ params.forEach((param) => {
182
+ const eqIndex = param.indexOf("=");
183
+ if (eqIndex === -1) return;
184
+ const key = param.slice(0, eqIndex);
139
185
  if (!managedKeys.has(key)) {
140
- result.push(p);
186
+ result.push(param);
141
187
  return;
142
188
  }
143
- if (valuesToWrite.has(key)) {
144
- const encoded = defaultedOptions.format.stringify(
145
- valuesToWrite.get(key),
146
- standalone
147
- );
148
- result.push(`${key}=${encoded}`);
149
- valuesToWrite.delete(key);
189
+ const values = valuesToWrite.get(key);
190
+ if (values && values.length > 0) {
191
+ const value = values.shift();
192
+ result.push(`${key}=${value}`);
150
193
  }
151
194
  });
152
- valuesToWrite.forEach((value, key) => {
153
- const encoded = defaultedOptions.format.stringify(value, standalone);
154
- result.push(`${key}=${encoded}`);
155
- });
195
+ for (const key of keyOrder) {
196
+ const values = valuesToWrite.get(key);
197
+ if (values) {
198
+ values.forEach((v) => result.push(`${key}=${v}`));
199
+ }
200
+ }
156
201
  url.search = result.length ? "?" + result.join("&") : "";
157
202
  if (url.search !== previous) {
158
203
  history.replaceState(history.state, "", url);
@@ -189,21 +234,6 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
189
234
  return fn(set, get, api);
190
235
  };
191
236
  var querystring = queryStringImpl;
192
-
193
- // src/utils.ts
194
- var createURL = ({
195
- baseUrl,
196
- key,
197
- state
198
- }) => {
199
- const url = new URL(baseUrl);
200
- const stringified = stringify(state);
201
- url.searchParams.set(key, stringified);
202
- return url.href;
203
- };
204
237
  export {
205
- createURL,
206
- parse,
207
- querystring,
208
- stringify
238
+ querystring
209
239
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zustand-querystring",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -25,17 +25,33 @@
25
25
  "import": "./dist/index.mjs",
26
26
  "require": "./dist/index.js"
27
27
  },
28
- "./format/readable": {
29
- "types": "./dist/format/readable.d.ts",
30
- "import": "./dist/format/readable.mjs",
31
- "require": "./dist/format/readable.js"
28
+ "./format/marked": {
29
+ "types": "./dist/format/marked.d.ts",
30
+ "import": "./dist/format/marked.mjs",
31
+ "require": "./dist/format/marked.js"
32
+ },
33
+ "./format/plain": {
34
+ "types": "./dist/format/plain.d.ts",
35
+ "import": "./dist/format/plain.mjs",
36
+ "require": "./dist/format/plain.js"
37
+ },
38
+ "./format/json": {
39
+ "types": "./dist/format/json.d.ts",
40
+ "import": "./dist/format/json.mjs",
41
+ "require": "./dist/format/json.js"
32
42
  },
33
43
  "./package.json": "./package.json"
34
44
  },
35
45
  "typesVersions": {
36
46
  "*": {
37
- "format/readable": [
38
- "./dist/format/readable.d.ts"
47
+ "format/marked": [
48
+ "./dist/format/marked.d.ts"
49
+ ],
50
+ "format/plain": [
51
+ "./dist/format/plain.d.ts"
52
+ ],
53
+ "format/json": [
54
+ "./dist/format/json.d.ts"
39
55
  ]
40
56
  }
41
57
  },
@@ -48,7 +64,9 @@
48
64
  ],
49
65
  "entry": {
50
66
  "index": "src/index.ts",
51
- "format/readable": "src/format/readable.ts"
67
+ "format/marked": "src/format/marked.ts",
68
+ "format/plain": "src/format/plain.ts",
69
+ "format/json": "src/format/json.ts"
52
70
  },
53
71
  "dts": true
54
72
  },
@@ -64,10 +82,18 @@
64
82
  },
65
83
  "devDependencies": {
66
84
  "@types/lodash-es": "^4.17.12",
85
+ "@types/react": "^19.0.10",
86
+ "@types/react-dom": "^19.0.4",
87
+ "@vitejs/plugin-react": "^4.3.4",
88
+ "@vitest/browser-playwright": "^4.0.14",
89
+ "@vitest/coverage-v8": "^4.0.14",
90
+ "react": "^19.0.0",
91
+ "react-dom": "^19.0.0",
67
92
  "rimraf": "^6.0.1",
68
93
  "tsup": "^8.4.0",
69
94
  "typescript": "^5.8.2",
70
95
  "vitest": "^4.0.12",
96
+ "vitest-browser-react": "^2.0.2",
71
97
  "zustand": "^5.0.3"
72
98
  }
73
99
  }
@@ -1,14 +0,0 @@
1
- /**
2
- * URL-Safe Serialization
3
- *
4
- * Inspired by URLON (https://github.com/cerebral/urlon)
5
- * Copyright (c) 2021 Cerebral - MIT License
6
- */
7
- declare function stringify(value: unknown, standalone?: boolean): string;
8
- declare function parse<T = unknown>(input: string, standalone?: boolean): T;
9
- declare const readable: {
10
- stringify: typeof stringify;
11
- parse: typeof parse;
12
- };
13
-
14
- export { parse, readable, stringify };
@@ -1,14 +0,0 @@
1
- /**
2
- * URL-Safe Serialization
3
- *
4
- * Inspired by URLON (https://github.com/cerebral/urlon)
5
- * Copyright (c) 2021 Cerebral - MIT License
6
- */
7
- declare function stringify(value: unknown, standalone?: boolean): string;
8
- declare function parse<T = unknown>(input: string, standalone?: boolean): T;
9
- declare const readable: {
10
- stringify: typeof stringify;
11
- parse: typeof parse;
12
- };
13
-
14
- export { parse, readable, stringify };
@@ -1,238 +0,0 @@
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/format/readable.ts
20
- var readable_exports = {};
21
- __export(readable_exports, {
22
- parse: () => parse,
23
- readable: () => readable,
24
- stringify: () => stringify
25
- });
26
- module.exports = __toCommonJS(readable_exports);
27
- var TYPE_OBJECT = ".";
28
- var TYPE_ARRAY = "@";
29
- var TYPE_STRING = "=";
30
- var TYPE_PRIMITIVE = ":";
31
- var SEPARATOR = ",";
32
- var TERMINATOR = "~";
33
- var ESCAPE = "/";
34
- var DATE_PREFIX = "D";
35
- var esc = (c) => /[.$^*+?()[\]{}|\\]/.test(c) ? `\\${c}` : c;
36
- var KEY_STOP = new RegExp(
37
- `[${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${esc(TYPE_OBJECT)}${esc(SEPARATOR)}]`
38
- );
39
- var VALUE_STOP = new RegExp(`[${esc(SEPARATOR)}${TERMINATOR}]`);
40
- var KEY_ESCAPE = new RegExp(
41
- `([${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${esc(TYPE_OBJECT)}${ESCAPE}${esc(SEPARATOR)}])`,
42
- "g"
43
- );
44
- var VALUE_ESCAPE = new RegExp(
45
- `([${esc(SEPARATOR)}${TERMINATOR}${ESCAPE}])`,
46
- "g"
47
- );
48
- function escapeStr(str, pattern) {
49
- return encodeURI(str.replace(pattern, `${ESCAPE}$1`));
50
- }
51
- function cleanResult(str, standalone) {
52
- while (str.endsWith(TERMINATOR)) str = str.slice(0, -1);
53
- const datePattern = new RegExp(`^${esc(TYPE_STRING)}${DATE_PREFIX}-?\\d+$`);
54
- if (standalone && datePattern.test(str)) {
55
- return str.slice(1);
56
- }
57
- if (!standalone && str.startsWith(TYPE_OBJECT)) {
58
- return str.slice(1);
59
- }
60
- return str;
61
- }
62
- function stringify(value, standalone = false) {
63
- return cleanResult(serialize(value, standalone), standalone);
64
- }
65
- function serialize(value, standalone, inArray = false) {
66
- if (value === null) return `${TYPE_PRIMITIVE}null`;
67
- if (value === void 0) return `${TYPE_PRIMITIVE}undefined`;
68
- if (typeof value === "function") return "";
69
- if (typeof value === "number") {
70
- return `${TYPE_PRIMITIVE}${String(value).replace(/\./g, `${ESCAPE}.`)}`;
71
- }
72
- if (typeof value === "boolean") {
73
- return `${TYPE_PRIMITIVE}${value}`;
74
- }
75
- if (value instanceof Date) {
76
- return `${TYPE_STRING}${DATE_PREFIX}${value.getTime()}`;
77
- }
78
- if (Array.isArray(value)) {
79
- const items = value.map((v) => serialize(v, standalone, true));
80
- return `${TYPE_ARRAY}${items.join(SEPARATOR)}${TERMINATOR}`;
81
- }
82
- if (typeof value === "object") {
83
- const entries = [];
84
- for (const [k, v] of Object.entries(value)) {
85
- const val = serialize(v, false, false);
86
- if (val || v === void 0) {
87
- entries.push(`${escapeStr(k, KEY_ESCAPE)}${val}`);
88
- }
89
- }
90
- return `${TYPE_OBJECT}${entries.join(SEPARATOR)}${TERMINATOR}`;
91
- }
92
- const strVal = String(value);
93
- let escaped = escapeStr(strVal, VALUE_ESCAPE);
94
- const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d`);
95
- if (datePattern.test(strVal)) {
96
- escaped = ESCAPE + escaped;
97
- }
98
- return standalone || inArray ? escaped : `${TYPE_STRING}${escaped}`;
99
- }
100
- function parse(input, standalone = false) {
101
- const str = decodeURI(input);
102
- if (str.length === 0) {
103
- return standalone ? "" : {};
104
- }
105
- const first = str[0];
106
- const hasMarker = first === TYPE_STRING || first === TYPE_PRIMITIVE || first === TYPE_ARRAY || first === TYPE_OBJECT;
107
- let source;
108
- if (hasMarker) {
109
- source = str;
110
- } else if (standalone) {
111
- const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d+$`);
112
- if (datePattern.test(str)) {
113
- const timestamp = parseInt(str.slice(DATE_PREFIX.length), 10);
114
- if (!isNaN(timestamp)) {
115
- return new Date(timestamp);
116
- }
117
- }
118
- return str;
119
- } else {
120
- const markers = `${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${TYPE_OBJECT}`;
121
- const objectPattern = new RegExp(
122
- `[^${ESCAPE}${markers}${SEPARATOR}${TERMINATOR}][${markers}]`
123
- );
124
- source = objectPattern.test(str) ? `${TYPE_OBJECT}${str}` : `${TYPE_STRING}${str}`;
125
- }
126
- let pos = 0;
127
- function peek() {
128
- return source[pos] || "";
129
- }
130
- function advance() {
131
- pos++;
132
- }
133
- function readUntil(stopPattern, checkEscape = false) {
134
- let result = "";
135
- let wasEscaped = false;
136
- while (pos < source.length) {
137
- const ch = peek();
138
- if (ch === ESCAPE) {
139
- if (checkEscape && pos + 1 < source.length && source[pos + 1] === DATE_PREFIX) {
140
- wasEscaped = true;
141
- }
142
- advance();
143
- if (pos < source.length) {
144
- result += peek();
145
- advance();
146
- }
147
- continue;
148
- }
149
- if (stopPattern.test(ch)) break;
150
- result += ch;
151
- advance();
152
- }
153
- return { value: result, wasEscaped };
154
- }
155
- function parseString() {
156
- const { value, wasEscaped } = readUntil(VALUE_STOP, true);
157
- const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d+$`);
158
- if (!wasEscaped && datePattern.test(value)) {
159
- const timestamp = parseInt(value.slice(DATE_PREFIX.length), 10);
160
- if (!isNaN(timestamp)) {
161
- return new Date(timestamp);
162
- }
163
- }
164
- return value;
165
- }
166
- function parsePrimitive() {
167
- const { value } = readUntil(VALUE_STOP, false);
168
- if (value === "null") return null;
169
- if (value === "undefined") return void 0;
170
- if (value === "true") return true;
171
- if (value === "false") return false;
172
- return parseFloat(value);
173
- }
174
- function parseArray() {
175
- const result = [];
176
- let lastWasSeparator = false;
177
- while (pos < source.length && peek() !== TERMINATOR) {
178
- if (peek() === SEPARATOR) {
179
- result.push("");
180
- advance();
181
- lastWasSeparator = true;
182
- continue;
183
- }
184
- lastWasSeparator = false;
185
- const ch = peek();
186
- if (ch === TYPE_PRIMITIVE || ch === TYPE_ARRAY || ch === TYPE_OBJECT || ch === TYPE_STRING) {
187
- result.push(parseValue());
188
- } else {
189
- result.push(parseString());
190
- }
191
- if (pos < source.length && peek() === SEPARATOR) {
192
- advance();
193
- lastWasSeparator = true;
194
- }
195
- }
196
- if (lastWasSeparator && (peek() === TERMINATOR || pos >= source.length)) {
197
- result.push("");
198
- }
199
- if (peek() === TERMINATOR) advance();
200
- return result;
201
- }
202
- function parseObject() {
203
- const result = {};
204
- while (pos < source.length && peek() !== TERMINATOR) {
205
- const { value: key } = readUntil(KEY_STOP, false);
206
- result[key] = parseValue();
207
- if (pos < source.length && peek() !== TERMINATOR && peek() === SEPARATOR) {
208
- advance();
209
- }
210
- }
211
- if (peek() === TERMINATOR) advance();
212
- return result;
213
- }
214
- function parseValue() {
215
- const type = peek();
216
- advance();
217
- switch (type) {
218
- case TYPE_PRIMITIVE:
219
- return parsePrimitive();
220
- case TYPE_ARRAY:
221
- return parseArray();
222
- case TYPE_OBJECT:
223
- return parseObject();
224
- case TYPE_STRING:
225
- return parseString();
226
- default:
227
- throw new Error(`Unexpected type "${type}" at position ${pos}`);
228
- }
229
- }
230
- return parseValue();
231
- }
232
- var readable = { stringify, parse };
233
- // Annotate the CommonJS export names for ESM import in node:
234
- 0 && (module.exports = {
235
- parse,
236
- readable,
237
- stringify
238
- });