From 94ebcd416fab024f1725268bb0a1303132158293 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 30 Dec 2020 14:02:35 +0700 Subject: [PATCH 1/9] Work around TypeScript bug Fixes #298 --- index.d.ts | 7 ++++++- index.test-d.ts | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index d828894..026d287 100644 --- a/index.d.ts +++ b/index.d.ts @@ -350,7 +350,12 @@ export type StringifiableRecord = Record< Stringify an object into a query string and sort the keys. */ export function stringify( - object: StringifiableRecord, + // TODO: Use the below instead when the following TS issues are fixed: + // - https://github.com/microsoft/TypeScript/issues/15300 + // - https://github.com/microsoft/TypeScript/issues/42021 + // Context: https://github.com/sindresorhus/query-string/issues/298 + // object: StringifiableRecord, + object: Record, options?: StringifyOptions ): string; diff --git a/index.test-d.ts b/index.test-d.ts index 9c46344..2aab3fe 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -35,6 +35,17 @@ expectType( ) ); +// Ensure it accepts an `interface`. +interface Query { + foo: string; +} + +const query: Query = { + foo: 'bar' +}; + +queryString.stringify(query); + // Parse expectType(queryString.parse('?foo=bar')); From fbe496cc62974bb039cd7618721b87e7fb0dbcf1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 30 Dec 2020 14:03:52 +0700 Subject: [PATCH 2/9] 6.13.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d41fcc..df8a1f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "query-string", - "version": "6.13.7", + "version": "6.13.8", "description": "Parse and stringify URL query strings", "license": "MIT", "repository": "sindresorhus/query-string", From 27453b5e49adaaa5419637732c19ab19b502585f Mon Sep 17 00:00:00 2001 From: Richie Bendall Date: Sat, 2 Jan 2021 17:22:29 +1300 Subject: [PATCH 3/9] Move to GitHub Actions (#300) --- .github/workflows/main.yml | 24 ++++++++++++++++++++++++ .travis.yml | 7 ------- readme.md | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b85fc2a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,24 @@ +name: CI +on: + - push + - pull_request +jobs: + test: + name: Node.js ${{ matrix.node-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: + - 14 + - 12 + - 10 + - 8 + - 6 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 81f3378..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: node_js -node_js: - - '14' - - '12' - - '10' - - '8' - - '6' diff --git a/readme.md b/readme.md index 46e58b5..0914d74 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# query-string [![Build Status](https://travis-ci.com/sindresorhus/query-string.svg?branch=master)](https://travis-ci.com/github/sindresorhus/query-string) [![](https://badgen.net/bundlephobia/minzip/query-string)](https://bundlephobia.com/result?p=query-string) +# query-string > Parse and stringify URL [query strings](https://en.wikipedia.org/wiki/Query_string) From 39aef9164653dd7a35877e67ea731a1bbb7ae736 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 20 Jan 2021 14:44:06 +0700 Subject: [PATCH 4/9] Update a link --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0914d74..85f72ce 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ Special thanks to:

- +

From 667c9e9d296a7f7197bcc8d6abe4a41c8cf4b912 Mon Sep 17 00:00:00 2001 From: Oleksii Teplenko Date: Tue, 9 Feb 2021 20:03:27 +0200 Subject: [PATCH 5/9] Ignore ending ampersand when parsing (#306) --- index.js | 4 ++++ test/parse.js | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/index.js b/index.js index 559ecd4..39e86e3 100644 --- a/index.js +++ b/index.js @@ -244,6 +244,10 @@ function parse(query, options) { } for (const param of query.split('&')) { + if (param === '') { + continue; + } + let [key, value] = splitOnFirst(options.decode ? param.replace(/\+/g, ' ') : param, '='); // Missing `=` should be `null`: diff --git a/test/parse.js b/test/parse.js index 365eb2f..9bfc034 100644 --- a/test/parse.js +++ b/test/parse.js @@ -13,6 +13,11 @@ test('query strings starting with a `&`', t => { t.deepEqual(queryString.parse('&foo=bar&foo=baz'), {foo: ['bar', 'baz']}); }); +test('query strings ending with a `&`', t => { + t.deepEqual(queryString.parse('foo=bar&'), {foo: 'bar'}); + t.deepEqual(queryString.parse('foo=bar&&&'), {foo: 'bar'}); +}); + test('parse a query string', t => { t.deepEqual(queryString.parse('foo=bar'), {foo: 'bar'}); }); From 6ed5cb3d36f3e12eb024293c5d262e2d0efed9ec Mon Sep 17 00:00:00 2001 From: Richie Bendall Date: Wed, 10 Feb 2021 19:13:18 +1300 Subject: [PATCH 6/9] Add `.pick()` and `.exclude()` (#282) Co-authored-by: Sindre Sorhus --- index.d.ts | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 20 +++++++++++++ index.test-d.ts | 6 ++++ package.json | 4 ++- readme.md | 58 ++++++++++++++++++++++++++++++++++++ test/exclude.js | 17 +++++++++++ test/pick.js | 17 +++++++++++ 7 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 test/exclude.js create mode 100644 test/pick.js diff --git a/index.d.ts b/index.d.ts index 026d287..b6d651b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -409,3 +409,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 39e86e3..423b9d6 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; @@ -382,3 +383,22 @@ exports.stringifyUrl = (object, 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 2aab3fe..2032584 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -124,3 +124,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 df8a1f2..6f5372b 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 85f72ce..280972e 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 0000000..91e0d4f --- /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 0000000..e5e4381 --- /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'); +}); From 2a178815cf9b31ea4eef31efd48d9017a29d9519 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 10 Feb 2021 13:15:35 +0700 Subject: [PATCH 7/9] 6.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f5372b..3b90b26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "query-string", - "version": "6.13.8", + "version": "6.14.0", "description": "Parse and stringify URL query strings", "license": "MIT", "repository": "sindresorhus/query-string", From a6d4a3f480b2810a8cce3c0118a2aacc6c6c7add Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 26 Feb 2021 18:15:35 +0700 Subject: [PATCH 8/9] Fix TypeScript type for `stringifyUrl()` Fixes #308 --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index b6d651b..4a115fb 100644 --- a/index.d.ts +++ b/index.d.ts @@ -372,7 +372,7 @@ export interface UrlObject { /** Overrides queries in the `url` property. */ - readonly query: StringifiableRecord; + readonly query?: StringifiableRecord; /** Overrides the fragment identifier in the `url` property. From 0090a3418253eea4b2c437ba034dd445361325b2 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 26 Feb 2021 18:16:40 +0700 Subject: [PATCH 9/9] 6.14.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b90b26..c75f01a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "query-string", - "version": "6.14.0", + "version": "6.14.1", "description": "Parse and stringify URL query strings", "license": "MIT", "repository": "sindresorhus/query-string",