diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..8d34e213 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,135 @@ +export interface ParseOptions { + /** + * Decode the keys and values. URI components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component). + * + * @default true + */ + readonly decode?: boolean; + + /** + * Supports both `index` for an indexed array representation or `bracket` for a *bracketed* array representation. + * + * @default 'none' + * + * - `bracket`: stands for parsing correctly arrays with bracket representation on the query string, such as: + * + * + * queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'}); + * //=> foo: [1,2,3] + * + * - `index`: stands for parsing taking the index into account, such as: + * + * + * queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'}); + * //=> foo: [1,2,3] + * + * - `none`: is the **default** option and removes any bracket representation, such as: + * + * + * queryString.parse('foo=1&foo=2&foo=3'); + * //=> foo: [1,2,3] + */ + readonly arrayFormat?: 'bracket' | 'index' | 'none'; +} + +export interface ParsedQuery { + readonly [key: string]: string | string[] | undefined; +} + +/** + * Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly. + * + * The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`. + * + * @param query - The query string to parse. + */ +export function parse(query: string, options?: ParseOptions): ParsedQuery; + +export interface ParsedUrl { + readonly url: string; + readonly query: ParsedQuery; +} + +/** + * Extract the URL and the query string as an object. + * + * @param url - The URL to parse. + * + * @example + * + * queryString.parseUrl('https://foo.bar?foo=bar'); + * //=> {url: 'https://foo.bar', query: {foo: 'bar'}} + */ +export function parseUrl(url: string, options?: ParseOptions): ParsedUrl; + +export interface StringifyOptions { + /** + * Strictly encode URI components with [`strict-uri-encode`](https://github.com/kevva/strict-uri-encode). It uses [`encodeURIComponent`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) if set to `false`. You probably [don't care](https://github.com/sindresorhus/query-string/issues/42) about this option. + * + * @default true + */ + readonly strict?: boolean; + + /** + * [URL encode](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) the keys and values. + * + * @default true + */ + readonly encode?: boolean; + + /** + * Supports both `index` for an indexed array representation or `bracket` for a *bracketed* array representation. + * + * @default 'none' + * + * - `bracket`: stands for parsing correctly arrays with bracket representation on the query string, such as: + * + * + * queryString.stringify({foo: [1,2,3]}, {arrayFormat: 'bracket'}); + * // => foo[]=1&foo[]=2&foo[]=3 + * + * - `index`: stands for parsing taking the index into account, such as: + * + * + * queryString.stringify({foo: [1,2,3]}, {arrayFormat: 'index'}); + * // => foo[0]=1&foo[1]=2&foo[3]=3 + * + * - `none`: is the **default** option and removes any bracket representation, such as: + * + * + * queryString.stringify({foo: [1,2,3]}); + * // => foo=1&foo=2&foo=3 + */ + readonly arrayFormat?: 'bracket' | 'index' | 'none'; + + /** + * Supports both `Function` as a custom sorting function or `false` to disable sorting. + * + * If omitted, keys are sorted using `Array#sort`, which means, converting them to strings and comparing strings in Unicode code point order. + * + * @example + * + * const order = ['c', 'a', 'b']; + * queryString.stringify({ a: 1, b: 2, c: 3}, { + * sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight) + * }); + * // => 'c=3&a=1&b=2' + * + * queryString.stringify({ b: 1, c: 2, a: 3}, {sort: false}); + * // => 'b=1&c=2&a=3' + */ + readonly sort?: ((itemLeft: string, itemRight: string) => number) | false; +} + +/** + * Stringify an object into a query string, sorting the keys. + */ +export function stringify( + object: {[key: string]: unknown}, + options?: StringifyOptions +): string; + +/** + * Extract a query string from a URL that can be passed into `.parse()`. + */ +export function extract(url: string): string; diff --git a/index.js b/index.js index 6a1b3c2a..58744d36 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,7 @@ function encoderForArrayFormat(options) { encode(value, options) ].join(''); }; + case 'bracket': return (key, value) => { return value === null ? [encode(key, options), '[]'].join('') : [ @@ -27,6 +28,7 @@ function encoderForArrayFormat(options) { encode(value, options) ].join(''); }; + default: return (key, value) => { return value === null ? encode(key, options) : [ @@ -59,6 +61,7 @@ function parserForArrayFormat(options) { accumulator[key][result[1]] = value; }; + case 'bracket': return (key, value, accumulator) => { result = /(\[\])$/.exec(key); @@ -76,6 +79,7 @@ function parserForArrayFormat(options) { accumulator[key] = [].concat(accumulator[key], value); }; + default: return (key, value, accumulator) => { if (accumulator[key] === undefined) { diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 00000000..243c241d --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,68 @@ +import {expectType} from 'tsd-check'; +import * as queryString from '.'; + +// Stringify +expectType( + queryString.stringify({ + str: 'bar', + strArray: ['baz'], + num: 123, + numArray: [456], + bool: true, + boolArray: [false] + }) +); + +expectType(queryString.stringify({foo: 'bar'}, {strict: false})); +expectType(queryString.stringify({foo: 'bar'}, {encode: false})); +expectType( + queryString.stringify({foo: 'bar'}, {arrayFormat: 'bracket'}) +); +expectType(queryString.stringify({foo: 'bar'}, {arrayFormat: 'index'})); +expectType(queryString.stringify({foo: 'bar'}, {arrayFormat: 'none'})); +expectType(queryString.stringify({foo: 'bar'}, {sort: false})); +const order = ['c', 'a', 'b']; +expectType( + queryString.stringify( + {foo: 'bar'}, + { + sort: (itemLeft, itemRight) => + order.indexOf(itemLeft) - order.indexOf(itemRight) + } + ) +); + +// Parse +expectType(queryString.parse('?foo=bar')); + +expectType( + queryString.parse('?foo=bar', {decode: false}) +); +expectType( + queryString.parse('?foo=bar', {arrayFormat: 'bracket'}) +); +expectType( + queryString.parse('?foo=bar', {arrayFormat: 'index'}) +); +expectType( + queryString.parse('?foo=bar', {arrayFormat: 'none'}) +); + +// Parse URL +expectType(queryString.parseUrl('?foo=bar')); + +expectType( + queryString.parseUrl('?foo=bar', {decode: false}) +); +expectType( + queryString.parseUrl('?foo=bar', {arrayFormat: 'bracket'}) +); +expectType( + queryString.parseUrl('?foo=bar', {arrayFormat: 'index'}) +); +expectType( + queryString.parseUrl('?foo=bar', {arrayFormat: 'none'}) +); + +// Extract +expectType(queryString.extract('http://foo.bar/?abc=def&hij=klm')); diff --git a/package.json b/package.json index c93d5b91..15cdae00 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "node": ">=6" }, "scripts": { - "test": "xo && ava" + "test": "xo && ava && tsd-check" }, "files": [ - "index.js" + "index.js", + "index.d.ts" ], "keywords": [ "browser", @@ -38,9 +39,10 @@ "strict-uri-encode": "^2.0.0" }, "devDependencies": { - "ava": "^0.25.0", + "ava": "^1.2.1", "deep-equal": "^1.0.1", "fast-check": "^1.5.0", - "xo": "^0.23.0" + "tsd-check": "^0.3.0", + "xo": "^0.24.0" } }