diff --git a/index.d.ts b/index.d.ts index 32712bbf..ff8371da 100644 --- a/index.d.ts +++ b/index.d.ts @@ -404,3 +404,81 @@ export function stringifyUrl( object: UrlObject, options?: StringifyOptions ): string; + +/** +Pick query parameters from a URL. + +@param url - The URL containing the query parameters to pick. +@param keys - The names of the query parameters to keep. All other query parameters will be removed from the URL. +@param filter - A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`. + +@returns The URL with the picked query parameters. + +@example +``` +queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']); +//=> 'https://foo.bar?foo=1#hello' + +queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true}); +//=> 'https://foo.bar?bar=2#hello' +``` +*/ +export function pick( + url: string, + keys: readonly string[], + options?: ParseOptions & StringifyOptions +): string +export function pick( + url: string, + filter: (key: string, value: string | boolean | number) => boolean, + options?: {parseBooleans: true, parseNumbers: true} & ParseOptions & StringifyOptions +): string +export function pick( + url: string, + filter: (key: string, value: string | boolean) => boolean, + options?: {parseBooleans: true} & ParseOptions & StringifyOptions +): string +export function pick( + url: string, + filter: (key: string, value: string | number) => boolean, + options?: {parseNumbers: true} & ParseOptions & StringifyOptions +): string + +/** +Exclude query parameters from a URL. Like `.pick()` but reversed. + +@param url - The URL containing the query parameters to exclude. +@param keys - The names of the query parameters to remove. All other query parameters will remain in the URL. +@param filter - A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`. + +@returns The URL without the excluded the query parameters. + +@example +``` +queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']); +//=> 'https://foo.bar?bar=2#hello' + +queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true}); +//=> 'https://foo.bar?foo=1#hello' +``` +*/ +export function exclude( + url: string, + keys: readonly string[], + options?: ParseOptions & StringifyOptions +): string +export function exclude( + url: string, + filter: (key: string, value: string | boolean | number) => boolean, + options?: {parseBooleans: true, parseNumbers: true} & ParseOptions & StringifyOptions +): string +export function exclude( + url: string, + filter: (key: string, value: string | boolean) => boolean, + options?: {parseBooleans: true} & ParseOptions & StringifyOptions +): string +export function exclude( + url: string, + filter: (key: string, value: string | number) => boolean, + options?: {parseNumbers: true} & ParseOptions & StringifyOptions +): string diff --git a/index.js b/index.js index de1d7b18..555c8897 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const strictUriEncode = require('strict-uri-encode'); const decodeComponent = require('decode-uri-component'); const splitOnFirst = require('split-on-first'); +const filterObject = require('filter-obj'); const isNullOrUndefined = value => value === null || value === undefined; @@ -376,3 +377,22 @@ exports.stringifyUrl = (input, options) => { return `${url}${queryString}${hash}`; }; + +exports.pick = (input, filter, options) => { + options = Object.assign({ + parseFragmentIdentifier: true + }, options); + + const {url, query, fragmentIdentifier} = exports.parseUrl(input, options); + return exports.stringifyUrl({ + url, + query: filterObject(query, filter), + fragmentIdentifier + }, options); +}; + +exports.exclude = (input, filter, options) => { + const exclusionFilter = Array.isArray(filter) ? key => !filter.includes(key) : (key, value) => !filter(key, value); + + return exports.pick(input, exclusionFilter, options); +}; diff --git a/index.test-d.ts b/index.test-d.ts index 9c463449..d4f775cd 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -113,3 +113,9 @@ expectType( }, }) ); + +// Pick +expectType(queryString.pick('http://foo.bar/?abc=def&hij=klm', ['abc'])) + +// Exclude +expectType(queryString.exclude('http://foo.bar/?abc=def&hij=klm', ['abc'])) diff --git a/package.json b/package.json index 28486f6a..513af092 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,12 @@ "stringify", "encode", "decode", - "searchparams" + "searchparams", + "filter" ], "dependencies": { "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" }, diff --git a/readme.md b/readme.md index 46e58b5b..309b23e6 100644 --- a/readme.md +++ b/readme.md @@ -415,6 +415,64 @@ Type: `object` Query items to add to the URL. +### .pick(url, keys, options?) +### .pick(url, filter, options?) + +Pick query parameters from a URL. + +Returns a string with the new URL. + +```js +const queryString = require('query-string'); + +queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']); +//=> 'https://foo.bar?foo=1#hello' + +queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true}); +//=> 'https://foo.bar?bar=2#hello' +``` + +### .exclude(url, keys, options?) +### .exclude(url, filter, options?) + +Exclude query parameters from a URL. + +Returns a string with the new URL. + +```js +const queryString = require('query-string'); + +queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']); +//=> 'https://foo.bar?bar=2#hello' + +queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true}); +//=> 'https://foo.bar?foo=1#hello' +``` + +#### url + +Type: `string` + +The URL containing the query parameters to filter. + +#### keys + +Type: `string[]` + +The names of the query parameters to filter based on the function used. + +#### filter + +Type: `(key, value) => boolean` + +A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`. + +#### options + +Type: `object` + +[Parse options](#options) and [stringify options](#options-1). + ## Nesting This module intentionally doesn't support nesting as it's not spec'd and varies between implementations, which causes a lot of [edge cases](https://github.com/visionmedia/node-querystring/issues). diff --git a/test/exclude.js b/test/exclude.js new file mode 100644 index 00000000..91e0d4f5 --- /dev/null +++ b/test/exclude.js @@ -0,0 +1,17 @@ +import test from 'ava'; +import queryString from '..'; + +test('excludes elements in a URL with a filter array', t => { + t.is(queryString.exclude('http://example.com/?a=1&b=2&c=3#a', ['c']), 'http://example.com/?a=1&b=2#a'); +}); + +test('excludes elements in a URL with a filter predicate', t => { + t.is(queryString.exclude('http://example.com/?a=1&b=2&c=3#a', (name, value) => { + t.is(typeof name, 'string'); + t.is(typeof value, 'number'); + + return name === 'a'; + }, { + parseNumbers: true + }), 'http://example.com/?b=2&c=3#a'); +}); diff --git a/test/pick.js b/test/pick.js new file mode 100644 index 00000000..e5e43812 --- /dev/null +++ b/test/pick.js @@ -0,0 +1,17 @@ +import test from 'ava'; +import queryString from '..'; + +test('picks elements in a URL with a filter array', t => { + t.is(queryString.pick('http://example.com/?a=1&b=2&c=3#a', ['a', 'b']), 'http://example.com/?a=1&b=2#a'); +}); + +test('picks elements in a URL with a filter predicate', t => { + t.is(queryString.pick('http://example.com/?a=1&b=2&c=3#a', (name, value) => { + t.is(typeof name, 'string'); + t.is(typeof value, 'number'); + + return name === 'a'; + }, { + parseNumbers: true + }), 'http://example.com/?a=1#a'); +});