query-string 7.1.3 → 8.0.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/base.d.ts +561 -0
- package/base.js +517 -0
- package/index.d.ts +11 -563
- package/index.js +1 -482
- package/license +1 -1
- package/package.json +22 -11
- package/readme.md +33 -48
package/base.js
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import decodeComponent from 'decode-uri-component';
|
|
2
|
+
import splitOnFirst from 'split-on-first';
|
|
3
|
+
import {includeKeys} from 'filter-obj';
|
|
4
|
+
|
|
5
|
+
const isNullOrUndefined = value => value === null || value === undefined;
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
8
|
+
const strictUriEncode = string => encodeURIComponent(string).replace(/[!'()*]/g, x => `%${x.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
9
|
+
|
|
10
|
+
const encodeFragmentIdentifier = Symbol('encodeFragmentIdentifier');
|
|
11
|
+
|
|
12
|
+
function encoderForArrayFormat(options) {
|
|
13
|
+
switch (options.arrayFormat) {
|
|
14
|
+
case 'index': {
|
|
15
|
+
return key => (result, value) => {
|
|
16
|
+
const index = result.length;
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
value === undefined
|
|
20
|
+
|| (options.skipNull && value === null)
|
|
21
|
+
|| (options.skipEmptyString && value === '')
|
|
22
|
+
) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (value === null) {
|
|
27
|
+
return [
|
|
28
|
+
...result, [encode(key, options), '[', index, ']'].join(''),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [
|
|
33
|
+
...result,
|
|
34
|
+
[encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join(''),
|
|
35
|
+
];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case 'bracket': {
|
|
40
|
+
return key => (result, value) => {
|
|
41
|
+
if (
|
|
42
|
+
value === undefined
|
|
43
|
+
|| (options.skipNull && value === null)
|
|
44
|
+
|| (options.skipEmptyString && value === '')
|
|
45
|
+
) {
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (value === null) {
|
|
50
|
+
return [
|
|
51
|
+
...result,
|
|
52
|
+
[encode(key, options), '[]'].join(''),
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
...result,
|
|
58
|
+
[encode(key, options), '[]=', encode(value, options)].join(''),
|
|
59
|
+
];
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'colon-list-separator': {
|
|
64
|
+
return key => (result, value) => {
|
|
65
|
+
if (
|
|
66
|
+
value === undefined
|
|
67
|
+
|| (options.skipNull && value === null)
|
|
68
|
+
|| (options.skipEmptyString && value === '')
|
|
69
|
+
) {
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (value === null) {
|
|
74
|
+
return [
|
|
75
|
+
...result,
|
|
76
|
+
[encode(key, options), ':list='].join(''),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
...result,
|
|
82
|
+
[encode(key, options), ':list=', encode(value, options)].join(''),
|
|
83
|
+
];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'comma':
|
|
88
|
+
case 'separator':
|
|
89
|
+
case 'bracket-separator': {
|
|
90
|
+
const keyValueSep = options.arrayFormat === 'bracket-separator'
|
|
91
|
+
? '[]='
|
|
92
|
+
: '=';
|
|
93
|
+
|
|
94
|
+
return key => (result, value) => {
|
|
95
|
+
if (
|
|
96
|
+
value === undefined
|
|
97
|
+
|| (options.skipNull && value === null)
|
|
98
|
+
|| (options.skipEmptyString && value === '')
|
|
99
|
+
) {
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Translate null to an empty string so that it doesn't serialize as 'null'
|
|
104
|
+
value = value === null ? '' : value;
|
|
105
|
+
|
|
106
|
+
if (result.length === 0) {
|
|
107
|
+
return [[encode(key, options), keyValueSep, encode(value, options)].join('')];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
default: {
|
|
115
|
+
return key => (result, value) => {
|
|
116
|
+
if (
|
|
117
|
+
value === undefined
|
|
118
|
+
|| (options.skipNull && value === null)
|
|
119
|
+
|| (options.skipEmptyString && value === '')
|
|
120
|
+
) {
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (value === null) {
|
|
125
|
+
return [
|
|
126
|
+
...result,
|
|
127
|
+
encode(key, options),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return [
|
|
132
|
+
...result,
|
|
133
|
+
[encode(key, options), '=', encode(value, options)].join(''),
|
|
134
|
+
];
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parserForArrayFormat(options) {
|
|
141
|
+
let result;
|
|
142
|
+
|
|
143
|
+
switch (options.arrayFormat) {
|
|
144
|
+
case 'index': {
|
|
145
|
+
return (key, value, accumulator) => {
|
|
146
|
+
result = /\[(\d*)]$/.exec(key);
|
|
147
|
+
|
|
148
|
+
key = key.replace(/\[\d*]$/, '');
|
|
149
|
+
|
|
150
|
+
if (!result) {
|
|
151
|
+
accumulator[key] = value;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (accumulator[key] === undefined) {
|
|
156
|
+
accumulator[key] = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
accumulator[key][result[1]] = value;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'bracket': {
|
|
164
|
+
return (key, value, accumulator) => {
|
|
165
|
+
result = /(\[])$/.exec(key);
|
|
166
|
+
key = key.replace(/\[]$/, '');
|
|
167
|
+
|
|
168
|
+
if (!result) {
|
|
169
|
+
accumulator[key] = value;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (accumulator[key] === undefined) {
|
|
174
|
+
accumulator[key] = [value];
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
accumulator[key] = [...accumulator[key], value];
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'colon-list-separator': {
|
|
183
|
+
return (key, value, accumulator) => {
|
|
184
|
+
result = /(:list)$/.exec(key);
|
|
185
|
+
key = key.replace(/:list$/, '');
|
|
186
|
+
|
|
187
|
+
if (!result) {
|
|
188
|
+
accumulator[key] = value;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (accumulator[key] === undefined) {
|
|
193
|
+
accumulator[key] = [value];
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
accumulator[key] = [...accumulator[key], value];
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case 'comma':
|
|
202
|
+
case 'separator': {
|
|
203
|
+
return (key, value, accumulator) => {
|
|
204
|
+
const isArray = typeof value === 'string' && value.includes(options.arrayFormatSeparator);
|
|
205
|
+
const isEncodedArray = (typeof value === 'string' && !isArray && decode(value, options).includes(options.arrayFormatSeparator));
|
|
206
|
+
value = isEncodedArray ? decode(value, options) : value;
|
|
207
|
+
const newValue = isArray || isEncodedArray ? value.split(options.arrayFormatSeparator).map(item => decode(item, options)) : (value === null ? value : decode(value, options));
|
|
208
|
+
accumulator[key] = newValue;
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case 'bracket-separator': {
|
|
213
|
+
return (key, value, accumulator) => {
|
|
214
|
+
const isArray = /(\[])$/.test(key);
|
|
215
|
+
key = key.replace(/\[]$/, '');
|
|
216
|
+
|
|
217
|
+
if (!isArray) {
|
|
218
|
+
accumulator[key] = value ? decode(value, options) : value;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const arrayValue = value === null
|
|
223
|
+
? []
|
|
224
|
+
: value.split(options.arrayFormatSeparator).map(item => decode(item, options));
|
|
225
|
+
|
|
226
|
+
if (accumulator[key] === undefined) {
|
|
227
|
+
accumulator[key] = arrayValue;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
accumulator[key] = [...accumulator[key], ...arrayValue];
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
default: {
|
|
236
|
+
return (key, value, accumulator) => {
|
|
237
|
+
if (accumulator[key] === undefined) {
|
|
238
|
+
accumulator[key] = value;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
accumulator[key] = [...[accumulator[key]].flat(), value];
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function validateArrayFormatSeparator(value) {
|
|
249
|
+
if (typeof value !== 'string' || value.length !== 1) {
|
|
250
|
+
throw new TypeError('arrayFormatSeparator must be single character string');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function encode(value, options) {
|
|
255
|
+
if (options.encode) {
|
|
256
|
+
return options.strict ? strictUriEncode(value) : encodeURIComponent(value);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function decode(value, options) {
|
|
263
|
+
if (options.decode) {
|
|
264
|
+
return decodeComponent(value);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function keysSorter(input) {
|
|
271
|
+
if (Array.isArray(input)) {
|
|
272
|
+
return input.sort();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (typeof input === 'object') {
|
|
276
|
+
return keysSorter(Object.keys(input))
|
|
277
|
+
.sort((a, b) => Number(a) - Number(b))
|
|
278
|
+
.map(key => input[key]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return input;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function removeHash(input) {
|
|
285
|
+
const hashStart = input.indexOf('#');
|
|
286
|
+
if (hashStart !== -1) {
|
|
287
|
+
input = input.slice(0, hashStart);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return input;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getHash(url) {
|
|
294
|
+
let hash = '';
|
|
295
|
+
const hashStart = url.indexOf('#');
|
|
296
|
+
if (hashStart !== -1) {
|
|
297
|
+
hash = url.slice(hashStart);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return hash;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseValue(value, options) {
|
|
304
|
+
if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
|
|
305
|
+
value = Number(value);
|
|
306
|
+
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
|
|
307
|
+
value = value.toLowerCase() === 'true';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function extract(input) {
|
|
314
|
+
input = removeHash(input);
|
|
315
|
+
const queryStart = input.indexOf('?');
|
|
316
|
+
if (queryStart === -1) {
|
|
317
|
+
return '';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return input.slice(queryStart + 1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function parse(query, options) {
|
|
324
|
+
options = {
|
|
325
|
+
decode: true,
|
|
326
|
+
sort: true,
|
|
327
|
+
arrayFormat: 'none',
|
|
328
|
+
arrayFormatSeparator: ',',
|
|
329
|
+
parseNumbers: false,
|
|
330
|
+
parseBooleans: false,
|
|
331
|
+
...options,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
validateArrayFormatSeparator(options.arrayFormatSeparator);
|
|
335
|
+
|
|
336
|
+
const formatter = parserForArrayFormat(options);
|
|
337
|
+
|
|
338
|
+
// Create an object with no prototype
|
|
339
|
+
const returnValue = Object.create(null);
|
|
340
|
+
|
|
341
|
+
if (typeof query !== 'string') {
|
|
342
|
+
return returnValue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
query = query.trim().replace(/^[?#&]/, '');
|
|
346
|
+
|
|
347
|
+
if (!query) {
|
|
348
|
+
return returnValue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
for (const parameter of query.split('&')) {
|
|
352
|
+
if (parameter === '') {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let [key, value] = splitOnFirst(options.decode ? parameter.replace(/\+/g, ' ') : parameter, '=');
|
|
357
|
+
|
|
358
|
+
// Missing `=` should be `null`:
|
|
359
|
+
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
|
|
360
|
+
value = value === undefined ? null : (['comma', 'separator', 'bracket-separator'].includes(options.arrayFormat) ? value : decode(value, options));
|
|
361
|
+
formatter(decode(key, options), value, returnValue);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
for (const [key, value] of Object.entries(returnValue)) {
|
|
365
|
+
if (typeof value === 'object' && value !== null) {
|
|
366
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
367
|
+
value[key2] = parseValue(value2, options);
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
returnValue[key] = parseValue(value, options);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (options.sort === false) {
|
|
375
|
+
return returnValue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// TODO: Remove the use of `reduce`.
|
|
379
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
380
|
+
return (options.sort === true ? Object.keys(returnValue).sort() : Object.keys(returnValue).sort(options.sort)).reduce((result, key) => {
|
|
381
|
+
const value = returnValue[key];
|
|
382
|
+
if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) {
|
|
383
|
+
// Sort object keys, not values
|
|
384
|
+
result[key] = keysSorter(value);
|
|
385
|
+
} else {
|
|
386
|
+
result[key] = value;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return result;
|
|
390
|
+
}, Object.create(null));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function stringify(object, options) {
|
|
394
|
+
if (!object) {
|
|
395
|
+
return '';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
options = {encode: true,
|
|
399
|
+
strict: true,
|
|
400
|
+
arrayFormat: 'none',
|
|
401
|
+
arrayFormatSeparator: ',', ...options};
|
|
402
|
+
|
|
403
|
+
validateArrayFormatSeparator(options.arrayFormatSeparator);
|
|
404
|
+
|
|
405
|
+
const shouldFilter = key => (
|
|
406
|
+
(options.skipNull && isNullOrUndefined(object[key]))
|
|
407
|
+
|| (options.skipEmptyString && object[key] === '')
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const formatter = encoderForArrayFormat(options);
|
|
411
|
+
|
|
412
|
+
const objectCopy = {};
|
|
413
|
+
|
|
414
|
+
for (const [key, value] of Object.entries(object)) {
|
|
415
|
+
if (!shouldFilter(key)) {
|
|
416
|
+
objectCopy[key] = value;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const keys = Object.keys(objectCopy);
|
|
421
|
+
|
|
422
|
+
if (options.sort !== false) {
|
|
423
|
+
keys.sort(options.sort);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return keys.map(key => {
|
|
427
|
+
const value = object[key];
|
|
428
|
+
|
|
429
|
+
if (value === undefined) {
|
|
430
|
+
return '';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (value === null) {
|
|
434
|
+
return encode(key, options);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (Array.isArray(value)) {
|
|
438
|
+
if (value.length === 0 && options.arrayFormat === 'bracket-separator') {
|
|
439
|
+
return encode(key, options) + '[]';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return value
|
|
443
|
+
.reduce(formatter(key), [])
|
|
444
|
+
.join('&');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return encode(key, options) + '=' + encode(value, options);
|
|
448
|
+
}).filter(x => x.length > 0).join('&');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function parseUrl(url, options) {
|
|
452
|
+
options = {
|
|
453
|
+
decode: true,
|
|
454
|
+
...options,
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const [url_, hash] = splitOnFirst(url, '#');
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
url: url_?.split('?')?.[0] ?? '',
|
|
461
|
+
query: parse(extract(url), options),
|
|
462
|
+
...(options && options.parseFragmentIdentifier && hash ? {fragmentIdentifier: decode(hash, options)} : {}),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function stringifyUrl(object, options) {
|
|
467
|
+
options = {
|
|
468
|
+
encode: true,
|
|
469
|
+
strict: true,
|
|
470
|
+
[encodeFragmentIdentifier]: true,
|
|
471
|
+
...options,
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const url = removeHash(object.url).split('?')[0] || '';
|
|
475
|
+
const queryFromUrl = extract(object.url);
|
|
476
|
+
|
|
477
|
+
const query = {
|
|
478
|
+
...parse(queryFromUrl, {sort: false}),
|
|
479
|
+
...object.query,
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
let queryString = stringify(query, options);
|
|
483
|
+
if (queryString) {
|
|
484
|
+
queryString = `?${queryString}`;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let hash = getHash(object.url);
|
|
488
|
+
if (object.fragmentIdentifier) {
|
|
489
|
+
const urlObjectForFragmentEncode = new URL(url);
|
|
490
|
+
urlObjectForFragmentEncode.hash = object.fragmentIdentifier;
|
|
491
|
+
hash = options[encodeFragmentIdentifier] ? urlObjectForFragmentEncode.hash : `#${object.fragmentIdentifier}`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return `${url}${queryString}${hash}`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function pick(input, filter, options) {
|
|
498
|
+
options = {
|
|
499
|
+
parseFragmentIdentifier: true,
|
|
500
|
+
[encodeFragmentIdentifier]: false,
|
|
501
|
+
...options,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const {url, query, fragmentIdentifier} = parseUrl(input, options);
|
|
505
|
+
|
|
506
|
+
return stringifyUrl({
|
|
507
|
+
url,
|
|
508
|
+
query: includeKeys(query, filter),
|
|
509
|
+
fragmentIdentifier,
|
|
510
|
+
}, options);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function exclude(input, filter, options) {
|
|
514
|
+
const exclusionFilter = Array.isArray(filter) ? key => !filter.includes(key) : (key, value) => !filter(key, value);
|
|
515
|
+
|
|
516
|
+
return pick(input, exclusionFilter, options);
|
|
517
|
+
}
|