├── .circleci └── config.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .snyk ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── package-lock.json ├── package.json ├── pouchy.code-workspace ├── readme.md ├── renovate.json ├── scripts └── bundle.js ├── src ├── __test__ │ ├── browser │ │ └── index.ts │ ├── common │ │ └── index.ts │ ├── demo.ts │ └── node │ │ ├── index.ts │ │ ├── server.ts │ │ └── util.ts ├── browser │ ├── path.ts │ └── perish.ts ├── index.ts └── to-underscore-prefix.ts ├── testem.js └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | machine: true 5 | steps: 6 | - checkout 7 | - restore_cache: 8 | keys: 9 | - v1-nvm-{{ checksum ".nvmrc" }} 10 | # fallback to using the latest cache if no exact match is found 11 | - v1-nvm- 12 | - run: 13 | name: Install node via .nvmrc 14 | command: | 15 | set +e 16 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash 17 | export NVM_DIR="/opt/circleci/.nvm" 18 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 19 | nvm install 20 | nvm use 21 | nvm alias default $(cat .nvmrc) 22 | 23 | # Each step uses the same `$BASH_ENV`, so need to modify it 24 | echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV 25 | echo "[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"" >> $BASH_ENV 26 | - save_cache: 27 | paths: 28 | - /opt/circleci/.nvm 29 | key: v1-nvm-{{ checksum ".nvmrc" }} 30 | - restore_cache: 31 | keys: 32 | - v1-dependencies-{{ checksum "package.json" }} 33 | # fallback to using the latest cache if no exact match is found 34 | - v1-dependencies- 35 | - run: 36 | name: install 37 | command: NODE_ENV=development npm install 38 | - save_cache: 39 | paths: 40 | - node_modules 41 | key: v1-dependencies-{{ checksum "package.json" }} 42 | # test 43 | - run: npm run build 44 | - run: npm run test 45 | - run: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 46 | - run: 47 | name: release 48 | command: npm run semantic-release || true 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | test-db* 30 | bundle* 31 | docs 32 | .nyc_output 33 | ./*.html 34 | *.html 35 | build 36 | bundle.js 37 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/__test__/** 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.15.1 2 | # we use node 8 because leveldown always has trouble 3 | # reliable keeping up with node@latest on all platforms 4 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.7.1 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | 'npm:ms:20170412': 7 | - pouchdb-core > pouchdb-debug > debug > ms: 8 | patched: '2017-05-26T00:54:04.749Z' 9 | 'npm:debug:20170905': 10 | - pouchdb-core > pouchdb-debug > debug: 11 | patched: '2017-09-29T00:54:07.800Z' 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Test Node", 8 | "program": "${workspaceRoot}/build/__test__/node/index.js", 9 | "runtimeVersion": "8.14.1", 10 | "skipFiles": [ 11 | "/**/*.js" 12 | ] 13 | }, 14 | { 15 | "name": "Launch", 16 | "type": "node", 17 | "request": "launch", 18 | "program": "${file}", 19 | "stopOnEntry": false, 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "preLaunchTask": null, 23 | "runtimeExecutable": null, 24 | "runtimeArgs": [ 25 | "--nolazy" 26 | ], 27 | "env": { 28 | "NODE_ENV": "development" 29 | }, 30 | "externalConsole": false, 31 | "sourceMaps": true, 32 | "outDir": null 33 | }, 34 | { 35 | "name": "Test - Node", 36 | "type": "node", 37 | "request": "launch", 38 | "program": "${workspaceRoot}/build/__test__/node/index.js", 39 | "stopOnEntry": false, 40 | "args": [], 41 | "cwd": "${workspaceRoot}", 42 | "preLaunchTask": null, 43 | "runtimeExecutable": null, 44 | "runtimeArgs": [ 45 | "--nolazy" 46 | ], 47 | "env": { 48 | "NODE_ENV": "development" 49 | }, 50 | "externalConsole": false, 51 | "sourceMaps": true, 52 | "outDir": null 53 | }, 54 | { 55 | "name": "Attach", 56 | "type": "node", 57 | "request": "attach", 58 | "port": 5858, 59 | "address": "localhost", 60 | "restart": false, 61 | "sourceMaps": false, 62 | "outDir": null, 63 | "localRoot": "${workspaceRoot}", 64 | "remoteRoot": null 65 | }, 66 | { 67 | "name": "Attach to Process", 68 | "type": "node", 69 | "request": "attach", 70 | "processId": "${command.PickProcess}", 71 | "port": 5858, 72 | "sourceMaps": false, 73 | "outDir": null 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": false 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christopher Dieringer 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouchy", 3 | "version": "14.0.0", 4 | "description": "A simple, opinionated interface for the amazing PouchDB", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "docs:build": "typedoc --name Pouchy --exclude '**/__test__/**,**/browser/**' --readme ./readme.md --out docs/ src/", 9 | "predocs:build": "rm -rf docs", 10 | "docs:publish": "gh-pages -d docs", 11 | "lint-staged": "lint-staged", 12 | "precommit": "run-p lint-staged snyk-protect", 13 | "postpublish": "run-s docs:build docs:publish", 14 | "preversion": "git checkout master && git pull", 15 | "publish-major": "npm run preversion && npm version major && git push origin master --tags && npm publish", 16 | "publish-minor": "npm run preversion && npm version minor && git push origin master --tags && npm publish", 17 | "publish-patch": "npm run preversion && npm version patch && git push origin master --tags && npm publish", 18 | "semantic-release": "semantic-release", 19 | "snyk-protect": "snyk protect", 20 | "test:browser": "testem ci -P 10", 21 | "test:node": "BLUEBIRD_WARNINGS=0 nyc -x '**/__test__/**' --reporter=lcov --check-coverage --functions=90 --branches=80 node build/__test__/node", 22 | "test": "npm-run-all -p test:*", 23 | "format": "prettier-standard '{src,test,scripts}/**/*.{js,jsx,ts,tsx}'", 24 | "lint": "standardx '{src,test,scripts}/**/*.{js,jsx,ts,tsx}' --fix" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/cdaringe/pouchy.git" 29 | }, 30 | "keywords": [ 31 | "pouchdb", 32 | "pouch", 33 | "wrapper", 34 | "api" 35 | ], 36 | "author": "cdaringe", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/cdaringe/pouchy/issues" 40 | }, 41 | "homepage": "https://github.com/cdaringe/pouchy#readme", 42 | "devDependencies": { 43 | "@babel/core": "7.14.6", 44 | "@babel/polyfill": "7.12.1", 45 | "@babel/preset-env": "7.14.7", 46 | "@types/blue-tape": "0.1.33", 47 | "@types/bluebird": "3.5.36", 48 | "@types/fs-extra": "9.0.12", 49 | "@types/lodash": "4.14.171", 50 | "@types/pouchdb": "6.4.0", 51 | "@types/pouchdb-adapter-leveldb": "6.1.3", 52 | "@types/tape": "4.13.1", 53 | "@typescript-eslint/eslint-plugin": "4.28.2", 54 | "@typescript-eslint/parser": "4.28.2", 55 | "babelify": "10.0.0", 56 | "blue-tape": "1.0.0", 57 | "browserify": "17.0.0", 58 | "coveralls": "3.1.1", 59 | "eslint-plugin-typescript": "0.14.0", 60 | "fs-extra": "10.0.0", 61 | "gh-pages": "3.2.3", 62 | "husky": "7.0.1", 63 | "jsdock": "1.0.4", 64 | "lint-staged": "11.0.0", 65 | "npm-run-all": "4.1.5", 66 | "nyc": "15.1.0", 67 | "perish": "1.0.3", 68 | "pouchdb-adapter-leveldb": "7.2.2", 69 | "pouchdb-adapter-memory": "7.2.2", 70 | "pouchdb-adapter-websql": "7.0.0", 71 | "prettier-standard": "16.4.1", 72 | "semantic-release": "17.4.4", 73 | "snyk": "1.657.0", 74 | "spawn-pouchdb-server": "3.3.3", 75 | "standardx": "7.0.0", 76 | "tape": "5.2.2", 77 | "testem": "3.4.2", 78 | "typedoc": "0.21.2", 79 | "typedoc-plugin-external-module-name": "4.0.6", 80 | "typescript": "4.3.5" 81 | }, 82 | "dependencies": { 83 | "bluebird": "^3.5.3", 84 | "lodash": "^4.17.4", 85 | "path": "0.12.7", 86 | "pouchdb-adapter-http": "^7.0.0", 87 | "pouchdb-core": "^7.0.0", 88 | "pouchdb-find": "^7.0.0", 89 | "pouchdb-replication": "^7.0.0", 90 | "url": "0.11.0" 91 | }, 92 | "browser": { 93 | "perish": "./build/browser/perish.js", 94 | "path": "./build/browser/path.js" 95 | }, 96 | "snyk": true, 97 | "husky": { 98 | "hooks": { 99 | "pre-commit": "npm run precommit" 100 | } 101 | }, 102 | "lint-staged": { 103 | "{src,test,scripts}/**/*.{js,jsx,ts,tsx}": [ 104 | "npm run format", 105 | "git add" 106 | ] 107 | }, 108 | "standardzzz": { 109 | "parser": "typescript-eslint-parser", 110 | "plugins": [ 111 | "typescript" 112 | ], 113 | "ignore": [ 114 | "build", 115 | "bundle.js" 116 | ] 117 | }, 118 | "eslintConfig": { 119 | "rules": { 120 | "no-unused-vars": 0 121 | } 122 | }, 123 | "standardx": { 124 | "parser": "@typescript-eslint/parser", 125 | "plugins": [ 126 | "@typescript-eslint/eslint-plugin" 127 | ], 128 | "ignore": [ 129 | "**/*.d.ts" 130 | ] 131 | }, 132 | "nyc": { 133 | "extension": [ 134 | ".ts" 135 | ], 136 | "exclude": [ 137 | "**/__test__/**" 138 | ] 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pouchy.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "git.ignoreLimitWarning": true, 9 | "emeraldwalk.runonsave": { 10 | "autoClearConsole": true, 11 | "commands": [ 12 | { 13 | "match": "\\.[jt]sx?$", 14 | "cmd": "npx standard --fix ${file}" 15 | } 16 | ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pouchy 2 | 3 | > [_"Pouchy is the sugar API for PouchDB that I've been hoping someone would write"_](https://twitter.com/nolanlawson/status/647224028529299456) 4 | > @nolanlawson 5 | 6 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![CircleCI](https://circleci.com/gh/cdaringe/pouchy.svg?style=svg)](https://circleci.com/gh/cdaringe/pouchy) [![Coverage Status](https://coveralls.io/repos/github/cdaringe/pouchy/badge.svg?branch=master)](https://coveralls.io/github/cdaringe/pouchy?branch=master) [![TypeScript package](https://img.shields.io/badge/typings-included-blue.svg)](https://www.typescriptlang.org) 7 | 8 | [![NPM](https://nodei.co/npm/pouchy.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/pouchy/) 9 | 10 | 11 | simple, enhanced [PouchDB](https://github.com/pouchdb/pouchdb). `Pouchy` wraps & extends `PouchDB` and provides various sorely needed sugar methods. further, it assists by standardizing the returned document format. most methods provided are _very_ simple PouchDB-native method modifiers, but are targeted to save you frequent boilerplate re-typing! this library also proxies the PouchDB API directly, so you can use it like a PouchDB instance itself! 12 | 13 | ## install 14 | 15 | `yarn add pouchy` 16 | 17 | if you need node support to flush databases to disk, versus using pouchy as an 18 | api client to remote couch/pouches, add your preferred adapters too. e.g.: 19 | 20 | `yarn add pouchy pouchdb-adapter-leveldb` 21 | 22 | ## usage 23 | 24 | api docs and examples officially live [here](http://cdaringe.github.io/pouchy/). 25 | 26 | here are some basic examples: 27 | 28 | ```ts 29 | // local, node database 30 | import Pouchy from 'pouchy' 31 | import level from 'pouchdb-adapter-leveldb' 32 | Pouchy.plugin(level) 33 | type Fruit = { type: string, tastes: string } 34 | const fruit = new Pouchy({ name: 'fruit' }) 35 | const orange = await fruit.save({ type: 'orange', tastes: 'delicious' }) 36 | console.log(orange) 37 | 38 | /** 39 | { 40 | type: 'orange', 41 | tastes: 'delicious', 42 | _id: 'EA1F2B55-2482-89C6-907E-6F938C59F734', 43 | _rev: '1-f60b9b7d775a89d280e1f4c07122b863' 44 | } 45 | */ 46 | ``` 47 | 48 | ```js 49 | // local & remote replicated database! 50 | import Pouchy from 'pouchy' 51 | import memory from 'pouchdb-adapter-memory' 52 | Pouchy.plugin(memory) 53 | 54 | const customers = new Pouchy({ 55 | name: 'customers', 56 | replicate: 'sync', 57 | url: 'http://mydomain.org/db/customers' 58 | }) 59 | customers.save({ firstName: 'bill', lastName: 'brasky' }) 60 | // wait for it... and couchdb/pouchdb-server @ http://mydomain.org/db/customers 61 | // will receive bill brasky! 62 | ``` 63 | 64 | ## why 65 | 66 | why use `pouchy` over `pouchdb`? 67 | 68 | - because managing `_id` and `_rev` can be obnoxious with pouchdb (no hard feelings, of course). 69 | - pouchdb methods return document `_id`s and `_rev`s inconsistently. some methods return docs with an `id` attribute. some return docs with `_id`. the same happens for `rev`. 70 | - different methods return `_rev` nested under _other attributes_, vs. being at the top of the document. 71 | - pouchy lets you get your documents back _in the same way they are represented in the store_. if you are expecting an `_id` and a `_rev` in a return result, you'll get those attributes back on the top of your documents, every time. 72 | - because you need some frequently used sugar methods that aren't keys-included from pouchdb. **there are many sugar methods available**, make sure to check out the API docs! 73 | - e.g. `.all()`, to get all full documents in your store, in a simple array. 74 | - e.g. `.clear()/.deleteAll()` to purge your store of its docs. 75 | - because you want `.find` to return simply an array of docs! 76 | - note: pouchy pre-loads the `pouchdb-find` plugin, which is super handy and regularly recommended for use. 77 | - because you want to pre-define \*ouchdb synchronization behavior on construction! start syncing pronto, declaratively! 78 | 79 | Thanks! [cdaringe](http://cdaringe.com/) 80 | 81 | # changelog 82 | 83 | - 12.3.+ - switched to semantic-release. please view the "Releases" GitHub tab 84 | - 12.0.0 85 | - refactor all of the things! 86 | - better handle all `rev/id` ==> `_rev/_id` mapping 87 | - **if `rev` or `id` exist on docs returned from pouch exist, but no `_rev` or `_id` exist, that particular kv pair will be moved to the `_`-prefixed key and the non prefixed key will be removed**. 88 | - more tests! 89 | - 11.0.2 90 | - drop es6 content. es5 friendly-ify! 91 | - 11.0.0 92 | - pouchdb 6.0.5! 93 | - 10.1.0 94 | - expose replication options via `getReplicationOptions` 95 | - on `destroy`, _actually_ destroy gracefully. that means, don't resolve the promise/callback until `live` dbs have all http requests fully settled. see [here](https://github.com/pouchdb/express-pouchdb/issues/316#issuecomment-241247448) for more info. 96 | - 10.0.4 - be compatible with latest bluebird `.asCallback`. 97 | - 10.0.0 - migrate to PouchDB 5.4.x. @NOTE, some APIs are not available by default anymore. See [the custom build](https://pouchdb.com/custom.html) blog post on how to add features to your pouch `Pouchy.PouchDB.plugin(...)`. The following plugins are available by default: 98 | - pouchdb-adapter-http 99 | - pouchdb-find 100 | - pouchdb-replication 101 | - 9.0.2 - fix `bulkGet` when no docs are provided 102 | - 9.0.0-1 103 | - fix `.all({ include_docs: false })` to properly handle `.rev/._rev` 104 | - improve docs! 105 | - 8.0.5 - fix issues w/ promise/cbs. sorry for 8.0.x-8.0.5 churn! 106 | - 8.0.0 - support cb & promise interface. added bluebird to make this seamless and less verbose 107 | - 7.1.0 - add `hasLikelySynced` event 108 | - 7.0.0 - modify replicate API. dropped `'both'` sync option, added `{}` option. dropped `replicateLive` 109 | - 6.3.0 - add `destroy`, which `.cancel`s any replication from `.syncEmitter` (see `replicate`). deprecate 6.2.0-1. changeEmitter => syncEmitter (rapid patch, so no major bump) 110 | - 6.2.1 - add `this.syncEmitter` when using the `replicate` API 111 | - 6.1.0 - add `bulkGet` 112 | - 6.0.6 - fix issue where `_id` was still `id` when doing `.all({ include_docs: false })` 113 | - 6.0.4 - fix replication issue where db backend not honored 114 | - 6.0.0 - db will store locally via leveldown as `name` if passed. `url` will still be used for replication if requested. prior versions preferred `url` to the Pouch constructor over name 115 | - 5.2.1 - permit / in couchdb db name 116 | - 5.2.0 - bump with couch 117 | - 5.1.0 - deps bump & add cb interface 118 | - 5.0.0 - deps bump only. ~~all future releases with track major version #s with PouchDB~~ 119 | - 4.0.0 - major bump with PouchDB 120 | - 3.0.0 - remove default `changes`, and associated `on/off`. didn't work out-of-the-box anyway. may return in 4.x 121 | - 2.0.1 - Don't modify constructor opts.name 122 | - 2.0.0 - Fix synced db `fs` location. Previously was not honoring `path` option 123 | - 1.0.0 - 2.0.1 pouchdb-wrapper => pouchy 124 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "automerge": true, 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/bundle.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var browserify = require('browserify') 4 | browserify(path.resolve(__dirname, '../build/__test__/browser/index.js')) 5 | .transform('babelify', { presets: ['@babel/preset-env'] }) 6 | .bundle() 7 | .pipe(fs.createWriteStream(path.resolve(__dirname, '../bundle.js'))) 8 | -------------------------------------------------------------------------------- /src/__test__/browser/index.ts: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill' 2 | import common from '../common/' 3 | global.Promise = require('bluebird') 4 | common({}) 5 | -------------------------------------------------------------------------------- /src/__test__/common/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | import test from 'blue-tape' 3 | import Pouchy, { PouchyOptions, MaybeSavedPouchDoc } from '../../' 4 | require('perish') 5 | 6 | export type TestDbData = { 7 | id?: string 8 | rev?: string 9 | test?: string 10 | } 11 | 12 | export default function (opts: any) { 13 | opts = opts || {} 14 | Pouchy.plugin(require('pouchdb-adapter-memory')) 15 | Pouchy.plugin(require('pouchdb-adapter-websql')) 16 | var pouchyFactory: (...args: any[]) => Pouchy = 17 | opts.pouchyFactory || 18 | opts.factory || 19 | function (opts: PouchyOptions) { 20 | return new Pouchy(opts) 21 | } 22 | 23 | var couchdbInvalidName = 'TEsT dB' 24 | var couchdbInvalidUrl = 'https://www.me.org/eeek/invalidPathname' 25 | var couchdbInvalidConn = { 26 | protocol: 'https', 27 | hostname: 'localhost', 28 | port: 3001, 29 | pathname: 'invalidPathname' 30 | } 31 | var conn = { 32 | protocol: 'https', 33 | hostname: 'localhost', 34 | port: 3001, 35 | pathname: 'validpathname' 36 | } 37 | var p: Pouchy 38 | 39 | test('constructor', function (t) { 40 | t.plan(7) 41 | try { 42 | p = new Pouchy(null as any) 43 | t.fail('pouchy requires input') 44 | } catch (err) { 45 | t.pass('requires args') 46 | } 47 | 48 | // name requirement 49 | try { 50 | pouchyFactory({}) 51 | t.fail('pouchdb didnt have name') 52 | } catch (err) { 53 | t.ok(true, 'enforced name') 54 | } 55 | 56 | p = new Pouchy({ name: 'nameonly', pouchConfig: { adapter: 'memory' } }) 57 | t.ok(p, 'name only db ok') 58 | 59 | // invalid name 60 | try { 61 | p = pouchyFactory({ name: couchdbInvalidName }) 62 | } catch (err) { 63 | t.ok(true, 'Errored on couchdbInvalidName') 64 | } 65 | 66 | // invalid url 67 | try { 68 | pouchyFactory({ name: couchdbInvalidUrl }) 69 | } catch (err) { 70 | t.ok(true, 'Errored on couchdbInvalidUrl') 71 | } 72 | 73 | // invalid conn 74 | try { 75 | pouchyFactory({ conn: couchdbInvalidConn }) 76 | } catch (err) { 77 | t.ok(true, 'Errored on couchdbInvalidUrl') 78 | } 79 | 80 | // conn building url 81 | var pFail = pouchyFactory({ conn: conn }) 82 | t.ok(pFail.url, 'conn url built successfully') 83 | }) 84 | 85 | test('get, all, add, save, delete', async t => { 86 | const docs: (TestDbData & MaybeSavedPouchDoc)[] = [ 87 | { _id: 'test-doc-1', test: 'will put on `add` with _id' }, 88 | { id: 'test-doc-2', test: 'will post on `add` without _id' }, 89 | { _id: 'test-doc-3', test: 'will put on `save` with _id' }, 90 | { _id: 'test-doc-4', test: 'dummyVal' } 91 | ] 92 | p = pouchyFactory({ name: 'testdb-' + Date.now() }) 93 | const added1 = await p.add(docs[0]) // add1 94 | t.equal(docs[0]._id, added1._id, '.add kept _id via put') 95 | docs[0] = added1 96 | const docGet0 = await p.get(docs[0]._id!) 97 | t.equals(docs[0]._id, docGet0._id, 'basic get') 98 | const added2 = await p.add(docs[1]) 99 | docs[1] = added2 100 | t.notEqual(added2._id, 'test-doc-2', 'id === _id') 101 | await Promise.all([p.add(docs[2]), p.add(docs[3])]) 102 | let r = await p.all() 103 | t.equal(r.length, docs.length, 'all, include_docs: true (promise mode)') 104 | t.equal(r[3].test, docs[3].test, 'actual docs returned by .all') 105 | r = await p.all({ include_docs: false }) 106 | t.equal(r.length, docs.length, 'all, include_docs: false (promise mode)') 107 | const deleted = await p.delete(added1) 108 | t.true(deleted.ok, 'deleted ok') 109 | }) 110 | 111 | test('getMany', async t => { 112 | p = pouchyFactory({ name: 'test_db_' + Date.now() }) 113 | var dummyDocs: any[] = [{ _id: 'a', data: 'a' }, { _id: 'b', data: 'b' }] 114 | return Promise.resolve() 115 | .then(() => p.getMany(null as any)) 116 | .catch(err => t.ok(err.message.match(/getMany/))) 117 | .then(() => p.getMany({} as any)) 118 | .catch(err => t.ok(err.message.match(/getMany/))) 119 | .then(() => p.getMany([])) 120 | .then(docs => t.equal(docs.length, 0, 'empty set passed on getMany')) 121 | .then(() => p.save(dummyDocs[0])) 122 | .then(doc => (dummyDocs[0] = doc)) 123 | .then(() => p.save(dummyDocs[1])) 124 | .then(doc => (dummyDocs[1] = doc)) 125 | .then(() => 126 | dummyDocs.map(dummy => ({ _id: dummy._id, _rev: (dummy as any)._rev })) 127 | ) 128 | .then(toFetch => p.getMany(toFetch as any)) 129 | .then(docs => 130 | t.deepEqual(docs, dummyDocs, 'getMany returns sane results') 131 | ) 132 | .then(() => p.getMany([{ _id: 'bananas' }])) 133 | .catch(err => t.ok(err, 'errors when _id not in getMany result set')) 134 | }) 135 | 136 | test('indicies & find', async t => { 137 | p = pouchyFactory({ name: 'testdb-indicies-' + Date.now() }) 138 | const index: any = await p.upsertIndex('testSingleIndex') 139 | t.ok(index.name, 'single index ok') 140 | const indexResults = await p.createIndicies('test') 141 | await p.createIndicies('test') // prove that it won't error 142 | t.ok(indexResults, 'indicies created') 143 | const bulkRes = await p.bulkDocs([ 144 | { test: 't1', _id: 'doc1' }, 145 | { test: 't2', _id: 'doc2' } 146 | ]) 147 | t.ok(bulkRes.length === 2, '2 docs bulk added') 148 | t.ok(bulkRes[0]._id, '_id transformed on pouch native proxied call') 149 | const result = await p.findMany({ 150 | selector: { test: 't2' }, 151 | fields: ['_id'] 152 | }) 153 | t.equal('doc2', result[0]._id, 'find on index') 154 | const info = await p.info() 155 | t.ok(info, 'proxy method ok') 156 | t.ok(info.db_name, 'info reports back db_name') 157 | }) 158 | 159 | test('update', async t => { 160 | p = pouchyFactory({ name: 'testdb-' + Date.now() }) 161 | var rev: string 162 | t.plan(3) 163 | const doc = await p.add({ test: 'update-test' }) 164 | rev = doc._rev 165 | doc.test = 'new-value' 166 | const updatedDoc = await p.update(doc) 167 | t.notOk(rev === updatedDoc._rev, 'update updates _rev') 168 | t.equal(updatedDoc.test, 'new-value', 'update actually updates') 169 | await p.clear() 170 | const docs = await p.all() 171 | t.equal(0, docs.length, 'docs cleared') 172 | }) 173 | 174 | test('proxies loaded', function (t) { 175 | p = pouchyFactory({ name: 'blah' + Date.now() }) 176 | t.ok(p.info, 'proxied function present') 177 | t.end() 178 | }) 179 | 180 | test('contructor proxied method', function (t) { 181 | Pouchy.defaults({ 182 | prefix: '/dummy-prefix', 183 | adapter: 'memory' 184 | }) 185 | t.pass('defaults applied') 186 | t.end() 187 | }) 188 | } 189 | -------------------------------------------------------------------------------- /src/__test__/demo.ts: -------------------------------------------------------------------------------- 1 | import Pouchy from '..' 2 | import memory from 'pouchdb-adapter-memory' 3 | require('perish') 4 | Pouchy.plugin(memory) 5 | 6 | type DemoMovieSchema = { 7 | title: string 8 | stars: number 9 | } 10 | 11 | async function runDemo () { 12 | const pouch = new Pouchy({ 13 | name: 'demodb' 14 | }) 15 | const saved = await pouch.save({ 16 | title: 'LOTR', 17 | stars: 3 18 | }) 19 | console.log(saved) 20 | const all = await pouch.all() 21 | all.forEach((doc, i) => console.log(`doc "${i}":`, doc)) 22 | const updated = await pouch.save({ 23 | ...saved, 24 | title: 'LOTRZ', 25 | stars: 5 26 | }) 27 | const allUpdated = await pouch.all() 28 | console.log(`total docs should equal 1: ${allUpdated.length}`) 29 | await pouch.save({ 30 | title: 'spiderman 8million', 31 | stars: 0 32 | }) 33 | const allUpdated2 = await pouch.all() 34 | console.log(`total docs should equal 2: ${allUpdated2.length}`) 35 | } 36 | 37 | runDemo() 38 | -------------------------------------------------------------------------------- /src/__test__/node/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | factory, 3 | mkdir, 4 | rmrf, 5 | setup, 6 | timeoutDeferred, 7 | teardown, 8 | testDir 9 | } from './util' 10 | import common from '../common' 11 | import fs from 'fs-extra' 12 | import path from 'path' 13 | import test from 'blue-tape' 14 | require('perish') 15 | 16 | test('setup', async t => { 17 | await setup() 18 | t.pass('intial test setup') 19 | }) 20 | 21 | common({ factory }) 22 | 23 | test('ondisk db', t => { 24 | t.test('basic pathing', async t => { 25 | var name = 'test_path_as_relative_db_name' 26 | await factory({ name }).save({ _id: 'test-path' }) 27 | const lstat = await fs.lstat(path.resolve(testDir, name)) 28 | t.ok(lstat.isDirectory, 'db path honored') 29 | }) 30 | 31 | t.test('custom pathing', async t => { 32 | var customDir = path.join(__dirname, 'custom-db-path') 33 | await rmrf(customDir) 34 | await mkdir(customDir) 35 | const db = factory({ name: 'custom-dir-db', path: customDir }) 36 | await db.save({ _id: 'custom-path-test' }) 37 | const stat = await fs.stat(path.join(customDir, 'custom-dir-db', 'LOG')) 38 | t.ok(stat, 'custom db paths') 39 | await rmrf(customDir) 40 | }) 41 | 42 | t.test('gracefully handle slashes', async t => { 43 | var name = 'db/with/slash' 44 | var db = factory({ name }) 45 | t.equals(db.name, name, 'allows / in db name') 46 | try { 47 | await db.save({ _id: 'slash-test' }) 48 | return t.end('permitted writing db with slashes/in/db/name to disk') 49 | } catch (err) { 50 | t.pass('forbids writing dbs with slashes/in/name to disk') 51 | } 52 | }) 53 | }) 54 | 55 | test('sync', t => { 56 | t.test('syncEmitter emits errors', async t => { 57 | const deferred = timeoutDeferred() 58 | var url = 'http://www.bogus-sync-db.com/bogusdb' 59 | var db = factory({ url, replicate: 'sync' }) 60 | var handled = false 61 | var handleNaughtySyncEvent = function (evt: any) { 62 | if (handled) return 63 | handled = true 64 | t.pass('paused handler (retry default)') 65 | return db.destroy().catch((err: any) => { 66 | t.ok( 67 | ['ENOTFOUND', 'ECONNRESET'].some(code => code === err.code), 68 | 'pauses/errors on destroy on invalid remote db request' 69 | ) 70 | deferred.resolve() 71 | }) 72 | } 73 | db.syncEmitter!.on('paused', handleNaughtySyncEvent) 74 | db.syncEmitter!.on('error', handleNaughtySyncEvent) 75 | return deferred.promise 76 | }) 77 | 78 | t.test('custom replication options', async t => { 79 | var url = 'http://www.bogus-sync-db.com/bogusdb' 80 | var replicate = { sync: { live: true, heartbeat: 1, timeout: 1 } } 81 | var db = factory({ url, replicate }) 82 | const deferred = timeoutDeferred() 83 | db.syncEmitter!.on('error', async () => { 84 | t.pass('syncEmitter enters error on bogus url w/out') 85 | try { 86 | await db.destroy() 87 | } catch (err) { 88 | t.ok( 89 | ['ENOTFOUND', 'ECONNRESET'].some(code => code === err.code), 90 | 'errors on destroy on invalid remote db request' 91 | ) 92 | deferred.resolve() 93 | } 94 | }) 95 | return deferred.promise 96 | }) 97 | }) 98 | 99 | test('teardown', async t => { 100 | await teardown() 101 | t.pass('tests teardown') 102 | }) 103 | -------------------------------------------------------------------------------- /src/__test__/node/server.ts: -------------------------------------------------------------------------------- 1 | const url = require('url') 2 | 3 | const config = { 4 | backend: false, // e.g. memdown 5 | config: { file: false }, 6 | log: { file: false }, 7 | port: 5989, 8 | timeout: 10000, // in ms 9 | verbose: false 10 | } 11 | 12 | const pdbs = require('spawn-pouchdb-server') 13 | const cp = require('child_process') 14 | const cloneDeep = require('lodash/cloneDeep') 15 | 16 | /** 17 | * @function diehard 18 | * @description handle setup/teardown errors mercilessly. kill the process 19 | * and abandon tests 20 | */ 21 | const diehard = (err: any) => { 22 | console.error(err.message) 23 | console.error(err.stack) 24 | process.exit(1) 25 | } 26 | 27 | export function dbURL (dbname: string) { 28 | if (!dbname) { 29 | throw new ReferenceError('dbname required') 30 | } 31 | return url.format({ 32 | protocol: 'http', 33 | hostname: 'localhost', 34 | port: config.port, 35 | pathname: dbname 36 | }) 37 | } 38 | 39 | let hackStatefulServer: any 40 | 41 | /** 42 | * @function setup 43 | * @description boots a pouchdb-server, a dbRegistry instance, and 44 | * a computation registry instance. these utilities are commonly 45 | * required for PipelineRunnerPool testing 46 | */ 47 | export function setup () { 48 | return new Promise((resolve, reject) => { 49 | try { 50 | cp.execSync( 51 | `lsof -i :${config.port} | awk 'NR!=1 {print $2}' | xargs kill` 52 | ) 53 | } catch (err) { 54 | // return rej(err) // permit failure 55 | } 56 | 57 | // spawn-pouchdb-server mutates user input >:( 58 | // https://github.com/hoodiehq/spawn-pouchdb-server/pull/33 59 | pdbs(cloneDeep(config), (err: any, srv: any) => { 60 | if (err) diehard(err) 61 | hackStatefulServer = srv 62 | return resolve(srv) 63 | }) 64 | }) 65 | } 66 | 67 | export function teardown () { 68 | return new Promise((resolve, reject) => { 69 | return hackStatefulServer.stop((err: any) => { 70 | if (err) { 71 | diehard(err.message) 72 | } 73 | return resolve() 74 | }) 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /src/__test__/node/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | import { TestDbData } from '../common' 3 | import bb from 'bluebird' 4 | import fs from 'fs-extra' 5 | import path from 'path' 6 | import Pouchy, { PouchyOptions } from '../../' // eslint-disable-line 7 | import level from 'pouchdb-adapter-leveldb' 8 | // load node test plugins 9 | bb.config({ warnings: false }) 10 | Pouchy.PouchDB.plugin(level).plugin(require('pouchdb-adapter-memory')) 11 | 12 | export const factory = function (opts: PouchyOptions) { 13 | if (!opts.path) opts.path = testDir 14 | opts.couchdbSafe = true 15 | return new Pouchy(opts) 16 | } 17 | export const mkdir = (dir: string) => 18 | fs.mkdirp(dir[0] === '.' ? path.join(__dirname, dir) : dir) 19 | export const rmrf = (dir: string) => mkdir(dir).then(() => fs.remove(dir)) 20 | export const setup = () => rmrf(testDir).then(() => mkdir(testDir)) 21 | export const teardown = () => rmrf(testDir) 22 | export const testDir = path.join(__dirname, './_testdb-dir') 23 | export function timeoutDeferred () { 24 | const deferred = bb.defer() 25 | const timeout = setTimeout( 26 | () => deferred.reject(new Error('timeout')), 27 | (process.env.NODE_ENV || '').match(/dev/) ? 30000 : 4000 28 | ) 29 | return { 30 | promise: deferred.promise, 31 | resolve: (value?: any) => { 32 | clearTimeout(timeout) 33 | deferred.resolve(value) 34 | }, 35 | reject: deferred.reject 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/browser/path.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: function resolve (root: any, name: any) { 3 | return root + name 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/browser/perish.ts: -------------------------------------------------------------------------------- 1 | module.exports = '' 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | /** 3 | * @module Pouchy 4 | */ 5 | import { toUnderscorePrefix } from './to-underscore-prefix' 6 | import adapterFind from 'pouchdb-find' 7 | import adapterHttp from 'pouchdb-adapter-http' 8 | import adapterReplication from 'pouchdb-replication' 9 | import defaults from 'lodash/defaults' 10 | import get from 'lodash/get' 11 | import isNil from 'lodash/isNil' 12 | import path from 'path' 13 | import PouchDB from 'pouchdb-core' 14 | import unique from 'lodash/uniq' 15 | import url, { UrlObject } from 'url' 16 | 17 | PouchDB.plugin(adapterHttp) 18 | .plugin(adapterFind) 19 | .plugin(adapterReplication) 20 | 21 | export type MaybeSavedPouchDoc = { 22 | _id?: PouchDB.Core.DocumentId 23 | _rev?: PouchDB.Core.RevisionId 24 | } 25 | 26 | export type SavedPouchDoc = { 27 | _id: PouchDB.Core.DocumentId 28 | _rev: PouchDB.Core.RevisionId 29 | } 30 | 31 | /** 32 | * @private 33 | */ 34 | type FirstArgument = T extends (arg1: infer U, ...args: any[]) => any 35 | ? U 36 | : any 37 | 38 | export const couchUrlify = (url: string) => 39 | url.replace(/[^/a-z0-9_$()+-]/gi, '') 40 | export const POUCHY_API_DOCS_URI = 'https://cdaringe.github.io/pouchy' 41 | 42 | export type PouchyOptions = { 43 | conn?: UrlObject // creates `url` using the awesome and simple [url.format](https://www.npmjs.com/package/url#url-format-urlobj) 44 | couchdbSafe?: boolean // default: true. asserts that `name` provided or `url` provided will work with couchdb. tests by asserting str conforms to [couch specs](https://wiki.apache.org/couchdb/HTTP_database_API#Naming_and_Addressing), minus the `/`. This _may complain that some valid urls are invalid_. Please be aware and disable if necessary. 45 | name?: string // name of db. recommended for most dbs. calculated from derived url string if `conn` or `url` provided. otherwise, required 46 | path?: string // path to store db on filesystem, if using a filesystem adapter. defaults to _PouchDB_'s default of `cwd` if not specified 47 | pouchConfig?: PouchDB.Configuration.DatabaseConfiguration // PouchDB constructor input [options](http://pouchdb.com/api.html#create_database). be mindful of pouchy options you set, because they may comingle :) 48 | /** 49 | * in object form you can try `{ out/in/sync: ... }` where ... refers to the 50 | * [official PouchDB replication options](http://pouchdb.com/api.html#replication). in string form, simply provide 51 | * 'out/in/sync'. please note that the string shorthand applies default 52 | * heartbeat/retry options. 53 | */ 54 | replicate?: 55 | | string 56 | | { 57 | out?: PouchDB.Replication.ReplicateOptions 58 | in?: PouchDB.Replication.ReplicateOptions 59 | sync?: PouchDB.Replication.ReplicateOptions 60 | } 61 | replicateLive?: boolean // default: true. activates only if `replicate` is set 62 | url?: string // url to remote CouchDB. user may use the `conn` option instead as well 63 | } 64 | 65 | export class Pouchy { 66 | static PouchDB = PouchDB 67 | static plugin = PouchDB.plugin 68 | static defaults = PouchDB.defaults 69 | static debug = PouchDB.debug 70 | 71 | // tap into your instance's replication `changes()` so you may listen to events. 72 | // calling `.destroy` will scrap this emitter. emitter only present when 73 | // `replicate` options intially provided 74 | public syncEmitter: 75 | | PouchDB.Replication.Replication 76 | | PouchDB.Replication.Sync 77 | | null = null 78 | 79 | public db: PouchDB.Database // internal PouchDB instance 80 | public hasLocalDb: boolean 81 | public isEnforcingCouchDbSafe: boolean 82 | public url: string | null 83 | public path: string | null 84 | private _replicationOpts: PouchDB.Replication.ReplicateOptions | null = null 85 | 86 | constructor (opts: PouchyOptions) { 87 | this._validatePouchyOpts(opts) 88 | this.isEnforcingCouchDbSafe = isNil(opts.couchdbSafe) 89 | ? true 90 | : opts.couchdbSafe 91 | this.url = this._getUrlFromOpts(opts) 92 | this.hasLocalDb = !!opts.name 93 | /* istanbul ignore next */ 94 | if (!this.url && !this.hasLocalDb) { 95 | throw new Error('remote database requires url') 96 | } 97 | this.name = this.hasLocalDb 98 | ? this._setDbNameFromOpts(opts) 99 | : this._setDbNameFromUri(this.url!) 100 | if (this.isEnforcingCouchDbSafe) this._validateDbName() 101 | this.path = this.hasLocalDb 102 | ? path.resolve(opts.path || '', this.name) 103 | : null 104 | this.db = new PouchDB( 105 | opts.name ? this.path! : this.url!, 106 | opts.pouchConfig 107 | ) 108 | if (opts.replicate) this._handleReplication(opts.replicate) 109 | } 110 | 111 | /** 112 | * @private 113 | */ 114 | _validatePouchyOpts (opts: PouchyOptions) { 115 | if (!opts || (!opts.name && (!opts.url && !opts.conn))) { 116 | throw new ReferenceError( 117 | [ 118 | 'missing pouchy database paramters. please see: ' + 119 | POUCHY_API_DOCS_URI + 120 | '\n', 121 | '\tif you are creating a local database (browser or node), provide a `name` key.\n', 122 | '\tif you are just using pouchy to access a remote database, provide a `url` or `conn` key\n', 123 | '\tif you are creating a database to replicate with a remote database, provide a', 124 | '`url` or `conn` key, plus a replicate key.\n' 125 | ].join('') 126 | ) 127 | } 128 | /* istanbul ignore next */ 129 | if (opts.url && opts.conn) { 130 | throw new ReferenceError('provide only a `url` or `conn` option') 131 | } 132 | /* istanbul ignore next */ 133 | if (!this) { 134 | throw new ReferenceError('no `this` context. did you forget `new`?') 135 | } 136 | } 137 | 138 | /** 139 | * @private 140 | */ 141 | _handleReplication (opts: PouchyOptions['replicate']) { 142 | var replOpts: PouchDB.Replication.ReplicateOptions 143 | var mode: string 144 | /* istanbul ignore next */ 145 | if (!this.url) { 146 | throw new ReferenceError('url or conn object required to replicate') 147 | } 148 | /* istanbul ignore else */ 149 | if (typeof opts === 'string') { 150 | mode = opts 151 | replOpts = { live: true, retry: true } 152 | } else if (opts) { 153 | mode = Object.keys(opts)[0] 154 | /* istanbul ignore else */ 155 | if (mode in opts) { 156 | replOpts = (opts as any)[mode] 157 | } else { 158 | throw new Error(`mode "${mode}" is not a valid replication option`) 159 | } 160 | } else { 161 | throw new Error('invalid replication options') 162 | } 163 | this._replicationOpts = replOpts 164 | switch (mode) { 165 | /* istanbul ignore next */ 166 | case 'out': 167 | this.syncEmitter = this.db.replicate.to(this.url, replOpts) 168 | break 169 | /* istanbul ignore next */ 170 | case 'in': 171 | this.syncEmitter = this.db.replicate.from(this.url, replOpts) 172 | break 173 | case 'sync': 174 | this.syncEmitter = this.db.sync(this.url, replOpts) 175 | break 176 | default: 177 | /* istanbul ignore next */ 178 | throw new Error( 179 | [ 180 | "in/out replication direction must be specified, got '", 181 | mode + "'" 182 | ].join(' ') 183 | ) 184 | } 185 | } 186 | 187 | /** 188 | * @private 189 | */ 190 | _getUrlFromOpts (opts: PouchyOptions) { 191 | if (!isNil(opts.url)) return opts.url 192 | if (!isNil(opts.conn)) return url.format(opts.conn) 193 | return null 194 | } 195 | 196 | /** 197 | * @private 198 | */ 199 | _setDbNameFromUri (uri: string) { 200 | const pathname = url.parse(uri).pathname // eslint-disable-line 201 | /* istanbul ignore next */ 202 | if (!pathname && !this.name) { 203 | throw new Error( 204 | [ 205 | 'unable to infer database name from uri. try adding a pathname', 206 | 'to the uri (e.g. host.org/my-db-name) or pass a `name` option' 207 | ].join(' ') 208 | ) 209 | } 210 | var pathParts = (pathname || '').split('/') 211 | this.name = this.name || pathParts[pathParts.length - 1] 212 | return this.name 213 | } 214 | 215 | /** 216 | * @private 217 | */ 218 | _setDbNameFromOpts (opts: PouchyOptions) { 219 | /* istanbul ignore next */ 220 | if (!opts.name) { 221 | throw new Error('local pouchy database requires a `name` field') 222 | } 223 | return opts.name 224 | } 225 | 226 | /** 227 | * @private 228 | */ 229 | _validateDbName () { 230 | var couchDbSafeName = couchUrlify(this.name.toLowerCase()) 231 | if (this.name === couchDbSafeName) return 232 | throw new Error( 233 | [ 234 | 'database name may not be couchdb safe.', 235 | '\tunsafe name: ' + this.name, 236 | '\tsafe name: ' + couchDbSafeName 237 | ].join('\n') 238 | ) 239 | } 240 | 241 | /** 242 | * add a document to the db. 243 | * @see save 244 | * @example 245 | * // with _id 246 | * const doc = await p.add({ _id: 'my-sauce', bbq: 'sauce' }) 247 | * console.log(doc._id, doc._rev, doc.bbq); // 'my-sauce', '1-a76...46c', 'sauce' 248 | * 249 | * // no _id 250 | * const doc = await p.add({ peanut: 'butter' }) 251 | * console.log(doc._id, doc._rev, doc.peanut); // '66188...00BF885E', '1-0d74...7ac', 'butter' 252 | */ 253 | add (doc: Content & MaybeSavedPouchDoc): Promise { 254 | return this.save.apply(this, arguments as any) 255 | } 256 | 257 | /** 258 | * get all documents from db 259 | * @example 260 | * const docs = await p.all() 261 | * console.log(`total # of docs: ${docs.length}!`)) 262 | * 263 | * @example 264 | * const docs = await p.all({ includeDesignDocs: true }) 265 | */ 266 | async all ( 267 | allOpts?: FirstArgument['allDocs']> 268 | ): Promise<(Content & SavedPouchDoc)[]> { 269 | const opts = defaults(allOpts || {}, { include_docs: true }) 270 | const docs = await this.db.allDocs(opts) 271 | return docs.rows.reduce(function simplifyAllDocSet (r, v) { 272 | var doc: any = opts.include_docs ? v.doc : v 273 | // rework doc format to always have id ==> _id 274 | if (!opts.include_docs) { 275 | doc._id = doc.id 276 | doc._rev = doc.value.rev 277 | delete doc.id 278 | delete doc.value 279 | delete doc.key 280 | } 281 | ;(r as any).push(doc) 282 | return r 283 | }, []) 284 | } 285 | 286 | /** 287 | * The native bulkGet PouchDB API is not very user friendly. 288 | * In fact, it's down right wacky! 289 | * This method patches PouchDB's `bulkGet` and assumes that _all_ of your 290 | * requested docs exist. If they do not, it will error via the usual error 291 | * control flows. 292 | * 293 | * @example 294 | * // A good example of what you can expect is actually right out of the tests! 295 | * let dummyDocs = [ 296 | * { _id: 'a', data: 'a' }, 297 | * { _id: 'b', data: 'b' } 298 | * ] 299 | * Promise.resolve() 300 | * .then(() => p.save(dummyDocs[0])) // add our first doc to the db 301 | * .then((doc) => (dummyDocs[0] = doc)) // update our closure doc it knows the _rev 302 | * .then(() => p.save(dummyDocs[1])) 303 | * .then((doc) => (dummyDocs[1] = doc)) 304 | * .then(() => { 305 | * // prepare getMany query (set of { _id, _rev}'s are required) 306 | * const toFetch = dummyDocs.map(dummy => ({ 307 | * _id: dummy._id, 308 | * _rev: dummy._rev 309 | * // or you can provide .id, .rev 310 | * })) 311 | * p.getMany(toFetch) 312 | * .then((docs) => { 313 | * t.deepEqual(docs, dummyDocs, 'getMany returns sane results') 314 | * t.end() 315 | * }) 316 | * }) 317 | * @param {object|array} opts array of {_id, _rev}s, or { docs: [ ... } } where 318 | * ... is an array of {_id, _rev}s 319 | * @param {function} [cb] 320 | */ 321 | async getMany ( 322 | docMetas: { _id: string; _rev?: string | undefined }[] 323 | ): Promise<(Content & SavedPouchDoc)[]> { 324 | /* istanbul ignore else */ 325 | if (!docMetas || !Array.isArray(docMetas)) { 326 | throw new Error('getMany: missing doc metadatas') 327 | } 328 | const opts = { 329 | docs: docMetas.map(function remapIdRev (lastDoc: any) { 330 | const doc = { ...lastDoc } 331 | // we need to map back to id and rev here 332 | /* istanbul ignore else */ 333 | if (doc._id) doc.id = doc._id 334 | /* istanbul ignore else */ 335 | if (doc._rev) doc.rev = doc._rev 336 | delete doc._rev 337 | delete doc._id 338 | return doc 339 | }) 340 | } 341 | if (!opts.docs.length) return Promise.resolve([]) 342 | const r = await this.db.bulkGet(opts) 343 | return r.results.map(function tidyBulkGetDocs (docGroup) { 344 | var doc = get(docGroup, 'docs[0].ok') 345 | if (!doc) { 346 | throw new ReferenceError('doc ' + docGroup.id + 'not found') 347 | } 348 | return doc 349 | }) 350 | } 351 | 352 | /** 353 | * easy way to create a db index. 354 | * @see createIndicies 355 | * @example 356 | * await p.upsertIndex('myIndex') 357 | */ 358 | upsertIndex (indexName: string) { 359 | return this.createIndicies.apply(this, arguments as any) 360 | } 361 | 362 | /** 363 | * allow single or bulk creation of indicies. also, doesn't flip out if you've 364 | * already set an index. 365 | * @example 366 | * const indicies = await p.createIndicies('test') 367 | * console.dir(indicies) 368 | * // ==> 369 | * [{ 370 | * id: "_design/idx-28933dfe7bc072c94e2646126133dc0d" 371 | * name: "idx-28933dfe7bc072c94e2646126133dc0d" 372 | * result: "created" 373 | * }] 374 | */ 375 | createIndicies (indicies: string | string[]) { 376 | indicies = Array.isArray(indicies) ? indicies : [indicies] 377 | return ( 378 | this.db 379 | .createIndex({ index: { fields: unique(indicies) } }) 380 | /* istanbul ignore next */ 381 | .catch(function handleFailCreateIndicies (err) { 382 | /* istanbul ignore next */ 383 | if (err.status !== 409) throw err 384 | }) 385 | ) 386 | } 387 | 388 | /** 389 | * @see deleteAll 390 | * @returns {Promise} 391 | */ 392 | clear () { 393 | return this.deleteAll.apply(this, arguments as any) 394 | } 395 | 396 | /** 397 | * delete a document. 398 | * @example 399 | * // same as pouch.remove 400 | * const deleted = await p.delete(doc) 401 | * console.dir(deleted) 402 | * // ==> 403 | * { 404 | * id: "test-doc-1" 405 | * ok: true 406 | * rev: "2-5cf6a4725ed4b9398d609fc8d7af2553" 407 | * } 408 | */ 409 | delete (doc: PouchDB.Core.RemoveDocument, opts?: PouchDB.Core.Options) { 410 | return this.db.remove(doc, opts) 411 | } 412 | 413 | /** 414 | * clears the db of documents. under the hood, `_deleted` flags are added to docs 415 | */ 416 | async deleteAll () { 417 | var deleteSingleDoc = (doc: any) => this.delete(doc) 418 | const docs = await this.all() 419 | return Promise.all(docs.map(deleteSingleDoc)) 420 | } 421 | 422 | /** 423 | * Destroys the database. Proxies to pouchdb.destroy after completing 424 | * some internal cleanup first 425 | */ 426 | async destroy () { 427 | /* istanbul ignore next */ 428 | if (this.syncEmitter && !(this.syncEmitter).canceled) { 429 | let isSyncCancelledP = Promise.resolve() 430 | if (this._replicationOpts && this._replicationOpts.live) { 431 | // early bind the `complete` event listener. careful not to bind it 432 | // inside the .then, otherwise binding happens at the end of the event 433 | // loop, which is too late! `.cancel` is a sync call! 434 | isSyncCancelledP = new Promise((resolve, reject) => { 435 | if (!this.syncEmitter) { 436 | return reject(new Error('syncEmitter not found')) 437 | } 438 | this.syncEmitter.on('complete' as any, () => { 439 | resolve() 440 | }) 441 | }) 442 | } 443 | this.syncEmitter.cancel() // will trigger a `complete` event 444 | await isSyncCancelledP 445 | } 446 | return this.db.destroy() 447 | } 448 | 449 | /** 450 | * Similar to standard pouchdb.find, but returns simple set of results 451 | */ 452 | async findMany ( 453 | opts: FirstArgument> 454 | ): Promise<(Content & SavedPouchDoc)[]> { 455 | const rslt = await this.db.find(opts) 456 | return rslt.docs 457 | } 458 | 459 | /** 460 | * update a document, and get your sensibly updated doc in return. 461 | * 462 | * @example 463 | * const doc = await p.update({ _id: 'my-doc', _rev: '1-abc123' }) 464 | * console.log(doc) 465 | * // ==> 466 | * { 467 | * _id: 'my-doc', 468 | * _rev: '2-abc234' 469 | * } 470 | * @param {object} doc 471 | * @param {function} [cb] 472 | * @returns {Promise} 473 | */ 474 | async update ( 475 | doc: FirstArgument['put']> 476 | ): Promise { 477 | // http://pouchdb.com/api.html#create_document 478 | // db.put(doc, [docId], [docRev], [options], [callback]) 479 | const meta = await this.db.put(doc) 480 | doc._id = meta.id 481 | doc._rev = meta.rev 482 | return doc as any 483 | } 484 | 485 | /** 486 | * Adds or updates a document. If `_id` is set, a `put` is performed (basic add operation). If no `_id` present, a `post` is performed, in which the doc is added, and large-random-string is assigned as `_id`. 487 | * @example 488 | * const doc = await p.save({ beep: 'bop' }) 489 | * console.log(doc) 490 | * // ==> 491 | * { 492 | * _id: 'AFEALJW-234LKJASDF-2A;LKFJDA', 493 | * _rev: '1-asdblkue242kjsa0f', 494 | * beep: 'bop' 495 | * } 496 | */ 497 | async save ( 498 | doc: Content & MaybeSavedPouchDoc 499 | ): Promise { 500 | // http://pouchdb.com/api.html#create_document 501 | // db.post(doc, [docId], [docRev], [options], [callback]) 502 | /* istanbul ignore next */ 503 | var method = 504 | Object.prototype.hasOwnProperty.call(doc, '_id') && 505 | (doc._id || (doc as any)._id === 0) 506 | ? 'put' 507 | : 'post' 508 | const meta = 509 | method === 'put' 510 | ? await this.db.put(doc as any) 511 | : await this.db.post(doc as any) 512 | delete (meta as any).status 513 | doc._id = meta.id 514 | doc._rev = meta.rev 515 | return doc as any 516 | } 517 | 518 | /** 519 | * START POUCHDB IMPLEMENTATIONS FOR MIXIN SUPPORT 520 | * START POUCHDB IMPLEMENTATIONS FOR MIXIN SUPPORT 521 | * START POUCHDB IMPLEMENTATIONS FOR MIXIN SUPPORT 522 | */ 523 | 524 | /** 525 | * database name 526 | */ 527 | name: string 528 | 529 | /* istanbul ignore next */ 530 | /** Fetch all documents matching the given options. */ 531 | allDocs ( 532 | options?: 533 | | PouchDB.Core.AllDocsWithKeyOptions 534 | | PouchDB.Core.AllDocsWithKeysOptions 535 | | PouchDB.Core.AllDocsWithinRangeOptions 536 | | PouchDB.Core.AllDocsOptions 537 | ) { 538 | return {} as Promise> 539 | } 540 | 541 | /* istanbul ignore next */ 542 | /** 543 | * Create, update or delete multiple documents. The docs argument is an array of documents. 544 | * If you omit an _id parameter on a given document, the database will create a new document and assign the ID for you. 545 | * To update a document, you must include both an _id parameter and a _rev parameter, 546 | * which should match the ID and revision of the document on which to base your updates. 547 | * Finally, to delete a document, include a _deleted parameter with the value true. 548 | */ 549 | bulkDocs ( 550 | docs: Array>, 551 | options?: PouchDB.Core.BulkDocsOptions 552 | ) { 553 | return {} as Promise> 554 | } 555 | 556 | /* istanbul ignore next */ 557 | /** Compact the database */ 558 | compact (options?: PouchDB.Core.CompactOptions) { 559 | return {} as Promise 560 | } 561 | 562 | /* istanbul ignore next */ 563 | /** Fetch a document */ 564 | get ( 565 | docId: PouchDB.Core.DocumentId, 566 | options?: PouchDB.Core.GetOptions 567 | ) { 568 | return {} as Promise< 569 | PouchDB.Core.Document & PouchDB.Core.GetMeta 570 | > 571 | } 572 | 573 | /* istanbul ignore next */ 574 | /** 575 | * Create a new document without providing an id. 576 | * 577 | * You should prefer put() to post(), because when you post(), you are 578 | * missing an opportunity to use allDocs() to sort documents by _id 579 | * (because your _ids are random). 580 | * 581 | * @see {@link https://pouchdb.com/2014/06/17/12-pro-tips-for-better-code-with-pouchdb.html|PouchDB Pro Tips} 582 | */ 583 | post ( 584 | doc: PouchDB.Core.PostDocument, 585 | options?: PouchDB.Core.Options 586 | ) { 587 | return {} as Promise 588 | } 589 | 590 | /* istanbul ignore next */ 591 | /** 592 | * Create a new document or update an existing document. 593 | * 594 | * If the document already exists, you must specify its revision _rev, 595 | * otherwise a conflict will occur. 596 | * There are some restrictions on valid property names of the documents. 597 | * If you try to store non-JSON data (for instance Date objects) you may 598 | * see inconsistent results. 599 | */ 600 | put ( 601 | doc: PouchDB.Core.PutDocument, 602 | options?: PouchDB.Core.PutOptions 603 | ) { 604 | return {} as Promise 605 | } 606 | 607 | /* istanbul ignore next */ 608 | /** Remove a doc from the database */ 609 | remove (doc: PouchDB.Core.RemoveDocument, options?: PouchDB.Core.Options) { 610 | return {} as Promise 611 | } 612 | 613 | /* istanbul ignore next */ 614 | /** Get database information */ 615 | info () { 616 | return {} as Promise 617 | } 618 | 619 | /* istanbul ignore next */ 620 | /** 621 | * A list of changes made to documents in the database, in the order they were made. 622 | * It returns an object with the method cancel(), which you call if you don’t want to listen to new changes anymore. 623 | * 624 | * It is an event emitter and will emit a 'change' event on each document change, 625 | * a 'complete' event when all the changes have been processed, and an 'error' event when an error occurs. 626 | * Calling cancel() will unsubscribe all event listeners automatically. 627 | */ 628 | changes (options?: PouchDB.Core.ChangesOptions) { 629 | return {} as PouchDB.Core.Changes 630 | } 631 | 632 | /* istanbul ignore next */ 633 | /** Close the database */ 634 | close () { 635 | return {} as Promise 636 | } 637 | 638 | /* istanbul ignore next */ 639 | /** 640 | * Attaches a binary object to a document. 641 | * This method will update an existing document to add the attachment, so it requires a rev if the document already exists. 642 | * If the document doesn’t already exist, then this method will create an empty document containing the attachment. 643 | */ 644 | putAttachment ( 645 | docId: PouchDB.Core.DocumentId, 646 | attachmentId: PouchDB.Core.AttachmentId, 647 | attachment: PouchDB.Core.AttachmentData, 648 | type: string 649 | ) { 650 | return {} as Promise 651 | } 652 | 653 | /* istanbul ignore next */ 654 | /** Get attachment data */ 655 | getAttachment ( 656 | docId: PouchDB.Core.DocumentId, 657 | attachmentId: PouchDB.Core.AttachmentId, 658 | options?: { rev?: PouchDB.Core.RevisionId } 659 | ) { 660 | return {} as Promise 661 | } 662 | 663 | /* istanbul ignore next */ 664 | /** Delete an attachment from a doc. You must supply the rev of the existing doc. */ 665 | removeAttachment ( 666 | docId: PouchDB.Core.DocumentId, 667 | attachmentId: PouchDB.Core.AttachmentId, 668 | rev: PouchDB.Core.RevisionId 669 | ) { 670 | return {} as Promise 671 | } 672 | 673 | /* istanbul ignore next */ 674 | /** Given a set of document/revision IDs, returns the document bodies (and, optionally, attachment data) for each ID/revision pair specified. */ 675 | bulkGet (options: PouchDB.Core.BulkGetOptions) { 676 | return {} as Promise> 677 | } 678 | 679 | /* istanbul ignore next */ 680 | /** Given a set of document/revision IDs, returns the subset of those that do not correspond to revisions stored in the database */ 681 | revsDiff (diff: PouchDB.Core.RevisionDiffOptions) { 682 | return {} as Promise 683 | } 684 | /** 685 | * END POUCHDB IMPLEMENTATIONS FOR MIXIN SUPPORT 686 | * END POUCHDB IMPLEMENTATIONS FOR MIXIN SUPPORT 687 | * END POUCHDB IMPLEMENTATIONS FOR MIXIN SUPPORT 688 | */ 689 | } 690 | 691 | /** 692 | * @private 693 | * call Pouchy or native PouchDB methods and transform all repsonses to 694 | * unify ids & revs 695 | */ 696 | /* istanbul ignore next */ 697 | const nonTransformingPrototype: any = {} 698 | /* istanbul ignore next */ 699 | for (const key of Object.getOwnPropertyNames(Pouchy.prototype)) { 700 | const value = (Pouchy.prototype as any)[key] 701 | if ( 702 | typeof value !== 'function' || 703 | (value && value.name === 'Pouchy') || 704 | (value && value.name && value.name[0] === '_') 705 | ) { 706 | continue 707 | } 708 | nonTransformingPrototype[key] = value 709 | ;(Pouchy.prototype as any)[key] = async function transformResponse ( 710 | ...args: any[] 711 | ) { 712 | let res = nonTransformingPrototype[key].call(this, ...args) 713 | if (res && res.then && res.catch) res = await res 714 | if (typeof args[args.length - 1] === 'function') { 715 | throw new Error( 716 | [ 717 | 'the pouchy-pouchdb callback interface has been removed.', 718 | 'please use the promise interface.' 719 | ].join(' ') 720 | ) 721 | } 722 | return toUnderscorePrefix(res) 723 | } 724 | } 725 | 726 | export const pouchProxyMethods = [ 727 | 'bulkDocs', 728 | 'bulkGet', 729 | 'changes', 730 | 'close', 731 | 'compact', 732 | 'get', 733 | 'getAttachment', 734 | 'info', 735 | 'post', 736 | 'put', 737 | 'putAttachment', 738 | 'remove', 739 | 'removeAttachment', 740 | 'revsDiff' 741 | ] 742 | for (const key of pouchProxyMethods) { 743 | const value = PouchDB.prototype[key] 744 | /* istanbul ignore next */ 745 | if (!value) throw new Error(`pouchdb method "${key}" not found`) 746 | /* istanbul ignore next */ 747 | if (typeof value !== 'function') continue 748 | ;(Pouchy.prototype as any)[key] = async function proxyAndTransform ( 749 | ...args: any[] 750 | ) { 751 | const pouchMethod: Function = PouchDB.prototype[key] 752 | let res = pouchMethod.call(this.db, ...args) 753 | if (res && res.then && res.catch) res = await res 754 | /* istanbul ignore next */ 755 | if (typeof args[args.length - 1] === 'function') { 756 | throw new Error( 757 | [ 758 | 'the pouchy-pouchdb callback interface has been removed.', 759 | 'please use the promise interface.' 760 | ].join(' ') 761 | ) 762 | } 763 | return toUnderscorePrefix(res) 764 | } 765 | } 766 | 767 | export default Pouchy 768 | -------------------------------------------------------------------------------- /src/to-underscore-prefix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module document-key-prefixer 3 | */ 4 | 5 | import { MaybeSavedPouchDoc } from '.' // eslint-disable-line 6 | 7 | var KEYS = ['id', 'rev'] 8 | 9 | /* istanbul ignore next */ 10 | export function toUnderscorePrefix ( 11 | data: Content & MaybeSavedPouchDoc | (Content & MaybeSavedPouchDoc)[] 12 | ): Content & MaybeSavedPouchDoc | (Content & MaybeSavedPouchDoc)[] { 13 | if (!data) return data 14 | if (Array.isArray(data)) { 15 | return data.map(item => toUnderscorePrefix(item as any)) 16 | } 17 | if ((data).results) return toUnderscorePrefix((data).results as any) 18 | if ((data).docs) return toUnderscorePrefix((data).docs as any) 19 | var key: string 20 | var value: any 21 | const contentData = data as { [key: string]: any } 22 | for (var i in KEYS) { 23 | key = KEYS[i] 24 | value = contentData[key] 25 | if (value) { 26 | if (!contentData['_' + key]) { 27 | contentData['_' + key] = value 28 | delete contentData[key] 29 | } 30 | } 31 | } 32 | return data 33 | } 34 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launchers: { 3 | Node: { 4 | command: 'node tests.js', 5 | protocol: 'tap' 6 | } 7 | }, 8 | framework: 'tap', 9 | src_files_ignore: ['bundle.js'], 10 | serve_files: ['bundle.js'], 11 | serve_files_ignore: [], 12 | before_tests: 'node scripts/bundle.js', 13 | // 'after_tests': 'rm -f bundle.js .source.*html', 14 | launch_in_dev: ['Chrome'], 15 | launch_in_ci: ['Chrome'] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": ["es2015"], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./build", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | "removeComments": false, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } 61 | --------------------------------------------------------------------------------