├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── cjs ├── index.js └── package.json ├── esm └── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | node_modules/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | test/ 4 | package-lock.json 5 | .travis.yml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - master 9 | after_success: 10 | - "npm run coveralls" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2019, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pg-tag 2 | 3 | [![Build Status](https://travis-ci.com/WebReflection/pg-tag.svg?branch=master)](https://travis-ci.com/WebReflection/pg-tag) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/pg-tag/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/pg-tag?branch=master) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC) 4 | 5 | **Social Media Photo by [Hu Chen](https://unsplash.com/@huchenme) on [Unsplash](https://unsplash.com/)** 6 | 7 | A [tiny](https://github.com/WebReflection/pg-tag/blob/master/esm/index.js) utility to safely query [pg](https://www.npmjs.com/package/pg) via template literals. 8 | 9 | Available for [SQLite](https://github.com/WebReflection/sqlite-tag/#readme) too. 10 | 11 | ```js 12 | const {Pool} = require('pg'); 13 | 14 | const pg = require('pg-tag')(new Pool); 15 | 16 | // returns result.rows[0] 17 | const user = await pg.get` 18 | SELECT 19 | id, name, address 20 | FROM 21 | users 22 | WHERE 23 | email = ${email} 24 | `; 25 | 26 | // returns result.rows 27 | const users = await pg.all` 28 | SELECT * 29 | FROM users 30 | WHERE status = ${activeUser} 31 | `; 32 | 33 | // returns regular pg.query results 34 | await pg.query` 35 | SELECT * 36 | FROM users 37 | WHERE status = ${activeUser} 38 | `; 39 | 40 | // allow partial entries 41 | let x = 1; 42 | await pg.all`SELECT * FROM ${raw`table_${x}`}`; 43 | 44 | pg.pool.end(); 45 | ``` 46 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const plain = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('plain-tag')); 3 | const {asStatic, asParams} = require('static-params/sql'); 4 | 5 | const {defineProperty} = Object; 6 | 7 | const create = (pool, $) => (tpl, ...values) => { 8 | if (tpl.some(chunk => chunk.includes('$'))) 9 | throw new Error('SQL injection hazard'); 10 | const [sql, ...params] = asParams(tpl, ...values); 11 | return pool.query(sql.map(holes).join(''), params).then($); 12 | }; 13 | 14 | const holes = (value, i) => (0 < i ? ('$' + i) : '') + value; 15 | 16 | function PGTag(pool) { 17 | const query = create(pool, $ => $); 18 | return { 19 | transaction() { 20 | let t = query(['BEGIN']); 21 | return defineProperty( 22 | (..._) => { 23 | t = t.then(() => query(..._)); 24 | }, 25 | 'commit', 26 | {value() { 27 | return t = t.then(() => query(['COMMIT'])); 28 | }} 29 | ); 30 | }, 31 | all: create(pool, $ => $.rows), 32 | get: create(pool, $ => $.rows.shift()), 33 | raw: (tpl, ...values) => asStatic(plain(tpl, ...values)), 34 | query, 35 | pool 36 | }; 37 | } 38 | module.exports = PGTag; 39 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | import plain from 'plain-tag'; 2 | import {asStatic, asParams} from 'static-params/sql'; 3 | 4 | const {defineProperty} = Object; 5 | 6 | const create = (pool, $) => (tpl, ...values) => { 7 | if (tpl.some(chunk => chunk.includes('$'))) 8 | throw new Error('SQL injection hazard'); 9 | const [sql, ...params] = asParams(tpl, ...values); 10 | return pool.query(sql.map(holes).join(''), params).then($); 11 | }; 12 | 13 | const holes = (value, i) => (0 < i ? ('$' + i) : '') + value; 14 | 15 | export default function PGTag(pool) { 16 | const query = create(pool, $ => $); 17 | return { 18 | transaction() { 19 | let t = query(['BEGIN']); 20 | return defineProperty( 21 | (..._) => { 22 | t = t.then(() => query(..._)); 23 | }, 24 | 'commit', 25 | {value() { 26 | return t = t.then(() => query(['COMMIT'])); 27 | }} 28 | ); 29 | }, 30 | all: create(pool, $ => $.rows), 31 | get: create(pool, $ => $.rows.shift()), 32 | raw: (tpl, ...values) => asStatic(plain(tpl, ...values)), 33 | query, 34 | pool 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg-tag", 3 | "version": "2.2.0", 4 | "description": "A tiny utility to safely query pg via template literals.", 5 | "main": "./cjs/index.js", 6 | "module": "./esm/index.js", 7 | "type": "module", 8 | "exports": { 9 | "import": "./esm/index.js", 10 | "default": "./cjs/index.js" 11 | }, 12 | "scripts": { 13 | "build": "ascjs --no-default esm cjs && npm test", 14 | "coveralls": "c8 report --reporter=text-lcov | coveralls", 15 | "test": "c8 node test/index.js" 16 | }, 17 | "keywords": [ 18 | "PostgreSQL", 19 | "template", 20 | "literal", 21 | "tag", 22 | "query" 23 | ], 24 | "author": "Andrea Giammarchi", 25 | "license": "ISC", 26 | "devDependencies": { 27 | "ascjs": "^5.0.1", 28 | "c8": "^7.11.0", 29 | "coveralls": "^3.1.1", 30 | "pg": "^8.7.1" 31 | }, 32 | "dependencies": { 33 | "plain-tag": "^0.1.3", 34 | "static-params": "^0.3.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import PGSql from '../esm/index.js'; 2 | 3 | // example 4 | const pg = { 5 | query: (...args) => new Promise((resolve, reject) => { 6 | if (args[1].shift() === 'FAIL') 7 | reject(); 8 | else 9 | resolve({rows: [1, 2, 3]}); 10 | }) 11 | }; 12 | 13 | const {pool, get, all, query, raw, transaction} = PGSql(pg); 14 | 15 | console.assert(pool === pg, 'unexpected pool'); 16 | 17 | const exit = err => { 18 | console.log('✔ failure works: ' + !err); 19 | process.exit(!!err * 1); 20 | }; 21 | 22 | try { 23 | get`INSERT INTO table VALUES ($1)`; 24 | } 25 | catch (sqlInjection) { 26 | console.log('✔ safe against SQL injections'); 27 | } 28 | 29 | query` 30 | SELECT * 31 | FROM users 32 | ` 33 | .then(result => { 34 | console.assert(result.rows.join(',') === '1,2,3', 'unexpected query result'); 35 | console.log('✔ query works'); 36 | }) 37 | .catch(exit); 38 | 39 | get` 40 | SELECT * 41 | FROM users 42 | WHERE email = ${raw`test@email.me`} 43 | ` 44 | .then(result => { 45 | console.assert(result === 1, 'unexpected get result'); 46 | console.log('✔ get works'); 47 | }) 48 | .catch(exit); 49 | 50 | all` 51 | SELECT * 52 | FROM users 53 | WHERE email IN (${['test@email.me']}) 54 | ` 55 | .then(result => { 56 | console.assert(result.join(',') === '1,2,3', 'unexpected all result'); 57 | console.log('✔ all works'); 58 | }) 59 | .catch(exit); 60 | 61 | get` 62 | SELECT * 63 | FROM users 64 | WHERE email = ${'FAIL'} 65 | ` 66 | .catch(exit); 67 | 68 | 69 | const populate = transaction(); 70 | populate`INSERT INTO table VALUES (${1})`; 71 | populate`INSERT INTO table VALUES (${2})`; 72 | populate`INSERT INTO table VALUES (${3})`; 73 | populate.commit(); 74 | --------------------------------------------------------------------------------