├── .gitattributes ├── .npmrc ├── media ├── logo.jpg ├── logo.png ├── logo.sketch └── logo.svg ├── test ├── snapshots │ ├── normalize.js.snap │ └── normalize.js.md ├── map.js ├── parse.js ├── normalize.js └── index.js ├── map.js ├── .github ├── dependabot.yml └── workflows │ ├── pull_request.yml │ └── main.yml ├── .editorconfig ├── parse.js ├── benchmark.js ├── index.js ├── .gitignore ├── normalize.js ├── LICENSE ├── package.json ├── README.md └── CHANGELOG.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | unsafe-perm=true 2 | save-prefix=~ 3 | save=false 4 | -------------------------------------------------------------------------------- /media/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kikobeats/to-query/HEAD/media/logo.jpg -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kikobeats/to-query/HEAD/media/logo.png -------------------------------------------------------------------------------- /media/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kikobeats/to-query/HEAD/media/logo.sketch -------------------------------------------------------------------------------- /test/snapshots/normalize.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kikobeats/to-query/HEAD/test/snapshots/normalize.js.snap -------------------------------------------------------------------------------- /map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { camelCase } = require('lodash') 4 | const mapKeysDeep = require('@kikobeats/map-keys-deep') 5 | 6 | module.exports = obj => mapKeysDeep(obj, (value, key) => camelCase(key)) 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | # Check for updates to GitHub Actions every weekday 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | max_line_length = 80 13 | indent_brace_style = 1TBS 14 | spaces_around_operators = true 15 | quote_type = auto 16 | 17 | [package.json] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /test/map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('ava') 4 | const map = require('../map') 5 | 6 | test('camelize keys', t => { 7 | t.deepEqual(map({ foo_bar: 'fooz' }), { fooBar: 'fooz' }) 8 | t.deepEqual(map({ plan_id: 123 }), { planId: 123 }) 9 | t.deepEqual(map({ foo: { plan_id: 123 } }), { foo: { planId: 123 } }) 10 | t.deepEqual(map({ foo: [{ plan_id: 123 }] }), { 11 | foo: [{ planId: 123 }] 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /parse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { flow } = require('lodash') 4 | const { URL } = require('url') 5 | 6 | module.exports = flow([ 7 | url => { 8 | if (!url) { 9 | throw TypeError( 10 | `Expected \`url\` to be of type \`string\` but received type \`${typeof url}\`` 11 | ) 12 | } 13 | return new URL(url, 'http://localhost').searchParams 14 | }, 15 | searchParams => Object.fromEntries(searchParams.entries()) 16 | ]) 17 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const bench = require('nanobench') 4 | const pkg = require('./package.json') 5 | 6 | const parse = require('./parse') 7 | 8 | const ITERATIONS = 1000000 9 | 10 | const PAYLOAD = 11 | '/?url=https%3A%2F%2Fedna-pxmb461cy.now.sh&force&embed=screenshot.url' 12 | 13 | bench(`parse v${pkg.version}`, function (b) { 14 | b.start() 15 | 16 | for (let i = 0; i < ITERATIONS; i++) { 17 | parse(PAYLOAD) 18 | } 19 | 20 | b.end() 21 | }) 22 | -------------------------------------------------------------------------------- /test/snapshots/normalize.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/normalize.js` 2 | 3 | The actual snapshot is saved in `normalize.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## dot access 8 | 9 | > Snapshot 1 10 | 11 | { 12 | data: { 13 | photos: [ 14 | { 15 | attr: 'href', 16 | selector: '.logos > img', 17 | trim: true, 18 | }, 19 | { 20 | attr: 'href', 21 | selector: 'img', 22 | trim: true, 23 | }, 24 | ], 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { isObject, isEmpty, noop, flow } = require('lodash') 4 | const osom = require('osom') 5 | 6 | const normalize = require('./normalize') 7 | const parse = require('./parse') 8 | const mapper = require('./map') 9 | 10 | const getQuery = flow([parse, normalize]) 11 | 12 | module.exports = ({ map = mapper, ...opts } = {}) => { 13 | const validator = isEmpty(opts) ? noop : osom(opts, { type: String }) 14 | 15 | return opts => { 16 | const query = map(isObject(opts) ? normalize(opts) : getQuery(opts)) 17 | return { ...query, ...validator(query) } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # npm 3 | ############################ 4 | node_modules 5 | npm-debug.log 6 | .node_history 7 | yarn.lock 8 | package-lock.json 9 | 10 | ############################ 11 | # tmp, editor & OS files 12 | ############################ 13 | .tmp 14 | *.swo 15 | *.swp 16 | *.swn 17 | *.swm 18 | .DS_Store 19 | *# 20 | *~ 21 | .idea 22 | *sublime* 23 | nbproject 24 | 25 | ############################ 26 | # Tests 27 | ############################ 28 | testApp 29 | coverage 30 | .nyc_output 31 | 32 | ############################ 33 | # Other 34 | ############################ 35 | .env 36 | .envrc 37 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull_request 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | if: github.ref != 'refs/heads/master' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v6 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v6 22 | with: 23 | node-version: lts/* 24 | - name: Install 25 | run: npm install --no-package-lock 26 | - name: Test 27 | run: npm test 28 | - name: Report 29 | run: mkdir -p coverage && npx c8 report --reporter=text-lcov > coverage/lcov.info 30 | - name: Coverage 31 | uses: coverallsapp/github-action@master 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /test/parse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('ava') 4 | const parse = require('../parse') 5 | 6 | test('plain object', t => { 7 | t.deepEqual(parse('/?foo=bar'), { 8 | foo: 'bar' 9 | }) 10 | }) 11 | 12 | test('decode url', t => { 13 | t.deepEqual(parse('/?url=https%3A%2F%2Fedna-pxmb461cy.now.sh'), { 14 | url: 'https://edna-pxmb461cy.now.sh' 15 | }) 16 | }) 17 | 18 | test('nested objects', t => { 19 | t.deepEqual( 20 | parse( 21 | '/?url=https%3A%2F%2Fkikobeats.com&data.avatar.selector=%23avatar&data.avatar.attr=src&data.avatar.type=image&force=true&data.resume.selector=h1&data.resume.attr=text' 22 | ), 23 | { 24 | 'data.avatar.attr': 'src', 25 | 'data.avatar.selector': '#avatar', 26 | 'data.avatar.type': 'image', 27 | 'data.resume.attr': 'text', 28 | 'data.resume.selector': 'h1', 29 | force: 'true', 30 | url: 'https://kikobeats.com' 31 | } 32 | ) 33 | }) 34 | -------------------------------------------------------------------------------- /normalize.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { set, isArray, isObject, concat, reduce } = require('lodash') 4 | const autoParse = require('auto-parse') 5 | 6 | const castValues = value => { 7 | if (isArray(value)) return castArrayValues(value) 8 | if (isObject(value)) return castObjectValues(value) 9 | if (value === '') return true 10 | return autoParse(value) 11 | } 12 | 13 | const castObjectValues = query => 14 | reduce( 15 | query, 16 | (acc, value, key) => { 17 | acc[key] = castValues(value) 18 | return acc 19 | }, 20 | {} 21 | ) 22 | 23 | const castArrayValues = query => 24 | reduce(query, (acc, value) => concat(acc, castValues(value)), []) 25 | 26 | const normalize = query => 27 | reduce( 28 | query, 29 | (acc, value, key) => { 30 | set(acc, key, value === '' ? true : value) 31 | return acc 32 | }, 33 | {} 34 | ) 35 | 36 | module.exports = query => castValues(normalize(query)) 37 | -------------------------------------------------------------------------------- /test/normalize.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('ava') 4 | const normalize = require('../normalize') 5 | 6 | test('booleans', t => { 7 | t.deepEqual(normalize({ foo: '' }), { foo: true }) 8 | t.deepEqual(normalize({ foo: 'true' }), { foo: true }) 9 | t.deepEqual(normalize({ foo: 'false' }), { foo: false }) 10 | }) 11 | 12 | test('number', t => { 13 | t.deepEqual(normalize({ foo: '123' }), { foo: 123 }) 14 | t.deepEqual(normalize({ foo: '3.14' }), { foo: 3.14 }) 15 | }) 16 | 17 | test('string', t => { 18 | t.deepEqual(normalize({ foo: '123.lel' }), { foo: '123.lel' }) 19 | }) 20 | 21 | test('other', t => { 22 | t.deepEqual(normalize({ foo: 'NaN' }), { foo: NaN }) 23 | t.deepEqual(normalize({ foo: 'null' }), { foo: null }) 24 | t.deepEqual(normalize({ foo: 'undefined' }), { foo: undefined }) 25 | }) 26 | 27 | test('dot access', t => { 28 | t.snapshot( 29 | normalize({ 30 | 'data.photos.0.attr': 'href', 31 | 'data.photos.0.selector': '.logos > img', 32 | 'data.photos.0.trim': true, 33 | 'data.photos.1.attr': 'href', 34 | 'data.photos.1.selector': 'img', 35 | 'data.photos.1.trim': true 36 | }) 37 | ) 38 | }) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2019 Kiko Beats (kikobeats.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | contributors: 10 | if: "${{ github.event.head_commit.message != 'build: contributors' }}" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v6 15 | with: 16 | fetch-depth: 0 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v6 20 | with: 21 | node-version: lts/* 22 | - name: Contributors 23 | run: | 24 | git config --global user.email ${{ secrets.GIT_EMAIL }} 25 | git config --global user.name ${{ secrets.GIT_USERNAME }} 26 | npm run contributors 27 | - name: Push changes 28 | uses: ad-m/github-push-action@master 29 | with: 30 | github_token: ${{ secrets.GH_TOKEN }} 31 | branch: ${{ github.head_ref }} 32 | 33 | release: 34 | if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') && !startsWith(github.event.head_commit.message, 'docs:') && !startsWith(github.event.head_commit.message, 'ci:') }} 'docs:') && !startsWith(github.event.head_commit.message, 'ci:') }} 35 | needs: [contributors] 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v6 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | - name: Setup Node.js 43 | uses: actions/setup-node@v6 44 | with: 45 | node-version: lts/* 46 | - name: Install 47 | run: npm install --no-package-lock 48 | - name: Test 49 | run: npm test 50 | - name: Report 51 | run: mkdir -p coverage && npx c8 report --reporter=text-lcov > coverage/lcov.info 52 | - name: Coverage 53 | uses: coverallsapp/github-action@master 54 | with: 55 | github-token: ${{ secrets.GITHUB_TOKEN }} 56 | - name: Release 57 | env: 58 | CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{ secrets.GH_TOKEN }} 59 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 60 | run: | 61 | git config --global user.email ${{ secrets.GIT_EMAIL }} 62 | git config --global user.name ${{ secrets.GIT_USERNAME }} 63 | git pull origin master 64 | npm run release 65 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('ava') 4 | const createQuery = require('..') 5 | 6 | const isUrlHttp = require('is-url-http') 7 | 8 | test('url is required', t => { 9 | const toQuery = createQuery() 10 | 11 | const error = t.throws( 12 | () => { 13 | toQuery() 14 | }, 15 | { instanceOf: TypeError } 16 | ) 17 | 18 | t.is( 19 | error.message, 20 | 'Expected `url` to be of type `string` but received type `undefined`' 21 | ) 22 | }) 23 | 24 | test('detect query parameters', t => { 25 | const toQuery = createQuery() 26 | t.deepEqual(toQuery('/?foo=bar'), { foo: 'bar' }) 27 | }) 28 | 29 | test('required fields', t => { 30 | const toQuery = createQuery({ 31 | url: { 32 | required: true 33 | } 34 | }) 35 | 36 | const genericError = t.throws( 37 | () => { 38 | toQuery('/?foo=bar') 39 | }, 40 | { instanceOf: TypeError } 41 | ) 42 | 43 | t.is(genericError.message, 'Expected `string` for `url`, got `undefined`') 44 | 45 | const error = t.throws( 46 | () => 47 | createQuery({ 48 | url: { 49 | required: 'You need to provide an URL' 50 | } 51 | })('/?foo=bar'), 52 | { instanceOf: TypeError } 53 | ) 54 | 55 | t.is(error.message, 'You need to provide an URL') 56 | 57 | t.deepEqual(toQuery('/?url=kikobeats.com&foo=bar'), { 58 | url: 'kikobeats.com', 59 | foo: 'bar' 60 | }) 61 | }) 62 | 63 | test('validation fields', t => { 64 | const toQuery = createQuery({ 65 | url: { 66 | type: String, 67 | validate: { 68 | validator: isUrlHttp, 69 | message: input => `The value '${input}' is not a valid http(s) URL.` 70 | } 71 | } 72 | }) 73 | 74 | const error = t.throws( 75 | () => { 76 | toQuery('/?url=bar') 77 | }, 78 | { instanceOf: TypeError } 79 | ) 80 | 81 | t.is(error.message, "The value 'bar' is not a valid http(s) URL.") 82 | }) 83 | 84 | test('default values', t => { 85 | const toQuery = createQuery({ 86 | userAgent: { 87 | default: 'foo' 88 | } 89 | }) 90 | 91 | t.deepEqual(toQuery('/?foo=bar'), { foo: 'bar', userAgent: 'foo' }) 92 | t.deepEqual(toQuery('/?user_agent=googlebot'), { userAgent: 'googlebot' }) 93 | }) 94 | 95 | test('transform field', t => { 96 | const split = str => str.split(',').map(item => item.trim()) 97 | 98 | t.deepEqual( 99 | createQuery({ filter: { transform: [split] } })('/?filter=foo,bar'), 100 | { filter: ['foo', 'bar'] } 101 | ) 102 | t.deepEqual( 103 | createQuery({ filter: { transform: split } })('/?filter=foo,bar'), 104 | { filter: ['foo', 'bar'] } 105 | ) 106 | }) 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "to-query", 3 | "description": "Get query object from a request url", 4 | "homepage": "https://github.com/Kikobeats/to-query", 5 | "version": "1.6.20", 6 | "main": "index.js", 7 | "author": { 8 | "email": "josefrancisco.verdu@gmail.com", 9 | "name": "Kiko Beats", 10 | "url": "https://kikobeats.com" 11 | }, 12 | "contributors": [], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Kikobeats/to-query.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/Kikobeats/to-query/issues" 19 | }, 20 | "keywords": [ 21 | "api", 22 | "query" 23 | ], 24 | "dependencies": { 25 | "@kikobeats/map-keys-deep": "~1.0.5", 26 | "auto-parse": "~2.4.0", 27 | "lodash": "~4.17.21", 28 | "osom": "~3.1.2" 29 | }, 30 | "devDependencies": { 31 | "@commitlint/cli": "latest", 32 | "@commitlint/config-conventional": "latest", 33 | "ava": "latest", 34 | "c8": "latest", 35 | "ci-publish": "latest", 36 | "conventional-github-releaser": "latest", 37 | "finepack": "latest", 38 | "git-authors-cli": "latest", 39 | "is-url-http": "latest", 40 | "nano-staged": "latest", 41 | "npm-check-updates": "latest", 42 | "prettier-standard": "latest", 43 | "simple-git-hooks": "latest", 44 | "standard": "latest", 45 | "standard-markdown": "latest", 46 | "standard-version": "latest" 47 | }, 48 | "engines": { 49 | "node": ">= 8" 50 | }, 51 | "files": [ 52 | "index.js", 53 | "map.js", 54 | "normalize.js", 55 | "parse.js" 56 | ], 57 | "scripts": { 58 | "clean": "rm -rf node_modules", 59 | "contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true", 60 | "coverage": "nyc report --reporter=text-lcov | coveralls", 61 | "lint": "standard-markdown README.md && standard", 62 | "postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)", 63 | "prerelease": "npm run update:check && npm run contributors", 64 | "pretest": "npm run lint", 65 | "pretty": "prettier-standard index.js {core,test,bin,scripts}/**/*.js --single-quote --print-width 100", 66 | "release": "standard-version -a", 67 | "release:github": "conventional-github-releaser -p angular", 68 | "release:tags": "git push --follow-tags origin HEAD:master", 69 | "test": "c8 ava", 70 | "update": "ncu -u", 71 | "update:check": "ncu -- --error-level 2" 72 | }, 73 | "license": "MIT", 74 | "commitlint": { 75 | "extends": [ 76 | "@commitlint/config-conventional" 77 | ] 78 | }, 79 | "nano-staged": { 80 | "*.js": [ 81 | "prettier-standard" 82 | ], 83 | "*.md": [ 84 | "standard-markdown" 85 | ], 86 | "package.json": [ 87 | "finepack" 88 | ] 89 | }, 90 | "simple-git-hooks": { 91 | "commit-msg": "npx commitlint --edit", 92 | "pre-commit": "npx nano-staged" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |
9 | 10 | ![Last version](https://img.shields.io/github/tag/Kikobeats/to-query.svg?style=flat-square) 11 | [![Coverage Status](https://img.shields.io/coveralls/Kikobeats/to-query.svg?style=flat-square)](https://coveralls.io/github/Kikobeats/to-query) 12 | [![NPM Status](https://img.shields.io/npm/dm/to-query.svg?style=flat-square)](https://www.npmjs.org/package/to-query) 13 | 14 | > Get query object from a request url as input. 15 | 16 | ## Highlights 17 | 18 | - Get query parameters for any URL.
19 | eg. `/?foo=bar` → `{ foo: 'bar' }` 20 | - Normalize keys to camel case.
21 | eg. `/?user_agent=googlebot` → `{ userAgent: 'googlebot' }` 22 | - Auto cast values to native types.
23 | eg. `/?plan_id=123` → `{ planId: 123 }` 24 | - Friendly boolean values.
25 | eg. `/?is_enabled` → `{ isEnabled: true }` 26 | - Declare default values.
27 | eg. `/?` → `{ accept: '*' }` 28 | 29 | Also it supports required fields, validation, error handling and more. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install to-query --save 35 | ``` 36 | 37 | ## Get Started 38 | 39 | **to-query** is a convenient way for getting query parameters from any request url as input. 40 | 41 | ```js 42 | const toQuery = require('to-query')() 43 | 44 | const query = toQuery('/?foo=bar') // => { foo: 'bar' } 45 | ``` 46 | 47 | ### Default 48 | 49 | Sometimes you need to associate a default value to be used in case if one is not provided: 50 | 51 | ```js 52 | const userAgentString = require('ua-string') 53 | const createQuery = require('to-query') 54 | 55 | const toQuery = createQuery({ 56 | userAgent: { 57 | default: userAgentString 58 | } 59 | }) 60 | 61 | toQuery('/?') // => { userAgent: 'Mozilla/5.0 (Macintosh; Intel…' } 62 | toQuery('/?user_agent=googlebot') // => { userAgent: 'googlebot' } 63 | ``` 64 | 65 | ### Required 66 | 67 | Declaring fields as **required** means it throw an error in case of non presence: 68 | 69 | ```js 70 | const createQuery = require('to-query') 71 | 72 | const toQuery = createQuery({ 73 | url: { 74 | required: true 75 | } 76 | }) 77 | 78 | toQuery('/?foo=bar') 79 | // => TypeError: Expected `string` for `url`, got `undefined` 80 | ``` 81 | 82 | #### Custom Error Message 83 | 84 | In case you provide an `string` instead of a `boolean` it will be used as the message to show under error: 85 | 86 | ```js 87 | const createQuery = require('to-query') 88 | 89 | const toQuery = createQuery({ 90 | url: { 91 | required: 'You need to provide an URL.' 92 | } 93 | }) 94 | 95 | toQuery('/?foo=bar') 96 | // => TypeError: You need to provide an URL. 97 | ``` 98 | 99 | ### Validate 100 | 101 | If you need granular control for type checking the shape of value in something you expect, you can declare any kind of validation, making easy connect with other packages: 102 | 103 | ```js 104 | const isUrlHttp = require('is-url-http') 105 | const createQuery = require('to-query') 106 | 107 | const toQuery = createQuery({ 108 | url: { 109 | validate: { 110 | validator: isUrlHttp, 111 | message: input => `The value '${input}' is not a valid http(s) URL.` 112 | } 113 | } 114 | }) 115 | 116 | toQuery('/?url=kikobeats.com') 117 | // => TypeError: The value 'kikobeats.com' is not a valid http(s) URL. 118 | ``` 119 | 120 | ### Transform 121 | 122 | You can mutate an individual value, using one or more functions to produce the final output: 123 | 124 | ```js 125 | const createQuery = require('to-query') 126 | 127 | const split = str => str.split(',').map(item => item.trim()) 128 | 129 | const toQuery = createQuery({ 130 | url: { 131 | filters: { 132 | transform: [split] 133 | } 134 | } 135 | }) 136 | 137 | toQuery('/?filters=prerender,auto,resize') 138 | // => { filters: ['prerender', 'auto', 'resize'] } 139 | ``` 140 | 141 | ## Usage 142 | 143 | **to-query** has been designed to do *just one thing well*. 144 | 145 | In this aspect, **to-query** is *framework agnostic*, giving you freedom to connect it with the rest of your software. 146 | 147 | In case you want ot use it with any HTTP server (Express, Micro, Koa, Hapi, Fastify,...) just provide the url of the request. 148 | 149 | ```js 150 | const toQuery = require('to-query')() 151 | 152 | module.exports = (req, res) => { 153 | req.query = createQuery(req.url) 154 | res.end('Your query is', req.query) 155 | } 156 | ``` 157 | 158 | That's all! 159 | 160 | ## API 161 | 162 | ### `toQuery = to-query([options])` 163 | 164 | It creates **to-query** instance. 165 | 166 | #### options 167 | 168 | Any option provided will be passed to [`osom`](https://www.npmjs.com/package/osom), check the [documentation](https://osom.js.org) to know more. 169 | 170 | Additionally you can setup 171 | 172 | ##### map 173 | 174 | Type: `function`
175 | 176 | A function to run as mapper *before* process the url. 177 | 178 | The default map just convert keys into camel case. 179 | 180 | ### `toQuery(input)` 181 | 182 | #### input 183 | 184 | *Required*
185 | Type: `string|object` 186 | 187 | The input value to convert into a query object. 188 | 189 | ## License 190 | 191 | **to-query** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/Kikobeats/to-query/blob/master/LICENSE.md) License.
192 | Authored and maintained by Kiko Beats with help from [contributors](https://github.com/Kikobeats/to-query/contributors). 193 | 194 | > [kikobeats.com](https://kikobeats.com) · GitHub [Kiko Beats](https://github.com/Kikobeats) · Twitter [@Kikobeats](https://twitter.com/Kikobeats) 195 | -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 2 5 | Created with Sketch. 6 | 7 | 45 | 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 1.6.20 (2025-11-21) 6 | 7 | ### 1.6.19 (2025-10-14) 8 | 9 | ### 1.6.18 (2025-09-05) 10 | 11 | ### 1.6.17 (2025-08-13) 12 | 13 | ### 1.6.16 (2025-06-09) 14 | 15 | ### 1.6.15 (2025-06-06) 16 | 17 | ### 1.6.14 (2023-10-23) 18 | 19 | ### 1.6.13 (2023-09-05) 20 | 21 | ### 1.6.12 (2023-01-02) 22 | 23 | ### 1.6.11 (2023-01-01) 24 | 25 | ### 1.6.10 (2022-06-12) 26 | 27 | ### 1.6.9 (2022-05-17) 28 | 29 | ### 1.6.8 (2022-04-11) 30 | 31 | ### 1.6.7 (2022-04-01) 32 | 33 | ### 1.6.6 (2022-03-02) 34 | 35 | ### 1.6.5 (2022-02-24) 36 | 37 | ### 1.6.4 (2022-02-22) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * ensure to unflatten values ([7e0804e](https://github.com/Kikobeats/to-query/commit/7e0804e20dc96c141f0dbb7ffe81a84c738b2371)) 43 | 44 | ### 1.6.3 (2022-02-21) 45 | 46 | ### 1.6.2 (2022-01-21) 47 | 48 | ### [1.6.1](https://github.com/Kikobeats/to-query/compare/v1.6.0...v1.6.1) (2020-11-10) 49 | 50 | ## [1.6.0](https://github.com/Kikobeats/to-query/compare/v1.5.4...v1.6.0) (2020-04-02) 51 | 52 | 53 | ### Features 54 | 55 | * parse using searchParams ([630e622](https://github.com/Kikobeats/to-query/commit/630e6226ed6d82d5f65244e1fdde9b7af20f0ba6)) 56 | 57 | ### [1.5.4](https://github.com/Kikobeats/to-query/compare/v1.5.3...v1.5.4) (2019-09-18) 58 | 59 | ### [1.5.3](https://github.com/Kikobeats/to-query/compare/v1.5.2...v1.5.3) (2019-06-19) 60 | 61 | 62 | 63 | ### [1.5.2](https://github.com/Kikobeats/to-query/compare/v1.5.1...v1.5.2) (2019-06-19) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * typo ([a3aa998](https://github.com/Kikobeats/to-query/commit/a3aa998)) 69 | 70 | 71 | ### Build System 72 | 73 | * update travis ([2f925d1](https://github.com/Kikobeats/to-query/commit/2f925d1)) 74 | 75 | 76 | 77 | ### [1.5.1](https://github.com/Kikobeats/to-query/compare/v1.5.0...v1.5.1) (2019-06-04) 78 | 79 | 80 | ### Build System 81 | 82 | * update dependencies ([3a80d2d](https://github.com/Kikobeats/to-query/commit/3a80d2d)) 83 | 84 | 85 | 86 | ## [1.5.0](https://github.com/Kikobeats/to-query/compare/v1.4.0...v1.5.0) (2019-06-04) 87 | 88 | 89 | ### Build System 90 | 91 | * add automate-release workflow ([61872ef](https://github.com/Kikobeats/to-query/commit/61872ef)) 92 | * update dependencies ([383d08a](https://github.com/Kikobeats/to-query/commit/383d08a)) 93 | * use map-keys-deep instead of map-obj ([37b6228](https://github.com/Kikobeats/to-query/commit/37b6228)) 94 | 95 | 96 | ### Features 97 | 98 | * use map-obj dependency ([25cbc39](https://github.com/Kikobeats/to-query/commit/25cbc39)) 99 | 100 | 101 | 102 | ## [1.4.0](https://github.com/Kikobeats/to-query/compare/v1.3.2...v1.4.0) (2019-05-26) 103 | 104 | 105 | ### Features 106 | 107 | * add transform ([9006a86](https://github.com/Kikobeats/to-query/commit/9006a86)) 108 | 109 | 110 | 111 | ### [1.3.2](https://github.com/Kikobeats/to-query/compare/v1.3.1...v1.3.2) (2019-05-26) 112 | 113 | 114 | ### Build System 115 | 116 | * update dependencies ([146450e](https://github.com/Kikobeats/to-query/commit/146450e)) 117 | 118 | 119 | 120 | ### [1.3.1](https://github.com/Kikobeats/to-query/compare/v1.2.2...v1.3.1) (2019-05-26) 121 | 122 | 123 | ### Build System 124 | 125 | * normalize object provided ([2db0999](https://github.com/Kikobeats/to-query/commit/2db0999)) 126 | 127 | 128 | 129 | ## 1.3.0 (2019-05-25) 130 | 131 | 132 | ### Build System 133 | 134 | * add project logo ([650cd73](https://github.com/Kikobeats/to-query/commit/650cd73)), closes [#2](https://github.com/Kikobeats/to-query/issues/2) 135 | * add project logo ([#3](https://github.com/Kikobeats/to-query/issues/3)) ([aa9c67b](https://github.com/Kikobeats/to-query/commit/aa9c67b)) 136 | 137 | 138 | ### Features 139 | 140 | * allow pass an object ([25e7664](https://github.com/Kikobeats/to-query/commit/25e7664)) 141 | 142 | 143 | 144 | 145 | # 1.3.0 (2019-05-25) 146 | 147 | * feat: allow pass an object ([25e7664](https://github.com/Kikobeats/to-query/commit/25e7664)) 148 | * docs: tweaks ([90d61ac](https://github.com/Kikobeats/to-query/commit/90d61ac)) 149 | * Update README.md ([91427de](https://github.com/Kikobeats/to-query/commit/91427de)) 150 | * build: add project logo ([650cd73](https://github.com/Kikobeats/to-query/commit/650cd73)), closes [#2](https://github.com/Kikobeats/to-query/issues/2) 151 | 152 | 153 | 154 | # Changelog 155 | 156 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 157 | 158 | ### [1.2.2](https://github.com/Kikobeats/to-query/compare/v1.2.1...v1.2.2) (2019-05-23) 159 | 160 | 161 | ### Bug Fixes 162 | 163 | * expose files properly ([12968d7](https://github.com/Kikobeats/to-query/commit/12968d7)) 164 | 165 | 166 | 167 | ### [1.2.1](https://github.com/Kikobeats/to-query/compare/v1.2.0...v1.2.1) (2019-05-23) 168 | 169 | 170 | ### Bug Fixes 171 | 172 | * remove duplicate file ([a8e685f](https://github.com/Kikobeats/to-query/commit/a8e685f)) 173 | 174 | 175 | 176 | ## [1.2.0](https://github.com/Kikobeats/to-query/compare/v1.1.0...v1.2.0) (2019-05-23) 177 | 178 | 179 | ### Features 180 | 181 | * expose file methods ([05075c5](https://github.com/Kikobeats/to-query/commit/05075c5)) 182 | 183 | 184 | 185 | ## 1.1.0 (2019-05-23) 186 | 187 | 188 | ### Bug Fixes 189 | 190 | * linter ([b1c9b02](https://github.com/Kikobeats/to-query/commit/b1c9b02)) 191 | 192 | 193 | ### Features 194 | 195 | * map support ([91e3d5e](https://github.com/Kikobeats/to-query/commit/91e3d5e)) 196 | 197 | 198 | 199 | ## 1.0.0 (2019-05-23) 200 | 201 | 202 | ### Bug Fixes 203 | 204 | * linter ([801e805](https://github.com/Kikobeats/to-query/commit/801e805)) 205 | * typo ([cfcbe7e](https://github.com/Kikobeats/to-query/commit/cfcbe7e)) 206 | * typo ([5f5f486](https://github.com/Kikobeats/to-query/commit/5f5f486)) 207 | 208 | 209 | ### Build System 210 | 211 | * rename project :'( ([84a91b8](https://github.com/Kikobeats/to-query/commit/84a91b8)) 212 | * **no-release:** first commit ([70e0d23](https://github.com/Kikobeats/to-query/commit/70e0d23)) 213 | 214 | 215 | 216 | 217 | # 1.0.0 (2019-05-23) 218 | 219 | * fix: linter ([801e805](https://github.com/Kikobeats/to-query/commit/801e805)) 220 | * fix: typo ([cfcbe7e](https://github.com/Kikobeats/to-query/commit/cfcbe7e)) 221 | * fix: typo ([5f5f486](https://github.com/Kikobeats/to-query/commit/5f5f486)) 222 | * [ImgBot] Optimize images ([7098096](https://github.com/Kikobeats/to-query/commit/7098096)) 223 | * Update parse.js ([7dee2c3](https://github.com/Kikobeats/to-query/commit/7dee2c3)) 224 | * Update README.md ([22b7416](https://github.com/Kikobeats/to-query/commit/22b7416)) 225 | * Update README.md ([90663fa](https://github.com/Kikobeats/to-query/commit/90663fa)) 226 | * Update README.md ([d417dac](https://github.com/Kikobeats/to-query/commit/d417dac)) 227 | * docs: tweaks ([d73ab5f](https://github.com/Kikobeats/to-query/commit/d73ab5f)) 228 | * build: rename project :'( ([84a91b8](https://github.com/Kikobeats/to-query/commit/84a91b8)) 229 | * build(no-release): first commit ([70e0d23](https://github.com/Kikobeats/to-query/commit/70e0d23)) 230 | --------------------------------------------------------------------------------