query-string 7.1.3 → 8.0.1

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.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
+ }