From 2f96351389d167d5e87f8a37a60175b6a3e7a501 Mon Sep 17 00:00:00 2001 From: Art_Chen Date: Sat, 4 Sep 2021 22:28:30 +0800 Subject: [PATCH] init: fork node-postgres from github & Adapt openGauss SHA256 and SM3 password authentication --- .eslintrc | 15 + .gitignore | 11 + .travis.yml | 100 + .yarnrc | 1 + CHANGELOG.md | 428 ++ LICENSE | 21 + README.en.md | 80 +- README.md | 85 +- SPONSORS.md | 42 + lerna.json | 12 + package.json | 38 + packages/pg-connection-string/.coveralls.yml | 2 + packages/pg-connection-string/.gitignore | 26 + packages/pg-connection-string/.travis.yml | 6 + packages/pg-connection-string/LICENSE | 21 + packages/pg-connection-string/README.md | 77 + packages/pg-connection-string/index.d.ts | 15 + packages/pg-connection-string/index.js | 106 + packages/pg-connection-string/package.json | 39 + packages/pg-connection-string/test/example.ca | 1 + .../pg-connection-string/test/example.cert | 1 + .../pg-connection-string/test/example.key | 1 + packages/pg-connection-string/test/parse.js | 320 + packages/pg-cursor/README.md | 37 + packages/pg-cursor/index.js | 241 + packages/pg-cursor/package.json | 26 + packages/pg-cursor/test/close.js | 54 + packages/pg-cursor/test/error-handling.js | 86 + packages/pg-cursor/test/index.js | 181 + packages/pg-cursor/test/mocha.opts | 3 + packages/pg-cursor/test/no-data-handling.js | 34 + packages/pg-cursor/test/pool.js | 107 + packages/pg-cursor/test/query-config.js | 35 + packages/pg-cursor/test/transactions.js | 43 + packages/pg-pool/LICENSE | 21 + packages/pg-pool/README.md | 376 + packages/pg-pool/index.js | 410 + packages/pg-pool/package.json | 40 + .../pg-pool/test/bring-your-own-promise.js | 42 + packages/pg-pool/test/connection-strings.js | 29 + packages/pg-pool/test/connection-timeout.js | 229 + packages/pg-pool/test/ending.js | 40 + packages/pg-pool/test/error-handling.js | 248 + packages/pg-pool/test/events.js | 86 + packages/pg-pool/test/idle-timeout.js | 87 + packages/pg-pool/test/index.js | 226 + packages/pg-pool/test/logging.js | 20 + packages/pg-pool/test/max-uses.js | 98 + packages/pg-pool/test/releasing-clients.js | 54 + packages/pg-pool/test/setup.js | 10 + packages/pg-pool/test/sizing.js | 58 + packages/pg-pool/test/submittable.js | 19 + packages/pg-pool/test/timeout.js | 0 packages/pg-pool/test/verify.js | 24 + packages/pg-protocol/README.md | 3 + packages/pg-protocol/package.json | 34 + packages/pg-protocol/src/b.ts | 28 + packages/pg-protocol/src/buffer-reader.ts | 53 + packages/pg-protocol/src/buffer-writer.ts | 85 + .../pg-protocol/src/inbound-parser.test.ts | 557 ++ packages/pg-protocol/src/index.ts | 11 + packages/pg-protocol/src/messages.ts | 236 + .../src/outbound-serializer.test.ts | 272 + packages/pg-protocol/src/parser.ts | 386 + packages/pg-protocol/src/serializer.ts | 274 + .../pg-protocol/src/testing/buffer-list.ts | 75 + .../pg-protocol/src/testing/test-buffers.ts | 166 + packages/pg-protocol/src/types/chunky.d.ts | 1 + packages/pg-protocol/tsconfig.json | 25 + packages/pg-query-stream/LICENSE | 9 + packages/pg-query-stream/README.md | 64 + packages/pg-query-stream/package.json | 49 + packages/pg-query-stream/src/index.ts | 67 + .../pg-query-stream/test/async-iterator.ts | 116 + .../pg-query-stream/test/client-options.ts | 28 + packages/pg-query-stream/test/close.ts | 93 + packages/pg-query-stream/test/concat.ts | 30 + packages/pg-query-stream/test/config.ts | 26 + packages/pg-query-stream/test/empty-query.ts | 21 + packages/pg-query-stream/test/error.ts | 92 + packages/pg-query-stream/test/fast-reader.ts | 35 + packages/pg-query-stream/test/helper.ts | 18 + packages/pg-query-stream/test/instant.ts | 17 + packages/pg-query-stream/test/issue-3.ts | 33 + .../pg-query-stream/test/passing-options.ts | 38 + packages/pg-query-stream/test/pauses.ts | 26 + packages/pg-query-stream/test/slow-reader.ts | 31 + .../test/stream-tester-timestamp.ts | 26 + .../pg-query-stream/test/stream-tester.ts | 12 + packages/pg-query-stream/tsconfig.json | 26 + packages/pg/Makefile | 61 + packages/pg/bench.js | 87 + packages/pg/lib/client.js | 623 ++ packages/pg/lib/connection-parameters.js | 166 + packages/pg/lib/connection.js | 213 + packages/pg/lib/defaults.js | 80 + packages/pg/lib/index.js | 55 + packages/pg/lib/native/client.js | 294 + packages/pg/lib/native/index.js | 2 + packages/pg/lib/native/query.js | 165 + packages/pg/lib/query.js | 234 + packages/pg/lib/result.js | 100 + packages/pg/lib/rfc5802.js | 59 + packages/pg/lib/sasl.js | 209 + packages/pg/lib/type-overrides.js | 35 + packages/pg/lib/utils.js | 187 + packages/pg/package.json | 57 + packages/pg/script/create-test-tables.js | 71 + packages/pg/script/dump-db-types.js | 18 + packages/pg/script/list-db-types.js | 10 + packages/pg/test-1.js | 57 + packages/pg/test/buffer-list.js | 76 + packages/pg/test/cli.js | 25 + .../pg/test/integration/client/api-tests.js | 259 + .../test/integration/client/appname-tests.js | 97 + .../pg/test/integration/client/array-tests.js | 231 + .../client/big-simple-query-tests.js | 121 + .../integration/client/configuration-tests.js | 84 + .../client/connection-parameter-tests.js | 15 + .../client/connection-timeout-tests.js | 87 + .../integration/client/custom-types-tests.js | 42 + .../integration/client/empty-query-tests.js | 20 + .../client/error-handling-tests.js | 259 + .../client/field-name-escape-tests.js | 10 + .../integration/client/huge-numeric-tests.js | 27 + ...le_in_transaction_session_timeout-tests.js | 95 + .../client/json-type-parsing-tests.js | 37 + .../client/multiple-results-tests.js | 78 + .../client/network-partition-tests.js | 94 + .../test/integration/client/no-data-tests.js | 45 + .../integration/client/no-row-result-tests.js | 30 + .../test/integration/client/notice-tests.js | 75 + .../integration/client/parse-int-8-tests.js | 37 + .../client/prepared-statement-tests.js | 188 + .../integration/client/promise-api-tests.js | 43 + .../client/query-as-promise-tests.js | 54 + .../client/query-column-names-tests.js | 20 + ...error-handling-prepared-statement-tests.js | 125 + .../client/query-error-handling-tests.js | 122 + .../client/quick-disconnect-tests.js | 8 + .../client/result-metadata-tests.js | 47 + .../client/results-as-array-tests.js | 36 + .../row-description-on-results-tests.js | 50 + .../integration/client/sasl-scram-tests.js | 75 + .../integration/client/simple-query-tests.js | 98 + .../pg/test/integration/client/ssl-tests.js | 21 + .../client/statement_timeout-tests.js | 84 + .../pg/test/integration/client/test-helper.js | 4 + .../test/integration/client/timezone-tests.js | 34 + .../integration/client/transaction-tests.js | 99 + .../integration/client/type-coercion-tests.js | 243 + .../client/type-parser-override-tests.js | 44 + .../connection-pool-size-tests.js | 10 + .../connection-pool/error-tests.js | 166 + .../connection-pool/idle-timeout-tests.js | 14 + .../connection-pool/native-instance-tests.js | 18 + .../connection-pool/test-helper.js | 31 + .../integration/connection-pool/tls-tests.js | 23 + .../connection-pool/yield-support-tests.js | 23 + .../connection/bound-command-tests.js | 58 + .../test/integration/connection/copy-tests.js | 43 + .../connection/dynamic-password-tests.js | 119 + .../connection/notification-tests.js | 16 + .../integration/connection/query-tests.js | 26 + .../integration/connection/test-helper.js | 40 + packages/pg/test/integration/domain-tests.js | 62 + .../test/integration/gh-issues/1105-tests.js | 20 + .../test/integration/gh-issues/130-tests.js | 28 + .../test/integration/gh-issues/131-tests.js | 33 + .../test/integration/gh-issues/1382-tests.js | 34 + .../test/integration/gh-issues/1542-tests.js | 22 + .../test/integration/gh-issues/1854-tests.js | 33 + .../test/integration/gh-issues/199-tests.js | 23 + .../test/integration/gh-issues/1992-tests.js | 10 + .../test/integration/gh-issues/2056-tests.js | 21 + .../test/integration/gh-issues/2064-tests.js | 31 + .../test/integration/gh-issues/2079-tests.js | 53 + .../test/integration/gh-issues/2085-tests.js | 35 + .../test/integration/gh-issues/2108-tests.js | 13 + .../test/integration/gh-issues/2303-tests.js | 47 + .../test/integration/gh-issues/2307-tests.js | 24 + .../test/integration/gh-issues/507-tests.js | 19 + .../test/integration/gh-issues/600-tests.js | 90 + .../test/integration/gh-issues/675-tests.js | 30 + .../test/integration/gh-issues/699-tests.js | 31 + .../test/integration/gh-issues/787-tests.js | 14 + .../test/integration/gh-issues/882-tests.js | 9 + .../test/integration/gh-issues/981-tests.js | 37 + packages/pg/test/integration/test-helper.js | 29 + packages/pg/test/native/callback-api-tests.js | 41 + packages/pg/test/native/evented-api-tests.js | 94 + packages/pg/test/native/missing-native.js | 7 + .../test/native/native-vs-js-error-tests.js | 21 + packages/pg/test/native/stress-tests.js | 51 + packages/pg/test/suite.js | 96 + packages/pg/test/test-buffers.js | 121 + packages/pg/test/test-helper.js | 244 + packages/pg/test/tls/GNUmakefile | 71 + packages/pg/test/tls/test-client-ca.crt | 11 + packages/pg/test/tls/test-client-ca.key | 5 + packages/pg/test/tls/test-client.crt | 9 + packages/pg/test/tls/test-client.key | 5 + packages/pg/test/tls/test-server-ca.crt | 11 + packages/pg/test/tls/test-server-ca.key | 5 + packages/pg/test/tls/test-server.crt | 9 + packages/pg/test/tls/test-server.key | 5 + .../unit/client/cleartext-password-tests.js | 30 + .../test/unit/client/configuration-tests.js | 168 + .../unit/client/early-disconnect-tests.js | 19 + packages/pg/test/unit/client/escape-tests.js | 65 + .../pg/test/unit/client/md5-password-tests.js | 24 + .../pg/test/unit/client/notification-tests.js | 10 + packages/pg/test/unit/client/pgpass.file | 1 + .../unit/client/prepared-statement-tests.js | 157 + .../pg/test/unit/client/query-queue-tests.js | 52 + .../test/unit/client/result-metadata-tests.js | 42 + .../pg/test/unit/client/sasl-scram-tests.js | 189 + .../test/unit/client/set-keepalives-tests.js | 32 + .../pg/test/unit/client/simple-query-tests.js | 153 + ...tream-and-query-error-interaction-tests.js | 38 + packages/pg/test/unit/client/test-helper.js | 24 + .../unit/client/throw-in-type-parser-tests.js | 69 + .../connection-parameters/creation-tests.js | 345 + .../environment-variable-tests.js | 128 + .../connection-pool/configuration-tests.js | 14 + .../pg/test/unit/connection/error-tests.js | 87 + .../unit/connection/inbound-parser-tests.js | 510 ++ .../pg/test/unit/connection/startup-tests.js | 66 + .../pg/test/unit/connection/test-helper.js | 2 + packages/pg/test/unit/test-helper.js | 48 + packages/pg/test/unit/utils-tests.js | 241 + tsconfig.json | 12 + yarn.lock | 6770 +++++++++++++++++ 233 files changed, 24887 insertions(+), 47 deletions(-) create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 .yarnrc create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 SPONSORS.md create mode 100644 lerna.json create mode 100644 package.json create mode 100644 packages/pg-connection-string/.coveralls.yml create mode 100644 packages/pg-connection-string/.gitignore create mode 100644 packages/pg-connection-string/.travis.yml create mode 100644 packages/pg-connection-string/LICENSE create mode 100644 packages/pg-connection-string/README.md create mode 100644 packages/pg-connection-string/index.d.ts create mode 100644 packages/pg-connection-string/index.js create mode 100644 packages/pg-connection-string/package.json create mode 100644 packages/pg-connection-string/test/example.ca create mode 100644 packages/pg-connection-string/test/example.cert create mode 100644 packages/pg-connection-string/test/example.key create mode 100644 packages/pg-connection-string/test/parse.js create mode 100644 packages/pg-cursor/README.md create mode 100644 packages/pg-cursor/index.js create mode 100644 packages/pg-cursor/package.json create mode 100644 packages/pg-cursor/test/close.js create mode 100644 packages/pg-cursor/test/error-handling.js create mode 100644 packages/pg-cursor/test/index.js create mode 100644 packages/pg-cursor/test/mocha.opts create mode 100644 packages/pg-cursor/test/no-data-handling.js create mode 100644 packages/pg-cursor/test/pool.js create mode 100644 packages/pg-cursor/test/query-config.js create mode 100644 packages/pg-cursor/test/transactions.js create mode 100644 packages/pg-pool/LICENSE create mode 100644 packages/pg-pool/README.md create mode 100644 packages/pg-pool/index.js create mode 100644 packages/pg-pool/package.json create mode 100644 packages/pg-pool/test/bring-your-own-promise.js create mode 100644 packages/pg-pool/test/connection-strings.js create mode 100644 packages/pg-pool/test/connection-timeout.js create mode 100644 packages/pg-pool/test/ending.js create mode 100644 packages/pg-pool/test/error-handling.js create mode 100644 packages/pg-pool/test/events.js create mode 100644 packages/pg-pool/test/idle-timeout.js create mode 100644 packages/pg-pool/test/index.js create mode 100644 packages/pg-pool/test/logging.js create mode 100644 packages/pg-pool/test/max-uses.js create mode 100644 packages/pg-pool/test/releasing-clients.js create mode 100644 packages/pg-pool/test/setup.js create mode 100644 packages/pg-pool/test/sizing.js create mode 100644 packages/pg-pool/test/submittable.js create mode 100644 packages/pg-pool/test/timeout.js create mode 100644 packages/pg-pool/test/verify.js create mode 100644 packages/pg-protocol/README.md create mode 100644 packages/pg-protocol/package.json create mode 100644 packages/pg-protocol/src/b.ts create mode 100644 packages/pg-protocol/src/buffer-reader.ts create mode 100644 packages/pg-protocol/src/buffer-writer.ts create mode 100644 packages/pg-protocol/src/inbound-parser.test.ts create mode 100644 packages/pg-protocol/src/index.ts create mode 100644 packages/pg-protocol/src/messages.ts create mode 100644 packages/pg-protocol/src/outbound-serializer.test.ts create mode 100644 packages/pg-protocol/src/parser.ts create mode 100644 packages/pg-protocol/src/serializer.ts create mode 100644 packages/pg-protocol/src/testing/buffer-list.ts create mode 100644 packages/pg-protocol/src/testing/test-buffers.ts create mode 100644 packages/pg-protocol/src/types/chunky.d.ts create mode 100644 packages/pg-protocol/tsconfig.json create mode 100644 packages/pg-query-stream/LICENSE create mode 100644 packages/pg-query-stream/README.md create mode 100644 packages/pg-query-stream/package.json create mode 100644 packages/pg-query-stream/src/index.ts create mode 100644 packages/pg-query-stream/test/async-iterator.ts create mode 100644 packages/pg-query-stream/test/client-options.ts create mode 100644 packages/pg-query-stream/test/close.ts create mode 100644 packages/pg-query-stream/test/concat.ts create mode 100644 packages/pg-query-stream/test/config.ts create mode 100644 packages/pg-query-stream/test/empty-query.ts create mode 100644 packages/pg-query-stream/test/error.ts create mode 100644 packages/pg-query-stream/test/fast-reader.ts create mode 100644 packages/pg-query-stream/test/helper.ts create mode 100644 packages/pg-query-stream/test/instant.ts create mode 100644 packages/pg-query-stream/test/issue-3.ts create mode 100644 packages/pg-query-stream/test/passing-options.ts create mode 100644 packages/pg-query-stream/test/pauses.ts create mode 100644 packages/pg-query-stream/test/slow-reader.ts create mode 100644 packages/pg-query-stream/test/stream-tester-timestamp.ts create mode 100644 packages/pg-query-stream/test/stream-tester.ts create mode 100644 packages/pg-query-stream/tsconfig.json create mode 100644 packages/pg/Makefile create mode 100644 packages/pg/bench.js create mode 100644 packages/pg/lib/client.js create mode 100644 packages/pg/lib/connection-parameters.js create mode 100644 packages/pg/lib/connection.js create mode 100644 packages/pg/lib/defaults.js create mode 100644 packages/pg/lib/index.js create mode 100644 packages/pg/lib/native/client.js create mode 100644 packages/pg/lib/native/index.js create mode 100644 packages/pg/lib/native/query.js create mode 100644 packages/pg/lib/query.js create mode 100644 packages/pg/lib/result.js create mode 100644 packages/pg/lib/rfc5802.js create mode 100644 packages/pg/lib/sasl.js create mode 100644 packages/pg/lib/type-overrides.js create mode 100644 packages/pg/lib/utils.js create mode 100644 packages/pg/package.json create mode 100644 packages/pg/script/create-test-tables.js create mode 100644 packages/pg/script/dump-db-types.js create mode 100644 packages/pg/script/list-db-types.js create mode 100644 packages/pg/test-1.js create mode 100644 packages/pg/test/buffer-list.js create mode 100644 packages/pg/test/cli.js create mode 100644 packages/pg/test/integration/client/api-tests.js create mode 100644 packages/pg/test/integration/client/appname-tests.js create mode 100644 packages/pg/test/integration/client/array-tests.js create mode 100644 packages/pg/test/integration/client/big-simple-query-tests.js create mode 100644 packages/pg/test/integration/client/configuration-tests.js create mode 100644 packages/pg/test/integration/client/connection-parameter-tests.js create mode 100644 packages/pg/test/integration/client/connection-timeout-tests.js create mode 100644 packages/pg/test/integration/client/custom-types-tests.js create mode 100644 packages/pg/test/integration/client/empty-query-tests.js create mode 100644 packages/pg/test/integration/client/error-handling-tests.js create mode 100644 packages/pg/test/integration/client/field-name-escape-tests.js create mode 100644 packages/pg/test/integration/client/huge-numeric-tests.js create mode 100644 packages/pg/test/integration/client/idle_in_transaction_session_timeout-tests.js create mode 100644 packages/pg/test/integration/client/json-type-parsing-tests.js create mode 100644 packages/pg/test/integration/client/multiple-results-tests.js create mode 100644 packages/pg/test/integration/client/network-partition-tests.js create mode 100644 packages/pg/test/integration/client/no-data-tests.js create mode 100644 packages/pg/test/integration/client/no-row-result-tests.js create mode 100644 packages/pg/test/integration/client/notice-tests.js create mode 100644 packages/pg/test/integration/client/parse-int-8-tests.js create mode 100644 packages/pg/test/integration/client/prepared-statement-tests.js create mode 100644 packages/pg/test/integration/client/promise-api-tests.js create mode 100644 packages/pg/test/integration/client/query-as-promise-tests.js create mode 100644 packages/pg/test/integration/client/query-column-names-tests.js create mode 100644 packages/pg/test/integration/client/query-error-handling-prepared-statement-tests.js create mode 100644 packages/pg/test/integration/client/query-error-handling-tests.js create mode 100644 packages/pg/test/integration/client/quick-disconnect-tests.js create mode 100644 packages/pg/test/integration/client/result-metadata-tests.js create mode 100644 packages/pg/test/integration/client/results-as-array-tests.js create mode 100644 packages/pg/test/integration/client/row-description-on-results-tests.js create mode 100644 packages/pg/test/integration/client/sasl-scram-tests.js create mode 100644 packages/pg/test/integration/client/simple-query-tests.js create mode 100644 packages/pg/test/integration/client/ssl-tests.js create mode 100644 packages/pg/test/integration/client/statement_timeout-tests.js create mode 100644 packages/pg/test/integration/client/test-helper.js create mode 100644 packages/pg/test/integration/client/timezone-tests.js create mode 100644 packages/pg/test/integration/client/transaction-tests.js create mode 100644 packages/pg/test/integration/client/type-coercion-tests.js create mode 100644 packages/pg/test/integration/client/type-parser-override-tests.js create mode 100644 packages/pg/test/integration/connection-pool/connection-pool-size-tests.js create mode 100644 packages/pg/test/integration/connection-pool/error-tests.js create mode 100644 packages/pg/test/integration/connection-pool/idle-timeout-tests.js create mode 100644 packages/pg/test/integration/connection-pool/native-instance-tests.js create mode 100644 packages/pg/test/integration/connection-pool/test-helper.js create mode 100644 packages/pg/test/integration/connection-pool/tls-tests.js create mode 100644 packages/pg/test/integration/connection-pool/yield-support-tests.js create mode 100644 packages/pg/test/integration/connection/bound-command-tests.js create mode 100644 packages/pg/test/integration/connection/copy-tests.js create mode 100644 packages/pg/test/integration/connection/dynamic-password-tests.js create mode 100644 packages/pg/test/integration/connection/notification-tests.js create mode 100644 packages/pg/test/integration/connection/query-tests.js create mode 100644 packages/pg/test/integration/connection/test-helper.js create mode 100644 packages/pg/test/integration/domain-tests.js create mode 100644 packages/pg/test/integration/gh-issues/1105-tests.js create mode 100644 packages/pg/test/integration/gh-issues/130-tests.js create mode 100644 packages/pg/test/integration/gh-issues/131-tests.js create mode 100644 packages/pg/test/integration/gh-issues/1382-tests.js create mode 100644 packages/pg/test/integration/gh-issues/1542-tests.js create mode 100644 packages/pg/test/integration/gh-issues/1854-tests.js create mode 100644 packages/pg/test/integration/gh-issues/199-tests.js create mode 100644 packages/pg/test/integration/gh-issues/1992-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2056-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2064-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2079-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2085-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2108-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2303-tests.js create mode 100644 packages/pg/test/integration/gh-issues/2307-tests.js create mode 100644 packages/pg/test/integration/gh-issues/507-tests.js create mode 100644 packages/pg/test/integration/gh-issues/600-tests.js create mode 100644 packages/pg/test/integration/gh-issues/675-tests.js create mode 100644 packages/pg/test/integration/gh-issues/699-tests.js create mode 100644 packages/pg/test/integration/gh-issues/787-tests.js create mode 100644 packages/pg/test/integration/gh-issues/882-tests.js create mode 100644 packages/pg/test/integration/gh-issues/981-tests.js create mode 100644 packages/pg/test/integration/test-helper.js create mode 100644 packages/pg/test/native/callback-api-tests.js create mode 100644 packages/pg/test/native/evented-api-tests.js create mode 100644 packages/pg/test/native/missing-native.js create mode 100644 packages/pg/test/native/native-vs-js-error-tests.js create mode 100644 packages/pg/test/native/stress-tests.js create mode 100644 packages/pg/test/suite.js create mode 100644 packages/pg/test/test-buffers.js create mode 100644 packages/pg/test/test-helper.js create mode 100644 packages/pg/test/tls/GNUmakefile create mode 100644 packages/pg/test/tls/test-client-ca.crt create mode 100644 packages/pg/test/tls/test-client-ca.key create mode 100644 packages/pg/test/tls/test-client.crt create mode 100644 packages/pg/test/tls/test-client.key create mode 100644 packages/pg/test/tls/test-server-ca.crt create mode 100644 packages/pg/test/tls/test-server-ca.key create mode 100644 packages/pg/test/tls/test-server.crt create mode 100644 packages/pg/test/tls/test-server.key create mode 100644 packages/pg/test/unit/client/cleartext-password-tests.js create mode 100644 packages/pg/test/unit/client/configuration-tests.js create mode 100644 packages/pg/test/unit/client/early-disconnect-tests.js create mode 100644 packages/pg/test/unit/client/escape-tests.js create mode 100644 packages/pg/test/unit/client/md5-password-tests.js create mode 100644 packages/pg/test/unit/client/notification-tests.js create mode 100644 packages/pg/test/unit/client/pgpass.file create mode 100644 packages/pg/test/unit/client/prepared-statement-tests.js create mode 100644 packages/pg/test/unit/client/query-queue-tests.js create mode 100644 packages/pg/test/unit/client/result-metadata-tests.js create mode 100644 packages/pg/test/unit/client/sasl-scram-tests.js create mode 100644 packages/pg/test/unit/client/set-keepalives-tests.js create mode 100644 packages/pg/test/unit/client/simple-query-tests.js create mode 100644 packages/pg/test/unit/client/stream-and-query-error-interaction-tests.js create mode 100644 packages/pg/test/unit/client/test-helper.js create mode 100644 packages/pg/test/unit/client/throw-in-type-parser-tests.js create mode 100644 packages/pg/test/unit/connection-parameters/creation-tests.js create mode 100644 packages/pg/test/unit/connection-parameters/environment-variable-tests.js create mode 100644 packages/pg/test/unit/connection-pool/configuration-tests.js create mode 100644 packages/pg/test/unit/connection/error-tests.js create mode 100644 packages/pg/test/unit/connection/inbound-parser-tests.js create mode 100644 packages/pg/test/unit/connection/startup-tests.js create mode 100644 packages/pg/test/unit/connection/test-helper.js create mode 100644 packages/pg/test/unit/test-helper.js create mode 100644 packages/pg/test/unit/utils-tests.js create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..4766b98 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,15 @@ +{ + "plugins": ["prettier"], + "parser": "@typescript-eslint/parser", + "extends": ["plugin:prettier/recommended", "prettier/@typescript-eslint"], + "ignorePatterns": ["node_modules", "coverage", "packages/pg-protocol/dist/**/*", "packages/pg-query-stream/dist/**/*"], + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "env": { + "node": true, + "es6": true, + "mocha": true + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e058f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*~ +build/ +.lock-wscript +*.log +node_modules/ +package-lock.json +*.swp +dist +.DS_Store +.vscode/ +manually-test-on-heroku.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..011bd9e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,100 @@ +language: node_js +dist: bionic + +before_script: | + yarn build + node packages/pg/script/create-test-tables.js postgresql:/// + +env: + - CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres + +node_js: + - lts/dubnium + - lts/erbium + # node 13.7 seems to have changed behavior of async iterators exiting early on streams + # if 13.8 still has this problem when it comes down I'll talk to the node team about the change + # in the mean time...peg to 13.6 + - 13.6 + - 14 + +addons: + postgresql: '10' + +matrix: + include: + # Run tests/paths that require password authentication + - node_js: lts/erbium + env: + - CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres PGPASSWORD=test-password SCRAM_TEST_PGUSER=scram_test SCRAM_TEST_PGPASSWORD=test4scram + before_script: | + sudo -u postgres sed -i \ + -e '/^local/ s/trust$/peer/' \ + -e '/^host/ s/trust$/md5/' \ + /etc/postgresql/10/main/pg_hba.conf + sudo -u postgres psql -c "ALTER ROLE postgres PASSWORD 'test-password'; SELECT pg_reload_conf()" + yarn build + node packages/pg/script/create-test-tables.js postgresql:/// + sudo -u postgres -- psql \ + -c "SET password_encryption = 'scram-sha-256'" \ + -c "CREATE ROLE scram_test login password 'test4scram'" + + - node_js: lts/carbon + addons: + postgresql: '9.5' + dist: precise + + # Run tests/paths with client certificate authentication + - node_js: lts/* + env: + - CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres + PGSSLMODE=verify-full + PGSSLROOTCERT=$TRAVIS_BUILD_DIR/packages/pg/test/tls/test-server-ca.crt + PGSSLCERT=$TRAVIS_BUILD_DIR/packages/pg/test/tls/test-client.crt + PGSSLKEY=$TRAVIS_BUILD_DIR/packages/pg/test/tls/test-client.key + PG_CLIENT_CERT_TEST=1 + before_script: + - chmod go= packages/pg/test/tls/test-client.key + - | + sudo sed -i \ + -e '/^ssl_cert_file =/d' \ + -e '/^ssl_key_file =/d' \ + /etc/postgresql/10/main/postgresql.conf + + cat <<'travis ci breaks heredoc' | sudo tee -a /etc/postgresql/10/main/postgresql.conf > /dev/null + ssl_cert_file = 'test-server.crt' + ssl_key_file = 'test-server.key' + ssl_ca_file = 'test-client-ca.crt' + + - printf 'hostssl all all %s cert\n' 127.0.0.1/32 ::1/128 | sudo tee /etc/postgresql/10/main/pg_hba.conf > /dev/null + - sudo make -C packages/pg/test/tls install DESTDIR=/var/ramfs/postgresql/10/main + - sudo systemctl restart postgresql@10-main + - yarn build + script: + - cd packages/pg + - node test/integration/connection-pool/tls-tests.js + - npm install --no-save pg-native + - node test/integration/connection-pool/tls-tests.js native + + # different PostgreSQL versions on Node LTS + - node_js: lts/erbium + addons: + postgresql: '9.3' + - node_js: lts/erbium + addons: + postgresql: '9.4' + - node_js: lts/erbium + addons: + postgresql: '9.5' + - node_js: lts/erbium + addons: + postgresql: '9.6' + + # only run lint on latest Node LTS + - node_js: lts/* + script: yarn lint + + # PostgreSQL 9.2 only works on precise + - node_js: lts/carbon + addons: + postgresql: '9.2' + dist: precise diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 0000000..0366cbd --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--install.ignore-engines true \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..26e368f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,428 @@ +All major and minor releases are briefly explained below. + +For richer information consult the commit log on github with referenced pull requests. + +We do not include break-fix version release in this file. + +### pg@8.6.0 + +- Better [SASL](https://github.com/brianc/node-postgres/pull/2436) error messages & more validation on bad configuration. +- Export [DatabaseError](https://github.com/brianc/node-postgres/pull/2445). +- Add [ParameterDescription](https://github.com/brianc/node-postgres/pull/2464) support to protocol parsing. +- Fix typescript [typedefs](https://github.com/brianc/node-postgres/pull/2490) with `--isolatedModules`. + +### pg-query-stream@4.0.0 + +- Library has been [converted](https://github.com/brianc/node-postgres/pull/2376) to Typescript. The behavior is identical, but there could be subtle breaking changes due to class names changing or other small inconsistencies introduced by the conversion. + +### pg@8.5.0 + +- Fix bug forwarding [ssl key](https://github.com/brianc/node-postgres/pull/2394). +- Convert pg-query-stream internals to [typescript](https://github.com/brianc/node-postgres/pull/2376). +- Performance [improvements](https://github.com/brianc/node-postgres/pull/2286). + +### pg@8.4.0 + +- Switch to optional peer dependencies & remove [semver](https://github.com/brianc/node-postgres/commit/a02dfac5ad2e2abf0dc3a9817f953938acdc19b1) package which has been a small thorn in the side of a few users. +- Export `DatabaseError` from [pg-protocol](https://github.com/brianc/node-postgres/commit/58258430d52ee446721cc3e6611e26f8bcaa67f5). +- Add support for `sslmode` in the [connection string](https://github.com/brianc/node-postgres/commit/6be3b9022f83efc721596cc41165afaa07bfceb0). + +### pg@8.3.0 + +- Support passing a [string of command line options flags](https://github.com/brianc/node-postgres/pull/2216) via the `{ options: string }` field on client/pool config. + +### pg@8.2.0 + +- Switch internal protocol parser & serializer to [pg-protocol](https://github.com/brianc/node-postgres/tree/master/packages/pg-protocol). The change is backwards compatible but results in a significant performance improvement across the board, with some queries as much as 50% faster. This is the first work to land in an on-going performance improvment initiative I'm working on. Stay tuned as things are set to get much faster still! :rocket: + +### pg-cursor@2.2.0 + +- Switch internal protocol parser & serializer to [pg-protocol](https://github.com/brianc/node-postgres/tree/master/packages/pg-protocol). The change is backwards compatible but results in a significant performance improvement across the board, with some queries as much as 50% faster. + +### pg-query-stream@3.1.0 + +- Switch internal protocol parser & serializer to [pg-protocol](https://github.com/brianc/node-postgres/tree/master/packages/pg-protocol). The change is backwards compatible but results in a significant performance improvement across the board, with some queries as much as 50% faster. + +### pg@8.1.0 + +- Switch to using [monorepo](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string) version of `pg-connection-string`. This includes better support for SSL argument parsing from connection strings and ensures continuity of support. +- Add `&ssl=no-verify` option to connection string and `PGSSLMODE=no-verify` environment variable support for the pure JS driver. This is equivalent of passing `{ ssl: { rejectUnauthorized: false } }` to the client/pool constructor. The advantage of having support in connection strings and environment variables is it can be "externally" configured via environment variables and CLI arguments much more easily, and should remove the need to directly edit any application code for [the SSL default changes in 8.0](https://node-postgres.com/announcements#2020-02-25). This should make using `pg@8.x` significantly less difficult on environments like Heroku for example. + +### pg-pool@3.2.0 + +- Same changes to `pg` impact `pg-pool` as they both use the same connection parameter and connection string parsing code for configuring SSL. + +### pg-pool@3.1.0 + +- Add [maxUses](https://github.com/brianc/node-postgres/pull/2157) config option. + +### pg@8.0.0 + +#### note: for detailed release notes please [check here](https://node-postgres.com/announcements#2020-02-25) + +- Remove versions of node older than `6 lts` from the test matrix. `pg>=8.0` may still work on older versions but it is no longer officially supported. +- Change default behavior when not specifying `rejectUnauthorized` with the SSL connection parameters. Previously we defaulted to `rejectUnauthorized: false` when it was not specifically included. We now default to `rejectUnauthorized: true.` Manually specify `{ ssl: { rejectUnauthorized: false } }` for old behavior. +- Change [default database](https://github.com/brianc/node-postgres/pull/1679) when not specified to use the `user` config option if available. Previously `process.env.USER` was used. +- Change `pg.Pool` and `pg.Query` to [be](https://github.com/brianc/node-postgres/pull/2126) an [es6 class](https://github.com/brianc/node-postgres/pull/2063). +- Make `pg.native` non enumerable. +- `notice` messages are [no longer instances](https://github.com/brianc/node-postgres/pull/2090) of `Error`. +- Passwords no longer [show up](https://github.com/brianc/node-postgres/pull/2070) when instances of clients or pools are logged. + +### pg@7.18.0 + +- This will likely be the last minor release before pg@8.0. +- This version contains a few bug fixes and adds a deprecation warning for [a pending change in 8.0](https://github.com/brianc/node-postgres/issues/2009#issuecomment-579371651) which will flip the default behavior over SSL from `rejectUnauthorized` from `false` to `true` making things more secure in the general use case. + +### pg-query-stream@3.0.0 + +- [Rewrote stream internals](https://github.com/brianc/node-postgres/pull/2051) to better conform to node stream semantics. This should make pg-query-stream much better at respecting [highWaterMark](https://nodejs.org/api/stream.html#stream_new_stream_readable_options) and getting rid of some edge case bugs when using pg-query-stream as an async iterator. Due to the size and nature of this change (effectively a full re-write) it's safest to bump the semver major here, though almost all tests remain untouched and still passing, which brings us to a breaking change to the API.... +- Changed `stream.close` to `stream.destroy` which is the [official](https://nodejs.org/api/stream.html#stream_readable_destroy_error) way to terminate a readable stream. This is a **breaking change** if you rely on the `stream.close` method on pg-query-stream...though should be just a find/replace type operation to upgrade as the semantics remain very similar (not exactly the same, since internals are rewritten, but more in line with how streams are "supposed" to behave). +- Unified the `config.batchSize` and `config.highWaterMark` to both do the same thing: control how many rows are buffered in memory. The `ReadableStream` will manage exactly how many rows are requested from the cursor at a time. This should give better out of the box performance and help with efficient async iteration. + +### pg@7.17.0 + +- Add support for `idle_in_transaction_session_timeout` [option](https://github.com/brianc/node-postgres/pull/2049). + +### 7.16.0 + +- Add optional, opt-in behavior to test new, [faster query pipeline](https://github.com/brianc/node-postgres/pull/2044). This is experimental, and not documented yet. The pipeline changes will grow significantly after the 8.0 release. + +### 7.15.0 + +- Change repository structure to support lerna & future monorepo [development](https://github.com/brianc/node-postgres/pull/2014). +- [Warn about deprecation](https://github.com/brianc/node-postgres/pull/2021) for calling constructors without `new`. + +### 7.14.0 + +- Reverts 7.13.0 as it contained [an accidental breaking change](https://github.com/brianc/node-postgres/pull/2010) for self-signed SSL cert verification. 7.14.0 is identical to 7.12.1. + +### 7.13.0 + +- Add support for [all tls.connect()](https://github.com/brianc/node-postgres/pull/1996) options. + +### 7.12.0 + +- Add support for [async password lookup](https://github.com/brianc/node-postgres/pull/1926). + +### 7.11.0 + +- Add support for [connection_timeout](https://github.com/brianc/node-postgres/pull/1847/files#diff-5391bde944956870128be1136e7bc176R63) and [keepalives_idle](https://github.com/brianc/node-postgres/pull/1847). + +### 7.10.0 + +- Add support for [per-query types](https://github.com/brianc/node-postgres/pull/1825). + +### 7.9.0 + +- Add support for [sasl/scram authentication](https://github.com/brianc/node-postgres/pull/1835). + +### 7.8.0 + +- Add support for passing [secureOptions](https://github.com/brianc/node-postgres/pull/1804) SSL config. +- Upgrade [pg-types](https://github.com/brianc/node-postgres/pull/1806) to 2.0. + +### 7.7.0 + +- Add support for configurable [query timeout](https://github.com/brianc/node-postgres/pull/1760) on a client level. + +### 7.6.0 + +- Add support for ["bring your own promise"](https://github.com/brianc/node-postgres/pull/1518) + +### 7.5.0 + +- Better [error message](https://github.com/brianc/node-postgres/commit/11a4793452d618c53e019416cc886ad38deb1aa7) when passing `null` or `undefined` to `client.query`. +- Better [error handling](https://github.com/brianc/node-postgres/pull/1503) on queued queries. + +### 7.4.0 + +- Add support for [Uint8Array](https://github.com/brianc/node-postgres/pull/1448) values. + +### 7.3.0 + +- Add support for [statement timeout](https://github.com/brianc/node-postgres/pull/1436). + +### 7.2.0 + +- Pinned pg-pool and pg-types to a tighter semver range. This is likely not a noticeable change for you unless you were specifically installing older versions of those libraries for some reason, but making it a minor bump here just in case it could cause any confusion. + +### 7.1.0 + +#### Enhancements + +- [You can now supply both a connection string and additional config options to clients.](https://github.com/brianc/node-postgres/pull/1363) + +### 7.0.0 + +#### Breaking Changes + +- Drop support for node < `4.x`. +- Remove `pg.connect` `pg.end` and `pg.cancel` singleton methods. +- `Client#connect(callback)` now returns `undefined`. It used to return an event emitter. +- Upgrade [pg-pool](https://github.com/brianc/node-pg-pool) to `2.x`. +- Upgrade [pg-native](https://github.com/brianc/node-pg-native) to `2.x`. +- Standardize error message fields between JS and native driver. The only breaking changes were in the native driver as its field names were brought into alignment with the existing JS driver field names. +- Result from multi-statement text queries such as `SELECT 1; SELECT 2;` are now returned as an array of results instead of a single result with 1 array containing rows from both queries. + +[Please see here for a migration guide](https://node-postgres.com/guides/upgrading) + +#### Enhancements + +- Overhauled documentation: [https://node-postgres.com](https://node-postgres.com). +- Add `Client#connect() => Promise` and `Client#end() => Promise` calls. Promises are now returned from all async methods on clients _if and only if_ no callback was supplied to the method. +- Add `connectionTimeoutMillis` to pg-pool. + +### v6.2.0 + +- Add support for [parsing `replicationStart` messages](https://github.com/brianc/node-postgres/pull/1271/files). + +### v6.1.0 + +- Add optional callback parameter to the pure JavaScript `client.end` method. The native client already supported this. + +### v6.0.0 + +#### Breaking Changes + +- Remove `pg.pools`. There is still a reference kept to the pools created & tracked by `pg.connect` but it has been renamed, is considered private, and should not be used. Accessing this API directly was uncommon and was _supposed_ to be private but was incorrectly documented on the wiki. Therefore, it is a breaking change of an (unintentionally) public interface to remove it by renaming it & making it private. Eventually `pg.connect` itself will be deprecated in favor of instantiating pools directly via `new pg.Pool()` so this property should become completely moot at some point. In the mean time...check out the new features... + +#### New features + +- Replace internal pooling code with [pg-pool](https://github.com/brianc/node-pg-pool). This is the first step in eventually deprecating and removing the singleton `pg.connect`. The pg-pool constructor is exported from node-postgres at `require('pg').Pool`. It provides a backwards compatible interface with `pg.connect` as well as a promise based interface & additional niceties. + +You can now create an instance of a pool and don't have to rely on the `pg` singleton for anything: + +``` +var pg = require('pg') + +var pool = new pg.Pool() + +// your friendly neighborhood pool interface, without the singleton +pool.connect(function(err, client, done) { + // ... +}) +``` + +Promise support & other goodness lives now in [pg-pool](https://github.com/brianc/node-pg-pool). + +**Please** read the readme at [pg-pool](https://github.com/brianc/node-pg-pool) for the full api. + +- Included support for tcp keep alive. Enable it as follows: + +```js +var client = new Client({ keepAlive: true }) +``` + +This should help with backends incorrectly considering idle clients to be dead and prematurely disconnecting them. + +### v5.1.0 + +- Make the query object returned from `client.query` implement the promise interface. This is the first step towards promisifying more of the node-postgres api. + +Example: + +```js +var client = new Client() +client.connect() +client.query('SELECT $1::text as name', ['brianc']).then(function (res) { + console.log('hello from', res.rows[0]) + client.end() +}) +``` + +### v5.0.0 + +#### Breaking Changes + +- `require('pg').native` now returns null if the native bindings cannot be found; previously, this threw an exception. + +#### New Features + +- better error message when passing `undefined` as a query parameter +- support for `defaults.connectionString` +- support for `returnToHead` being passed to [generic pool](https://github.com/coopernurse/node-pool) + +### v4.5.0 + +- Add option to parse JS date objects in query parameters as [UTC](https://github.com/brianc/node-postgres/pull/943) + +### v4.4.0 + +- Warn to `stderr` if a named query exceeds 63 characters which is the max length supported by postgres. + +### v4.3.0 + +- Unpin `pg-types` semver. Allow it to float against `pg-types@1.x`. + +### v4.2.0 + +- Support for additional error fields in postgres >= 9.3 if available. + +### v4.1.0 + +- Allow type parser overrides on a [per-client basis](https://github.com/brianc/node-postgres/pull/679) + +### v4.0.0 + +- Make [native bindings](https://github.com/brianc/node-pg-native.git) an optional install with `npm install pg-native` +- No longer surround query result callback with `try/catch` block. +- Remove built in COPY IN / COPY OUT support - better implementations provided by [pg-copy-streams](https://github.com/brianc/node-pg-copy-streams.git) and [pg-native](https://github.com/brianc/node-pg-native.git) + +### v3.6.0 + +- Include support for (parsing JSONB)[https://github.com/brianc/node-pg-types/pull/13] (supported in postgres 9.4) + +### v3.5.0 + +- Include support for parsing boolean arrays + +### v3.4.0 + +- Include port as connection parameter to [unix sockets](https://github.com/brianc/node-postgres/pull/604) +- Better support for odd [date parsing](https://github.com/brianc/node-pg-types/pull/8) + +### v3.2.0 + +- Add support for parsing [date arrays](https://github.com/brianc/node-pg-types/pull/3) +- Expose array parsers on [pg.types](https://github.com/brianc/node-pg-types/pull/2) +- Allow [pool](https://github.com/brianc/node-postgres/pull/591) to be configured + +### v3.1.0 + +- Add [count of the number of times a client has been checked out from the pool](https://github.com/brianc/node-postgres/pull/556) +- Emit `end` from `pg` object [when a pool is drained](https://github.com/brianc/node-postgres/pull/571) + +### v3.0.0 + +#### Breaking changes + +- [Parse the DATE PostgreSQL type as local time](https://github.com/brianc/node-postgres/pull/514) + +After [some discussion](https://github.com/brianc/node-postgres/issues/510) it was decided node-postgres was non-compliant in how it was handling DATE results. They were being converted to UTC, but the PostgreSQL documentation specifies they should be returned in the client timezone. This is a breaking change, and if you use the `date` type you might want to examine your code and make sure nothing is impacted. + +- [Fix possible numeric precision loss on numeric & int8 arrays](https://github.com/brianc/node-postgres/pull/501) + +pg@v2.0 included changes to not convert large integers into their JavaScript number representation because of possibility for numeric precision loss. The same types in arrays were not taken into account. This fix applies the same type of type-coercion rules to arrays of those types, so there will be no more possible numeric loss on an array of very large int8s for example. This is a breaking change because now a return type from a query of `int8[]` will contain _string_ representations +of the integers. Use your favorite JavaScript bignum module to represent them without precision loss, or punch over the type converter to return the old style arrays again. + +- [Fix to input array of dates being improperly converted to utc](https://github.com/benesch/node-postgres/commit/c41eedc3e01e5527a3d5c242fa1896f02ef0b261#diff-7172adb1fec2457a2700ed29008a8e0aR108) + +Single `date` parameters were properly sent to the PostgreSQL server properly in local time, but an input array of dates was being changed into utc dates. This is a violation of what PostgreSQL expects. Small breaking change, but none-the-less something you should check out if you are inserting an array of dates. + +- [Query no longer emits `end` event if it ends due to an error](https://github.com/brianc/node-postgres/commit/357b64d70431ec5ca721eb45a63b082c18e6ffa3) + +This is a small change to bring the semantics of query more in line with other EventEmitters. The tests all passed after this change, but I suppose it could still be a breaking change in certain use cases. If you are doing clever things with the `end` and `error` events of a query object you might want to check to make sure its still behaving normally, though it is most likely not an issue. + +#### New features + +- [Supercharge `prepareValue`](https://github.com/brianc/node-postgres/pull/555) + +The long & short of it is now any object you supply in the list of query values will be inspected for a `.toPostgres` method. If the method is present it will be called and its result used as the raw text value sent to PostgreSQL for that value. This allows the same type of custom type coercion on query parameters as was previously afforded to query result values. + +- [Domain aware connection pool](https://github.com/brianc/node-postgres/pull/531) + +If domains are active node-postgres will honor them and do everything it can to ensure all callbacks are properly fired in the active domain. If you have tried to use domains with node-postgres (or many other modules which pool long lived event emitters) you may have run into an issue where the active domain changes before and after a callback. This has been a longstanding footgun within node-postgres and I am happy to get it fixed. + +- [Disconnected clients now removed from pool](https://github.com/brianc/node-postgres/pull/543) + +Avoids a scenario where your pool could fill up with disconnected & unusable clients. + +- [Break type parsing code into separate module](https://github.com/brianc/node-postgres/pull/541) + +To provide better documentation and a clearer explanation of how to override the query result parsing system we broke the type converters [into their own module](https://github.com/brianc/node-pg-types). There is still work around removing the 'global-ness' of the type converters so each query or connection can return types differently, but this is a good first step and allow a lot more obvious way to return int8 results as JavaScript numbers, for example + +### v2.11.0 + +- Add support for [application_name](https://github.com/brianc/node-postgres/pull/497) + +### v2.10.0 + +- Add support for [the password file](http://www.postgresql.org/docs/9.3/static/libpq-pgpass.html) + +### v2.9.0 + +- Add better support for [unix domain socket](https://github.com/brianc/node-postgres/pull/487) connections + +### v2.8.0 + +- Add support for parsing JSON[] and UUID[] result types + +### v2.7.0 + +- Use single row mode in native bindings when available [@rpedela] + - reduces memory consumption when handling row values in 'row' event +- Automatically bind buffer type parameters as binary [@eugeneware] + +### v2.6.0 + +- Respect PGSSLMODE environment variable + +### v2.5.0 + +- Ability to opt-in to int8 parsing via `pg.defaults.parseInt8 = true` + +### v2.4.0 + +- Use eval in the result set parser to increase performance + +### v2.3.0 + +- Remove built-in support for binary Int64 parsing. + _Due to the low usage & required compiled dependency this will be pushed into a 3rd party add-on_ + +### v2.2.0 + +- [Add support for excapeLiteral and escapeIdentifier in both JavaScript and the native bindings](https://github.com/brianc/node-postgres/pull/396) + +### v2.1.0 + +- Add support for SSL connections in JavaScript driver +- this means you can connect to heroku postgres from your local machine without the native bindings! +- [Add field metadata to result object](https://github.com/brianc/node-postgres/blob/master/test/integration/client/row-description-on-results-tests.js) +- [Add ability for rows to be returned as arrays instead of objects](https://github.com/brianc/node-postgres/blob/master/test/integration/client/results-as-array-tests.js) + +### v2.0.0 + +- Properly handle various PostgreSQL to JavaScript type conversions to avoid data loss: + +``` +PostgreSQL | pg@v2.0 JavaScript | pg@v1.0 JavaScript +--------------------------------|---------------- +float4 | number (float) | string +float8 | number (float) | string +int8 | string | number (int) +numeric | string | number (float) +decimal | string | number (float) +``` + +For more information see https://github.com/brianc/node-postgres/pull/353 +If you are unhappy with these changes you can always [override the built in type parsing fairly easily](https://github.com/brianc/node-pg-parse-float). + +### v1.3.0 + +- Make client_encoding configurable and optional + +### v1.2.0 + +- return field metadata on result object: access via result.fields[i].name/dataTypeID + +### v1.1.0 + +- built in support for `JSON` data type for PostgreSQL Server @ v9.2.0 or greater + +### v1.0.0 + +- remove deprecated functionality + - Callback function passed to `pg.connect` now **requires** 3 arguments + - Client#pauseDrain() / Client#resumeDrain removed + - numeric, decimal, and float data types no longer parsed into float before being returned. Will be returned from query results as `String` + +### v0.15.0 + +- client now emits `end` when disconnected from back-end server +- if client is disconnected in the middle of a query, query receives an error + +### v0.14.0 + +- add deprecation warnings in prep for v1.0 +- fix read/write failures in native module under node v0.9.x diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5c14056 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010 - 2021 Brian Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.en.md b/README.en.md index 6f03e2a..5638de7 100644 --- a/README.en.md +++ b/README.en.md @@ -1,36 +1,72 @@ # openGauss-connector-nodejs #### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description +A simple [openGauss](https://opengauss.org) connector for Node.js based on [node-postgres](https://github.com/brianc/node-postgres). #### Installation -1. xxxx -2. xxxx -3. xxxx +```bash +npm install & npm run build +``` #### Instructions -1. xxxx -2. xxxx -3. xxxx +- [Configure the client access authentication method](https://opengauss.org/zh/docs/2.0.1/docs/Developerguide/%E9%85%8D%E7%BD%AE%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%8E%A5%E5%85%A5%E8%AE%A4%E8%AF%81.html) +- [Please refer to here for specific implementation] + (https://github.com/brianc/node-postgres) +- [Refer to the JDBC driver for encryption] + (https://gitee.com/opengauss/openGauss-connector-jdbc) + +- [How to Use] + (https://node-postgres.com) + +#### Posts + + +- [SEMMNI ERROR] + + When doing simpleinstall, we may get this error + + `"On systemwide basis, the maximum number of SEMMNI is not correct. the current SEMMNI value is: .... Please check it."` + + According to the installation script, SEMMNI should be greater than 321.875 + + On CentOS 7 we can do this to modify it + ``` bash + $ sudo vim /etc/sysctl.conf + ``` + Add this line to the end of file + + `kernel.sem = 250 32000 100 400` + + Then use `shift + double z` or `:wq` to save and exit the `vim` + + Finally execute it to apply our settings + ``` bash + $ /sbin/sysctl -p + ``` + +- [To change the encryption mode, you need to update your password or create a new user] + + Otherwise, an invalid user name or password will be displayed even if both the user name and password are correct(Invalid username/password, login denied) + + You can run the SQL command 'select rolname,rolpassword from pg_authID; 'to determine the encryption mode of the current user password. + +- [The account was locked] + + Enter the gsql on your server + + Then run this + + alter user `[username]` account unlock; -#### Contribution +- [Adding IPv4 rules allows all external links to the password to be verified by SHA256] -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request + Open this file: `/opt/software/openGauss/data/single_node/pg_hba.conf` + add this: `host all all 0.0.0.0/0 sha256 ` -#### Gitee Feature +- [GUC] + + Click [here](https://opengauss.org/zh/docs/1.0.0/docs/Quickstart/GUC%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E.html) -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index e2f6944..6ac9908 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,74 @@ # openGauss-connector-nodejs #### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} - -#### 软件架构 -软件架构说明 +基于[node-postgres](https://github.com/brianc/node-postgres)的一个简单的[openGauss](https://opengauss.org)Node.js驱动. #### 安装教程 -1. xxxx -2. xxxx -3. xxxx - +```bash + npm install & npm run build +``` #### 使用说明 -1. xxxx -2. xxxx -3. xxxx +- [配置客户端接入认证方式](https://opengauss.org/zh/docs/2.0.1/docs/Developerguide/%E9%85%8D%E7%BD%AE%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%8E%A5%E5%85%A5%E8%AE%A4%E8%AF%81.html) + +- 驱动具体实现参考[node-postgres](https://github.com/brianc/node-postgres) + +- SHA256 & SM3加密实现参考[openGauss-connector-jdbc](https://gitee.com/opengauss/openGauss-connector-jdbc) + +- 如何使用请参考[node-postgres wiki](https://node-postgres.com) + +#### 注意事项 + +- [安装时SEMMNI错误] + + 简易安装的时候,可能会遇到以下错误 + + `"On systemwide basis, the maximum number of SEMMNI is not correct. the current SEMMNI value is: .... Please check it."` + + 根据安装脚本计算出,信号量SEMMNI应该大于321.875 + + 在CentOS 7 可以用一下命令修改 SEMMNI + + ```bash + $ sudo vim /etc/sysctl.conf + ``` + + 添加以下数据到文件末尾 + + `kernel.sem = 250 32000 100 400` + + 使用指令 `shift+ double z` 或者 `:wq`保存文件修改 -#### 参与贡献 + 最后执行修改 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request + ```bash + $ /sbin/sysctl -p + ``` +- [修改加密方式需要更新自己的密码或者新建一个用户] + + 否则就算用户名密码都正确的情况下仍会报错非法用户名/密码(Invalid username/password, login denied) + + 可以通过sql命令 `select rolname,rolpassword from pg_authid;` 来判断当前用户密码的加密方式。 + + [账号锁定] + + openGauss账号被锁定解决办法,进入openGauss输入以下命令: + [database_name]# alter user [username] account unlock; + +- [添加IPv4规则允许所有外部链接的密码都由SHA256验证] -#### 特技 + 打开/opt/software/openGauss/data/single_node/pg_hba.conf + 添加以下数据 + add this: `host all all 0.0.0.0/0 sha256 ` + 详情请看[这里](https://opengauss.org/zh/docs/1.0.0/docs/Quickstart/GUC%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E.html) + +#### 简单测试 +修改packages/pg/test-1.js的服务器配置并执行如下指令 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +```bash +cd packages/pg/ +node test-1.js +``` \ No newline at end of file diff --git a/SPONSORS.md b/SPONSORS.md new file mode 100644 index 0000000..9d7d314 --- /dev/null +++ b/SPONSORS.md @@ -0,0 +1,42 @@ +node-postgres is made possible by the helpful contributors from the community as well as the following generous supporters on [GitHub Sponsors](https://github.com/sponsors/brianc) and [Patreon](https://www.patreon.com/node_postgres). + +# Leaders + +- [MadKudu](https://www.madkudu.com) - [@madkudu](https://twitter.com/madkudu) +- [Third Iron](https://thirdiron.com/) +- [Timescale](https://timescale.com) +- [Nafundi](https://nafundi.com) +- [CrateDB](https://crate.io/) +- [BitMEX](https://www.bitmex.com/app/trade/XBTUSD) +- [Dataform](https://dataform.co/) +- [Eaze](https://www.eaze.com/) +- [simpleanalytics](https://simpleanalytics.com/) +- [n8n.io]https://n8n.io/ + +# Supporters + +- John Fawcett +- Lalit Kapoor [@lalitkapoor](https://twitter.com/lalitkapoor) +- Paul Frazee [@pfrazee](https://twitter.com/pfrazee) +- Rein Petersen +- Arnaud Benhamdine [@abenhamdine](https://twitter.com/abenhamdine) +- Matthew Welke +- Matthew Weber +- Andrea De Simon +- Todd Kennedy +- Alexander Robson +- Benjie Gillam +- David Hanson +- Franklin Davenport +- [Eventbot](https://geteventbot.com/) +- Chuck T +- Paul Cothenet +- Pelle Wessman +- Raul Murray +- Simple Analytics +- Trevor Linton +- Ian Walter +- @Guido4000 +- [Martti Laine](https://github.com/codeclown) +- [Tim Nolet](https://github.com/tnolet) +- [checkly](https://github.com/checkly) diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..eb36670 --- /dev/null +++ b/lerna.json @@ -0,0 +1,12 @@ +{ + "packages": [ + "packages/*" + ], + "npmClient": "yarn", + "useWorkspaces": true, + "version": "independent", + "ignoreChanges": [ + "**/*.md", + "**/test/**" + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9ea0a6a --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "node-postgres", + "description": "node postgres monorepo", + "main": "index.js", + "private": true, + "repository": "git@github.com:brianc/node-postgres.git", + "author": "Brian M. Carlson ", + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "scripts": { + "test": "yarn lerna exec yarn test", + "build": "tsc --build", + "build:watch": "tsc --build --watch", + "pretest": "yarn build", + "prepublish": "yarn build", + "lint": "eslint '*/**/*.{js,ts,tsx}'" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.4.0", + "@typescript-eslint/parser": "^4.4.0", + "eslint": "^7.11.0", + "eslint-config-prettier": "^6.12.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.1.4", + "lerna": "^3.19.0", + "prettier": "2.1.2", + "typescript": "^4.3.5" + }, + "prettier": { + "semi": false, + "printWidth": 120, + "arrowParens": "always", + "trailingComma": "es5", + "singleQuote": true + } +} diff --git a/packages/pg-connection-string/.coveralls.yml b/packages/pg-connection-string/.coveralls.yml new file mode 100644 index 0000000..0709f6e --- /dev/null +++ b/packages/pg-connection-string/.coveralls.yml @@ -0,0 +1,2 @@ +service_name: travis-pro +repo_token: 5F6dODinz9L9uFR6HatKmtsYDoV1A5S2N diff --git a/packages/pg-connection-string/.gitignore b/packages/pg-connection-string/.gitignore new file mode 100644 index 0000000..f28f01f --- /dev/null +++ b/packages/pg-connection-string/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Deployed apps should consider commenting this line out: +# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git +node_modules +package-lock.json \ No newline at end of file diff --git a/packages/pg-connection-string/.travis.yml b/packages/pg-connection-string/.travis.yml new file mode 100644 index 0000000..daf50ba --- /dev/null +++ b/packages/pg-connection-string/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - '0.10' + - '6.9' + - '8' +after_success: 'npm run coveralls' diff --git a/packages/pg-connection-string/LICENSE b/packages/pg-connection-string/LICENSE new file mode 100644 index 0000000..b068a6c --- /dev/null +++ b/packages/pg-connection-string/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Iced Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md new file mode 100644 index 0000000..360505e --- /dev/null +++ b/packages/pg-connection-string/README.md @@ -0,0 +1,77 @@ +pg-connection-string +==================== + +[![NPM](https://nodei.co/npm/pg-connection-string.png?compact=true)](https://nodei.co/npm/pg-connection-string/) + +[![Build Status](https://travis-ci.org/iceddev/pg-connection-string.svg?branch=master)](https://travis-ci.org/iceddev/pg-connection-string) +[![Coverage Status](https://coveralls.io/repos/github/iceddev/pg-connection-string/badge.svg?branch=master)](https://coveralls.io/github/iceddev/pg-connection-string?branch=master) + +Functions for dealing with a PostgresSQL connection string + +`parse` method taken from [node-postgres](https://github.com/brianc/node-postgres.git) +Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com) +MIT License + +## Usage + +```js +var parse = require('pg-connection-string').parse; + +var config = parse('postgres://someuser:somepassword@somehost:381/somedatabase') +``` + +The resulting config contains a subset of the following properties: + +* `host` - Postgres server hostname or, for UNIX domain sockets, the socket filename +* `port` - port on which to connect +* `user` - User with which to authenticate to the server +* `password` - Corresponding password +* `database` - Database name within the server +* `client_encoding` - string encoding the client will use +* `ssl`, either a boolean or an object with properties + * `rejectUnauthorized` + * `cert` + * `key` + * `ca` +* any other query parameters (for example, `application_name`) are preserved intact. + +## Connection Strings + +The short summary of acceptable URLs is: + + * `socket:?` - UNIX domain socket + * `postgres://:@:/?` - TCP connection + +But see below for more details. + +### UNIX Domain Sockets + +When user and password are not given, the socket path follows `socket:`, as in `socket:/var/run/pgsql`. +This form can be shortened to just a path: `/var/run/pgsql`. + +When user and password are given, they are included in the typical URL positions, with an empty `host`, as in `socket://user:pass@/var/run/pgsql`. + +Query parameters follow a `?` character, including the following special query parameters: + + * `db=` - sets the database name (urlencoded) + * `encoding=` - sets the `client_encoding` property + +### TCP Connections + +TCP connections to the Postgres server are indicated with `pg:` or `postgres:` schemes (in fact, any scheme but `socket:` is accepted). +If username and password are included, they should be urlencoded. +The database name, however, should *not* be urlencoded. + +Query parameters follow a `?` character, including the following special query parameters: + * `host=` - sets `host` property, overriding the URL's host + * `encoding=` - sets the `client_encoding` property + * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly + * `sslmode=` + * `sslmode=disable` - sets `ssl` to false + * `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }` + * `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true + * `sslcert=` - reads data from the given file and includes the result as `ssl.cert` + * `sslkey=` - reads data from the given file and includes the result as `ssl.key` + * `sslrootcert=` - reads data from the given file and includes the result as `ssl.ca` + +A bare relative URL, such as `salesdata`, will indicate a database name while leaving other properties empty. diff --git a/packages/pg-connection-string/index.d.ts b/packages/pg-connection-string/index.d.ts new file mode 100644 index 0000000..3081270 --- /dev/null +++ b/packages/pg-connection-string/index.d.ts @@ -0,0 +1,15 @@ +export function parse(connectionString: string): ConnectionOptions + +export interface ConnectionOptions { + host: string | null + password?: string + user?: string + port?: string | null + database: string | null | undefined + client_encoding?: string + ssl?: boolean | string + + application_name?: string + fallback_application_name?: string + options?: string +} diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js new file mode 100644 index 0000000..995ff06 --- /dev/null +++ b/packages/pg-connection-string/index.js @@ -0,0 +1,106 @@ +'use strict' + +var url = require('url') +var fs = require('fs') + +//Parse method copied from https://github.com/brianc/node-postgres +//Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com) +//MIT License + +//parses a connection string +function parse(str) { + //unix socket + if (str.charAt(0) === '/') { + var config = str.split(' ') + return { host: config[0], database: config[1] } + } + + // url parse expects spaces encoded as %20 + var result = url.parse( + / |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str) ? encodeURI(str).replace(/\%25(\d\d)/g, '%$1') : str, + true + ) + var config = result.query + for (var k in config) { + if (Array.isArray(config[k])) { + config[k] = config[k][config[k].length - 1] + } + } + + var auth = (result.auth || ':').split(':') + config.user = auth[0] + config.password = auth.splice(1).join(':') + + config.port = result.port + if (result.protocol == 'socket:') { + config.host = decodeURI(result.pathname) + config.database = result.query.db + config.client_encoding = result.query.encoding + return config + } + if (!config.host) { + // Only set the host if there is no equivalent query param. + config.host = result.hostname + } + + // If the host is missing it might be a URL-encoded path to a socket. + var pathname = result.pathname + if (!config.host && pathname && /^%2f/i.test(pathname)) { + var pathnameSplit = pathname.split('/') + config.host = decodeURIComponent(pathnameSplit[0]) + pathname = pathnameSplit.splice(1).join('/') + } + // result.pathname is not always guaranteed to have a '/' prefix (e.g. relative urls) + // only strip the slash if it is present. + if (pathname && pathname.charAt(0) === '/') { + pathname = pathname.slice(1) || null + } + config.database = pathname && decodeURI(pathname) + + if (config.ssl === 'true' || config.ssl === '1') { + config.ssl = true + } + + if (config.ssl === '0') { + config.ssl = false + } + + if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) { + config.ssl = {} + } + + if (config.sslcert) { + config.ssl.cert = fs.readFileSync(config.sslcert).toString() + } + + if (config.sslkey) { + config.ssl.key = fs.readFileSync(config.sslkey).toString() + } + + if (config.sslrootcert) { + config.ssl.ca = fs.readFileSync(config.sslrootcert).toString() + } + + switch (config.sslmode) { + case 'disable': { + config.ssl = false + break + } + case 'prefer': + case 'require': + case 'verify-ca': + case 'verify-full': { + break + } + case 'no-verify': { + config.ssl.rejectUnauthorized = false + break + } + } + + return config +} + +module.exports = parse + +parse.parse = parse diff --git a/packages/pg-connection-string/package.json b/packages/pg-connection-string/package.json new file mode 100644 index 0000000..6754327 --- /dev/null +++ b/packages/pg-connection-string/package.json @@ -0,0 +1,39 @@ +{ + "name": "pg-connection-string", + "version": "2.5.0", + "description": "Functions for dealing with a PostgresSQL connection string", + "main": "./index.js", + "types": "./index.d.ts", + "scripts": { + "test": "istanbul cover _mocha && npm run check-coverage", + "check-coverage": "istanbul check-coverage --statements 100 --branches 100 --lines 100 --functions 100", + "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-connection-string" + }, + "keywords": [ + "pg", + "connection", + "string", + "parse" + ], + "author": "Blaine Bublitz (http://iceddev.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/brianc/node-postgres/issues" + }, + "homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string", + "devDependencies": { + "chai": "^4.1.1", + "coveralls": "^3.0.4", + "istanbul": "^0.4.5", + "mocha": "^7.1.2" + }, + "files": [ + "index.js", + "index.d.ts" + ] +} diff --git a/packages/pg-connection-string/test/example.ca b/packages/pg-connection-string/test/example.ca new file mode 100644 index 0000000..0a6dcf4 --- /dev/null +++ b/packages/pg-connection-string/test/example.ca @@ -0,0 +1 @@ +example ca diff --git a/packages/pg-connection-string/test/example.cert b/packages/pg-connection-string/test/example.cert new file mode 100644 index 0000000..7693b3f --- /dev/null +++ b/packages/pg-connection-string/test/example.cert @@ -0,0 +1 @@ +example cert diff --git a/packages/pg-connection-string/test/example.key b/packages/pg-connection-string/test/example.key new file mode 100644 index 0000000..1aef993 --- /dev/null +++ b/packages/pg-connection-string/test/example.key @@ -0,0 +1 @@ +example key diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js new file mode 100644 index 0000000..a0cd263 --- /dev/null +++ b/packages/pg-connection-string/test/parse.js @@ -0,0 +1,320 @@ +'use strict' + +var chai = require('chai') +var expect = chai.expect +chai.should() + +var parse = require('../').parse + +describe('parse', function () { + it('using connection string in client constructor', function () { + var subject = parse('postgres://brian:pw@boom:381/lala') + subject.user.should.equal('brian') + subject.password.should.equal('pw') + subject.host.should.equal('boom') + subject.port.should.equal('381') + subject.database.should.equal('lala') + }) + + it('escape spaces if present', function () { + var subject = parse('postgres://localhost/post gres') + subject.database.should.equal('post gres') + }) + + it('do not double escape spaces', function () { + var subject = parse('postgres://localhost/post%20gres') + subject.database.should.equal('post gres') + }) + + it('initializing with unix domain socket', function () { + var subject = parse('/var/run/') + subject.host.should.equal('/var/run/') + }) + + it('initializing with unix domain socket and a specific database, the simple way', function () { + var subject = parse('/var/run/ mydb') + subject.host.should.equal('/var/run/') + subject.database.should.equal('mydb') + }) + + it('initializing with unix domain socket, the health way', function () { + var subject = parse('socket:/some path/?db=my[db]&encoding=utf8') + subject.host.should.equal('/some path/') + subject.database.should.equal('my[db]', 'must to be escaped and unescaped trough "my%5Bdb%5D"') + subject.client_encoding.should.equal('utf8') + }) + + it('initializing with unix domain socket, the escaped health way', function () { + var subject = parse('socket:/some%20path/?db=my%2Bdb&encoding=utf8') + subject.host.should.equal('/some path/') + subject.database.should.equal('my+db') + subject.client_encoding.should.equal('utf8') + }) + + it('initializing with unix domain socket, username and password', function () { + var subject = parse('socket://brian:pw@/var/run/?db=mydb') + subject.user.should.equal('brian') + subject.password.should.equal('pw') + subject.host.should.equal('/var/run/') + subject.database.should.equal('mydb') + }) + + it('password contains < and/or > characters', function () { + var sourceConfig = { + user: 'brian', + password: 'helloe', + port: 5432, + host: 'localhost', + database: 'postgres', + } + var connectionString = + 'postgres://' + + sourceConfig.user + + ':' + + sourceConfig.password + + '@' + + sourceConfig.host + + ':' + + sourceConfig.port + + '/' + + sourceConfig.database + var subject = parse(connectionString) + subject.password.should.equal(sourceConfig.password) + }) + + it('password contains colons', function () { + var sourceConfig = { + user: 'brian', + password: 'hello:pass:world', + port: 5432, + host: 'localhost', + database: 'postgres', + } + var connectionString = + 'postgres://' + + sourceConfig.user + + ':' + + sourceConfig.password + + '@' + + sourceConfig.host + + ':' + + sourceConfig.port + + '/' + + sourceConfig.database + var subject = parse(connectionString) + subject.password.should.equal(sourceConfig.password) + }) + + it('username or password contains weird characters', function () { + var strang = 'pg://my f%irst name:is&%awesome!@localhost:9000' + var subject = parse(strang) + subject.user.should.equal('my f%irst name') + subject.password.should.equal('is&%awesome!') + subject.host.should.equal('localhost') + }) + + it('url is properly encoded', function () { + var encoded = 'pg://bi%25na%25%25ry%20:s%40f%23@localhost/%20u%2520rl' + var subject = parse(encoded) + subject.user.should.equal('bi%na%%ry ') + subject.password.should.equal('s@f#') + subject.host.should.equal('localhost') + subject.database.should.equal(' u%20rl') + }) + + it('relative url sets database', function () { + var relative = 'different_db_on_default_host' + var subject = parse(relative) + subject.database.should.equal('different_db_on_default_host') + }) + + it('no pathname returns null database', function () { + var subject = parse('pg://myhost') + ;(subject.database === null).should.equal(true) + }) + + it('pathname of "/" returns null database', function () { + var subject = parse('pg://myhost/') + subject.host.should.equal('myhost') + ;(subject.database === null).should.equal(true) + }) + + it('configuration parameter host', function () { + var subject = parse('pg://user:pass@/dbname?host=/unix/socket') + subject.user.should.equal('user') + subject.password.should.equal('pass') + subject.host.should.equal('/unix/socket') + subject.database.should.equal('dbname') + }) + + it('configuration parameter host overrides url host', function () { + var subject = parse('pg://user:pass@localhost/dbname?host=/unix/socket') + subject.host.should.equal('/unix/socket') + }) + + it('url with encoded socket', function () { + var subject = parse('pg://user:pass@%2Funix%2Fsocket/dbname') + subject.user.should.equal('user') + subject.password.should.equal('pass') + subject.host.should.equal('/unix/socket') + subject.database.should.equal('dbname') + }) + + it('url with real host and an encoded db name', function () { + var subject = parse('pg://user:pass@localhost/%2Fdbname') + subject.user.should.equal('user') + subject.password.should.equal('pass') + subject.host.should.equal('localhost') + subject.database.should.equal('%2Fdbname') + }) + + it('configuration parameter host treats encoded socket as part of the db name', function () { + var subject = parse('pg://user:pass@%2Funix%2Fsocket/dbname?host=localhost') + subject.user.should.equal('user') + subject.password.should.equal('pass') + subject.host.should.equal('localhost') + subject.database.should.equal('%2Funix%2Fsocket/dbname') + }) + + it('configuration parameter application_name', function () { + var connectionString = 'pg:///?application_name=TheApp' + var subject = parse(connectionString) + subject.application_name.should.equal('TheApp') + }) + + it('configuration parameter fallback_application_name', function () { + var connectionString = 'pg:///?fallback_application_name=TheAppFallback' + var subject = parse(connectionString) + subject.fallback_application_name.should.equal('TheAppFallback') + }) + + it('configuration parameter options', function () { + var connectionString = 'pg:///?options=-c geqo=off' + var subject = parse(connectionString) + subject.options.should.equal('-c geqo=off') + }) + + it('configuration parameter ssl=true', function () { + var connectionString = 'pg:///?ssl=true' + var subject = parse(connectionString) + subject.ssl.should.equal(true) + }) + + it('configuration parameter ssl=1', function () { + var connectionString = 'pg:///?ssl=1' + var subject = parse(connectionString) + subject.ssl.should.equal(true) + }) + + it('configuration parameter ssl=0', function () { + var connectionString = 'pg:///?ssl=0' + var subject = parse(connectionString) + subject.ssl.should.equal(false) + }) + + it('set ssl', function () { + var subject = parse('pg://myhost/db?ssl=1') + subject.ssl.should.equal(true) + }) + + it('configuration parameter sslcert=/path/to/cert', function () { + var connectionString = 'pg:///?sslcert=' + __dirname + '/example.cert' + var subject = parse(connectionString) + subject.ssl.should.eql({ + cert: 'example cert\n', + }) + }) + + it('configuration parameter sslkey=/path/to/key', function () { + var connectionString = 'pg:///?sslkey=' + __dirname + '/example.key' + var subject = parse(connectionString) + subject.ssl.should.eql({ + key: 'example key\n', + }) + }) + + it('configuration parameter sslrootcert=/path/to/ca', function () { + var connectionString = 'pg:///?sslrootcert=' + __dirname + '/example.ca' + var subject = parse(connectionString) + subject.ssl.should.eql({ + ca: 'example ca\n', + }) + }) + + it('configuration parameter sslmode=no-verify', function () { + var connectionString = 'pg:///?sslmode=no-verify' + var subject = parse(connectionString) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) + }) + + it('configuration parameter sslmode=disable', function () { + var connectionString = 'pg:///?sslmode=disable' + var subject = parse(connectionString) + subject.ssl.should.eql(false) + }) + + it('configuration parameter sslmode=prefer', function () { + var connectionString = 'pg:///?sslmode=prefer' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter sslmode=require', function () { + var connectionString = 'pg:///?sslmode=require' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter sslmode=verify-ca', function () { + var connectionString = 'pg:///?sslmode=verify-ca' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter sslmode=verify-full', function () { + var connectionString = 'pg:///?sslmode=verify-full' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca', function () { + var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require' + var subject = parse(connectionString) + subject.ssl.should.eql({ + ca: 'example ca\n', + }) + }) + + it('allow other params like max, ...', function () { + var subject = parse('pg://myhost/db?max=18&min=4') + subject.max.should.equal('18') + subject.min.should.equal('4') + }) + + it('configuration parameter keepalives', function () { + var connectionString = 'pg:///?keepalives=1' + var subject = parse(connectionString) + subject.keepalives.should.equal('1') + }) + + it('unknown configuration parameter is passed into client', function () { + var connectionString = 'pg:///?ThereIsNoSuchPostgresParameter=1234' + var subject = parse(connectionString) + subject.ThereIsNoSuchPostgresParameter.should.equal('1234') + }) + + it('do not override a config field with value from query string', function () { + var subject = parse('socket:/some path/?db=my[db]&encoding=utf8&client_encoding=bogus') + subject.host.should.equal('/some path/') + subject.database.should.equal('my[db]', 'must to be escaped and unescaped through "my%5Bdb%5D"') + subject.client_encoding.should.equal('utf8') + }) + + it('return last value of repeated parameter', function () { + var connectionString = 'pg:///?keepalives=1&keepalives=0' + var subject = parse(connectionString) + subject.keepalives.should.equal('0') + }) +}) diff --git a/packages/pg-cursor/README.md b/packages/pg-cursor/README.md new file mode 100644 index 0000000..1b01b3d --- /dev/null +++ b/packages/pg-cursor/README.md @@ -0,0 +1,37 @@ +node-pg-cursor +============== + +Use a PostgreSQL result cursor from node with an easy to use API. + +### install + +```sh +$ npm install pg-cursor +``` +___note___: this depends on _either_ `npm install pg` or `npm install pg.js`, but you __must__ be using the pure JavaScript client. This will __not work__ with the native bindings. + +### :star: [Documentation](https://node-postgres.com/api/cursor) :star: + +### license + +The MIT License (MIT) + +Copyright (c) 2013 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/pg-cursor/index.js b/packages/pg-cursor/index.js new file mode 100644 index 0000000..8e8552b --- /dev/null +++ b/packages/pg-cursor/index.js @@ -0,0 +1,241 @@ +'use strict' +const Result = require('pg/lib/result.js') +const prepare = require('pg/lib/utils.js').prepareValue +const EventEmitter = require('events').EventEmitter +const util = require('util') + +let nextUniqueID = 1 // concept borrowed from org.postgresql.core.v3.QueryExecutorImpl + +class Cursor extends EventEmitter { + constructor(text, values, config) { + super() + + this._conf = config || {} + this.text = text + this.values = values ? values.map(prepare) : null + this.connection = null + this._queue = [] + this.state = 'initialized' + this._result = new Result(this._conf.rowMode, this._conf.types) + this._cb = null + this._rows = null + this._portal = null + this._ifNoData = this._ifNoData.bind(this) + this._rowDescription = this._rowDescription.bind(this) + } + + _ifNoData() { + this.state = 'idle' + this._shiftQueue() + if (this.connection) { + this.connection.removeListener('rowDescription', this._rowDescription) + } + } + + _rowDescription() { + if (this.connection) { + this.connection.removeListener('noData', this._ifNoData) + } + } + + submit(connection) { + this.state = 'submitted' + this.connection = connection + this._portal = 'C_' + nextUniqueID++ + + const con = connection + + con.parse( + { + text: this.text, + }, + true + ) + + con.bind( + { + portal: this._portal, + values: this.values, + }, + true + ) + + con.describe( + { + type: 'P', + name: this._portal, // AWS Redshift requires a portal name + }, + true + ) + + con.flush() + + if (this._conf.types) { + this._result._getTypeParser = this._conf.types.getTypeParser + } + + con.once('noData', this._ifNoData) + con.once('rowDescription', this._rowDescription) + } + + _shiftQueue() { + if (this._queue.length) { + this._getRows.apply(this, this._queue.shift()) + } + } + + _closePortal() { + // because we opened a named portal to stream results + // we need to close the same named portal. Leaving a named portal + // open can lock tables for modification if inside a transaction. + // see https://github.com/brianc/node-pg-cursor/issues/56 + this.connection.close({ type: 'P', name: this._portal }) + + // If we've received an error we already sent a sync message. + // do not send another sync as it triggers another readyForQuery message. + if (this.state !== 'error') { + this.connection.sync() + } + } + + handleRowDescription(msg) { + this._result.addFields(msg.fields) + this.state = 'idle' + this._shiftQueue() + } + + handleDataRow(msg) { + const row = this._result.parseRow(msg.fields) + this.emit('row', row, this._result) + this._rows.push(row) + } + + _sendRows() { + this.state = 'idle' + setImmediate(() => { + const cb = this._cb + // remove callback before calling it + // because likely a new one will be added + // within the call to this callback + this._cb = null + if (cb) { + this._result.rows = this._rows + cb(null, this._rows, this._result) + } + this._rows = [] + }) + } + + handleCommandComplete(msg) { + this._result.addCommandComplete(msg) + this._closePortal() + } + + handlePortalSuspended() { + this._sendRows() + } + + handleReadyForQuery() { + this._sendRows() + this.state = 'done' + this.emit('end', this._result) + } + + handleEmptyQuery() { + this.connection.sync() + } + + handleError(msg) { + // If we're in an initialized state we've never been submitted + // and don't have a connection instance reference yet. + // This can happen if you queue a stream and close the client before + // the client has submitted the stream. In this scenario we don't have + // a connection so there's nothing to unsubscribe from. + if (this.state !== 'initialized') { + this.connection.removeListener('noData', this._ifNoData) + this.connection.removeListener('rowDescription', this._rowDescription) + // call sync to trigger a readyForQuery + this.connection.sync() + } + + this.state = 'error' + this._error = msg + // satisfy any waiting callback + if (this._cb) { + this._cb(msg) + } + // dispatch error to all waiting callbacks + for (let i = 0; i < this._queue.length; i++) { + this._queue.pop()[1](msg) + } + + if (this.listenerCount('error') > 0) { + // only dispatch error events if we have a listener + this.emit('error', msg) + } + } + + _getRows(rows, cb) { + this.state = 'busy' + this._cb = cb + this._rows = [] + const msg = { + portal: this._portal, + rows: rows, + } + this.connection.execute(msg, true) + this.connection.flush() + } + + // users really shouldn't be calling 'end' here and terminating a connection to postgres + // via the low level connection.end api + end(cb) { + if (this.state !== 'initialized') { + this.connection.sync() + } + this.connection.once('end', cb) + this.connection.end() + } + + close(cb) { + if (!this.connection || this.state === 'done') { + if (cb) { + return setImmediate(cb) + } else { + return + } + } + + this._closePortal() + this.state = 'done' + if (cb) { + this.connection.once('readyForQuery', function () { + cb() + }) + } + } + + read(rows, cb) { + if (this.state === 'idle' || this.state === 'submitted') { + return this._getRows(rows, cb) + } + if (this.state === 'busy' || this.state === 'initialized') { + return this._queue.push([rows, cb]) + } + if (this.state === 'error') { + return setImmediate(() => cb(this._error)) + } + if (this.state === 'done') { + return setImmediate(() => cb(null, [])) + } else { + throw new Error('Unknown state: ' + this.state) + } + } +} + +Cursor.prototype.end = util.deprecate( + Cursor.prototype.end, + 'Cursor.end is deprecated. Call end on the client itself to end a connection to the database.' +) + +module.exports = Cursor diff --git a/packages/pg-cursor/package.json b/packages/pg-cursor/package.json new file mode 100644 index 0000000..5607ea9 --- /dev/null +++ b/packages/pg-cursor/package.json @@ -0,0 +1,26 @@ +{ + "name": "pg-cursor", + "version": "2.6.0", + "description": "Query cursor extension for node-postgres", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "mocha" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-cursor" + }, + "author": "Brian M. Carlson", + "license": "MIT", + "devDependencies": { + "mocha": "^7.1.2", + "pg": "^8.6.0" + }, + "peerDependencies": { + "pg": "^8" + } +} diff --git a/packages/pg-cursor/test/close.js b/packages/pg-cursor/test/close.js new file mode 100644 index 0000000..e63512a --- /dev/null +++ b/packages/pg-cursor/test/close.js @@ -0,0 +1,54 @@ +const assert = require('assert') +const Cursor = require('../') +const pg = require('pg') + +const text = 'SELECT generate_series as num FROM generate_series(0, 50)' +describe('close', function () { + beforeEach(function (done) { + const client = (this.client = new pg.Client()) + client.connect(done) + }) + + this.afterEach(function (done) { + this.client.end(done) + }) + + it('can close a finished cursor without a callback', function (done) { + const cursor = new Cursor(text) + this.client.query(cursor) + this.client.query('SELECT NOW()', done) + cursor.read(100, function (err) { + assert.ifError(err) + cursor.close() + }) + }) + + it('closes cursor early', function (done) { + const cursor = new Cursor(text) + this.client.query(cursor) + this.client.query('SELECT NOW()', done) + cursor.read(25, function (err) { + assert.ifError(err) + cursor.close() + }) + }) + + it('works with callback style', function (done) { + const cursor = new Cursor(text) + const client = this.client + client.query(cursor) + cursor.read(25, function (err, rows) { + assert.ifError(err) + assert.strictEqual(rows.length, 25) + cursor.close(function (err) { + assert.ifError(err) + client.query('SELECT NOW()', done) + }) + }) + }) + + it('is a no-op to "close" the cursor before submitting it', function (done) { + const cursor = new Cursor(text) + cursor.close(done) + }) +}) diff --git a/packages/pg-cursor/test/error-handling.js b/packages/pg-cursor/test/error-handling.js new file mode 100644 index 0000000..f6edef6 --- /dev/null +++ b/packages/pg-cursor/test/error-handling.js @@ -0,0 +1,86 @@ +'use strict' +const assert = require('assert') +const Cursor = require('../') +const pg = require('pg') + +const text = 'SELECT generate_series as num FROM generate_series(0, 4)' + +describe('error handling', function () { + it('can continue after error', function (done) { + const client = new pg.Client() + client.connect() + const cursor = client.query(new Cursor('asdfdffsdf')) + cursor.read(1, function (err) { + assert(err) + client.query('SELECT NOW()', function (err) { + assert.ifError(err) + client.end() + done() + }) + }) + }) +}) + +describe('read callback does not fire sync', () => { + it('does not fire error callback sync', (done) => { + const client = new pg.Client() + client.connect() + const cursor = client.query(new Cursor('asdfdffsdf')) + let after = false + cursor.read(1, function (err) { + assert(err, 'error should be returned') + assert.strictEqual(after, true, 'should not call read sync') + after = false + cursor.read(1, function (err) { + assert(err, 'error should be returned') + assert.strictEqual(after, true, 'should not call read sync') + client.end() + done() + }) + after = true + }) + after = true + }) + + it('does not fire result sync after finished', (done) => { + const client = new pg.Client() + client.connect() + const cursor = client.query(new Cursor('SELECT NOW()')) + let after = false + cursor.read(1, function (err) { + assert(!err) + assert.strictEqual(after, true, 'should not call read sync') + cursor.read(1, function (err) { + assert(!err) + after = false + cursor.read(1, function (err) { + assert(!err) + assert.strictEqual(after, true, 'should not call read sync') + client.end() + done() + }) + after = true + }) + }) + after = true + }) +}) + +describe('proper cleanup', function () { + it('can issue multiple cursors on one client', function (done) { + const client = new pg.Client() + client.connect() + const cursor1 = client.query(new Cursor(text)) + cursor1.read(8, function (err, rows) { + assert.ifError(err) + assert.strictEqual(rows.length, 5) + const cursor2 = client.query(new Cursor(text)) + cursor2.read(8, function (err, rows) { + assert.ifError(err) + assert.strictEqual(rows.length, 5) + client.end() + done() + }) + }) + }) +}) diff --git a/packages/pg-cursor/test/index.js b/packages/pg-cursor/test/index.js new file mode 100644 index 0000000..24d3cfd --- /dev/null +++ b/packages/pg-cursor/test/index.js @@ -0,0 +1,181 @@ +const assert = require('assert') +const Cursor = require('../') +const pg = require('pg') + +const text = 'SELECT generate_series as num FROM generate_series(0, 5)' + +describe('cursor', function () { + beforeEach(function (done) { + const client = (this.client = new pg.Client()) + client.connect(done) + + this.pgCursor = function (text, values) { + return client.query(new Cursor(text, values || [])) + } + }) + + afterEach(function () { + this.client.end() + }) + + it('fetch 6 when asking for 10', function (done) { + const cursor = this.pgCursor(text) + cursor.read(10, function (err, res) { + assert.ifError(err) + assert.strictEqual(res.length, 6) + done() + }) + }) + + it('end before reading to end', function (done) { + const cursor = this.pgCursor(text) + cursor.read(3, function (err, res) { + assert.ifError(err) + assert.strictEqual(res.length, 3) + done() + }) + }) + + it('callback with error', function (done) { + const cursor = this.pgCursor('select asdfasdf') + cursor.read(1, function (err) { + assert(err) + done() + }) + }) + + it('read a partial chunk of data', function (done) { + const cursor = this.pgCursor(text) + cursor.read(2, function (err, res) { + assert.ifError(err) + assert.strictEqual(res.length, 2) + cursor.read(3, function (err, res) { + assert(!err) + assert.strictEqual(res.length, 3) + cursor.read(1, function (err, res) { + assert(!err) + assert.strictEqual(res.length, 1) + cursor.read(1, function (err, res) { + assert(!err) + assert.ifError(err) + assert.strictEqual(res.length, 0) + done() + }) + }) + }) + }) + }) + + it('read return length 0 past the end', function (done) { + const cursor = this.pgCursor(text) + cursor.read(2, function (err) { + assert(!err) + cursor.read(100, function (err, res) { + assert(!err) + assert.strictEqual(res.length, 4) + cursor.read(100, function (err, res) { + assert(!err) + assert.strictEqual(res.length, 0) + done() + }) + }) + }) + }) + + it('read huge result', function (done) { + this.timeout(10000) + const text = 'SELECT generate_series as num FROM generate_series(0, 100000)' + const values = [] + const cursor = this.pgCursor(text, values) + let count = 0 + const read = function () { + cursor.read(100, function (err, rows) { + if (err) return done(err) + if (!rows.length) { + assert.strictEqual(count, 100001) + return done() + } + count += rows.length + if (count % 10000 === 0) { + // console.log(count) + } + setImmediate(read) + }) + } + read() + }) + + it('normalizes parameter values', function (done) { + const text = 'SELECT $1::json me' + const values = [{ name: 'brian' }] + const cursor = this.pgCursor(text, values) + cursor.read(1, function (err, rows) { + if (err) return done(err) + assert.strictEqual(rows[0].me.name, 'brian') + cursor.read(1, function (err, rows) { + assert(!err) + assert.strictEqual(rows.length, 0) + done() + }) + }) + }) + + it('returns result along with rows', function (done) { + const cursor = this.pgCursor(text) + cursor.read(1, function (err, rows, result) { + assert.ifError(err) + assert.strictEqual(rows.length, 1) + assert.strictEqual(rows, result.rows) + assert.deepStrictEqual( + result.fields.map((f) => f.name), + ['num'] + ) + done() + }) + }) + + it('emits row events', function (done) { + const cursor = this.pgCursor(text) + cursor.read(10) + cursor.on('row', (row, result) => result.addRow(row)) + cursor.on('end', (result) => { + assert.strictEqual(result.rows.length, 6) + done() + }) + }) + + it('emits row events when cursor is closed manually', function (done) { + const cursor = this.pgCursor(text) + cursor.on('row', (row, result) => result.addRow(row)) + cursor.on('end', (result) => { + assert.strictEqual(result.rows.length, 3) + done() + }) + + cursor.read(3, () => cursor.close()) + }) + + it('emits error events', function (done) { + const cursor = this.pgCursor('select asdfasdf') + cursor.on('error', function (err) { + assert(err) + done() + }) + }) + + it('returns rowCount on insert', function (done) { + const pgCursor = this.pgCursor + this.client + .query('CREATE TEMPORARY TABLE pg_cursor_test (foo VARCHAR(1), bar VARCHAR(1))') + .then(function () { + const cursor = pgCursor('insert into pg_cursor_test values($1, $2)', ['a', 'b']) + cursor.read(1, function (err, rows, result) { + assert.ifError(err) + assert.strictEqual(rows.length, 0) + assert.strictEqual(result.rowCount, 1) + done() + }) + }) + .catch(done) + }) +}) diff --git a/packages/pg-cursor/test/mocha.opts b/packages/pg-cursor/test/mocha.opts new file mode 100644 index 0000000..eb60d62 --- /dev/null +++ b/packages/pg-cursor/test/mocha.opts @@ -0,0 +1,3 @@ +--reporter spec +--no-exit +--bail diff --git a/packages/pg-cursor/test/no-data-handling.js b/packages/pg-cursor/test/no-data-handling.js new file mode 100644 index 0000000..9c860b9 --- /dev/null +++ b/packages/pg-cursor/test/no-data-handling.js @@ -0,0 +1,34 @@ +const assert = require('assert') +const pg = require('pg') +const Cursor = require('../') + +describe('queries with no data', function () { + beforeEach(function (done) { + const client = (this.client = new pg.Client()) + client.connect(done) + }) + + afterEach(function () { + this.client.end() + }) + + it('handles queries that return no data', function (done) { + const cursor = new Cursor('CREATE TEMPORARY TABLE whatwhat (thing int)') + this.client.query(cursor) + cursor.read(100, function (err, rows) { + assert.ifError(err) + assert.strictEqual(rows.length, 0) + done() + }) + }) + + it('handles empty query', function (done) { + let cursor = new Cursor('-- this is a comment') + cursor = this.client.query(cursor) + cursor.read(100, function (err, rows) { + assert.ifError(err) + assert.strictEqual(rows.length, 0) + done() + }) + }) +}) diff --git a/packages/pg-cursor/test/pool.js b/packages/pg-cursor/test/pool.js new file mode 100644 index 0000000..9d8ca77 --- /dev/null +++ b/packages/pg-cursor/test/pool.js @@ -0,0 +1,107 @@ +'use strict' +const assert = require('assert') +const Cursor = require('../') +const pg = require('pg') + +const text = 'SELECT generate_series as num FROM generate_series(0, 50)' + +function poolQueryPromise(pool, readRowCount) { + return new Promise((resolve, reject) => { + pool.connect((err, client, done) => { + if (err) { + done(err) + return reject(err) + } + const cursor = client.query(new Cursor(text)) + cursor.read(readRowCount, (err) => { + if (err) { + done(err) + return reject(err) + } + cursor.close((err) => { + if (err) { + done(err) + return reject(err) + } + done() + resolve() + }) + }) + }) + }) +} + +describe('pool', function () { + beforeEach(function () { + this.pool = new pg.Pool({ max: 1 }) + }) + + afterEach(function () { + this.pool.end() + }) + + it('closes cursor early, single pool query', function (done) { + poolQueryPromise(this.pool, 25) + .then(() => done()) + .catch((err) => { + assert.ifError(err) + done() + }) + }) + + it('closes cursor early, saturated pool', function (done) { + const promises = [] + for (let i = 0; i < 10; i++) { + promises.push(poolQueryPromise(this.pool, 25)) + } + Promise.all(promises) + .then(() => done()) + .catch((err) => { + assert.ifError(err) + done() + }) + }) + + it('closes exhausted cursor, single pool query', function (done) { + poolQueryPromise(this.pool, 100) + .then(() => done()) + .catch((err) => { + assert.ifError(err) + done() + }) + }) + + it('closes exhausted cursor, saturated pool', function (done) { + const promises = [] + for (let i = 0; i < 10; i++) { + promises.push(poolQueryPromise(this.pool, 100)) + } + Promise.all(promises) + .then(() => done()) + .catch((err) => { + assert.ifError(err) + done() + }) + }) + + it('can close multiple times on a pool', async function () { + const pool = new pg.Pool({ max: 1 }) + const run = async () => { + const cursor = new Cursor(text) + const client = await pool.connect() + client.query(cursor) + await new Promise((resolve) => { + cursor.read(25, function (err) { + assert.ifError(err) + cursor.close(function (err) { + assert.ifError(err) + client.release() + resolve() + }) + }) + }) + } + await Promise.all([run(), run(), run()]) + await pool.end() + }) +}) diff --git a/packages/pg-cursor/test/query-config.js b/packages/pg-cursor/test/query-config.js new file mode 100644 index 0000000..855af30 --- /dev/null +++ b/packages/pg-cursor/test/query-config.js @@ -0,0 +1,35 @@ +'use strict' +const assert = require('assert') +const Cursor = require('../') +const pg = require('pg') + +describe('query config passed to result', () => { + it('passes rowMode to result', (done) => { + const client = new pg.Client() + client.connect() + const text = 'SELECT generate_series as num FROM generate_series(0, 5)' + const cursor = client.query(new Cursor(text, null, { rowMode: 'array' })) + cursor.read(10, (err, rows) => { + assert(!err) + assert.deepStrictEqual(rows, [[0], [1], [2], [3], [4], [5]]) + client.end() + done() + }) + }) + + it('passes types to result', (done) => { + const client = new pg.Client() + client.connect() + const text = 'SELECT generate_series as num FROM generate_series(0, 2)' + const types = { + getTypeParser: () => () => 'foo', + } + const cursor = client.query(new Cursor(text, null, { types })) + cursor.read(10, (err, rows) => { + assert(!err) + assert.deepStrictEqual(rows, [{ num: 'foo' }, { num: 'foo' }, { num: 'foo' }]) + client.end() + done() + }) + }) +}) diff --git a/packages/pg-cursor/test/transactions.js b/packages/pg-cursor/test/transactions.js new file mode 100644 index 0000000..37ca7db --- /dev/null +++ b/packages/pg-cursor/test/transactions.js @@ -0,0 +1,43 @@ +const assert = require('assert') +const Cursor = require('../') +const pg = require('pg') + +describe('transactions', () => { + it('can execute multiple statements in a transaction', async () => { + const client = new pg.Client() + await client.connect() + await client.query('begin') + await client.query('CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)') + const cursor = client.query(new Cursor('SELECT * FROM foobar')) + const rows = await new Promise((resolve, reject) => { + cursor.read(10, (err, rows) => (err ? reject(err) : resolve(rows))) + }) + assert.strictEqual(rows.length, 0) + await client.query('ALTER TABLE foobar ADD COLUMN name TEXT') + await client.end() + }) + + it('can execute multiple statements in a transaction if ending cursor early', async () => { + const client = new pg.Client() + await client.connect() + await client.query('begin') + await client.query('CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)') + const cursor = client.query(new Cursor('SELECT * FROM foobar')) + await new Promise((resolve) => cursor.close(resolve)) + await client.query('ALTER TABLE foobar ADD COLUMN name TEXT') + await client.end() + }) + + it('can execute multiple statements in a transaction if no data', async () => { + const client = new pg.Client() + await client.connect() + await client.query('begin') + // create a cursor that has no data response + const createText = 'CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)' + const cursor = client.query(new Cursor(createText)) + const err = await new Promise((resolve) => cursor.read(100, resolve)) + assert.ifError(err) + await client.query('ALTER TABLE foobar ADD COLUMN name TEXT') + await client.end() + }) +}) diff --git a/packages/pg-pool/LICENSE b/packages/pg-pool/LICENSE new file mode 100644 index 0000000..4e90581 --- /dev/null +++ b/packages/pg-pool/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/pg-pool/README.md b/packages/pg-pool/README.md new file mode 100644 index 0000000..c6d7e92 --- /dev/null +++ b/packages/pg-pool/README.md @@ -0,0 +1,376 @@ +# pg-pool +[![Build Status](https://travis-ci.org/brianc/node-pg-pool.svg?branch=master)](https://travis-ci.org/brianc/node-pg-pool) + +A connection pool for node-postgres + +## install +```sh +npm i pg-pool pg +``` + +## use + +### create + +to use pg-pool you must first create an instance of a pool + +```js +var Pool = require('pg-pool') + +// by default the pool uses the same +// configuration as whatever `pg` version you have installed +var pool = new Pool() + +// you can pass properties to the pool +// these properties are passed unchanged to both the node-postgres Client constructor +// and the node-pool (https://github.com/coopernurse/node-pool) constructor +// allowing you to fully configure the behavior of both +var pool2 = new Pool({ + database: 'postgres', + user: 'brianc', + password: 'secret!', + port: 5432, + ssl: true, + max: 20, // set pool max size to 20 + idleTimeoutMillis: 1000, // close idle clients after 1 second + connectionTimeoutMillis: 1000, // return an error after 1 second if connection could not be established + maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion) +}) + +//you can supply a custom client constructor +//if you want to use the native postgres client +var NativeClient = require('pg').native.Client +var nativePool = new Pool({ Client: NativeClient }) + +//you can even pool pg-native clients directly +var PgNativeClient = require('pg-native') +var pgNativePool = new Pool({ Client: PgNativeClient }) +``` + +##### Note: +The Pool constructor does not support passing a Database URL as the parameter. To use pg-pool on heroku, for example, you need to parse the URL into a config object. Here is an example of how to parse a Database URL. + +```js +const Pool = require('pg-pool'); +const url = require('url') + +const params = url.parse(process.env.DATABASE_URL); +const auth = params.auth.split(':'); + +const config = { + user: auth[0], + password: auth[1], + host: params.hostname, + port: params.port, + database: params.pathname.split('/')[1], + ssl: true +}; + +const pool = new Pool(config); + +/* + Transforms, 'postgres://DBuser:secret@DBHost:#####/myDB', into + config = { + user: 'DBuser', + password: 'secret', + host: 'DBHost', + port: '#####', + database: 'myDB', + ssl: true + } +*/ +``` + +### acquire clients with a promise + +pg-pool supports a fully promise-based api for acquiring clients + +```js +var pool = new Pool() +pool.connect().then(client => { + client.query('select $1::text as name', ['pg-pool']).then(res => { + client.release() + console.log('hello from', res.rows[0].name) + }) + .catch(e => { + client.release() + console.error('query error', e.message, e.stack) + }) +}) +``` + +### plays nice with async/await + +this ends up looking much nicer if you're using [co](https://github.com/tj/co) or async/await: + +```js +// with async/await +(async () => { + var pool = new Pool() + var client = await pool.connect() + try { + var result = await client.query('select $1::text as name', ['brianc']) + console.log('hello from', result.rows[0]) + } finally { + client.release() + } +})().catch(e => console.error(e.message, e.stack)) + +// with co +co(function * () { + var client = yield pool.connect() + try { + var result = yield client.query('select $1::text as name', ['brianc']) + console.log('hello from', result.rows[0]) + } finally { + client.release() + } +}).catch(e => console.error(e.message, e.stack)) +``` + +### your new favorite helper method + +because its so common to just run a query and return the client to the pool afterward pg-pool has this built-in: + +```js +var pool = new Pool() +var time = await pool.query('SELECT NOW()') +var name = await pool.query('select $1::text as name', ['brianc']) +console.log(name.rows[0].name, 'says hello at', time.rows[0].name) +``` + +you can also use a callback here if you'd like: + +```js +var pool = new Pool() +pool.query('SELECT $1::text as name', ['brianc'], function (err, res) { + console.log(res.rows[0].name) // brianc +}) +``` + +__pro tip:__ unless you need to run a transaction (which requires a single client for multiple queries) or you +have some other edge case like [streaming rows](https://github.com/brianc/node-pg-query-stream) or using a [cursor](https://github.com/brianc/node-pg-cursor) +you should almost always just use `pool.query`. Its easy, it does the right thing :tm:, and wont ever forget to return +clients back to the pool after the query is done. + +### drop-in backwards compatible + +pg-pool still and will always support the traditional callback api for acquiring a client. This is the exact API node-postgres has shipped with for years: + +```js +var pool = new Pool() +pool.connect((err, client, done) => { + if (err) return done(err) + + client.query('SELECT $1::text as name', ['pg-pool'], (err, res) => { + done() + if (err) { + return console.error('query error', e.message, e.stack) + } + console.log('hello from', res.rows[0].name) + }) +}) +``` + +### shut it down + +When you are finished with the pool if all the clients are idle the pool will close them after `config.idleTimeoutMillis` and your app +will shutdown gracefully. If you don't want to wait for the timeout you can end the pool as follows: + +```js +var pool = new Pool() +var client = await pool.connect() +console.log(await client.query('select now()')) +client.release() +await pool.end() +``` + +### a note on instances + +The pool should be a __long-lived object__ in your application. Generally you'll want to instantiate one pool when your app starts up and use the same instance of the pool throughout the lifetime of your application. If you are frequently creating a new pool within your code you likely don't have your pool initialization code in the correct place. Example: + +```js +// assume this is a file in your program at ./your-app/lib/db.js + +// correct usage: create the pool and let it live +// 'globally' here, controlling access to it through exported methods +var pool = new pg.Pool() + +// this is the right way to export the query method +module.exports.query = (text, values) => { + console.log('query:', text, values) + return pool.query(text, values) +} + +// this would be the WRONG way to export the connect method +module.exports.connect = () => { + // notice how we would be creating a pool instance here + // every time we called 'connect' to get a new client? + // that's a bad thing & results in creating an unbounded + // number of pools & therefore connections + var aPool = new pg.Pool() + return aPool.connect() +} +``` + +### events + +Every instance of a `Pool` is an event emitter. These instances emit the following events: + +#### error + +Emitted whenever an idle client in the pool encounters an error. This is common when your PostgreSQL server shuts down, reboots, or a network partition otherwise causes it to become unavailable while your pool has connected clients. + +Example: + +```js +const Pool = require('pg-pool') +const pool = new Pool() + +// attach an error handler to the pool for when a connected, idle client +// receives an error by being disconnected, etc +pool.on('error', function(error, client) { + // handle this in the same way you would treat process.on('uncaughtException') + // it is supplied the error as well as the idle client which received the error +}) +``` + +#### connect + +Fired whenever the pool creates a __new__ `pg.Client` instance and successfully connects it to the backend. + +Example: + +```js +const Pool = require('pg-pool') +const pool = new Pool() + +var count = 0 + +pool.on('connect', client => { + client.count = count++ +}) + +pool + .connect() + .then(client => { + return client + .query('SELECT $1::int AS "clientCount"', [client.count]) + .then(res => console.log(res.rows[0].clientCount)) // outputs 0 + .then(() => client) + }) + .then(client => client.release()) + +``` + +#### acquire + +Fired whenever the a client is acquired from the pool + +Example: + +This allows you to count the number of clients which have ever been acquired from the pool. + +```js +var Pool = require('pg-pool') +var pool = new Pool() + +var acquireCount = 0 +pool.on('acquire', function (client) { + acquireCount++ +}) + +var connectCount = 0 +pool.on('connect', function () { + connectCount++ +}) + +for (var i = 0; i < 200; i++) { + pool.query('SELECT NOW()') +} + +setTimeout(function () { + console.log('connect count:', connectCount) // output: connect count: 10 + console.log('acquire count:', acquireCount) // output: acquire count: 200 +}, 100) + +``` + +### environment variables + +pg-pool & node-postgres support some of the same environment variables as `psql` supports. The most common are: + +``` +PGDATABASE=my_db +PGUSER=username +PGPASSWORD="my awesome password" +PGPORT=5432 +PGSSLMODE=require +``` + +Usually I will export these into my local environment via a `.env` file with environment settings or export them in `~/.bash_profile` or something similar. This way I get configurability which works with both the postgres suite of tools (`psql`, `pg_dump`, `pg_restore`) and node, I can vary the environment variables locally and in production, and it supports the concept of a [12-factor app](http://12factor.net/) out of the box. + +## bring your own promise + +In versions of node `<=0.12.x` there is no native promise implementation available globally. You can polyfill the promise globally like this: + +```js +// first run `npm install promise-polyfill --save +if (typeof Promise == 'undefined') { + global.Promise = require('promise-polyfill') +} +``` + +You can use any other promise implementation you'd like. The pool also allows you to configure the promise implementation on a per-pool level: + +```js +var bluebirdPool = new Pool({ + Promise: require('bluebird') +}) +``` + +__please note:__ in node `<=0.12.x` the pool will throw if you do not provide a promise constructor in one of the two ways mentioned above. In node `>=4.0.0` the pool will use the native promise implementation by default; however, the two methods above still allow you to "bring your own." + +## maxUses and read-replica autoscaling (e.g. AWS Aurora) + +The maxUses config option can help an application instance rebalance load against a replica set that has been auto-scaled after the connection pool is already full of healthy connections. + +The mechanism here is that a connection is considered "expended" after it has been acquired and released `maxUses` number of times. Depending on the load on your system, this means there will be an approximate time in which any given connection will live, thus creating a window for rebalancing. + +Imagine a scenario where you have 10 app instances providing an API running against a replica cluster of 3 that are accessed via a round-robin DNS entry. Each instance runs a connection pool size of 20. With an ambient load of 50 requests per second, the connection pool will likely fill up in a few minutes with healthy connections. + +If you have weekly bursts of traffic which peak at 1,000 requests per second, you might want to grow your replicas to 10 during this period. Without setting `maxUses`, the new replicas will not be adopted by the app servers without an intervention -- namely, restarting each in turn in order to build up new connection pools that are balanced against all the replicas. Adding additional app server instances will help to some extent because they will adopt all the replicas in an even way, but the initial app servers will continue to focus additional load on the original replicas. + +This is where the `maxUses` configuration option comes into play. Setting `maxUses` to 7500 will ensure that over a period of 30 minutes or so the new replicas will be adopted as the pre-existing connections are closed and replaced with new ones, thus creating a window for eventual balance. + +You'll want to test based on your own scenarios, but one way to make a first guess at `maxUses` is to identify an acceptable window for rebalancing and then solve for the value: + +``` +maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize +``` + +In the example above, assuming we acquire and release 1 connection per request and we are aiming for a 30 minute rebalancing window: + +``` +maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize + 7200 = 1800 * 1000 / 10 / 25 +``` + +## tests + +To run tests clone the repo, `npm i` in the working dir, and then run `npm test` + +## contributions + +I love contributions. Please make sure they have tests, and submit a PR. If you're not sure if the issue is worth it or will be accepted it never hurts to open an issue to begin the conversation. If you're interested in keeping up with node-postgres releated stuff, you can follow me on twitter at [@briancarlson](https://twitter.com/briancarlson) - I generally announce any noteworthy updates there. + +## license + +The MIT License (MIT) +Copyright (c) 2016 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pg-pool/index.js b/packages/pg-pool/index.js new file mode 100644 index 0000000..403d05a --- /dev/null +++ b/packages/pg-pool/index.js @@ -0,0 +1,410 @@ +'use strict' +const EventEmitter = require('events').EventEmitter + +const NOOP = function () {} + +const removeWhere = (list, predicate) => { + const i = list.findIndex(predicate) + + return i === -1 ? undefined : list.splice(i, 1)[0] +} + +class IdleItem { + constructor(client, idleListener, timeoutId) { + this.client = client + this.idleListener = idleListener + this.timeoutId = timeoutId + } +} + +class PendingItem { + constructor(callback) { + this.callback = callback + } +} + +function throwOnDoubleRelease() { + throw new Error('Release called on client which has already been released to the pool.') +} + +function promisify(Promise, callback) { + if (callback) { + return { callback: callback, result: undefined } + } + let rej + let res + const cb = function (err, client) { + err ? rej(err) : res(client) + } + const result = new Promise(function (resolve, reject) { + res = resolve + rej = reject + }) + return { callback: cb, result: result } +} + +function makeIdleListener(pool, client) { + return function idleListener(err) { + err.client = client + + client.removeListener('error', idleListener) + client.on('error', () => { + pool.log('additional client error after disconnection due to error', err) + }) + pool._remove(client) + // TODO - document that once the pool emits an error + // the client has already been closed & purged and is unusable + pool.emit('error', err, client) + } +} + +class Pool extends EventEmitter { + constructor(options, Client) { + super() + this.options = Object.assign({}, options) + + if (options != null && 'password' in options) { + // "hiding" the password so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this.options, 'password', { + configurable: true, + enumerable: false, + writable: true, + value: options.password, + }) + } + if (options != null && options.ssl && options.ssl.key) { + // "hiding" the ssl->key so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this.options.ssl, 'key', { + enumerable: false, + }) + } + + this.options.max = this.options.max || this.options.poolSize || 10 + this.options.maxUses = this.options.maxUses || Infinity + this.log = this.options.log || function () {} + this.Client = this.options.Client || Client || require('pg').Client + this.Promise = this.options.Promise || global.Promise + + if (typeof this.options.idleTimeoutMillis === 'undefined') { + this.options.idleTimeoutMillis = 10000 + } + + this._clients = [] + this._idle = [] + this._pendingQueue = [] + this._endCallback = undefined + this.ending = false + this.ended = false + } + + _isFull() { + return this._clients.length >= this.options.max + } + + _pulseQueue() { + this.log('pulse queue') + if (this.ended) { + this.log('pulse queue ended') + return + } + if (this.ending) { + this.log('pulse queue on ending') + if (this._idle.length) { + this._idle.slice().map((item) => { + this._remove(item.client) + }) + } + if (!this._clients.length) { + this.ended = true + this._endCallback() + } + return + } + // if we don't have any waiting, do nothing + if (!this._pendingQueue.length) { + this.log('no queued requests') + return + } + // if we don't have any idle clients and we have no more room do nothing + if (!this._idle.length && this._isFull()) { + return + } + const pendingItem = this._pendingQueue.shift() + if (this._idle.length) { + const idleItem = this._idle.pop() + clearTimeout(idleItem.timeoutId) + const client = idleItem.client + const idleListener = idleItem.idleListener + + return this._acquireClient(client, pendingItem, idleListener, false) + } + if (!this._isFull()) { + return this.newClient(pendingItem) + } + throw new Error('unexpected condition') + } + + _remove(client) { + const removed = removeWhere(this._idle, (item) => item.client === client) + + if (removed !== undefined) { + clearTimeout(removed.timeoutId) + } + + this._clients = this._clients.filter((c) => c !== client) + client.end() + this.emit('remove', client) + } + + connect(cb) { + if (this.ending) { + const err = new Error('Cannot use a pool after calling end on the pool') + return cb ? cb(err) : this.Promise.reject(err) + } + + const response = promisify(this.Promise, cb) + const result = response.result + + // if we don't have to connect a new client, don't do so + if (this._isFull() || this._idle.length) { + // if we have idle clients schedule a pulse immediately + if (this._idle.length) { + process.nextTick(() => this._pulseQueue()) + } + + if (!this.options.connectionTimeoutMillis) { + this._pendingQueue.push(new PendingItem(response.callback)) + return result + } + + const queueCallback = (err, res, done) => { + clearTimeout(tid) + response.callback(err, res, done) + } + + const pendingItem = new PendingItem(queueCallback) + + // set connection timeout on checking out an existing client + const tid = setTimeout(() => { + // remove the callback from pending waiters because + // we're going to call it with a timeout error + removeWhere(this._pendingQueue, (i) => i.callback === queueCallback) + pendingItem.timedOut = true + response.callback(new Error('timeout exceeded when trying to connect')) + }, this.options.connectionTimeoutMillis) + + this._pendingQueue.push(pendingItem) + return result + } + + this.newClient(new PendingItem(response.callback)) + + return result + } + + newClient(pendingItem) { + const client = new this.Client(this.options) + this._clients.push(client) + const idleListener = makeIdleListener(this, client) + + this.log('checking client timeout') + + // connection timeout logic + let tid + let timeoutHit = false + if (this.options.connectionTimeoutMillis) { + tid = setTimeout(() => { + this.log('ending client due to timeout') + timeoutHit = true + // force kill the node driver, and let libpq do its teardown + client.connection ? client.connection.stream.destroy() : client.end() + }, this.options.connectionTimeoutMillis) + } + + this.log('connecting new client') + client.connect((err) => { + if (tid) { + clearTimeout(tid) + } + client.on('error', idleListener) + if (err) { + this.log('client failed to connect', err) + // remove the dead client from our list of clients + this._clients = this._clients.filter((c) => c !== client) + if (timeoutHit) { + err.message = 'Connection terminated due to connection timeout' + } + + // this client won’t be released, so move on immediately + this._pulseQueue() + + if (!pendingItem.timedOut) { + pendingItem.callback(err, undefined, NOOP) + } + } else { + this.log('new client connected') + + return this._acquireClient(client, pendingItem, idleListener, true) + } + }) + } + + // acquire a client for a pending work item + _acquireClient(client, pendingItem, idleListener, isNew) { + if (isNew) { + this.emit('connect', client) + } + + this.emit('acquire', client) + + client.release = this._releaseOnce(client, idleListener) + + client.removeListener('error', idleListener) + + if (!pendingItem.timedOut) { + if (isNew && this.options.verify) { + this.options.verify(client, (err) => { + if (err) { + client.release(err) + return pendingItem.callback(err, undefined, NOOP) + } + + pendingItem.callback(undefined, client, client.release) + }) + } else { + pendingItem.callback(undefined, client, client.release) + } + } else { + if (isNew && this.options.verify) { + this.options.verify(client, client.release) + } else { + client.release() + } + } + } + + // returns a function that wraps _release and throws if called more than once + _releaseOnce(client, idleListener) { + let released = false + + return (err) => { + if (released) { + throwOnDoubleRelease() + } + + released = true + this._release(client, idleListener, err) + } + } + + // release a client back to the poll, include an error + // to remove it from the pool + _release(client, idleListener, err) { + client.on('error', idleListener) + + client._poolUseCount = (client._poolUseCount || 0) + 1 + + // TODO(bmc): expose a proper, public interface _queryable and _ending + if (err || this.ending || !client._queryable || client._ending || client._poolUseCount >= this.options.maxUses) { + if (client._poolUseCount >= this.options.maxUses) { + this.log('remove expended client') + } + this._remove(client) + this._pulseQueue() + return + } + + // idle timeout + let tid + if (this.options.idleTimeoutMillis) { + tid = setTimeout(() => { + this.log('remove idle client') + this._remove(client) + }, this.options.idleTimeoutMillis) + } + + this._idle.push(new IdleItem(client, idleListener, tid)) + this._pulseQueue() + } + + query(text, values, cb) { + // guard clause against passing a function as the first parameter + if (typeof text === 'function') { + const response = promisify(this.Promise, text) + setImmediate(function () { + return response.callback(new Error('Passing a function as the first parameter to pool.query is not supported')) + }) + return response.result + } + + // allow plain text query without values + if (typeof values === 'function') { + cb = values + values = undefined + } + const response = promisify(this.Promise, cb) + cb = response.callback + + this.connect((err, client) => { + if (err) { + return cb(err) + } + + let clientReleased = false + const onError = (err) => { + if (clientReleased) { + return + } + clientReleased = true + client.release(err) + cb(err) + } + + client.once('error', onError) + this.log('dispatching query') + client.query(text, values, (err, res) => { + this.log('query dispatched') + client.removeListener('error', onError) + if (clientReleased) { + return + } + clientReleased = true + client.release(err) + if (err) { + return cb(err) + } else { + return cb(undefined, res) + } + }) + }) + return response.result + } + + end(cb) { + this.log('ending') + if (this.ending) { + const err = new Error('Called end on pool more than once') + return cb ? cb(err) : this.Promise.reject(err) + } + this.ending = true + const promised = promisify(this.Promise, cb) + this._endCallback = promised.callback + this._pulseQueue() + return promised.result + } + + get waitingCount() { + return this._pendingQueue.length + } + + get idleCount() { + return this._idle.length + } + + get totalCount() { + return this._clients.length + } +} +module.exports = Pool diff --git a/packages/pg-pool/package.json b/packages/pg-pool/package.json new file mode 100644 index 0000000..b92e7df --- /dev/null +++ b/packages/pg-pool/package.json @@ -0,0 +1,40 @@ +{ + "name": "pg-pool", + "version": "3.3.0", + "description": "Connection pool for node-postgres", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": " node_modules/.bin/mocha" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-pool" + }, + "keywords": [ + "pg", + "postgres", + "pool", + "database" + ], + "author": "Brian M. Carlson", + "license": "MIT", + "bugs": { + "url": "https://github.com/brianc/node-pg-pool/issues" + }, + "homepage": "https://github.com/brianc/node-pg-pool#readme", + "devDependencies": { + "bluebird": "3.4.1", + "co": "4.6.0", + "expect.js": "0.3.1", + "lodash": "^4.17.11", + "mocha": "^7.1.2", + "pg-cursor": "^1.3.0" + }, + "peerDependencies": { + "pg": ">=8.0" + } +} diff --git a/packages/pg-pool/test/bring-your-own-promise.js b/packages/pg-pool/test/bring-your-own-promise.js new file mode 100644 index 0000000..e905ccc --- /dev/null +++ b/packages/pg-pool/test/bring-your-own-promise.js @@ -0,0 +1,42 @@ +'use strict' +const co = require('co') +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it +const BluebirdPromise = require('bluebird') + +const Pool = require('../') + +const checkType = (promise) => { + expect(promise).to.be.a(BluebirdPromise) + return promise.catch((e) => undefined) +} + +describe('Bring your own promise', function () { + it( + 'uses supplied promise for operations', + co.wrap(function* () { + const pool = new Pool({ Promise: BluebirdPromise }) + const client1 = yield checkType(pool.connect()) + client1.release() + yield checkType(pool.query('SELECT NOW()')) + const client2 = yield checkType(pool.connect()) + // TODO - make sure pg supports BYOP as well + client2.release() + yield checkType(pool.end()) + }) + ) + + it( + 'uses promises in errors', + co.wrap(function* () { + const pool = new Pool({ Promise: BluebirdPromise, port: 48484 }) + yield checkType(pool.connect()) + yield checkType(pool.end()) + yield checkType(pool.connect()) + yield checkType(pool.query()) + yield checkType(pool.end()) + }) + ) +}) diff --git a/packages/pg-pool/test/connection-strings.js b/packages/pg-pool/test/connection-strings.js new file mode 100644 index 0000000..de45830 --- /dev/null +++ b/packages/pg-pool/test/connection-strings.js @@ -0,0 +1,29 @@ +const expect = require('expect.js') +const describe = require('mocha').describe +const it = require('mocha').it +const Pool = require('../') + +describe('Connection strings', function () { + it('pool delegates connectionString property to client', function (done) { + const connectionString = 'postgres://foo:bar@baz:1234/xur' + + const pool = new Pool({ + // use a fake client so we can check we're passed the connectionString + Client: function (args) { + expect(args.connectionString).to.equal(connectionString) + return { + connect: function (cb) { + cb(new Error('testing')) + }, + on: function () {}, + } + }, + connectionString: connectionString, + }) + + pool.connect(function (err, client) { + expect(err).to.not.be(undefined) + done() + }) + }) +}) diff --git a/packages/pg-pool/test/connection-timeout.js b/packages/pg-pool/test/connection-timeout.js new file mode 100644 index 0000000..05e8931 --- /dev/null +++ b/packages/pg-pool/test/connection-timeout.js @@ -0,0 +1,229 @@ +'use strict' +const net = require('net') +const co = require('co') +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it +const before = require('mocha').before +const after = require('mocha').after + +const Pool = require('../') + +describe('connection timeout', () => { + const connectionFailure = new Error('Temporary connection failure') + + before((done) => { + this.server = net.createServer((socket) => { + socket.on('data', () => { + // discard any buffered data or the server wont terminate + }) + }) + + this.server.listen(() => { + this.port = this.server.address().port + done() + }) + }) + + after((done) => { + this.server.close(done) + }) + + it('should callback with an error if timeout is passed', (done) => { + const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' }) + pool.connect((err, client, release) => { + expect(err).to.be.an(Error) + expect(err.message).to.contain('timeout') + expect(client).to.equal(undefined) + expect(pool.idleCount).to.equal(0) + done() + }) + }) + + it('should reject promise with an error if timeout is passed', (done) => { + const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' }) + pool.connect().catch((err) => { + expect(err).to.be.an(Error) + expect(err.message).to.contain('timeout') + expect(pool.idleCount).to.equal(0) + done() + }) + }) + + it( + 'should handle multiple timeouts', + co.wrap( + function* () { + const errors = [] + const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' }) + for (var i = 0; i < 15; i++) { + try { + yield pool.connect() + } catch (e) { + errors.push(e) + } + } + expect(errors).to.have.length(15) + }.bind(this) + ) + ) + + it('should timeout on checkout of used connection', (done) => { + const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 }) + pool.connect((err, client, release) => { + expect(err).to.be(undefined) + expect(client).to.not.be(undefined) + pool.connect((err, client) => { + expect(err).to.be.an(Error) + expect(client).to.be(undefined) + release() + pool.end(done) + }) + }) + }) + + it('should not break further pending checkouts on a timeout', (done) => { + const pool = new Pool({ connectionTimeoutMillis: 200, max: 1 }) + pool.connect((err, client, releaseOuter) => { + expect(err).to.be(undefined) + + pool.connect((err, client) => { + expect(err).to.be.an(Error) + expect(client).to.be(undefined) + releaseOuter() + }) + + setTimeout(() => { + pool.connect((err, client, releaseInner) => { + expect(err).to.be(undefined) + expect(client).to.not.be(undefined) + releaseInner() + pool.end(done) + }) + }, 100) + }) + }) + + it('should timeout on query if all clients are busy', (done) => { + const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 }) + pool.connect((err, client, release) => { + expect(err).to.be(undefined) + expect(client).to.not.be(undefined) + pool.query('select now()', (err, result) => { + expect(err).to.be.an(Error) + expect(result).to.be(undefined) + release() + pool.end(done) + }) + }) + }) + + it('should recover from timeout errors', (done) => { + const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 }) + pool.connect((err, client, release) => { + expect(err).to.be(undefined) + expect(client).to.not.be(undefined) + pool.query('select now()', (err, result) => { + expect(err).to.be.an(Error) + expect(result).to.be(undefined) + release() + pool.query('select $1::text as name', ['brianc'], (err, res) => { + expect(err).to.be(undefined) + expect(res.rows).to.have.length(1) + pool.end(done) + }) + }) + }) + }) + + it('continues processing after a connection failure', (done) => { + const Client = require('pg').Client + const orgConnect = Client.prototype.connect + let called = false + + Client.prototype.connect = function (cb) { + // Simulate a failure on first call + if (!called) { + called = true + + return setTimeout(() => { + cb(connectionFailure) + }, 100) + } + // And pass-through the second call + orgConnect.call(this, cb) + } + + const pool = new Pool({ + Client: Client, + connectionTimeoutMillis: 1000, + max: 1, + }) + + pool.connect((err, client, release) => { + expect(err).to.be(connectionFailure) + + pool.query('select $1::text as name', ['brianc'], (err, res) => { + expect(err).to.be(undefined) + expect(res.rows).to.have.length(1) + pool.end(done) + }) + }) + }) + + it('releases newly connected clients if the queued already timed out', (done) => { + const Client = require('pg').Client + + const orgConnect = Client.prototype.connect + + let connection = 0 + + Client.prototype.connect = function (cb) { + // Simulate a failure on first call + if (connection === 0) { + connection++ + + return setTimeout(() => { + cb(connectionFailure) + }, 300) + } + + // And second connect taking > connection timeout + if (connection === 1) { + connection++ + + return setTimeout(() => { + orgConnect.call(this, cb) + }, 1000) + } + + orgConnect.call(this, cb) + } + + const pool = new Pool({ + Client: Client, + connectionTimeoutMillis: 1000, + max: 1, + }) + + // Direct connect + pool.connect((err, client, release) => { + expect(err).to.be(connectionFailure) + }) + + // Queued + let called = 0 + pool.connect((err, client, release) => { + // Verify the callback is only called once + expect(called++).to.be(0) + expect(err).to.be.an(Error) + + pool.query('select $1::text as name', ['brianc'], (err, res) => { + expect(err).to.be(undefined) + expect(res.rows).to.have.length(1) + pool.end(done) + }) + }) + }) +}) diff --git a/packages/pg-pool/test/ending.js b/packages/pg-pool/test/ending.js new file mode 100644 index 0000000..e1839b4 --- /dev/null +++ b/packages/pg-pool/test/ending.js @@ -0,0 +1,40 @@ +'use strict' +const co = require('co') +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('pool ending', () => { + it('ends without being used', (done) => { + const pool = new Pool() + pool.end(done) + }) + + it('ends with a promise', () => { + return new Pool().end() + }) + + it( + 'ends with clients', + co.wrap(function* () { + const pool = new Pool() + const res = yield pool.query('SELECT $1::text as name', ['brianc']) + expect(res.rows[0].name).to.equal('brianc') + return pool.end() + }) + ) + + it( + 'allows client to finish', + co.wrap(function* () { + const pool = new Pool() + const query = pool.query('SELECT $1::text as name', ['brianc']) + yield pool.end() + const res = yield query + expect(res.rows[0].name).to.equal('brianc') + }) + ) +}) diff --git a/packages/pg-pool/test/error-handling.js b/packages/pg-pool/test/error-handling.js new file mode 100644 index 0000000..0a996b8 --- /dev/null +++ b/packages/pg-pool/test/error-handling.js @@ -0,0 +1,248 @@ +'use strict' +const net = require('net') +const co = require('co') +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('pool error handling', function () { + it('Should complete these queries without dying', function (done) { + const pool = new Pool() + let errors = 0 + let shouldGet = 0 + function runErrorQuery() { + shouldGet++ + return new Promise(function (resolve, reject) { + pool + .query("SELECT 'asd'+1 ") + .then(function (res) { + reject(res) // this should always error + }) + .catch(function (err) { + errors++ + resolve(err) + }) + }) + } + const ps = [] + for (let i = 0; i < 5; i++) { + ps.push(runErrorQuery()) + } + Promise.all(ps).then(function () { + expect(shouldGet).to.eql(errors) + pool.end(done) + }) + }) + + describe('calling release more than once', () => { + it( + 'should throw each time', + co.wrap(function* () { + const pool = new Pool() + const client = yield pool.connect() + client.release() + expect(() => client.release()).to.throwError() + expect(() => client.release()).to.throwError() + return yield pool.end() + }) + ) + + it('should throw each time with callbacks', function (done) { + const pool = new Pool() + + pool.connect(function (err, client, clientDone) { + expect(err).not.to.be.an(Error) + clientDone() + + expect(() => clientDone()).to.throwError() + expect(() => clientDone()).to.throwError() + + pool.end(done) + }) + }) + }) + + describe('using an ended pool', () => { + it('rejects all additional promises', (done) => { + const pool = new Pool() + const promises = [] + pool.end().then(() => { + const squash = (promise) => promise.catch((e) => 'okay!') + promises.push(squash(pool.connect())) + promises.push(squash(pool.query('SELECT NOW()'))) + promises.push(squash(pool.end())) + Promise.all(promises).then((res) => { + expect(res).to.eql(['okay!', 'okay!', 'okay!']) + done() + }) + }) + }) + + it('returns an error on all additional callbacks', (done) => { + const pool = new Pool() + pool.end(() => { + pool.query('SELECT *', (err) => { + expect(err).to.be.an(Error) + pool.connect((err) => { + expect(err).to.be.an(Error) + pool.end((err) => { + expect(err).to.be.an(Error) + done() + }) + }) + }) + }) + }) + }) + + describe('error from idle client', () => { + it( + 'removes client from pool', + co.wrap(function* () { + const pool = new Pool() + const client = yield pool.connect() + expect(pool.totalCount).to.equal(1) + expect(pool.waitingCount).to.equal(0) + expect(pool.idleCount).to.equal(0) + client.release() + yield new Promise((resolve, reject) => { + process.nextTick(() => { + let poolError + pool.once('error', (err) => { + poolError = err + }) + + let clientError + client.once('error', (err) => { + clientError = err + }) + + client.emit('error', new Error('expected')) + + expect(clientError.message).to.equal('expected') + expect(poolError.message).to.equal('expected') + expect(pool.idleCount).to.equal(0) + expect(pool.totalCount).to.equal(0) + pool.end().then(resolve, reject) + }) + }) + }) + ) + }) + + describe('error from in-use client', () => { + it( + 'keeps the client in the pool', + co.wrap(function* () { + const pool = new Pool() + const client = yield pool.connect() + expect(pool.totalCount).to.equal(1) + expect(pool.waitingCount).to.equal(0) + expect(pool.idleCount).to.equal(0) + + yield new Promise((resolve, reject) => { + process.nextTick(() => { + let poolError + pool.once('error', (err) => { + poolError = err + }) + + let clientError + client.once('error', (err) => { + clientError = err + }) + + client.emit('error', new Error('expected')) + + expect(clientError.message).to.equal('expected') + expect(poolError).not.to.be.ok() + expect(pool.idleCount).to.equal(0) + expect(pool.totalCount).to.equal(1) + client.release() + pool.end().then(resolve, reject) + }) + }) + }) + ) + }) + + describe('passing a function to pool.query', () => { + it('calls back with error', (done) => { + const pool = new Pool() + console.log('passing fn to query') + pool.query((err) => { + expect(err).to.be.an(Error) + pool.end(done) + }) + }) + }) + + describe('pool with lots of errors', () => { + it( + 'continues to work and provide new clients', + co.wrap(function* () { + const pool = new Pool({ max: 1 }) + const errors = [] + for (var i = 0; i < 20; i++) { + try { + yield pool.query('invalid sql') + } catch (err) { + errors.push(err) + } + } + expect(errors).to.have.length(20) + expect(pool.idleCount).to.equal(0) + expect(pool.query).to.be.a(Function) + const res = yield pool.query('SELECT $1::text as name', ['brianc']) + expect(res.rows).to.have.length(1) + expect(res.rows[0].name).to.equal('brianc') + return pool.end() + }) + ) + }) + + it('should continue with queued items after a connection failure', (done) => { + const closeServer = net + .createServer((socket) => { + socket.destroy() + }) + .unref() + + closeServer.listen(() => { + const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' }) + pool.connect((err) => { + expect(err).to.be.an(Error) + if (err.code) { + expect(err.code).to.be('ECONNRESET') + } + }) + pool.connect((err) => { + expect(err).to.be.an(Error) + if (err.code) { + expect(err.code).to.be('ECONNRESET') + } + closeServer.close(() => { + pool.end(done) + }) + }) + }) + }) + + it('handles post-checkout client failures in pool.query', (done) => { + const pool = new Pool({ max: 1 }) + pool.on('error', () => { + // We double close the connection in this test, prevent exception caused by that + }) + pool.query('SELECT pg_sleep(5)', [], (err) => { + expect(err).to.be.an(Error) + done() + }) + + setTimeout(() => { + pool._clients[0].end() + }, 1000) + }) +}) diff --git a/packages/pg-pool/test/events.js b/packages/pg-pool/test/events.js new file mode 100644 index 0000000..6197924 --- /dev/null +++ b/packages/pg-pool/test/events.js @@ -0,0 +1,86 @@ +'use strict' + +const expect = require('expect.js') +const EventEmitter = require('events').EventEmitter +const describe = require('mocha').describe +const it = require('mocha').it +const Pool = require('../') + +describe('events', function () { + it('emits connect before callback', function (done) { + const pool = new Pool() + let emittedClient = false + pool.on('connect', function (client) { + emittedClient = client + }) + + pool.connect(function (err, client, release) { + if (err) return done(err) + release() + pool.end() + expect(client).to.be(emittedClient) + done() + }) + }) + + it('emits "connect" only with a successful connection', function () { + const pool = new Pool({ + // This client will always fail to connect + Client: mockClient({ + connect: function (cb) { + process.nextTick(() => { + cb(new Error('bad news')) + }) + }, + }), + }) + pool.on('connect', function () { + throw new Error('should never get here') + }) + return pool.connect().catch((e) => expect(e.message).to.equal('bad news')) + }) + + it('emits acquire every time a client is acquired', function (done) { + const pool = new Pool() + let acquireCount = 0 + pool.on('acquire', function (client) { + expect(client).to.be.ok() + acquireCount++ + }) + for (let i = 0; i < 10; i++) { + pool.connect(function (err, client, release) { + if (err) return done(err) + release() + }) + pool.query('SELECT now()') + } + setTimeout(function () { + expect(acquireCount).to.be(20) + pool.end(done) + }, 100) + }) + + it('emits error and client if an idle client in the pool hits an error', function (done) { + const pool = new Pool() + pool.connect(function (err, client) { + expect(err).to.equal(undefined) + client.release() + setImmediate(function () { + client.emit('error', new Error('problem')) + }) + pool.once('error', function (err, errClient) { + expect(err.message).to.equal('problem') + expect(errClient).to.equal(client) + done() + }) + }) + }) +}) + +function mockClient(methods) { + return function () { + const client = new EventEmitter() + Object.assign(client, methods) + return client + } +} diff --git a/packages/pg-pool/test/idle-timeout.js b/packages/pg-pool/test/idle-timeout.js new file mode 100644 index 0000000..fd9fba4 --- /dev/null +++ b/packages/pg-pool/test/idle-timeout.js @@ -0,0 +1,87 @@ +'use strict' +const co = require('co') +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +const wait = (time) => new Promise((resolve) => setTimeout(resolve, time)) + +describe('idle timeout', () => { + it('should timeout and remove the client', (done) => { + const pool = new Pool({ idleTimeoutMillis: 10 }) + pool.query('SELECT NOW()') + pool.on('remove', () => { + expect(pool.idleCount).to.equal(0) + expect(pool.totalCount).to.equal(0) + done() + }) + }) + + it( + 'times out and removes clients when others are also removed', + co.wrap(function* () { + const pool = new Pool({ idleTimeoutMillis: 10 }) + const clientA = yield pool.connect() + const clientB = yield pool.connect() + clientA.release() + clientB.release(new Error()) + + const removal = new Promise((resolve) => { + pool.on('remove', () => { + expect(pool.idleCount).to.equal(0) + expect(pool.totalCount).to.equal(0) + resolve() + }) + }) + + const timeout = wait(100).then(() => Promise.reject(new Error('Idle timeout failed to occur'))) + + try { + yield Promise.race([removal, timeout]) + } finally { + pool.end() + } + }) + ) + + it( + 'can remove idle clients and recreate them', + co.wrap(function* () { + const pool = new Pool({ idleTimeoutMillis: 1 }) + const results = [] + for (var i = 0; i < 20; i++) { + let query = pool.query('SELECT NOW()') + expect(pool.idleCount).to.equal(0) + expect(pool.totalCount).to.equal(1) + results.push(yield query) + yield wait(2) + expect(pool.idleCount).to.equal(0) + expect(pool.totalCount).to.equal(0) + } + expect(results).to.have.length(20) + }) + ) + + it( + 'does not time out clients which are used', + co.wrap(function* () { + const pool = new Pool({ idleTimeoutMillis: 1 }) + const results = [] + for (var i = 0; i < 20; i++) { + let client = yield pool.connect() + expect(pool.totalCount).to.equal(1) + expect(pool.idleCount).to.equal(0) + yield wait(10) + results.push(yield client.query('SELECT NOW()')) + client.release() + expect(pool.idleCount).to.equal(1) + expect(pool.totalCount).to.equal(1) + } + expect(results).to.have.length(20) + return pool.end() + }) + ) +}) diff --git a/packages/pg-pool/test/index.js b/packages/pg-pool/test/index.js new file mode 100644 index 0000000..57a68e0 --- /dev/null +++ b/packages/pg-pool/test/index.js @@ -0,0 +1,226 @@ +'use strict' +const expect = require('expect.js') +const _ = require('lodash') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('pool', function () { + describe('with callbacks', function () { + it('works totally unconfigured', function (done) { + const pool = new Pool() + pool.connect(function (err, client, release) { + if (err) return done(err) + client.query('SELECT NOW()', function (err, res) { + release() + if (err) return done(err) + expect(res.rows).to.have.length(1) + pool.end(done) + }) + }) + }) + + it('passes props to clients', function (done) { + const pool = new Pool({ binary: true }) + pool.connect(function (err, client, release) { + release() + if (err) return done(err) + expect(client.binary).to.eql(true) + pool.end(done) + }) + }) + + it('can run a query with a callback without parameters', function (done) { + const pool = new Pool() + pool.query('SELECT 1 as num', function (err, res) { + expect(res.rows[0]).to.eql({ num: 1 }) + pool.end(function () { + done(err) + }) + }) + }) + + it('can run a query with a callback', function (done) { + const pool = new Pool() + pool.query('SELECT $1::text as name', ['brianc'], function (err, res) { + expect(res.rows[0]).to.eql({ name: 'brianc' }) + pool.end(function () { + done(err) + }) + }) + }) + + it('passes connection errors to callback', function (done) { + const pool = new Pool({ port: 53922 }) + pool.query('SELECT $1::text as name', ['brianc'], function (err, res) { + expect(res).to.be(undefined) + expect(err).to.be.an(Error) + // a connection error should not polute the pool with a dead client + expect(pool.totalCount).to.equal(0) + pool.end(function (err) { + done(err) + }) + }) + }) + + it('does not pass client to error callback', function (done) { + const pool = new Pool({ port: 58242 }) + pool.connect(function (err, client, release) { + expect(err).to.be.an(Error) + expect(client).to.be(undefined) + expect(release).to.be.a(Function) + pool.end(done) + }) + }) + + it('removes client if it errors in background', function (done) { + const pool = new Pool() + pool.connect(function (err, client, release) { + release() + if (err) return done(err) + client.testString = 'foo' + setTimeout(function () { + client.emit('error', new Error('on purpose')) + }, 10) + }) + pool.on('error', function (err) { + expect(err.message).to.be('on purpose') + expect(err.client).to.not.be(undefined) + expect(err.client.testString).to.be('foo') + err.client.connection.stream.on('end', function () { + pool.end(done) + }) + }) + }) + + it('should not change given options', function (done) { + const options = { max: 10 } + const pool = new Pool(options) + pool.connect(function (err, client, release) { + release() + if (err) return done(err) + expect(options).to.eql({ max: 10 }) + pool.end(done) + }) + }) + + it('does not create promises when connecting', function (done) { + const pool = new Pool() + const returnValue = pool.connect(function (err, client, release) { + release() + if (err) return done(err) + pool.end(done) + }) + expect(returnValue).to.be(undefined) + }) + + it('does not create promises when querying', function (done) { + const pool = new Pool() + const returnValue = pool.query('SELECT 1 as num', function (err) { + pool.end(function () { + done(err) + }) + }) + expect(returnValue).to.be(undefined) + }) + + it('does not create promises when ending', function (done) { + const pool = new Pool() + const returnValue = pool.end(done) + expect(returnValue).to.be(undefined) + }) + + it('never calls callback syncronously', function (done) { + const pool = new Pool() + pool.connect((err, client) => { + if (err) throw err + client.release() + setImmediate(() => { + let called = false + pool.connect((err, client) => { + if (err) throw err + called = true + client.release() + setImmediate(() => { + pool.end(done) + }) + }) + expect(called).to.equal(false) + }) + }) + }) + }) + + describe('with promises', function () { + it('connects, queries, and disconnects', function () { + const pool = new Pool() + return pool.connect().then(function (client) { + return client.query('select $1::text as name', ['hi']).then(function (res) { + expect(res.rows).to.eql([{ name: 'hi' }]) + client.release() + return pool.end() + }) + }) + }) + + it('executes a query directly', () => { + const pool = new Pool() + return pool.query('SELECT $1::text as name', ['hi']).then((res) => { + expect(res.rows).to.have.length(1) + expect(res.rows[0].name).to.equal('hi') + return pool.end() + }) + }) + + it('properly pools clients', function () { + const pool = new Pool({ poolSize: 9 }) + const promises = _.times(30, function () { + return pool.connect().then(function (client) { + return client.query('select $1::text as name', ['hi']).then(function (res) { + client.release() + return res + }) + }) + }) + return Promise.all(promises).then(function (res) { + expect(res).to.have.length(30) + expect(pool.totalCount).to.be(9) + return pool.end() + }) + }) + + it('supports just running queries', function () { + const pool = new Pool({ poolSize: 9 }) + const text = 'select $1::text as name' + const values = ['hi'] + const query = { text: text, values: values } + const promises = _.times(30, () => pool.query(query)) + return Promise.all(promises).then(function (queries) { + expect(queries).to.have.length(30) + return pool.end() + }) + }) + + it('recovers from query errors', function () { + const pool = new Pool() + + const errors = [] + const promises = _.times(30, () => { + return pool.query('SELECT asldkfjasldkf').catch(function (e) { + errors.push(e) + }) + }) + return Promise.all(promises).then(() => { + expect(errors).to.have.length(30) + expect(pool.totalCount).to.equal(0) + expect(pool.idleCount).to.equal(0) + return pool.query('SELECT $1::text as name', ['hi']).then(function (res) { + expect(res.rows).to.eql([{ name: 'hi' }]) + return pool.end() + }) + }) + }) + }) +}) diff --git a/packages/pg-pool/test/logging.js b/packages/pg-pool/test/logging.js new file mode 100644 index 0000000..839603b --- /dev/null +++ b/packages/pg-pool/test/logging.js @@ -0,0 +1,20 @@ +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('logging', function () { + it('logs to supplied log function if given', function () { + const messages = [] + const log = function (msg) { + messages.push(msg) + } + const pool = new Pool({ log: log }) + return pool.query('SELECT NOW()').then(function () { + expect(messages.length).to.be.greaterThan(0) + return pool.end() + }) + }) +}) diff --git a/packages/pg-pool/test/max-uses.js b/packages/pg-pool/test/max-uses.js new file mode 100644 index 0000000..c94ddec --- /dev/null +++ b/packages/pg-pool/test/max-uses.js @@ -0,0 +1,98 @@ +const expect = require('expect.js') +const co = require('co') +const _ = require('lodash') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('maxUses', () => { + it( + 'can create a single client and use it once', + co.wrap(function* () { + const pool = new Pool({ maxUses: 2 }) + expect(pool.waitingCount).to.equal(0) + const client = yield pool.connect() + const res = yield client.query('SELECT $1::text as name', ['hi']) + expect(res.rows[0].name).to.equal('hi') + client.release() + pool.end() + }) + ) + + it( + 'getting a connection a second time returns the same connection and releasing it also closes it', + co.wrap(function* () { + const pool = new Pool({ maxUses: 2 }) + expect(pool.waitingCount).to.equal(0) + const client = yield pool.connect() + client.release() + const client2 = yield pool.connect() + expect(client).to.equal(client2) + expect(client2._ending).to.equal(false) + client2.release() + expect(client2._ending).to.equal(true) + return yield pool.end() + }) + ) + + it( + 'getting a connection a third time returns a new connection', + co.wrap(function* () { + const pool = new Pool({ maxUses: 2 }) + expect(pool.waitingCount).to.equal(0) + const client = yield pool.connect() + client.release() + const client2 = yield pool.connect() + expect(client).to.equal(client2) + client2.release() + const client3 = yield pool.connect() + expect(client3).not.to.equal(client2) + client3.release() + return yield pool.end() + }) + ) + + it( + 'getting a connection from a pending request gets a fresh client when the released candidate is expended', + co.wrap(function* () { + const pool = new Pool({ max: 1, maxUses: 2 }) + expect(pool.waitingCount).to.equal(0) + const client1 = yield pool.connect() + pool.connect().then((client2) => { + expect(client2).to.equal(client1) + expect(pool.waitingCount).to.equal(1) + // Releasing the client this time should also expend it since maxUses is 2, causing client3 to be a fresh client + client2.release() + }) + const client3Promise = pool.connect().then((client3) => { + // client3 should be a fresh client since client2's release caused the first client to be expended + expect(pool.waitingCount).to.equal(0) + expect(client3).not.to.equal(client1) + return client3.release() + }) + // There should be two pending requests since we have 3 connect requests but a max size of 1 + expect(pool.waitingCount).to.equal(2) + // Releasing the client should not yet expend it since maxUses is 2 + client1.release() + yield client3Promise + return yield pool.end() + }) + ) + + it( + 'logs when removing an expended client', + co.wrap(function* () { + const messages = [] + const log = function (msg) { + messages.push(msg) + } + const pool = new Pool({ maxUses: 1, log }) + const client = yield pool.connect() + client.release() + expect(messages).to.contain('remove expended client') + return yield pool.end() + }) + ) +}) diff --git a/packages/pg-pool/test/releasing-clients.js b/packages/pg-pool/test/releasing-clients.js new file mode 100644 index 0000000..da8e09c --- /dev/null +++ b/packages/pg-pool/test/releasing-clients.js @@ -0,0 +1,54 @@ +const Pool = require('../') + +const expect = require('expect.js') +const net = require('net') + +describe('releasing clients', () => { + it('removes a client which cannot be queried', async () => { + // make a pool w/ only 1 client + const pool = new Pool({ max: 1 }) + expect(pool.totalCount).to.eql(0) + const client = await pool.connect() + expect(pool.totalCount).to.eql(1) + expect(pool.idleCount).to.eql(0) + // reach into the client and sever its connection + client.connection.end() + + // wait for the client to error out + const err = await new Promise((resolve) => client.once('error', resolve)) + expect(err).to.be.ok() + expect(pool.totalCount).to.eql(1) + expect(pool.idleCount).to.eql(0) + + // try to return it to the pool - this removes it because its broken + client.release() + expect(pool.totalCount).to.eql(0) + expect(pool.idleCount).to.eql(0) + + // make sure pool still works + const { rows } = await pool.query('SELECT NOW()') + expect(rows).to.have.length(1) + await pool.end() + }) + + it('removes a client which is ending', async () => { + // make a pool w/ only 1 client + const pool = new Pool({ max: 1 }) + expect(pool.totalCount).to.eql(0) + const client = await pool.connect() + expect(pool.totalCount).to.eql(1) + expect(pool.idleCount).to.eql(0) + // end the client gracefully (but you shouldn't do this with pooled clients) + client.end() + + // try to return it to the pool + client.release() + expect(pool.totalCount).to.eql(0) + expect(pool.idleCount).to.eql(0) + + // make sure pool still works + const { rows } = await pool.query('SELECT NOW()') + expect(rows).to.have.length(1) + await pool.end() + }) +}) diff --git a/packages/pg-pool/test/setup.js b/packages/pg-pool/test/setup.js new file mode 100644 index 0000000..811e956 --- /dev/null +++ b/packages/pg-pool/test/setup.js @@ -0,0 +1,10 @@ +const crash = (reason) => { + process.on(reason, (err) => { + console.error(reason, err.stack) + process.exit(-1) + }) +} + +crash('unhandledRejection') +crash('uncaughtError') +crash('warning') diff --git a/packages/pg-pool/test/sizing.js b/packages/pg-pool/test/sizing.js new file mode 100644 index 0000000..e7863ba --- /dev/null +++ b/packages/pg-pool/test/sizing.js @@ -0,0 +1,58 @@ +const expect = require('expect.js') +const co = require('co') +const _ = require('lodash') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('pool size of 1', () => { + it( + 'can create a single client and use it once', + co.wrap(function* () { + const pool = new Pool({ max: 1 }) + expect(pool.waitingCount).to.equal(0) + const client = yield pool.connect() + const res = yield client.query('SELECT $1::text as name', ['hi']) + expect(res.rows[0].name).to.equal('hi') + client.release() + pool.end() + }) + ) + + it( + 'can create a single client and use it multiple times', + co.wrap(function* () { + const pool = new Pool({ max: 1 }) + expect(pool.waitingCount).to.equal(0) + const client = yield pool.connect() + const wait = pool.connect() + expect(pool.waitingCount).to.equal(1) + client.release() + const client2 = yield wait + expect(client).to.equal(client2) + client2.release() + return yield pool.end() + }) + ) + + it( + 'can only send 1 query at a time', + co.wrap(function* () { + const pool = new Pool({ max: 1 }) + + // the query text column name changed in PostgreSQL 9.2 + const versionResult = yield pool.query('SHOW server_version_num') + const version = parseInt(versionResult.rows[0].server_version_num, 10) + const queryColumn = version < 90200 ? 'current_query' : 'query' + + const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1' + const queries = _.times(20, () => pool.query(queryText, [queryText])) + const results = yield Promise.all(queries) + const counts = results.map((res) => parseInt(res.rows[0].counts, 10)) + expect(counts).to.eql(_.times(20, (i) => 1)) + return yield pool.end() + }) + ) +}) diff --git a/packages/pg-pool/test/submittable.js b/packages/pg-pool/test/submittable.js new file mode 100644 index 0000000..7a1574d --- /dev/null +++ b/packages/pg-pool/test/submittable.js @@ -0,0 +1,19 @@ +'use strict' +const Cursor = require('pg-cursor') +const expect = require('expect.js') +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('submittle', () => { + it('is returned from the query method', false, (done) => { + const pool = new Pool() + const cursor = pool.query(new Cursor('SELECT * from generate_series(0, 1000)')) + cursor.read((err, rows) => { + expect(err).to.be(undefined) + expect(!!rows).to.be.ok() + cursor.close(done) + }) + }) +}) diff --git a/packages/pg-pool/test/timeout.js b/packages/pg-pool/test/timeout.js new file mode 100644 index 0000000..e69de29 diff --git a/packages/pg-pool/test/verify.js b/packages/pg-pool/test/verify.js new file mode 100644 index 0000000..9331e1a --- /dev/null +++ b/packages/pg-pool/test/verify.js @@ -0,0 +1,24 @@ +'use strict' +const expect = require('expect.js') + +const describe = require('mocha').describe +const it = require('mocha').it + +const Pool = require('../') + +describe('verify', () => { + it('verifies a client with a callback', (done) => { + const pool = new Pool({ + verify: (client, cb) => { + cb(new Error('nope')) + }, + }) + + pool.connect((err, client) => { + expect(err).to.be.an(Error) + expect(err.message).to.be('nope') + pool.end() + done() + }) + }) +}) diff --git a/packages/pg-protocol/README.md b/packages/pg-protocol/README.md new file mode 100644 index 0000000..8c52e40 --- /dev/null +++ b/packages/pg-protocol/README.md @@ -0,0 +1,3 @@ +# pg-protocol + +Low level postgres wire protocol parser and serializer written in Typescript. Used by node-postgres. Needs more documentation. :smile: diff --git a/packages/pg-protocol/package.json b/packages/pg-protocol/package.json new file mode 100644 index 0000000..ae9ba6f --- /dev/null +++ b/packages/pg-protocol/package.json @@ -0,0 +1,34 @@ +{ + "name": "pg-protocol", + "version": "1.5.0", + "description": "The postgres client/server binary protocol, implemented in TypeScript", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "MIT", + "devDependencies": { + "@types/chai": "^4.2.7", + "@types/mocha": "^5.2.7", + "@types/node": "^12.12.21", + "chai": "^4.2.0", + "chunky": "^0.0.0", + "mocha": "^7.1.2", + "ts-node": "^8.5.4", + "typescript": "^4.0.3" + }, + "scripts": { + "test": "mocha dist/**/*.test.js", + "build": "tsc", + "build:watch": "tsc --watch", + "prepublish": "yarn build", + "pretest": "yarn build" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-protocol" + }, + "files": [ + "/dist/*{js,ts,map}", + "/src" + ] +} diff --git a/packages/pg-protocol/src/b.ts b/packages/pg-protocol/src/b.ts new file mode 100644 index 0000000..028b763 --- /dev/null +++ b/packages/pg-protocol/src/b.ts @@ -0,0 +1,28 @@ +// file for microbenchmarking + +import { Writer } from './buffer-writer' +import { serialize } from './index' +import { BufferReader } from './buffer-reader' + +const LOOPS = 1000 +let count = 0 +let start = Date.now() +const writer = new Writer() + +const reader = new BufferReader() +const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0]) + +const run = () => { + if (count > LOOPS) { + console.log(Date.now() - start) + return + } + count++ + for (let i = 0; i < LOOPS; i++) { + reader.setBuffer(0, buffer) + reader.cstring() + } + setImmediate(run) +} + +run() diff --git a/packages/pg-protocol/src/buffer-reader.ts b/packages/pg-protocol/src/buffer-reader.ts new file mode 100644 index 0000000..2305e13 --- /dev/null +++ b/packages/pg-protocol/src/buffer-reader.ts @@ -0,0 +1,53 @@ +const emptyBuffer = Buffer.allocUnsafe(0) + +export class BufferReader { + private buffer: Buffer = emptyBuffer + + // TODO(bmc): support non-utf8 encoding? + private encoding: string = 'utf-8' + + constructor(private offset: number = 0) {} + + public setBuffer(offset: number, buffer: Buffer): void { + this.offset = offset + this.buffer = buffer + } + + public int16(): number { + const result = this.buffer.readInt16BE(this.offset) + this.offset += 2 + return result + } + + public byte(): number { + const result = this.buffer[this.offset] + this.offset++ + return result + } + + public int32(): number { + const result = this.buffer.readInt32BE(this.offset) + this.offset += 4 + return result + } + + public string(length: number): string { + const result = this.buffer.toString(this.encoding, this.offset, this.offset + length) + this.offset += length + return result + } + + public cstring(): string { + const start = this.offset + let end = start + while (this.buffer[end++] !== 0) {} + this.offset = end + return this.buffer.toString(this.encoding, start, end - 1) + } + + public bytes(length: number): Buffer { + const result = this.buffer.slice(this.offset, this.offset + length) + this.offset += length + return result + } +} diff --git a/packages/pg-protocol/src/buffer-writer.ts b/packages/pg-protocol/src/buffer-writer.ts new file mode 100644 index 0000000..756cdc9 --- /dev/null +++ b/packages/pg-protocol/src/buffer-writer.ts @@ -0,0 +1,85 @@ +//binary data writer tuned for encoding binary specific to the postgres binary protocol + +export class Writer { + private buffer: Buffer + private offset: number = 5 + private headerPosition: number = 0 + constructor(private size = 256) { + this.buffer = Buffer.allocUnsafe(size) + } + + private ensure(size: number): void { + var remaining = this.buffer.length - this.offset + if (remaining < size) { + var oldBuffer = this.buffer + // exponential growth factor of around ~ 1.5 + // https://stackoverflow.com/questions/2269063/buffer-growth-strategy + var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size + this.buffer = Buffer.allocUnsafe(newSize) + oldBuffer.copy(this.buffer) + } + } + + public addInt32(num: number): Writer { + this.ensure(4) + this.buffer[this.offset++] = (num >>> 24) & 0xff + this.buffer[this.offset++] = (num >>> 16) & 0xff + this.buffer[this.offset++] = (num >>> 8) & 0xff + this.buffer[this.offset++] = (num >>> 0) & 0xff + return this + } + + public addInt16(num: number): Writer { + this.ensure(2) + this.buffer[this.offset++] = (num >>> 8) & 0xff + this.buffer[this.offset++] = (num >>> 0) & 0xff + return this + } + + public addCString(string: string): Writer { + if (!string) { + this.ensure(1) + } else { + var len = Buffer.byteLength(string) + this.ensure(len + 1) // +1 for null terminator + this.buffer.write(string, this.offset, 'utf-8') + this.offset += len + } + + this.buffer[this.offset++] = 0 // null terminator + return this + } + + public addString(string: string = ''): Writer { + var len = Buffer.byteLength(string) + this.ensure(len) + this.buffer.write(string, this.offset) + this.offset += len + return this + } + + public add(otherBuffer: Buffer): Writer { + this.ensure(otherBuffer.length) + otherBuffer.copy(this.buffer, this.offset) + this.offset += otherBuffer.length + return this + } + + private join(code?: number): Buffer { + if (code) { + this.buffer[this.headerPosition] = code + //length is everything in this packet minus the code + const length = this.offset - (this.headerPosition + 1) + this.buffer.writeInt32BE(length, this.headerPosition + 1) + } + return this.buffer.slice(code ? 0 : 5, this.offset) + } + + public flush(code?: number): Buffer { + var result = this.join(code) + this.offset = 5 + this.headerPosition = 0 + this.buffer = Buffer.allocUnsafe(this.size) + return result + } +} diff --git a/packages/pg-protocol/src/inbound-parser.test.ts b/packages/pg-protocol/src/inbound-parser.test.ts new file mode 100644 index 0000000..364bd8d --- /dev/null +++ b/packages/pg-protocol/src/inbound-parser.test.ts @@ -0,0 +1,557 @@ +import buffers from './testing/test-buffers' +import BufferList from './testing/buffer-list' +import { parse } from '.' +import assert from 'assert' +import { PassThrough } from 'stream' +import { BackendMessage } from './messages' + +var authOkBuffer = buffers.authenticationOk() +var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8') +var readyForQueryBuffer = buffers.readyForQuery() +var backendKeyDataBuffer = buffers.backendKeyData(1, 2) +var commandCompleteBuffer = buffers.commandComplete('SELECT 3') +var parseCompleteBuffer = buffers.parseComplete() +var bindCompleteBuffer = buffers.bindComplete() +var portalSuspendedBuffer = buffers.portalSuspended() + +var addRow = function (bufferList: BufferList, name: string, offset: number) { + return bufferList + .addCString(name) // field name + .addInt32(offset++) // table id + .addInt16(offset++) // attribute of column number + .addInt32(offset++) // objectId of field's data type + .addInt16(offset++) // datatype size + .addInt32(offset++) // type modifier + .addInt16(0) // format code, 0 => text +} + +var row1 = { + name: 'id', + tableID: 1, + attributeNumber: 2, + dataTypeID: 3, + dataTypeSize: 4, + typeModifier: 5, + formatCode: 0, +} +var oneRowDescBuff = buffers.rowDescription([row1]) +row1.name = 'bang' + +var twoRowBuf = buffers.rowDescription([ + row1, + { + name: 'whoah', + tableID: 10, + attributeNumber: 11, + dataTypeID: 12, + dataTypeSize: 13, + typeModifier: 14, + formatCode: 0, + }, +]) + +var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D') + +var emptyRowFieldBuf = buffers.dataRow([]) + +var oneFieldBuf = new BufferList() + .addInt16(1) // number of fields + .addInt32(5) // length of bytes of fields + .addCString('test') + .join(true, 'D') + +var oneFieldBuf = buffers.dataRow(['test']) + +var expectedAuthenticationOkayMessage = { + name: 'authenticationOk', + length: 8, +} + +var expectedParameterStatusMessage = { + name: 'parameterStatus', + parameterName: 'client_encoding', + parameterValue: 'UTF8', + length: 25, +} + +var expectedBackendKeyDataMessage = { + name: 'backendKeyData', + processID: 1, + secretKey: 2, +} + +var expectedReadyForQueryMessage = { + name: 'readyForQuery', + length: 5, + status: 'I', +} + +var expectedCommandCompleteMessage = { + name: 'commandComplete', + length: 13, + text: 'SELECT 3', +} +var emptyRowDescriptionBuffer = new BufferList() + .addInt16(0) // number of fields + .join(true, 'T') + +var expectedEmptyRowDescriptionMessage = { + name: 'rowDescription', + length: 6, + fieldCount: 0, + fields: [], +} +var expectedOneRowMessage = { + name: 'rowDescription', + length: 27, + fieldCount: 1, + fields: [ + { + name: 'id', + tableID: 1, + columnID: 2, + dataTypeID: 3, + dataTypeSize: 4, + dataTypeModifier: 5, + format: 'text', + }, + ], +} + +var expectedTwoRowMessage = { + name: 'rowDescription', + length: 53, + fieldCount: 2, + fields: [ + { + name: 'bang', + tableID: 1, + columnID: 2, + dataTypeID: 3, + dataTypeSize: 4, + dataTypeModifier: 5, + format: 'text', + }, + { + name: 'whoah', + tableID: 10, + columnID: 11, + dataTypeID: 12, + dataTypeSize: 13, + dataTypeModifier: 14, + format: 'text', + }, + ], +} + +var emptyParameterDescriptionBuffer = new BufferList() + .addInt16(0) // number of parameters + .join(true, 't') + +var oneParameterDescBuf = buffers.parameterDescription([1111]) + +var twoParameterDescBuf = buffers.parameterDescription([2222, 3333]) + +var expectedEmptyParameterDescriptionMessage = { + name: 'parameterDescription', + length: 6, + parameterCount: 0, + dataTypeIDs: [], +} + +var expectedOneParameterMessage = { + name: 'parameterDescription', + length: 10, + parameterCount: 1, + dataTypeIDs: [1111], +} + +var expectedTwoParameterMessage = { + name: 'parameterDescription', + length: 14, + parameterCount: 2, + dataTypeIDs: [2222, 3333], +} + +var testForMessage = function (buffer: Buffer, expectedMessage: any) { + it('recieves and parses ' + expectedMessage.name, async () => { + const messages = await parseBuffers([buffer]) + const [lastMessage] = messages + + for (const key in expectedMessage) { + assert.deepEqual((lastMessage as any)[key], expectedMessage[key]) + } + }) +} + +var plainPasswordBuffer = buffers.authenticationCleartextPassword() +var md5PasswordBuffer = buffers.authenticationMD5Password() +var SASLBuffer = buffers.authenticationSASL() +var SASLContinueBuffer = buffers.authenticationSASLContinue() +var SASLFinalBuffer = buffers.authenticationSASLFinal() + +var expectedPlainPasswordMessage = { + name: 'authenticationCleartextPassword', +} + +var expectedMD5PasswordMessage = { + name: 'authenticationMD5Password', + salt: Buffer.from([1, 2, 3, 4]), +} + +var expectedSASLMessage = { + name: 'authenticationSASL', + mechanisms: ['SCRAM-SHA-256'], +} + +var expectedSASLContinueMessage = { + name: 'authenticationSASLContinue', + data: 'data', +} + +var expectedSASLFinalMessage = { + name: 'authenticationSASLFinal', + data: 'data', +} + +var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom') +var expectedNotificationResponseMessage = { + name: 'notification', + processId: 4, + channel: 'hi', + payload: 'boom', +} + +const parseBuffers = async (buffers: Buffer[]): Promise => { + const stream = new PassThrough() + for (const buffer of buffers) { + stream.write(buffer) + } + stream.end() + const msgs: BackendMessage[] = [] + await parse(stream, (msg) => msgs.push(msg)) + return msgs +} + +describe('PgPacketStream', function () { + testForMessage(authOkBuffer, expectedAuthenticationOkayMessage) + testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage) + testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage) + testForMessage(SASLBuffer, expectedSASLMessage) + testForMessage(SASLContinueBuffer, expectedSASLContinueMessage) + + // this exercises a found bug in the parser: + // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084 + // and adds a test which is deterministic, rather than relying on network packet chunking + const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])]) + testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage) + + testForMessage(SASLFinalBuffer, expectedSASLFinalMessage) + + // this exercises a found bug in the parser: + // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084 + // and adds a test which is deterministic, rather than relying on network packet chunking + const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])]) + testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage) + + testForMessage(paramStatusBuffer, expectedParameterStatusMessage) + testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage) + testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage) + testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage) + testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage) + testForMessage(buffers.emptyQuery(), { + name: 'emptyQuery', + length: 4, + }) + + testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), { + name: 'noData', + }) + + describe('rowDescription messages', function () { + testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage) + testForMessage(oneRowDescBuff, expectedOneRowMessage) + testForMessage(twoRowBuf, expectedTwoRowMessage) + }) + + describe('parameterDescription messages', function () { + testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage) + testForMessage(oneParameterDescBuf, expectedOneParameterMessage) + testForMessage(twoParameterDescBuf, expectedTwoParameterMessage) + }) + + describe('parsing rows', function () { + describe('parsing empty row', function () { + testForMessage(emptyRowFieldBuf, { + name: 'dataRow', + fieldCount: 0, + }) + }) + + describe('parsing data row with fields', function () { + testForMessage(oneFieldBuf, { + name: 'dataRow', + fieldCount: 1, + fields: ['test'], + }) + }) + }) + + describe('notice message', function () { + // this uses the same logic as error message + var buff = buffers.notice([{ type: 'C', value: 'code' }]) + testForMessage(buff, { + name: 'notice', + code: 'code', + }) + }) + + testForMessage(buffers.error([]), { + name: 'error', + }) + + describe('with all the fields', function () { + var buffer = buffers.error([ + { + type: 'S', + value: 'ERROR', + }, + { + type: 'C', + value: 'code', + }, + { + type: 'M', + value: 'message', + }, + { + type: 'D', + value: 'details', + }, + { + type: 'H', + value: 'hint', + }, + { + type: 'P', + value: '100', + }, + { + type: 'p', + value: '101', + }, + { + type: 'q', + value: 'query', + }, + { + type: 'W', + value: 'where', + }, + { + type: 'F', + value: 'file', + }, + { + type: 'L', + value: 'line', + }, + { + type: 'R', + value: 'routine', + }, + { + type: 'Z', // ignored + value: 'alsdkf', + }, + ]) + + testForMessage(buffer, { + name: 'error', + severity: 'ERROR', + code: 'code', + message: 'message', + detail: 'details', + hint: 'hint', + position: '100', + internalPosition: '101', + internalQuery: 'query', + where: 'where', + file: 'file', + line: 'line', + routine: 'routine', + }) + }) + + testForMessage(parseCompleteBuffer, { + name: 'parseComplete', + }) + + testForMessage(bindCompleteBuffer, { + name: 'bindComplete', + }) + + testForMessage(bindCompleteBuffer, { + name: 'bindComplete', + }) + + testForMessage(buffers.closeComplete(), { + name: 'closeComplete', + }) + + describe('parses portal suspended message', function () { + testForMessage(portalSuspendedBuffer, { + name: 'portalSuspended', + }) + }) + + describe('parses replication start message', function () { + testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), { + name: 'replicationStart', + length: 4, + }) + }) + + describe('copy', () => { + testForMessage(buffers.copyIn(0), { + name: 'copyInResponse', + length: 7, + binary: false, + columnTypes: [], + }) + + testForMessage(buffers.copyIn(2), { + name: 'copyInResponse', + length: 11, + binary: false, + columnTypes: [0, 1], + }) + + testForMessage(buffers.copyOut(0), { + name: 'copyOutResponse', + length: 7, + binary: false, + columnTypes: [], + }) + + testForMessage(buffers.copyOut(3), { + name: 'copyOutResponse', + length: 13, + binary: false, + columnTypes: [0, 1, 2], + }) + + testForMessage(buffers.copyDone(), { + name: 'copyDone', + length: 4, + }) + + testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), { + name: 'copyData', + length: 7, + chunk: Buffer.from([5, 6, 7]), + }) + }) + + // since the data message on a stream can randomly divide the incomming + // tcp packets anywhere, we need to make sure we can parse every single + // split on a tcp message + describe('split buffer, single message parsing', function () { + var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!']) + + it('parses when full buffer comes in', async function () { + const messages = await parseBuffers([fullBuffer]) + const message = messages[0] as any + assert.equal(message.fields.length, 5) + assert.equal(message.fields[0], null) + assert.equal(message.fields[1], 'bang') + assert.equal(message.fields[2], 'zug zug') + assert.equal(message.fields[3], null) + assert.equal(message.fields[4], '!') + }) + + var testMessageRecievedAfterSpiltAt = async function (split: number) { + var firstBuffer = Buffer.alloc(fullBuffer.length - split) + var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length) + fullBuffer.copy(firstBuffer, 0, 0) + fullBuffer.copy(secondBuffer, 0, firstBuffer.length) + const messages = await parseBuffers([fullBuffer]) + const message = messages[0] as any + assert.equal(message.fields.length, 5) + assert.equal(message.fields[0], null) + assert.equal(message.fields[1], 'bang') + assert.equal(message.fields[2], 'zug zug') + assert.equal(message.fields[3], null) + assert.equal(message.fields[4], '!') + } + + it('parses when split in the middle', function () { + testMessageRecievedAfterSpiltAt(6) + }) + + it('parses when split at end', function () { + testMessageRecievedAfterSpiltAt(2) + }) + + it('parses when split at beginning', function () { + testMessageRecievedAfterSpiltAt(fullBuffer.length - 2) + testMessageRecievedAfterSpiltAt(fullBuffer.length - 1) + testMessageRecievedAfterSpiltAt(fullBuffer.length - 5) + }) + }) + + describe('split buffer, multiple message parsing', function () { + var dataRowBuffer = buffers.dataRow(['!']) + var readyForQueryBuffer = buffers.readyForQuery() + var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length) + dataRowBuffer.copy(fullBuffer, 0, 0) + readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0) + + var verifyMessages = function (messages: any[]) { + assert.strictEqual(messages.length, 2) + assert.deepEqual(messages[0], { + name: 'dataRow', + fieldCount: 1, + length: 11, + fields: ['!'], + }) + assert.equal(messages[0].fields[0], '!') + assert.deepEqual(messages[1], { + name: 'readyForQuery', + length: 5, + status: 'I', + }) + } + // sanity check + it('recieves both messages when packet is not split', async function () { + const messages = await parseBuffers([fullBuffer]) + verifyMessages(messages) + }) + + var splitAndVerifyTwoMessages = async function (split: number) { + var firstBuffer = Buffer.alloc(fullBuffer.length - split) + var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length) + fullBuffer.copy(firstBuffer, 0, 0) + fullBuffer.copy(secondBuffer, 0, firstBuffer.length) + const messages = await parseBuffers([firstBuffer, secondBuffer]) + verifyMessages(messages) + } + + describe('recieves both messages when packet is split', function () { + it('in the middle', function () { + return splitAndVerifyTwoMessages(11) + }) + it('at the front', function () { + return Promise.all([ + splitAndVerifyTwoMessages(fullBuffer.length - 1), + splitAndVerifyTwoMessages(fullBuffer.length - 4), + splitAndVerifyTwoMessages(fullBuffer.length - 6), + ]) + }) + + it('at the end', function () { + return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]) + }) + }) + }) +}) diff --git a/packages/pg-protocol/src/index.ts b/packages/pg-protocol/src/index.ts new file mode 100644 index 0000000..00491ff --- /dev/null +++ b/packages/pg-protocol/src/index.ts @@ -0,0 +1,11 @@ +import { BackendMessage, DatabaseError } from './messages' +import { serialize } from './serializer' +import { Parser, MessageCallback } from './parser' + +export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise { + const parser = new Parser() + stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback)) + return new Promise((resolve) => stream.on('end', () => resolve())) +} + +export { serialize, DatabaseError } diff --git a/packages/pg-protocol/src/messages.ts b/packages/pg-protocol/src/messages.ts new file mode 100644 index 0000000..ada309c --- /dev/null +++ b/packages/pg-protocol/src/messages.ts @@ -0,0 +1,236 @@ +export type Mode = 'text' | 'binary' + +export type MessageName = + | 'parseComplete' + | 'bindComplete' + | 'closeComplete' + | 'noData' + | 'portalSuspended' + | 'replicationStart' + | 'emptyQuery' + | 'copyDone' + | 'copyData' + | 'rowDescription' + | 'parameterDescription' + | 'parameterStatus' + | 'backendKeyData' + | 'notification' + | 'readyForQuery' + | 'commandComplete' + | 'dataRow' + | 'copyInResponse' + | 'copyOutResponse' + | 'authenticationOk' + | 'authenticationMD5Password' + | 'authenticationSHA256Password' + | 'authenticationCleartextPassword' + | 'authenticationSASL' + | 'authenticationSASLContinue' + | 'authenticationSASLFinal' + | 'error' + | 'notice' + +export interface BackendMessage { + name: MessageName + length: number +} + +export const parseComplete: BackendMessage = { + name: 'parseComplete', + length: 5, +} + +export const bindComplete: BackendMessage = { + name: 'bindComplete', + length: 5, +} + +export const closeComplete: BackendMessage = { + name: 'closeComplete', + length: 5, +} + +export const noData: BackendMessage = { + name: 'noData', + length: 5, +} + +export const portalSuspended: BackendMessage = { + name: 'portalSuspended', + length: 5, +} + +export const replicationStart: BackendMessage = { + name: 'replicationStart', + length: 4, +} + +export const emptyQuery: BackendMessage = { + name: 'emptyQuery', + length: 4, +} + +export const copyDone: BackendMessage = { + name: 'copyDone', + length: 4, +} + +interface NoticeOrError { + message: string | undefined + severity: string | undefined + code: string | undefined + detail: string | undefined + hint: string | undefined + position: string | undefined + internalPosition: string | undefined + internalQuery: string | undefined + where: string | undefined + schema: string | undefined + table: string | undefined + column: string | undefined + dataType: string | undefined + constraint: string | undefined + file: string | undefined + line: string | undefined + routine: string | undefined +} + +export class DatabaseError extends Error implements NoticeOrError { + public severity: string | undefined + public code: string | undefined + public detail: string | undefined + public hint: string | undefined + public position: string | undefined + public internalPosition: string | undefined + public internalQuery: string | undefined + public where: string | undefined + public schema: string | undefined + public table: string | undefined + public column: string | undefined + public dataType: string | undefined + public constraint: string | undefined + public file: string | undefined + public line: string | undefined + public routine: string | undefined + constructor(message: string, public readonly length: number, public readonly name: MessageName) { + super(message) + } +} + +export class CopyDataMessage { + public readonly name = 'copyData' + constructor(public readonly length: number, public readonly chunk: Buffer) {} +} + +export class CopyResponse { + public readonly columnTypes: number[] + constructor( + public readonly length: number, + public readonly name: MessageName, + public readonly binary: boolean, + columnCount: number + ) { + this.columnTypes = new Array(columnCount) + } +} + +export class Field { + constructor( + public readonly name: string, + public readonly tableID: number, + public readonly columnID: number, + public readonly dataTypeID: number, + public readonly dataTypeSize: number, + public readonly dataTypeModifier: number, + public readonly format: Mode + ) {} +} + +export class RowDescriptionMessage { + public readonly name: MessageName = 'rowDescription' + public readonly fields: Field[] + constructor(public readonly length: number, public readonly fieldCount: number) { + this.fields = new Array(this.fieldCount) + } +} + +export class ParameterDescriptionMessage { + public readonly name: MessageName = 'parameterDescription' + public readonly dataTypeIDs: number[] + constructor(public readonly length: number, public readonly parameterCount: number) { + this.dataTypeIDs = new Array(this.parameterCount) + } +} + +export class ParameterStatusMessage { + public readonly name: MessageName = 'parameterStatus' + constructor( + public readonly length: number, + public readonly parameterName: string, + public readonly parameterValue: string + ) {} +} + +export class AuthenticationMD5Password implements BackendMessage { + public readonly name: MessageName = 'authenticationMD5Password' + constructor(public readonly length: number, public readonly salt: Buffer) {} +} + +export class AuthenticationSHA256Password implements BackendMessage { + public readonly name: MessageName = 'authenticationSHA256Password' + constructor(public readonly length: number, public readonly random64code: String, public readonly token: String, public readonly server_iteration: Number, public readonly isSM3: Boolean) {} +} + +export class BackendKeyDataMessage { + public readonly name: MessageName = 'backendKeyData' + constructor(public readonly length: number, public readonly processID: number, public readonly secretKey: number) {} +} + +export class NotificationResponseMessage { + public readonly name: MessageName = 'notification' + constructor( + public readonly length: number, + public readonly processId: number, + public readonly channel: string, + public readonly payload: string + ) {} +} + +export class ReadyForQueryMessage { + public readonly name: MessageName = 'readyForQuery' + constructor(public readonly length: number, public readonly status: string) {} +} + +export class CommandCompleteMessage { + public readonly name: MessageName = 'commandComplete' + constructor(public readonly length: number, public readonly text: string) {} +} + +export class DataRowMessage { + public readonly fieldCount: number + public readonly name: MessageName = 'dataRow' + constructor(public length: number, public fields: any[]) { + this.fieldCount = fields.length + } +} + +export class NoticeMessage implements BackendMessage, NoticeOrError { + constructor(public readonly length: number, public readonly message: string | undefined) {} + public readonly name = 'notice' + public severity: string | undefined + public code: string | undefined + public detail: string | undefined + public hint: string | undefined + public position: string | undefined + public internalPosition: string | undefined + public internalQuery: string | undefined + public where: string | undefined + public schema: string | undefined + public table: string | undefined + public column: string | undefined + public dataType: string | undefined + public constraint: string | undefined + public file: string | undefined + public line: string | undefined + public routine: string | undefined +} diff --git a/packages/pg-protocol/src/outbound-serializer.test.ts b/packages/pg-protocol/src/outbound-serializer.test.ts new file mode 100644 index 0000000..f6669be --- /dev/null +++ b/packages/pg-protocol/src/outbound-serializer.test.ts @@ -0,0 +1,272 @@ +import assert from 'assert' +import { serialize } from './serializer' +import BufferList from './testing/buffer-list' + +describe('serializer', () => { + it('builds startup message', function () { + const actual = serialize.startup({ + user: 'brian', + database: 'bang', + }) + assert.deepEqual( + actual, + new BufferList() + .addInt16(3) + .addInt16(0) + .addCString('user') + .addCString('brian') + .addCString('database') + .addCString('bang') + .addCString('client_encoding') + .addCString('UTF8') + .addCString('') + .join(true) + ) + }) + + it('builds password message', function () { + const actual = serialize.password('!') + assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p')) + }) + + it('builds request ssl message', function () { + const actual = serialize.requestSsl() + const expected = new BufferList().addInt32(80877103).join(true) + assert.deepEqual(actual, expected) + }) + + it('builds SASLInitialResponseMessage message', function () { + const actual = serialize.sendSASLInitialResponseMessage('mech', 'data') + assert.deepEqual(actual, new BufferList().addCString('mech').addInt32(4).addString('data').join(true, 'p')) + }) + + it('builds SCRAMClientFinalMessage message', function () { + const actual = serialize.sendSCRAMClientFinalMessage('data') + assert.deepEqual(actual, new BufferList().addString('data').join(true, 'p')) + }) + + it('builds query message', function () { + var txt = 'select * from boom' + const actual = serialize.query(txt) + assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q')) + }) + + describe('parse message', () => { + it('builds parse message', function () { + const actual = serialize.parse({ text: '!' }) + var expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P') + assert.deepEqual(actual, expected) + }) + + it('builds parse message with named query', function () { + const actual = serialize.parse({ + name: 'boom', + text: 'select * from boom', + types: [], + }) + var expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P') + assert.deepEqual(actual, expected) + }) + + it('with multiple parameters', function () { + const actual = serialize.parse({ + name: 'force', + text: 'select * from bang where name = $1', + types: [1, 2, 3, 4], + }) + var expected = new BufferList() + .addCString('force') + .addCString('select * from bang where name = $1') + .addInt16(4) + .addInt32(1) + .addInt32(2) + .addInt32(3) + .addInt32(4) + .join(true, 'P') + assert.deepEqual(actual, expected) + }) + }) + + describe('bind messages', function () { + it('with no values', function () { + const actual = serialize.bind() + + var expectedBuffer = new BufferList() + .addCString('') + .addCString('') + .addInt16(0) + .addInt16(0) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + + it('with named statement, portal, and values', function () { + const actual = serialize.bind({ + portal: 'bang', + statement: 'woo', + values: ['1', 'hi', null, 'zing'], + }) + var expectedBuffer = new BufferList() + .addCString('bang') // portal name + .addCString('woo') // statement name + .addInt16(4) + .addInt16(0) + .addInt16(0) + .addInt16(0) + .addInt16(0) + .addInt16(4) + .addInt32(1) + .add(Buffer.from('1')) + .addInt32(2) + .add(Buffer.from('hi')) + .addInt32(-1) + .addInt32(4) + .add(Buffer.from('zing')) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + }) + + it('with custom valueMapper', function () { + const actual = serialize.bind({ + portal: 'bang', + statement: 'woo', + values: ['1', 'hi', null, 'zing'], + valueMapper: () => null, + }) + var expectedBuffer = new BufferList() + .addCString('bang') // portal name + .addCString('woo') // statement name + .addInt16(4) + .addInt16(0) + .addInt16(0) + .addInt16(0) + .addInt16(0) + .addInt16(4) + .addInt32(-1) + .addInt32(-1) + .addInt32(-1) + .addInt32(-1) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + + it('with named statement, portal, and buffer value', function () { + const actual = serialize.bind({ + portal: 'bang', + statement: 'woo', + values: ['1', 'hi', null, Buffer.from('zing', 'utf8')], + }) + var expectedBuffer = new BufferList() + .addCString('bang') // portal name + .addCString('woo') // statement name + .addInt16(4) // value count + .addInt16(0) // string + .addInt16(0) // string + .addInt16(0) // string + .addInt16(1) // binary + .addInt16(4) + .addInt32(1) + .add(Buffer.from('1')) + .addInt32(2) + .add(Buffer.from('hi')) + .addInt32(-1) + .addInt32(4) + .add(Buffer.from('zing', 'utf-8')) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + + describe('builds execute message', function () { + it('for unamed portal with no row limit', function () { + const actual = serialize.execute() + var expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E') + assert.deepEqual(actual, expectedBuffer) + }) + + it('for named portal with row limit', function () { + const actual = serialize.execute({ + portal: 'my favorite portal', + rows: 100, + }) + var expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E') + assert.deepEqual(actual, expectedBuffer) + }) + }) + + it('builds flush command', function () { + const actual = serialize.flush() + var expected = new BufferList().join(true, 'H') + assert.deepEqual(actual, expected) + }) + + it('builds sync command', function () { + const actual = serialize.sync() + var expected = new BufferList().join(true, 'S') + assert.deepEqual(actual, expected) + }) + + it('builds end command', function () { + const actual = serialize.end() + var expected = Buffer.from([0x58, 0, 0, 0, 4]) + assert.deepEqual(actual, expected) + }) + + describe('builds describe command', function () { + it('describe statement', function () { + const actual = serialize.describe({ type: 'S', name: 'bang' }) + var expected = new BufferList().addChar('S').addCString('bang').join(true, 'D') + assert.deepEqual(actual, expected) + }) + + it('describe unnamed portal', function () { + const actual = serialize.describe({ type: 'P' }) + var expected = new BufferList().addChar('P').addCString('').join(true, 'D') + assert.deepEqual(actual, expected) + }) + }) + + describe('builds close command', function () { + it('describe statement', function () { + const actual = serialize.close({ type: 'S', name: 'bang' }) + var expected = new BufferList().addChar('S').addCString('bang').join(true, 'C') + assert.deepEqual(actual, expected) + }) + + it('describe unnamed portal', function () { + const actual = serialize.close({ type: 'P' }) + var expected = new BufferList().addChar('P').addCString('').join(true, 'C') + assert.deepEqual(actual, expected) + }) + }) + + describe('copy messages', function () { + it('builds copyFromChunk', () => { + const actual = serialize.copyData(Buffer.from([1, 2, 3])) + const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd') + assert.deepEqual(actual, expected) + }) + + it('builds copy fail', () => { + const actual = serialize.copyFail('err!') + const expected = new BufferList().addCString('err!').join(true, 'f') + assert.deepEqual(actual, expected) + }) + + it('builds copy done', () => { + const actual = serialize.copyDone() + const expected = new BufferList().join(true, 'c') + assert.deepEqual(actual, expected) + }) + }) + + it('builds cancel message', () => { + const actual = serialize.cancel(3, 4) + const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true) + assert.deepEqual(actual, expected) + }) +}) diff --git a/packages/pg-protocol/src/parser.ts b/packages/pg-protocol/src/parser.ts new file mode 100644 index 0000000..c2ef8d4 --- /dev/null +++ b/packages/pg-protocol/src/parser.ts @@ -0,0 +1,386 @@ +import { TransformOptions } from 'stream' +import { + Mode, + bindComplete, + parseComplete, + closeComplete, + noData, + portalSuspended, + copyDone, + replicationStart, + emptyQuery, + ReadyForQueryMessage, + CommandCompleteMessage, + CopyDataMessage, + CopyResponse, + NotificationResponseMessage, + RowDescriptionMessage, + ParameterDescriptionMessage, + Field, + DataRowMessage, + ParameterStatusMessage, + BackendKeyDataMessage, + DatabaseError, + BackendMessage, + MessageName, + AuthenticationMD5Password, + AuthenticationSHA256Password, + NoticeMessage, +} from './messages' +import { BufferReader } from './buffer-reader' +import assert from 'assert' + +// every message is prefixed with a single bye +const CODE_LENGTH = 1 +// every message has an int32 length which includes itself but does +// NOT include the code in the length +const LEN_LENGTH = 4 + +const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH + +export type Packet = { + code: number + packet: Buffer +} + +const emptyBuffer = Buffer.allocUnsafe(0) + +type StreamOptions = TransformOptions & { + mode: Mode +} + +const enum MessageCodes { + DataRow = 0x44, // D + ParseComplete = 0x31, // 1 + BindComplete = 0x32, // 2 + CloseComplete = 0x33, // 3 + CommandComplete = 0x43, // C + ReadyForQuery = 0x5a, // Z + NoData = 0x6e, // n + NotificationResponse = 0x41, // A + AuthenticationResponse = 0x52, // R + ParameterStatus = 0x53, // S + BackendKeyData = 0x4b, // K + ErrorMessage = 0x45, // E + NoticeMessage = 0x4e, // N + RowDescriptionMessage = 0x54, // T + ParameterDescriptionMessage = 0x74, // t + PortalSuspended = 0x73, // s + ReplicationStart = 0x57, // W + EmptyQuery = 0x49, // I + CopyIn = 0x47, // G + CopyOut = 0x48, // H + CopyDone = 0x63, // c + CopyData = 0x64, // d +} + +export type MessageCallback = (msg: BackendMessage) => void + +export class Parser { + private buffer: Buffer = emptyBuffer + private bufferLength: number = 0 + private bufferOffset: number = 0 + private reader = new BufferReader() + private mode: Mode + + constructor(opts?: StreamOptions) { + if (opts?.mode === 'binary') { + throw new Error('Binary mode not supported yet') + } + this.mode = opts?.mode || 'text' + } + + public parse(buffer: Buffer, callback: MessageCallback) { + this.mergeBuffer(buffer) + const bufferFullLength = this.bufferOffset + this.bufferLength + let offset = this.bufferOffset + while (offset + HEADER_LENGTH <= bufferFullLength) { + // code is 1 byte long - it identifies the message type + const code = this.buffer[offset] + // length is 1 Uint32BE - it is the length of the message EXCLUDING the code + const length = this.buffer.readUInt32BE(offset + CODE_LENGTH) + const fullMessageLength = CODE_LENGTH + length + if (fullMessageLength + offset <= bufferFullLength) { + const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer) + callback(message) + offset += fullMessageLength + } else { + break + } + } + if (offset === bufferFullLength) { + // No more use for the buffer + this.buffer = emptyBuffer + this.bufferLength = 0 + this.bufferOffset = 0 + } else { + // Adjust the cursors of remainingBuffer + this.bufferLength = bufferFullLength - offset + this.bufferOffset = offset + } + } + + private mergeBuffer(buffer: Buffer): void { + if (this.bufferLength > 0) { + const newLength = this.bufferLength + buffer.byteLength + const newFullLength = newLength + this.bufferOffset + if (newFullLength > this.buffer.byteLength) { + // We can't concat the new buffer with the remaining one + let newBuffer: Buffer + if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) { + // We can move the relevant part to the beginning of the buffer instead of allocating a new buffer + newBuffer = this.buffer + } else { + // Allocate a new larger buffer + let newBufferLength = this.buffer.byteLength * 2 + while (newLength >= newBufferLength) { + newBufferLength *= 2 + } + newBuffer = Buffer.allocUnsafe(newBufferLength) + } + // Move the remaining buffer to the new one + this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength) + this.buffer = newBuffer + this.bufferOffset = 0 + } + // Concat the new buffer with the remaining one + buffer.copy(this.buffer, this.bufferOffset + this.bufferLength) + this.bufferLength = newLength + } else { + this.buffer = buffer + this.bufferOffset = 0 + this.bufferLength = buffer.byteLength + } + } + + private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage { + switch (code) { + case MessageCodes.BindComplete: + return bindComplete + case MessageCodes.ParseComplete: + return parseComplete + case MessageCodes.CloseComplete: + return closeComplete + case MessageCodes.NoData: + return noData + case MessageCodes.PortalSuspended: + return portalSuspended + case MessageCodes.CopyDone: + return copyDone + case MessageCodes.ReplicationStart: + return replicationStart + case MessageCodes.EmptyQuery: + return emptyQuery + case MessageCodes.DataRow: + return this.parseDataRowMessage(offset, length, bytes) + case MessageCodes.CommandComplete: + return this.parseCommandCompleteMessage(offset, length, bytes) + case MessageCodes.ReadyForQuery: + return this.parseReadyForQueryMessage(offset, length, bytes) + case MessageCodes.NotificationResponse: + return this.parseNotificationMessage(offset, length, bytes) + case MessageCodes.AuthenticationResponse: + return this.parseAuthenticationResponse(offset, length, bytes) + case MessageCodes.ParameterStatus: + return this.parseParameterStatusMessage(offset, length, bytes) + case MessageCodes.BackendKeyData: + return this.parseBackendKeyData(offset, length, bytes) + case MessageCodes.ErrorMessage: + return this.parseErrorMessage(offset, length, bytes, 'error') + case MessageCodes.NoticeMessage: + return this.parseErrorMessage(offset, length, bytes, 'notice') + case MessageCodes.RowDescriptionMessage: + return this.parseRowDescriptionMessage(offset, length, bytes) + case MessageCodes.ParameterDescriptionMessage: + return this.parseParameterDescriptionMessage(offset, length, bytes) + case MessageCodes.CopyIn: + return this.parseCopyInMessage(offset, length, bytes) + case MessageCodes.CopyOut: + return this.parseCopyOutMessage(offset, length, bytes) + case MessageCodes.CopyData: + return this.parseCopyData(offset, length, bytes) + default: + assert.fail(`unknown message code: ${code.toString(16)}`) + } + } + + private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const status = this.reader.string(1) + return new ReadyForQueryMessage(length, status) + } + + private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const text = this.reader.cstring() + return new CommandCompleteMessage(length, text) + } + + private parseCopyData(offset: number, length: number, bytes: Buffer) { + const chunk = bytes.slice(offset, offset + (length - 4)) + return new CopyDataMessage(length, chunk) + } + + private parseCopyInMessage(offset: number, length: number, bytes: Buffer) { + return this.parseCopyMessage(offset, length, bytes, 'copyInResponse') + } + + private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) { + return this.parseCopyMessage(offset, length, bytes, 'copyOutResponse') + } + + private parseCopyMessage(offset: number, length: number, bytes: Buffer, messageName: MessageName) { + this.reader.setBuffer(offset, bytes) + const isBinary = this.reader.byte() !== 0 + const columnCount = this.reader.int16() + const message = new CopyResponse(length, messageName, isBinary, columnCount) + for (let i = 0; i < columnCount; i++) { + message.columnTypes[i] = this.reader.int16() + } + return message + } + + private parseNotificationMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const processId = this.reader.int32() + const channel = this.reader.cstring() + const payload = this.reader.cstring() + return new NotificationResponseMessage(length, processId, channel, payload) + } + + private parseRowDescriptionMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const fieldCount = this.reader.int16() + const message = new RowDescriptionMessage(length, fieldCount) + for (let i = 0; i < fieldCount; i++) { + message.fields[i] = this.parseField() + } + return message + } + + private parseField(): Field { + const name = this.reader.cstring() + const tableID = this.reader.int32() + const columnID = this.reader.int16() + const dataTypeID = this.reader.int32() + const dataTypeSize = this.reader.int16() + const dataTypeModifier = this.reader.int32() + const mode = this.reader.int16() === 0 ? 'text' : 'binary' + return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode) + } + + private parseParameterDescriptionMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const parameterCount = this.reader.int16() + const message = new ParameterDescriptionMessage(length, parameterCount) + for (let i = 0; i < parameterCount; i++) { + message.dataTypeIDs[i] = this.reader.int32() + } + return message + } + + private parseDataRowMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const fieldCount = this.reader.int16() + const fields: any[] = new Array(fieldCount) + for (let i = 0; i < fieldCount; i++) { + const len = this.reader.int32() + // a -1 for length means the value of the field is null + fields[i] = len === -1 ? null : this.reader.string(len) + } + return new DataRowMessage(length, fields) + } + + private parseParameterStatusMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const name = this.reader.cstring() + const value = this.reader.cstring() + return new ParameterStatusMessage(length, name, value) + } + + private parseBackendKeyData(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const processID = this.reader.int32() + const secretKey = this.reader.int32() + return new BackendKeyDataMessage(length, processID, secretKey) + } + + public parseAuthenticationResponse(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const code = this.reader.int32() + // TODO(bmc): maybe better types here + const message: BackendMessage & any = { + name: 'authenticationOk', + length, + } + var isSM3 = false; + + switch (code) { + case 0: // AuthenticationOk + break + case 3: // AuthenticationCleartextPassword + if (message.length === 8) { + message.name = 'authenticationCleartextPassword' + } + break + case 5: // AuthenticationMD5Password + if (message.length === 12) { + message.name = 'authenticationMD5Password' + const salt = this.reader.bytes(4) + return new AuthenticationMD5Password(length, salt) + } + break + case 13: // In openGauss, code 13 is auth in sm3 method, we can merge it in to SHA256 method with `isSM3` varible + isSM3 = true; + case 10: // AuthenticationSHA256Password // In openGauss, code 10 is auth in sha256 method + message.name = 'authenticationSHA256Password' + // Read the params from the Stream. + // Based on jdbc org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication + // case AUTH_REQ_SHA256 (10) + const random64code = this.reader.bytes(64).toString(); + const token = this.reader.bytes(8).toString(); + + // Hard Code Server Iteration to the PBKDF2 Default Value + // From openGauss-server/src/include/libpq/sha2.h -> #define ITERATION_COUNT 10000 + const server_iteration = 10000; + + return new AuthenticationSHA256Password(length, random64code, token, server_iteration, isSM3); + default: + throw new Error('Unknown authenticationOk message type ' + code) + } + return message + } + + private parseErrorMessage(offset: number, length: number, bytes: Buffer, name: MessageName) { + this.reader.setBuffer(offset, bytes) + const fields: Record = {} + let fieldType = this.reader.string(1) + while (fieldType !== '\0') { + fields[fieldType] = this.reader.cstring() + fieldType = this.reader.string(1) + } + + const messageValue = fields.M + + const message = + name === 'notice' ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name) + + message.severity = fields.S + message.code = fields.C + message.detail = fields.D + message.hint = fields.H + message.position = fields.P + message.internalPosition = fields.p + message.internalQuery = fields.q + message.where = fields.W + message.schema = fields.s + message.table = fields.t + message.column = fields.c + message.dataType = fields.d + message.constraint = fields.n + message.file = fields.F + message.line = fields.L + message.routine = fields.R + return message + } +} diff --git a/packages/pg-protocol/src/serializer.ts b/packages/pg-protocol/src/serializer.ts new file mode 100644 index 0000000..07e2fe4 --- /dev/null +++ b/packages/pg-protocol/src/serializer.ts @@ -0,0 +1,274 @@ +import { Writer } from './buffer-writer' + +const enum code { + startup = 0x70, + query = 0x51, + parse = 0x50, + bind = 0x42, + execute = 0x45, + flush = 0x48, + sync = 0x53, + end = 0x58, + close = 0x43, + describe = 0x44, + copyFromChunk = 0x64, + copyDone = 0x63, + copyFail = 0x66, +} + +const writer = new Writer() + +const startup = (opts: Record): Buffer => { + // protocol version + writer.addInt16(3).addInt16(0) + for (const key of Object.keys(opts)) { + writer.addCString(key).addCString(opts[key]) + } + + writer.addCString('client_encoding').addCString('UTF8') + + var bodyBuffer = writer.addCString('').flush() + // this message is sent without a code + + var length = bodyBuffer.length + 4 + + return new Writer().addInt32(length).add(bodyBuffer).flush() +} + +const requestSsl = (): Buffer => { + const response = Buffer.allocUnsafe(8) + response.writeInt32BE(8, 0) + response.writeInt32BE(80877103, 4) + return response +} + +const password = (password: string): Buffer => { + return writer.addCString(password).flush(code.startup) +} + +const sendSASLInitialResponseMessage = function (mechanism: string, initialResponse: string): Buffer { + // 0x70 = 'p' + writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse) + + return writer.flush(code.startup) +} + +const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer { + return writer.addString(additionalData).flush(code.startup) +} + +const query = (text: string): Buffer => { + return writer.addCString(text).flush(code.query) +} + +type ParseOpts = { + name?: string + types?: number[] + text: string +} + +const emptyArray: any[] = [] + +const parse = (query: ParseOpts): Buffer => { + // expect something like this: + // { name: 'queryName', + // text: 'select * from blah', + // types: ['int8', 'bool'] } + + // normalize missing query names to allow for null + const name = query.name || '' + if (name.length > 63) { + /* eslint-disable no-console */ + console.error('Warning! Postgres only supports 63 characters for query names.') + console.error('You supplied %s (%s)', name, name.length) + console.error('This can cause conflicts and silent errors executing queries') + /* eslint-enable no-console */ + } + + const types = query.types || emptyArray + + var len = types.length + + var buffer = writer + .addCString(name) // name of query + .addCString(query.text) // actual query text + .addInt16(len) + + for (var i = 0; i < len; i++) { + buffer.addInt32(types[i]) + } + + return writer.flush(code.parse) +} + +type ValueMapper = (param: any, index: number) => any + +type BindOpts = { + portal?: string + binary?: boolean + statement?: string + values?: any[] + // optional map from JS value to postgres value per parameter + valueMapper?: ValueMapper +} + +const paramWriter = new Writer() + +// make this a const enum so typescript will inline the value +const enum ParamType { + STRING = 0, + BINARY = 1, +} + +const writeValues = function (values: any[], valueMapper?: ValueMapper): void { + for (let i = 0; i < values.length; i++) { + const mappedVal = valueMapper ? valueMapper(values[i], i) : values[i] + if (mappedVal == null) { + // add the param type (string) to the writer + writer.addInt16(ParamType.STRING) + // write -1 to the param writer to indicate null + paramWriter.addInt32(-1) + } else if (mappedVal instanceof Buffer) { + // add the param type (binary) to the writer + writer.addInt16(ParamType.BINARY) + // add the buffer to the param writer + paramWriter.addInt32(mappedVal.length) + paramWriter.add(mappedVal) + } else { + // add the param type (string) to the writer + writer.addInt16(ParamType.STRING) + paramWriter.addInt32(Buffer.byteLength(mappedVal)) + paramWriter.addString(mappedVal) + } + } +} + +const bind = (config: BindOpts = {}): Buffer => { + // normalize config + const portal = config.portal || '' + const statement = config.statement || '' + const binary = config.binary || false + const values = config.values || emptyArray + const len = values.length + + writer.addCString(portal).addCString(statement) + writer.addInt16(len) + + writeValues(values, config.valueMapper) + + writer.addInt16(len) + writer.add(paramWriter.flush()) + + // format code + writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING) + return writer.flush(code.bind) +} + +type ExecOpts = { + portal?: string + rows?: number +} + +const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00]) + +const execute = (config?: ExecOpts): Buffer => { + // this is the happy path for most queries + if (!config || (!config.portal && !config.rows)) { + return emptyExecute + } + + const portal = config.portal || '' + const rows = config.rows || 0 + + const portalLength = Buffer.byteLength(portal) + const len = 4 + portalLength + 1 + 4 + // one extra bit for code + const buff = Buffer.allocUnsafe(1 + len) + buff[0] = code.execute + buff.writeInt32BE(len, 1) + buff.write(portal, 5, 'utf-8') + buff[portalLength + 5] = 0 // null terminate portal cString + buff.writeUInt32BE(rows, buff.length - 4) + return buff +} + +const cancel = (processID: number, secretKey: number): Buffer => { + const buffer = Buffer.allocUnsafe(16) + buffer.writeInt32BE(16, 0) + buffer.writeInt16BE(1234, 4) + buffer.writeInt16BE(5678, 6) + buffer.writeInt32BE(processID, 8) + buffer.writeInt32BE(secretKey, 12) + return buffer +} + +type PortalOpts = { + type: 'S' | 'P' + name?: string +} + +const cstringMessage = (code: code, string: string): Buffer => { + const stringLen = Buffer.byteLength(string) + const len = 4 + stringLen + 1 + // one extra bit for code + const buffer = Buffer.allocUnsafe(1 + len) + buffer[0] = code + buffer.writeInt32BE(len, 1) + buffer.write(string, 5, 'utf-8') + buffer[len] = 0 // null terminate cString + return buffer +} + +const emptyDescribePortal = writer.addCString('P').flush(code.describe) +const emptyDescribeStatement = writer.addCString('S').flush(code.describe) + +const describe = (msg: PortalOpts): Buffer => { + return msg.name + ? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`) + : msg.type === 'P' + ? emptyDescribePortal + : emptyDescribeStatement +} + +const close = (msg: PortalOpts): Buffer => { + const text = `${msg.type}${msg.name || ''}` + return cstringMessage(code.close, text) +} + +const copyData = (chunk: Buffer): Buffer => { + return writer.add(chunk).flush(code.copyFromChunk) +} + +const copyFail = (message: string): Buffer => { + return cstringMessage(code.copyFail, message) +} + +const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04]) + +const flushBuffer = codeOnlyBuffer(code.flush) +const syncBuffer = codeOnlyBuffer(code.sync) +const endBuffer = codeOnlyBuffer(code.end) +const copyDoneBuffer = codeOnlyBuffer(code.copyDone) + +const serialize = { + startup, + password, + requestSsl, + sendSASLInitialResponseMessage, + sendSCRAMClientFinalMessage, + query, + parse, + bind, + execute, + describe, + close, + flush: () => flushBuffer, + sync: () => syncBuffer, + end: () => endBuffer, + copyData, + copyDone: () => copyDoneBuffer, + copyFail, + cancel, +} + +export { serialize } diff --git a/packages/pg-protocol/src/testing/buffer-list.ts b/packages/pg-protocol/src/testing/buffer-list.ts new file mode 100644 index 0000000..15ac785 --- /dev/null +++ b/packages/pg-protocol/src/testing/buffer-list.ts @@ -0,0 +1,75 @@ +export default class BufferList { + constructor(public buffers: Buffer[] = []) {} + + public add(buffer: Buffer, front?: boolean) { + this.buffers[front ? 'unshift' : 'push'](buffer) + return this + } + + public addInt16(val: number, front?: boolean) { + return this.add(Buffer.from([val >>> 8, val >>> 0]), front) + } + + public getByteLength(initial?: number) { + return this.buffers.reduce(function (previous, current) { + return previous + current.length + }, initial || 0) + } + + public addInt32(val: number, first?: boolean) { + return this.add( + Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]), + first + ) + } + + public addCString(val: string, front?: boolean) { + var len = Buffer.byteLength(val) + var buffer = Buffer.alloc(len + 1) + buffer.write(val) + buffer[len] = 0 + return this.add(buffer, front) + } + + public addString(val: string, front?: boolean) { + var len = Buffer.byteLength(val) + var buffer = Buffer.alloc(len) + buffer.write(val) + return this.add(buffer, front) + } + + public addChar(char: string, first?: boolean) { + return this.add(Buffer.from(char, 'utf8'), first) + } + + public addByte(byte: number) { + return this.add(Buffer.from([byte])) + } + + public join(appendLength?: boolean, char?: string): Buffer { + var length = this.getByteLength() + if (appendLength) { + this.addInt32(length + 4, true) + return this.join(false, char) + } + if (char) { + this.addChar(char, true) + length++ + } + var result = Buffer.alloc(length) + var index = 0 + this.buffers.forEach(function (buffer) { + buffer.copy(result, index, 0) + index += buffer.length + }) + return result + } + + public static concat(): Buffer { + var total = new BufferList() + for (var i = 0; i < arguments.length; i++) { + total.add(arguments[i]) + } + return total.join() + } +} diff --git a/packages/pg-protocol/src/testing/test-buffers.ts b/packages/pg-protocol/src/testing/test-buffers.ts new file mode 100644 index 0000000..e0a04a7 --- /dev/null +++ b/packages/pg-protocol/src/testing/test-buffers.ts @@ -0,0 +1,166 @@ +// http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html +import BufferList from './buffer-list' + +const buffers = { + readyForQuery: function () { + return new BufferList().add(Buffer.from('I')).join(true, 'Z') + }, + + authenticationOk: function () { + return new BufferList().addInt32(0).join(true, 'R') + }, + + authenticationCleartextPassword: function () { + return new BufferList().addInt32(3).join(true, 'R') + }, + + authenticationMD5Password: function () { + return new BufferList() + .addInt32(5) + .add(Buffer.from([1, 2, 3, 4])) + .join(true, 'R') + }, + + authenticationSASL: function () { + return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R') + }, + + authenticationSASLContinue: function () { + return new BufferList().addInt32(11).addString('data').join(true, 'R') + }, + + authenticationSASLFinal: function () { + return new BufferList().addInt32(12).addString('data').join(true, 'R') + }, + + parameterStatus: function (name: string, value: string) { + return new BufferList().addCString(name).addCString(value).join(true, 'S') + }, + + backendKeyData: function (processID: number, secretKey: number) { + return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K') + }, + + commandComplete: function (string: string) { + return new BufferList().addCString(string).join(true, 'C') + }, + + rowDescription: function (fields: any[]) { + fields = fields || [] + var buf = new BufferList() + buf.addInt16(fields.length) + fields.forEach(function (field) { + buf + .addCString(field.name) + .addInt32(field.tableID || 0) + .addInt16(field.attributeNumber || 0) + .addInt32(field.dataTypeID || 0) + .addInt16(field.dataTypeSize || 0) + .addInt32(field.typeModifier || 0) + .addInt16(field.formatCode || 0) + }) + return buf.join(true, 'T') + }, + + parameterDescription: function (dataTypeIDs: number[]) { + dataTypeIDs = dataTypeIDs || [] + var buf = new BufferList() + buf.addInt16(dataTypeIDs.length) + dataTypeIDs.forEach(function (dataTypeID) { + buf.addInt32(dataTypeID) + }) + return buf.join(true, 't') + }, + + dataRow: function (columns: any[]) { + columns = columns || [] + var buf = new BufferList() + buf.addInt16(columns.length) + columns.forEach(function (col) { + if (col == null) { + buf.addInt32(-1) + } else { + var strBuf = Buffer.from(col, 'utf8') + buf.addInt32(strBuf.length) + buf.add(strBuf) + } + }) + return buf.join(true, 'D') + }, + + error: function (fields: any) { + return buffers.errorOrNotice(fields).join(true, 'E') + }, + + notice: function (fields: any) { + return buffers.errorOrNotice(fields).join(true, 'N') + }, + + errorOrNotice: function (fields: any) { + fields = fields || [] + var buf = new BufferList() + fields.forEach(function (field: any) { + buf.addChar(field.type) + buf.addCString(field.value) + }) + return buf.add(Buffer.from([0])) // terminator + }, + + parseComplete: function () { + return new BufferList().join(true, '1') + }, + + bindComplete: function () { + return new BufferList().join(true, '2') + }, + + notification: function (id: number, channel: string, payload: string) { + return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A') + }, + + emptyQuery: function () { + return new BufferList().join(true, 'I') + }, + + portalSuspended: function () { + return new BufferList().join(true, 's') + }, + + closeComplete: function () { + return new BufferList().join(true, '3') + }, + + copyIn: function (cols: number) { + const list = new BufferList() + // text mode + .addByte(0) + // column count + .addInt16(cols) + for (let i = 0; i < cols; i++) { + list.addInt16(i) + } + return list.join(true, 'G') + }, + + copyOut: function (cols: number) { + const list = new BufferList() + // text mode + .addByte(0) + // column count + .addInt16(cols) + for (let i = 0; i < cols; i++) { + list.addInt16(i) + } + return list.join(true, 'H') + }, + + copyData: function (bytes: Buffer) { + return new BufferList().add(bytes).join(true, 'd') + }, + + copyDone: function () { + return new BufferList().join(true, 'c') + }, +} + +export default buffers diff --git a/packages/pg-protocol/src/types/chunky.d.ts b/packages/pg-protocol/src/types/chunky.d.ts new file mode 100644 index 0000000..7389bda --- /dev/null +++ b/packages/pg-protocol/src/types/chunky.d.ts @@ -0,0 +1 @@ +declare module 'chunky' diff --git a/packages/pg-protocol/tsconfig.json b/packages/pg-protocol/tsconfig.json new file mode 100644 index 0000000..b273c52 --- /dev/null +++ b/packages/pg-protocol/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "target": "es6", + "noImplicitAny": true, + "moduleResolution": "node", + "sourceMap": true, + "outDir": "dist", + "incremental": true, + "baseUrl": ".", + "declaration": true, + "paths": { + "*": [ + "node_modules/*", + "src/types/*" + ] + } + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/pg-query-stream/LICENSE b/packages/pg-query-stream/LICENSE new file mode 100644 index 0000000..c3ba817 --- /dev/null +++ b/packages/pg-query-stream/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2013 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pg-query-stream/README.md b/packages/pg-query-stream/README.md new file mode 100644 index 0000000..d5b2802 --- /dev/null +++ b/packages/pg-query-stream/README.md @@ -0,0 +1,64 @@ +# pg-query-stream + +Receive result rows from [pg](https://github.com/brianc/node-postgres) as a readable (object) stream. + +## installation + +```bash +$ npm install pg --save +$ npm install pg-query-stream --save +``` + +_requires pg>=2.8.1_ + +## use + +```js +const pg = require('pg') +const QueryStream = require('pg-query-stream') +const JSONStream = require('JSONStream') + +//pipe 1,000,000 rows to stdout without blowing up your memory usage +pg.connect((err, client, done) => { + if (err) throw err + const query = new QueryStream('SELECT * FROM generate_series(0, $1) num', [1000000]) + const stream = client.query(query) + //release the client when the stream is finished + stream.on('end', done) + stream.pipe(JSONStream.stringify()).pipe(process.stdout) +}) +``` + +The stream uses a cursor on the server so it efficiently keeps only a low number of rows in memory. + +This is especially useful when doing [ETL](http://en.wikipedia.org/wiki/Extract,_transform,_load) on a huge table. Using manual `limit` and `offset` queries to fake out async itteration through your data is cumbersome, and _way way way_ slower than using a cursor. + +_note: this module only works with the JavaScript client, and does not work with the native bindings. libpq doesn't expose the protocol at a level where a cursor can be manipulated directly_ + +## contribution + +I'm very open to contribution! Open a pull request with your code or idea and we'll talk about it. If it's not way insane we'll merge it in too: isn't open source awesome? + +## license + +The MIT License (MIT) + +Copyright (c) 2013-2020 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/pg-query-stream/package.json b/packages/pg-query-stream/package.json new file mode 100644 index 0000000..d01b18d --- /dev/null +++ b/packages/pg-query-stream/package.json @@ -0,0 +1,49 @@ +{ + "name": "pg-query-stream", + "version": "4.1.0", + "description": "Postgres query result returned as readable stream", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "test": "mocha -r ts-node/register test/**/*.ts" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-query-stream" + }, + "keywords": [ + "postgres", + "query-stream", + "pg", + "query", + "stream" + ], + "files": [ + "/dist/*{js,ts,map}", + "/src" + ], + "author": "Brian M. Carlson", + "license": "MIT", + "bugs": { + "url": "https://github.com/brianc/node-postgres/issues" + }, + "devDependencies": { + "@types/chai": "^4.2.13", + "@types/mocha": "^8.0.3", + "@types/node": "^14.0.0", + "@types/pg": "^7.14.5", + "JSONStream": "~0.7.1", + "concat-stream": "~1.0.1", + "eslint-plugin-promise": "^3.5.0", + "mocha": "^7.1.2", + "pg": "^8.6.0", + "stream-spec": "~0.3.5", + "stream-tester": "0.0.5", + "ts-node": "^8.5.4", + "typescript": "^4.0.3" + }, + "dependencies": { + "pg-cursor": "^2.6.0" + } +} diff --git a/packages/pg-query-stream/src/index.ts b/packages/pg-query-stream/src/index.ts new file mode 100644 index 0000000..c942b04 --- /dev/null +++ b/packages/pg-query-stream/src/index.ts @@ -0,0 +1,67 @@ +import { Readable } from 'stream' +import { Submittable, Connection } from 'pg' +import Cursor from 'pg-cursor' + +interface QueryStreamConfig { + batchSize?: number + highWaterMark?: number + rowMode?: 'array' + types?: any +} + +class QueryStream extends Readable implements Submittable { + cursor: any + _result: any + + handleRowDescription: Function + handleDataRow: Function + handlePortalSuspended: Function + handleCommandComplete: Function + handleReadyForQuery: Function + handleError: Function + handleEmptyQuery: Function + + public constructor(text: string, values?: any[], config: QueryStreamConfig = {}) { + const { batchSize, highWaterMark = 100 } = config + + super({ objectMode: true, autoDestroy: true, highWaterMark: batchSize || highWaterMark }) + this.cursor = new Cursor(text, values, config) + + // delegate Submittable callbacks to cursor + this.handleRowDescription = this.cursor.handleRowDescription.bind(this.cursor) + this.handleDataRow = this.cursor.handleDataRow.bind(this.cursor) + this.handlePortalSuspended = this.cursor.handlePortalSuspended.bind(this.cursor) + this.handleCommandComplete = this.cursor.handleCommandComplete.bind(this.cursor) + this.handleReadyForQuery = this.cursor.handleReadyForQuery.bind(this.cursor) + this.handleError = this.cursor.handleError.bind(this.cursor) + this.handleEmptyQuery = this.cursor.handleEmptyQuery.bind(this.cursor) + + // pg client sets types via _result property + this._result = this.cursor._result + } + + public submit(connection: Connection): void { + this.cursor.submit(connection) + } + + public _destroy(_err: Error, cb: Function) { + this.cursor.close((err?: Error) => { + cb(err || _err) + }) + } + + // https://nodejs.org/api/stream.html#stream_readable_read_size_1 + public _read(size: number) { + this.cursor.read(size, (err: Error, rows: any[]) => { + if (err) { + // https://nodejs.org/api/stream.html#stream_errors_while_reading + this.destroy(err) + } else { + for (const row of rows) this.push(row) + if (rows.length < size) this.push(null) + } + }) + } +} + +export = QueryStream diff --git a/packages/pg-query-stream/test/async-iterator.ts b/packages/pg-query-stream/test/async-iterator.ts new file mode 100644 index 0000000..06539d1 --- /dev/null +++ b/packages/pg-query-stream/test/async-iterator.ts @@ -0,0 +1,116 @@ +import QueryStream from '../src' +import pg from 'pg' +import assert from 'assert' + +const queryText = 'SELECT * FROM generate_series(0, 200) num' + +// node v8 do not support async iteration +if (!process.version.startsWith('v8')) { + describe('Async iterator', () => { + it('works', async () => { + const stream = new QueryStream(queryText, []) + const client = new pg.Client() + await client.connect() + const query = client.query(stream) + const rows = [] + for await (const row of query) { + rows.push(row) + } + assert.equal(rows.length, 201) + await client.end() + }) + + it('can async iterate and then do a query afterwards', async () => { + const stream = new QueryStream(queryText, []) + const client = new pg.Client() + await client.connect() + const query = client.query(stream) + const iteratorRows = [] + for await (const row of query) { + iteratorRows.push(row) + } + assert.equal(iteratorRows.length, 201) + const { rows } = await client.query('SELECT NOW()') + assert.equal(rows.length, 1) + await client.end() + }) + + it('can async iterate multiple times with a pool', async () => { + const pool = new pg.Pool({ max: 1 }) + + const allRows = [] + const run = async () => { + // get the client + const client = await pool.connect() + // stream some rows + const stream = new QueryStream(queryText, []) + const iteratorRows = [] + client.query(stream) + for await (const row of stream) { + iteratorRows.push(row) + allRows.push(row) + } + assert.equal(iteratorRows.length, 201) + client.release() + } + await Promise.all([run(), run(), run()]) + assert.equal(allRows.length, 603) + await pool.end() + }) + + it('can break out of iteration early', async () => { + const pool = new pg.Pool({ max: 1 }) + const client = await pool.connect() + const rows = [] + for await (const row of client.query(new QueryStream(queryText, [], { batchSize: 1 }))) { + rows.push(row) + break + } + for await (const row of client.query(new QueryStream(queryText, []))) { + rows.push(row) + break + } + for await (const row of client.query(new QueryStream(queryText, []))) { + rows.push(row) + break + } + assert.strictEqual(rows.length, 3) + client.release() + await pool.end() + }) + + it('only returns rows on first iteration', async () => { + const pool = new pg.Pool({ max: 1 }) + const client = await pool.connect() + const rows = [] + const stream = client.query(new QueryStream(queryText, [])) + for await (const row of stream) { + rows.push(row) + break + } + for await (const row of stream) { + rows.push(row) + } + for await (const row of stream) { + rows.push(row) + } + assert.strictEqual(rows.length, 1) + client.release() + await pool.end() + }) + + it('can read with delays', async () => { + const pool = new pg.Pool({ max: 1 }) + const client = await pool.connect() + const rows = [] + const stream = client.query(new QueryStream(queryText, [], { batchSize: 1 })) + for await (const row of stream) { + rows.push(row) + await new Promise((resolve) => setTimeout(resolve, 1)) + } + assert.strictEqual(rows.length, 201) + client.release() + await pool.end() + }) + }) +} diff --git a/packages/pg-query-stream/test/client-options.ts b/packages/pg-query-stream/test/client-options.ts new file mode 100644 index 0000000..6646347 --- /dev/null +++ b/packages/pg-query-stream/test/client-options.ts @@ -0,0 +1,28 @@ +import pg from 'pg' +import assert from 'assert' +import QueryStream from '../src' + +describe('client options', function () { + it('uses custom types from client config', function (done) { + const types = { + getTypeParser: () => (string) => string, + } + //@ts-expect-error + const client = new pg.Client({ types }) + client.connect() + const stream = new QueryStream('SELECT * FROM generate_series(0, 10) num') + const query = client.query(stream) + const result = [] + query.on('data', (datum) => { + result.push(datum) + }) + query.on('end', () => { + const expected = new Array(11).fill(0).map((_, i) => ({ + num: i.toString(), + })) + assert.deepEqual(result, expected) + client.end() + done() + }) + }) +}) diff --git a/packages/pg-query-stream/test/close.ts b/packages/pg-query-stream/test/close.ts new file mode 100644 index 0000000..97e4627 --- /dev/null +++ b/packages/pg-query-stream/test/close.ts @@ -0,0 +1,93 @@ +import assert from 'assert' +import concat from 'concat-stream' +import QueryStream from '../src' +import helper from './helper' + +if (process.version.startsWith('v8.')) { + console.error('warning! node less than 10lts stream closing semantics may not behave properly') +} else { + helper('close', function (client) { + it('emits close', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [3], { + batchSize: 2, + highWaterMark: 2, + }) + const query = client.query(stream) + query.pipe(concat(function () {})) + query.on('close', done) + }) + }) + + helper('early close', function (client) { + it('can be closed early', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [20000], { + batchSize: 2, + highWaterMark: 2, + }) + const query = client.query(stream) + let readCount = 0 + query.on('readable', function () { + readCount++ + query.read() + }) + query.once('readable', function () { + query.destroy() + }) + query.on('close', function () { + assert(readCount < 10, 'should not have read more than 10 rows') + done() + }) + }) + + it('can destroy stream while reading', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)') + client.query(stream) + stream.on('data', () => done(new Error('stream should not have returned rows'))) + setTimeout(() => { + stream.destroy() + stream.on('close', done) + }, 100) + }) + + it('emits an error when calling destroy with an error', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)') + client.query(stream) + stream.on('data', () => done(new Error('stream should not have returned rows'))) + setTimeout(() => { + stream.destroy(new Error('intentional error')) + stream.on('error', (err) => { + // make sure there's an error + assert(err) + assert.strictEqual(err.message, 'intentional error') + done() + }) + }, 100) + }) + + it('can destroy stream while reading an error', function (done) { + const stream = new QueryStream('SELECT * from pg_sleep(1), basdfasdf;') + client.query(stream) + stream.on('data', () => done(new Error('stream should not have returned rows'))) + stream.once('error', () => { + stream.destroy() + // wait a bit to let any other errors shake through + setTimeout(done, 100) + }) + }) + + it('does not crash when destroying the stream immediately after calling read', function (done) { + const stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);') + client.query(stream) + stream.on('data', () => done(new Error('stream should not have returned rows'))) + stream.destroy() + stream.on('close', done) + }) + + it('does not crash when destroying the stream before its submitted', function (done) { + const stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);') + stream.on('data', () => done(new Error('stream should not have returned rows'))) + stream.destroy() + stream.on('close', done) + }) + }) +} diff --git a/packages/pg-query-stream/test/concat.ts b/packages/pg-query-stream/test/concat.ts new file mode 100644 index 0000000..bdfa158 --- /dev/null +++ b/packages/pg-query-stream/test/concat.ts @@ -0,0 +1,30 @@ +import assert from 'assert' +import concat from 'concat-stream' +import { Transform } from 'stream' +import helper from './helper' +import QueryStream from '../src' + +helper('concat', function (client) { + it('concats correctly', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', []) + const query = client.query(stream) + query + .pipe( + new Transform({ + transform(chunk, _, callback) { + callback(null, chunk.num) + }, + objectMode: true, + }) + ) + .pipe( + concat(function (result) { + const total = result.reduce(function (prev, cur) { + return prev + cur + }) + assert.equal(total, 20100) + }) + ) + stream.on('end', done) + }) +}) diff --git a/packages/pg-query-stream/test/config.ts b/packages/pg-query-stream/test/config.ts new file mode 100644 index 0000000..024b3d1 --- /dev/null +++ b/packages/pg-query-stream/test/config.ts @@ -0,0 +1,26 @@ +import assert from 'assert' +import QueryStream from '../src' + +describe('stream config options', () => { + // this is mostly for backwards compatibility. + it('sets readable.highWaterMark based on batch size', () => { + const stream = new QueryStream('SELECT NOW()', [], { + batchSize: 88, + }) + assert.equal(stream.readableHighWaterMark, 88) + }) + + it('sets readable.highWaterMark based on highWaterMark config', () => { + const stream = new QueryStream('SELECT NOW()', [], { + highWaterMark: 88, + }) + + assert.equal(stream.readableHighWaterMark, 88) + }) + + it('defaults to 100 for highWaterMark', () => { + const stream = new QueryStream('SELECT NOW()', []) + + assert.equal(stream.readableHighWaterMark, 100) + }) +}) diff --git a/packages/pg-query-stream/test/empty-query.ts b/packages/pg-query-stream/test/empty-query.ts new file mode 100644 index 0000000..68f137f --- /dev/null +++ b/packages/pg-query-stream/test/empty-query.ts @@ -0,0 +1,21 @@ +import helper from './helper' +import QueryStream from '../src' + +helper('empty-query', function (client) { + it('handles empty query', function (done) { + const stream = new QueryStream('-- this is a comment', []) + const query = client.query(stream) + query + .on('end', function () { + // nothing should happen for empty query + done() + }) + .on('data', function () { + // noop to kick off reading + }) + }) + + it('continues to function after stream', function (done) { + client.query('SELECT NOW()', done) + }) +}) diff --git a/packages/pg-query-stream/test/error.ts b/packages/pg-query-stream/test/error.ts new file mode 100644 index 0000000..220a524 --- /dev/null +++ b/packages/pg-query-stream/test/error.ts @@ -0,0 +1,92 @@ +import assert from 'assert' +import helper from './helper' +import QueryStream from '../src' +import { Pool, Client } from 'pg' + +helper('error', function (client) { + it('receives error on stream', function (done) { + const stream = new QueryStream('SELECT * FROM asdf num', []) + const query = client.query(stream) + query + .on('error', function (err) { + assert(err) + assert.equal(err.code, '42P01') + done() + }) + .on('data', function () { + // noop to kick of reading + }) + }) + + it('continues to function after stream', function (done) { + client.query('SELECT NOW()', done) + }) +}) + +describe('error recovery', () => { + // created from https://github.com/chrisdickinson/pg-test-case + it('recovers from a streaming error in a transaction', async () => { + const pool = new Pool() + const client = await pool.connect() + await client.query(`CREATE TEMP TABLE frobnicators ( + id serial primary key, + updated timestamp + )`) + await client.query(`BEGIN;`) + const query = new QueryStream(`INSERT INTO frobnicators ("updated") VALUES ($1) RETURNING "id"`, [Date.now()]) + let error: Error | undefined = undefined + query.on('data', console.log).on('error', (e) => { + error = e + }) + client.query(query) // useless callback necessitated by an older version of honeycomb-beeline + + await client.query(`ROLLBACK`) + assert(error, 'Error should not be undefined') + const { rows } = await client.query('SELECT NOW()') + assert.strictEqual(rows.length, 1) + client.release() + const client2 = await pool.connect() + await client2.query(`BEGIN`) + client2.release() + pool.end() + }) + + // created from https://github.com/brianc/node-postgres/pull/2333 + it('handles an error on a stream after a plain text non-stream error', async () => { + const client = new Client() + const stmt = 'SELECT * FROM goose;' + await client.connect() + return new Promise((resolve, reject) => { + client.query(stmt).catch((e) => { + assert(e, 'Query should have rejected with an error') + const stream = new QueryStream('SELECT * FROM duck') + client.query(stream) + stream.on('data', () => {}) + stream.on('error', () => { + client.end((err) => { + err ? reject(err) : resolve() + }) + }) + }) + }) + }) + + it('does not crash when closing a connection with a queued stream', async () => { + const client = new Client() + const stmt = 'SELECT * FROM goose;' + await client.connect() + return new Promise(async (resolve) => { + let queryError: Error | undefined + client.query(stmt).catch((e) => { + queryError = e + }) + const stream = client.query(new QueryStream(stmt)) + stream.on('data', () => {}) + stream.on('error', () => { + assert(queryError, 'query should have errored due to client ending') + resolve() + }) + await client.end() + }) + }) +}) diff --git a/packages/pg-query-stream/test/fast-reader.ts b/packages/pg-query-stream/test/fast-reader.ts new file mode 100644 index 0000000..5c0c021 --- /dev/null +++ b/packages/pg-query-stream/test/fast-reader.ts @@ -0,0 +1,35 @@ +import assert from 'assert' +import helper from './helper' +import QueryStream from '../src' + +helper('fast reader', function (client) { + it('works', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', []) + const query = client.query(stream) + const result = [] + stream.on('readable', function () { + let res = stream.read() + while (res) { + if (result.length !== 201) { + assert(res, 'should not return null on evented reader') + } else { + // a readable stream will emit a null datum when it finishes being readable + // https://nodejs.org/api/stream.html#stream_event_readable + assert.equal(res, null) + } + if (res) { + result.push(res.num) + } + res = stream.read() + } + }) + stream.on('end', function () { + const total = result.reduce(function (prev, cur) { + return prev + cur + }) + assert.equal(total, 20100) + done() + }) + assert.strictEqual(query.read(2), null) + }) +}) diff --git a/packages/pg-query-stream/test/helper.ts b/packages/pg-query-stream/test/helper.ts new file mode 100644 index 0000000..9e9b63a --- /dev/null +++ b/packages/pg-query-stream/test/helper.ts @@ -0,0 +1,18 @@ +import pg from 'pg' + +export default function (name, cb) { + describe(name, function () { + const client = new pg.Client() + + before(function (done) { + client.connect(done) + }) + + cb(client) + + after(function (done) { + client.end() + client.on('end', done) + }) + }) +} diff --git a/packages/pg-query-stream/test/instant.ts b/packages/pg-query-stream/test/instant.ts new file mode 100644 index 0000000..da4fcad --- /dev/null +++ b/packages/pg-query-stream/test/instant.ts @@ -0,0 +1,17 @@ +import helper from './helper' +import assert from 'assert' +import concat from 'concat-stream' +import QueryStream from '../src' + +helper('instant', function (client) { + it('instant', function (done) { + const query = new QueryStream('SELECT pg_sleep(1)', []) + const stream = client.query(query) + stream.pipe( + concat(function (res) { + assert.equal(res.length, 1) + done() + }) + ) + }) +}) diff --git a/packages/pg-query-stream/test/issue-3.ts b/packages/pg-query-stream/test/issue-3.ts new file mode 100644 index 0000000..8c2c044 --- /dev/null +++ b/packages/pg-query-stream/test/issue-3.ts @@ -0,0 +1,33 @@ +import pg from 'pg' +import QueryStream from '../src' + +describe('end semantics race condition', function () { + before(function (done) { + const client = new pg.Client() + client.connect() + client.on('drain', client.end.bind(client)) + client.on('end', done) + client.query('create table IF NOT EXISTS p(id serial primary key)') + client.query('create table IF NOT EXISTS c(id int primary key references p)') + }) + it('works', function (done) { + const client1 = new pg.Client() + client1.connect() + const client2 = new pg.Client() + client2.connect() + + const qr = new QueryStream('INSERT INTO p DEFAULT VALUES RETURNING id') + client1.query(qr) + let id = null + qr.on('data', function (row) { + id = row.id + }) + qr.on('end', function () { + client2.query('INSERT INTO c(id) VALUES ($1)', [id], function (err, rows) { + client1.end() + client2.end() + done(err) + }) + }) + }) +}) diff --git a/packages/pg-query-stream/test/passing-options.ts b/packages/pg-query-stream/test/passing-options.ts new file mode 100644 index 0000000..7aa924a --- /dev/null +++ b/packages/pg-query-stream/test/passing-options.ts @@ -0,0 +1,38 @@ +import assert from 'assert' +import helper from './helper' +import QueryStream from '../src' + +helper('passing options', function (client) { + it('passes row mode array', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { rowMode: 'array' }) + const query = client.query(stream) + const result = [] + query.on('data', (datum) => { + result.push(datum) + }) + query.on('end', () => { + const expected = new Array(11).fill(0).map((_, i) => [i]) + assert.deepEqual(result, expected) + done() + }) + }) + + it('passes custom types', function (done) { + const types = { + getTypeParser: () => (string) => string, + } + const stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { types }) + const query = client.query(stream) + const result = [] + query.on('data', (datum) => { + result.push(datum) + }) + query.on('end', () => { + const expected = new Array(11).fill(0).map((_, i) => ({ + num: i.toString(), + })) + assert.deepEqual(result, expected) + done() + }) + }) +}) diff --git a/packages/pg-query-stream/test/pauses.ts b/packages/pg-query-stream/test/pauses.ts new file mode 100644 index 0000000..daf8347 --- /dev/null +++ b/packages/pg-query-stream/test/pauses.ts @@ -0,0 +1,26 @@ +import helper from './helper' +import concat from 'concat-stream' +import tester from 'stream-tester' +import JSONStream from 'JSONStream' +import QueryStream from '../src' + +helper('pauses', function (client) { + it('pauses', function (done) { + this.timeout(5000) + const stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [200], { + batchSize: 2, + highWaterMark: 2, + }) + const query = client.query(stream) + const pauser = tester.createPauseStream(0.1, 100) + query + .pipe(JSONStream.stringify()) + .pipe(pauser) + .pipe( + concat(function (json) { + JSON.parse(json) + done() + }) + ) + }) +}) diff --git a/packages/pg-query-stream/test/slow-reader.ts b/packages/pg-query-stream/test/slow-reader.ts new file mode 100644 index 0000000..a62c0c2 --- /dev/null +++ b/packages/pg-query-stream/test/slow-reader.ts @@ -0,0 +1,31 @@ +import helper from './helper' +import QueryStream from '../src' +import concat from 'concat-stream' + +import { Transform } from 'stream' + +const mapper = new Transform({ objectMode: true }) + +mapper._transform = function (obj, enc, cb) { + this.push(obj) + setTimeout(cb, 5) +} + +helper('slow reader', function (client) { + it('works', function (done) { + this.timeout(50000) + const stream = new QueryStream('SELECT * FROM generate_series(0, 201) num', [], { + highWaterMark: 100, + batchSize: 50, + }) + stream.on('end', function () { + // console.log('stream end') + }) + client.query(stream) + stream.pipe(mapper).pipe( + concat(function (res) { + done() + }) + ) + }) +}) diff --git a/packages/pg-query-stream/test/stream-tester-timestamp.ts b/packages/pg-query-stream/test/stream-tester-timestamp.ts new file mode 100644 index 0000000..9819ba4 --- /dev/null +++ b/packages/pg-query-stream/test/stream-tester-timestamp.ts @@ -0,0 +1,26 @@ +import helper from './helper' +import QueryStream from '../src' +import spec from 'stream-spec' +import assert from 'assert' + +helper('stream tester timestamp', function (client) { + it('should not warn about max listeners', function (done) { + const sql = "SELECT * FROM generate_series('1983-12-30 00:00'::timestamp, '2013-12-30 00:00', '1 years')" + const stream = new QueryStream(sql, []) + let ended = false + const query = client.query(stream) + query.on('end', function () { + ended = true + }) + spec(query).readable().pausable({ strict: true }).validateOnExit() + const checkListeners = function () { + assert(stream.listeners('end').length < 10) + if (!ended) { + setImmediate(checkListeners) + } else { + done() + } + } + checkListeners() + }) +}) diff --git a/packages/pg-query-stream/test/stream-tester.ts b/packages/pg-query-stream/test/stream-tester.ts new file mode 100644 index 0000000..01c6827 --- /dev/null +++ b/packages/pg-query-stream/test/stream-tester.ts @@ -0,0 +1,12 @@ +import spec from 'stream-spec' +import helper from './helper' +import QueryStream from '../src' + +helper('stream tester', function (client) { + it('passes stream spec', function (done) { + const stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', []) + const query = client.query(stream) + spec(query).readable().pausable({ strict: true }).validateOnExit() + stream.on('end', done) + }) +}) diff --git a/packages/pg-query-stream/tsconfig.json b/packages/pg-query-stream/tsconfig.json new file mode 100644 index 0000000..15b962d --- /dev/null +++ b/packages/pg-query-stream/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": false, + "target": "es6", + "noImplicitAny": false, + "moduleResolution": "node", + "sourceMap": true, + "pretty": true, + "outDir": "dist", + "incremental": true, + "baseUrl": ".", + "declaration": true, + "types": [ + "node", + "pg", + "mocha", + "chai" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/pg/Makefile b/packages/pg/Makefile new file mode 100644 index 0000000..e05edbf --- /dev/null +++ b/packages/pg/Makefile @@ -0,0 +1,61 @@ +SHELL := /bin/sh + +connectionString=postgres:// + +params := $(connectionString) + +node-command := xargs -n 1 -I file node file $(params) + +.PHONY : test test-connection test-integration bench test-native \ + publish test-missing-native update-npm + +all: + npm install + +help: + @echo "make test-all [connectionString=postgres://]" + +test: test-unit + +test-all: test-missing-native test-unit test-integration test-native + + +update-npm: + @npm i npm --global + +bench: + @find benchmark -name "*-bench.js" | $(node-command) + +test-unit: + @find test/unit -name "*-tests.js" | $(node-command) + +test-connection: + @echo "***Testing connection***" + @node script/create-test-tables.js $(params) + +test-missing-native: + @echo "***Testing optional native install***" + @rm -rf node_modules/pg-native + @rm -rf node_modules/libpq + @node test/native/missing-native.js + @rm -rf node_modules/pg-native + @rm -rf node_modules/libpq + +node_modules/pg-native/index.js: + @npm i --no-save pg-native + +test-native: node_modules/pg-native/index.js test-connection + @echo "***Testing native bindings***" + @find test/native -name "*-tests.js" | $(node-command) + @find test/integration -name "*-tests.js" | $(node-command) native + +test-integration: test-connection + @echo "***Testing Pure Javascript***" + @find test/integration -name "*-tests.js" | $(node-command) + +test-binary: test-connection + @echo "***Testing Pure Javascript (binary)***" + @find test/integration -name "*-tests.js" | $(node-command) binary + +test-pool: + @find test/integration/connection-pool -name "*.js" | $(node-command) binary diff --git a/packages/pg/bench.js b/packages/pg/bench.js new file mode 100644 index 0000000..5cb42ac --- /dev/null +++ b/packages/pg/bench.js @@ -0,0 +1,87 @@ +const pg = require('./lib') + +const params = { + text: + 'select typname, typnamespace, typowner, typlen, typbyval, typcategory, typispreferred, typisdefined, typdelim, typrelid, typelem, typarray from pg_type where typtypmod = $1 and typisdefined = $2', + values: [-1, true], +} + +const insert = { + text: 'INSERT INTO foobar(name, age) VALUES ($1, $2)', + values: ['brian', 100], +} + +const seq = { + text: 'SELECT * FROM generate_series(1, 1000)', +} + +const exec = async (client, q) => { + await client.query({ + text: q.text, + values: q.values, + rowMode: 'array', + }) +} + +const bench = async (client, q, time) => { + let start = Date.now() + let count = 0 + while (true) { + await exec(client, q) + count++ + if (Date.now() - start > time) { + return count + } + } +} + +const run = async () => { + const client = new pg.Client() + await client.connect() + console.log('start') + await client.query('CREATE TEMP TABLE foobar(name TEXT, age NUMERIC)') + await client.query('CREATE TEMP TABLE buf(name TEXT, data BYTEA)') + await bench(client, params, 1000) + console.log('warmup done') + const seconds = 5 + + for (let i = 0; i < 4; i++) { + let queries = await bench(client, params, seconds * 1000) + console.log('') + console.log('little queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 733 qps') + + console.log('') + queries = await bench(client, seq, seconds * 1000) + console.log('sequence queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 1309 qps') + + console.log('') + queries = await bench(client, insert, seconds * 1000) + console.log('insert queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 6445 qps') + + console.log('') + console.log('Warming up bytea test') + await client.query({ + text: 'INSERT INTO buf(name, data) VALUES ($1, $2)', + values: ['test', Buffer.allocUnsafe(104857600)], + }) + console.log('bytea warmup done') + const start = Date.now() + const results = await client.query('SELECT * FROM buf') + const time = Date.now() - start + console.log('bytea time:', time, 'ms') + console.log('bytea length:', results.rows[0].data.byteLength, 'bytes') + console.log('on my laptop best so far seen 1107ms and 104857600 bytes') + await new Promise((resolve) => setTimeout(resolve, 250)) + } + + await client.end() + await client.end() +} + +run().catch((e) => console.error(e) || process.exit(-1)) diff --git a/packages/pg/lib/client.js b/packages/pg/lib/client.js new file mode 100644 index 0000000..1660027 --- /dev/null +++ b/packages/pg/lib/client.js @@ -0,0 +1,623 @@ +'use strict' + +var EventEmitter = require('events').EventEmitter +var util = require('util') +var utils = require('./utils') +var sasl = require('./sasl') +var rfc5802 = require('./rfc5802') +var pgPass = require('pgpass') +var TypeOverrides = require('./type-overrides') + +var ConnectionParameters = require('./connection-parameters') +var Query = require('./query') +var defaults = require('./defaults') +var Connection = require('./connection') + +class Client extends EventEmitter { + constructor(config) { + super() + + this.connectionParameters = new ConnectionParameters(config) + this.user = this.connectionParameters.user + this.database = this.connectionParameters.database + this.port = this.connectionParameters.port + this.host = this.connectionParameters.host + + // "hiding" the password so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this, 'password', { + configurable: true, + enumerable: false, + writable: true, + value: this.connectionParameters.password, + }) + + this.replication = this.connectionParameters.replication + + var c = config || {} + + this._Promise = c.Promise || global.Promise + this._types = new TypeOverrides(c.types) + this._ending = false + this._connecting = false + this._connected = false + this._connectionError = false + this._queryable = true + + this.connection = + c.connection || + new Connection({ + stream: c.stream, + ssl: this.connectionParameters.ssl, + keepAlive: c.keepAlive || false, + keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0, + encoding: this.connectionParameters.client_encoding || 'utf8', + }) + this.queryQueue = [] + this.binary = c.binary || defaults.binary + this.processID = null + this.secretKey = null + this.ssl = this.connectionParameters.ssl || false + // As with Password, make SSL->Key (the private key) non-enumerable. + // It won't show up in stack traces + // or if the client is console.logged + if (this.ssl && this.ssl.key) { + Object.defineProperty(this.ssl, 'key', { + enumerable: false, + }) + } + + this._connectionTimeoutMillis = c.connectionTimeoutMillis || 0 + } + + _errorAllQueries(err) { + const enqueueError = (query) => { + process.nextTick(() => { + query.handleError(err, this.connection) + }) + } + + if (this.activeQuery) { + enqueueError(this.activeQuery) + this.activeQuery = null + } + + this.queryQueue.forEach(enqueueError) + this.queryQueue.length = 0 + } + + _connect(callback) { + var self = this + var con = this.connection + this._connectionCallback = callback + + if (this._connecting || this._connected) { + const err = new Error('Client has already been connected. You cannot reuse a client.') + process.nextTick(() => { + callback(err) + }) + return + } + this._connecting = true + + this.connectionTimeoutHandle + if (this._connectionTimeoutMillis > 0) { + this.connectionTimeoutHandle = setTimeout(() => { + con._ending = true + con.stream.destroy(new Error('timeout expired')) + }, this._connectionTimeoutMillis) + } + + if (this.host && this.host.indexOf('/') === 0) { + con.connect(this.host + '/.s.PGSQL.' + this.port) + } else { + con.connect(this.port, this.host) + } + + // once connection is established send startup message + con.on('connect', function () { + if (self.ssl) { + con.requestSsl() + } else { + con.startup(self.getStartupConf()) + } + }) + + con.on('sslconnect', function () { + con.startup(self.getStartupConf()) + }) + + this._attachListeners(con) + + con.once('end', () => { + const error = this._ending ? new Error('Connection terminated') : new Error('Connection terminated unexpectedly') + + clearTimeout(this.connectionTimeoutHandle) + this._errorAllQueries(error) + + if (!this._ending) { + // if the connection is ended without us calling .end() + // on this client then we have an unexpected disconnection + // treat this as an error unless we've already emitted an error + // during connection. + if (this._connecting && !this._connectionError) { + if (this._connectionCallback) { + this._connectionCallback(error) + } else { + this._handleErrorEvent(error) + } + } else if (!this._connectionError) { + this._handleErrorEvent(error) + } + } + + process.nextTick(() => { + this.emit('end') + }) + }) + } + + connect(callback) { + if (callback) { + this._connect(callback) + return + } + + return new this._Promise((resolve, reject) => { + this._connect((error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) + }) + } + + _attachListeners(con) { + // password request handling + con.on('authenticationCleartextPassword', this._handleAuthCleartextPassword.bind(this)) + // password request handling + con.on('authenticationMD5Password', this._handleAuthMD5Password.bind(this)) + // password request handling + con.on('authenticationSHA256Password', this._handleAuthSHA256Password.bind(this)) + // password request handling (SASL) + con.on('authenticationSASL', this._handleAuthSASL.bind(this)) + con.on('authenticationSASLContinue', this._handleAuthSASLContinue.bind(this)) + con.on('authenticationSASLFinal', this._handleAuthSASLFinal.bind(this)) + con.on('backendKeyData', this._handleBackendKeyData.bind(this)) + con.on('error', this._handleErrorEvent.bind(this)) + con.on('errorMessage', this._handleErrorMessage.bind(this)) + con.on('readyForQuery', this._handleReadyForQuery.bind(this)) + con.on('notice', this._handleNotice.bind(this)) + con.on('rowDescription', this._handleRowDescription.bind(this)) + con.on('dataRow', this._handleDataRow.bind(this)) + con.on('portalSuspended', this._handlePortalSuspended.bind(this)) + con.on('emptyQuery', this._handleEmptyQuery.bind(this)) + con.on('commandComplete', this._handleCommandComplete.bind(this)) + con.on('parseComplete', this._handleParseComplete.bind(this)) + con.on('copyInResponse', this._handleCopyInResponse.bind(this)) + con.on('copyData', this._handleCopyData.bind(this)) + con.on('notification', this._handleNotification.bind(this)) + } + + // TODO(bmc): deprecate pgpass "built in" integration since this.password can be a function + // it can be supplied by the user if required - this is a breaking change! + _checkPgPass(cb) { + const con = this.connection + if (typeof this.password === 'function') { + this._Promise + .resolve() + .then(() => this.password()) + .then((pass) => { + if (pass !== undefined) { + if (typeof pass !== 'string') { + con.emit('error', new TypeError('Password must be a string')) + return + } + this.connectionParameters.password = this.password = pass + } else { + this.connectionParameters.password = this.password = null + } + cb() + }) + .catch((err) => { + con.emit('error', err) + }) + } else if (this.password !== null) { + cb() + } else { + pgPass(this.connectionParameters, (pass) => { + if (undefined !== pass) { + this.connectionParameters.password = this.password = pass + } + cb() + }) + } + } + + _handleAuthCleartextPassword(msg) { + this._checkPgPass(() => { + this.connection.password(this.password) + }) + } + + _handleAuthMD5Password(msg) { + this._checkPgPass(() => { + const hashedPassword = utils.postgresMd5PasswordHash(this.user, this.password, msg.salt) + this.connection.password(hashedPassword) + }) + } + + _handleAuthSHA256Password(msg) { + this._checkPgPass(() => { + const hashedPassword = rfc5802.postgresSha256PasswordHash(this.password, msg.random64code, msg.token, msg.server_iteration, msg.isSM3); + this.connection.password(hashedPassword) + }) + } + + _handleAuthSASL(msg) { + this._checkPgPass(() => { + this.saslSession = sasl.startSession(msg.mechanisms) + this.connection.sendSASLInitialResponseMessage(this.saslSession.mechanism, this.saslSession.response) + }) + } + + _handleAuthSASLContinue(msg) { + sasl.continueSession(this.saslSession, this.password, msg.data) + this.connection.sendSCRAMClientFinalMessage(this.saslSession.response) + } + + _handleAuthSASLFinal(msg) { + sasl.finalizeSession(this.saslSession, msg.data) + this.saslSession = null + } + + _handleBackendKeyData(msg) { + this.processID = msg.processID + this.secretKey = msg.secretKey + } + + _handleReadyForQuery(msg) { + if (this._connecting) { + this._connecting = false + this._connected = true + clearTimeout(this.connectionTimeoutHandle) + + // process possible callback argument to Client#connect + if (this._connectionCallback) { + this._connectionCallback(null, this) + // remove callback for proper error handling + // after the connect event + this._connectionCallback = null + } + this.emit('connect') + } + const { activeQuery } = this + this.activeQuery = null + this.readyForQuery = true + if (activeQuery) { + activeQuery.handleReadyForQuery(this.connection) + } + this._pulseQueryQueue() + } + + // if we receieve an error event or error message + // during the connection process we handle it here + _handleErrorWhileConnecting(err) { + if (this._connectionError) { + // TODO(bmc): this is swallowing errors - we shouldn't do this + return + } + this._connectionError = true + clearTimeout(this.connectionTimeoutHandle) + if (this._connectionCallback) { + return this._connectionCallback(err) + } + this.emit('error', err) + } + + // if we're connected and we receive an error event from the connection + // this means the socket is dead - do a hard abort of all queries and emit + // the socket error on the client as well + _handleErrorEvent(err) { + if (this._connecting) { + return this._handleErrorWhileConnecting(err) + } + this._queryable = false + this._errorAllQueries(err) + this.emit('error', err) + } + + // handle error messages from the postgres backend + _handleErrorMessage(msg) { + if (this._connecting) { + return this._handleErrorWhileConnecting(msg) + } + const activeQuery = this.activeQuery + + if (!activeQuery) { + this._handleErrorEvent(msg) + return + } + + this.activeQuery = null + activeQuery.handleError(msg, this.connection) + } + + _handleRowDescription(msg) { + // delegate rowDescription to active query + this.activeQuery.handleRowDescription(msg) + } + + _handleDataRow(msg) { + // delegate dataRow to active query + this.activeQuery.handleDataRow(msg) + } + + _handlePortalSuspended(msg) { + // delegate portalSuspended to active query + this.activeQuery.handlePortalSuspended(this.connection) + } + + _handleEmptyQuery(msg) { + // delegate emptyQuery to active query + this.activeQuery.handleEmptyQuery(this.connection) + } + + _handleCommandComplete(msg) { + // delegate commandComplete to active query + this.activeQuery.handleCommandComplete(msg, this.connection) + } + + _handleParseComplete(msg) { + // if a prepared statement has a name and properly parses + // we track that its already been executed so we don't parse + // it again on the same client + if (this.activeQuery.name) { + this.connection.parsedStatements[this.activeQuery.name] = this.activeQuery.text + } + } + + _handleCopyInResponse(msg) { + this.activeQuery.handleCopyInResponse(this.connection) + } + + _handleCopyData(msg) { + this.activeQuery.handleCopyData(msg, this.connection) + } + + _handleNotification(msg) { + this.emit('notification', msg) + } + + _handleNotice(msg) { + this.emit('notice', msg) + } + + getStartupConf() { + var params = this.connectionParameters + + var data = { + user: params.user, + database: params.database, + } + + var appName = params.application_name || params.fallback_application_name + if (appName) { + data.application_name = appName + } + if (params.replication) { + data.replication = '' + params.replication + } + if (params.statement_timeout) { + data.statement_timeout = String(parseInt(params.statement_timeout, 10)) + } + if (params.idle_in_transaction_session_timeout) { + data.idle_in_transaction_session_timeout = String(parseInt(params.idle_in_transaction_session_timeout, 10)) + } + if (params.options) { + data.options = params.options + } + + return data + } + + cancel(client, query) { + if (client.activeQuery === query) { + var con = this.connection + + if (this.host && this.host.indexOf('/') === 0) { + con.connect(this.host + '/.s.PGSQL.' + this.port) + } else { + con.connect(this.port, this.host) + } + + // once connection is established send cancel message + con.on('connect', function () { + con.cancel(client.processID, client.secretKey) + }) + } else if (client.queryQueue.indexOf(query) !== -1) { + client.queryQueue.splice(client.queryQueue.indexOf(query), 1) + } + } + + setTypeParser(oid, format, parseFn) { + return this._types.setTypeParser(oid, format, parseFn) + } + + getTypeParser(oid, format) { + return this._types.getTypeParser(oid, format) + } + + // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c + escapeIdentifier(str) { + return '"' + str.replace(/"/g, '""') + '"' + } + + // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c + escapeLiteral(str) { + var hasBackslash = false + var escaped = "'" + + for (var i = 0; i < str.length; i++) { + var c = str[i] + if (c === "'") { + escaped += c + c + } else if (c === '\\') { + escaped += c + c + hasBackslash = true + } else { + escaped += c + } + } + + escaped += "'" + + if (hasBackslash === true) { + escaped = ' E' + escaped + } + + return escaped + } + + _pulseQueryQueue() { + if (this.readyForQuery === true) { + this.activeQuery = this.queryQueue.shift() + if (this.activeQuery) { + this.readyForQuery = false + this.hasExecuted = true + + const queryError = this.activeQuery.submit(this.connection) + if (queryError) { + process.nextTick(() => { + this.activeQuery.handleError(queryError, this.connection) + this.readyForQuery = true + this._pulseQueryQueue() + }) + } + } else if (this.hasExecuted) { + this.activeQuery = null + this.emit('drain') + } + } + } + + query(config, values, callback) { + // can take in strings, config object or query object + var query + var result + var readTimeout + var readTimeoutTimer + var queryCallback + + if (config === null || config === undefined) { + throw new TypeError('Client was passed a null or undefined query') + } else if (typeof config.submit === 'function') { + readTimeout = config.query_timeout || this.connectionParameters.query_timeout + result = query = config + if (typeof values === 'function') { + query.callback = query.callback || values + } + } else { + readTimeout = this.connectionParameters.query_timeout + query = new Query(config, values, callback) + if (!query.callback) { + result = new this._Promise((resolve, reject) => { + query.callback = (err, res) => (err ? reject(err) : resolve(res)) + }) + } + } + + if (readTimeout) { + queryCallback = query.callback + + readTimeoutTimer = setTimeout(() => { + var error = new Error('Query read timeout') + + process.nextTick(() => { + query.handleError(error, this.connection) + }) + + queryCallback(error) + + // we already returned an error, + // just do nothing if query completes + query.callback = () => {} + + // Remove from queue + var index = this.queryQueue.indexOf(query) + if (index > -1) { + this.queryQueue.splice(index, 1) + } + + this._pulseQueryQueue() + }, readTimeout) + + query.callback = (err, res) => { + clearTimeout(readTimeoutTimer) + queryCallback(err, res) + } + } + + if (this.binary && !query.binary) { + query.binary = true + } + + if (query._result && !query._result._types) { + query._result._types = this._types + } + + if (!this._queryable) { + process.nextTick(() => { + query.handleError(new Error('Client has encountered a connection error and is not queryable'), this.connection) + }) + return result + } + + if (this._ending) { + process.nextTick(() => { + query.handleError(new Error('Client was closed and is not queryable'), this.connection) + }) + return result + } + + this.queryQueue.push(query) + this._pulseQueryQueue() + return result + } + + end(cb) { + this._ending = true + + // if we have never connected, then end is a noop, callback immediately + if (!this.connection._connecting) { + if (cb) { + cb() + } else { + return this._Promise.resolve() + } + } + + if (this.activeQuery || !this._queryable) { + // if we have an active query we need to force a disconnect + // on the socket - otherwise a hung query could block end forever + this.connection.stream.destroy() + } else { + this.connection.end() + } + + if (cb) { + this.connection.once('end', cb) + } else { + return new this._Promise((resolve) => { + this.connection.once('end', resolve) + }) + } + } +} + +// expose a Query constructor +Client.Query = Query + +module.exports = Client diff --git a/packages/pg/lib/connection-parameters.js b/packages/pg/lib/connection-parameters.js new file mode 100644 index 0000000..165e6d5 --- /dev/null +++ b/packages/pg/lib/connection-parameters.js @@ -0,0 +1,166 @@ +'use strict' + +var dns = require('dns') + +var defaults = require('./defaults') + +var parse = require('pg-connection-string').parse // parses a connection string + +var val = function (key, config, envVar) { + if (envVar === undefined) { + envVar = process.env['PG' + key.toUpperCase()] + } else if (envVar === false) { + // do nothing ... use false + } else { + envVar = process.env[envVar] + } + + return config[key] || envVar || defaults[key] +} + +var readSSLConfigFromEnvironment = function () { + switch (process.env.PGSSLMODE) { + case 'disable': + return false + case 'prefer': + case 'require': + case 'verify-ca': + case 'verify-full': + return true + case 'no-verify': + return { rejectUnauthorized: false } + } + return defaults.ssl +} + +// Convert arg to a string, surround in single quotes, and escape single quotes and backslashes +var quoteParamValue = function (value) { + return "'" + ('' + value).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'" +} + +var add = function (params, config, paramName) { + var value = config[paramName] + if (value !== undefined && value !== null) { + params.push(paramName + '=' + quoteParamValue(value)) + } +} + +class ConnectionParameters { + constructor(config) { + // if a string is passed, it is a raw connection string so we parse it into a config + config = typeof config === 'string' ? parse(config) : config || {} + + // if the config has a connectionString defined, parse IT into the config we use + // this will override other default values with what is stored in connectionString + if (config.connectionString) { + config = Object.assign({}, config, parse(config.connectionString)) + } + + this.user = val('user', config) + this.database = val('database', config) + + if (this.database === undefined) { + this.database = this.user + } + + this.port = parseInt(val('port', config), 10) + this.host = val('host', config) + + // "hiding" the password so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this, 'password', { + configurable: true, + enumerable: false, + writable: true, + value: val('password', config), + }) + + this.binary = val('binary', config) + this.options = val('options', config) + + this.ssl = typeof config.ssl === 'undefined' ? readSSLConfigFromEnvironment() : config.ssl + + if (typeof this.ssl === 'string') { + if (this.ssl === 'true') { + this.ssl = true + } + } + // support passing in ssl=no-verify via connection string + if (this.ssl === 'no-verify') { + this.ssl = { rejectUnauthorized: false } + } + if (this.ssl && this.ssl.key) { + Object.defineProperty(this.ssl, 'key', { + enumerable: false, + }) + } + + this.client_encoding = val('client_encoding', config) + this.replication = val('replication', config) + // a domain socket begins with '/' + this.isDomainSocket = !(this.host || '').indexOf('/') + + this.application_name = val('application_name', config, 'PGAPPNAME') + this.fallback_application_name = val('fallback_application_name', config, false) + this.statement_timeout = val('statement_timeout', config, false) + this.idle_in_transaction_session_timeout = val('idle_in_transaction_session_timeout', config, false) + this.query_timeout = val('query_timeout', config, false) + + if (config.connectionTimeoutMillis === undefined) { + this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0 + } else { + this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000) + } + + if (config.keepAlive === false) { + this.keepalives = 0 + } else if (config.keepAlive === true) { + this.keepalives = 1 + } + + if (typeof config.keepAliveInitialDelayMillis === 'number') { + this.keepalives_idle = Math.floor(config.keepAliveInitialDelayMillis / 1000) + } + } + + getLibpqConnectionString(cb) { + var params = [] + add(params, this, 'user') + add(params, this, 'password') + add(params, this, 'port') + add(params, this, 'application_name') + add(params, this, 'fallback_application_name') + add(params, this, 'connect_timeout') + add(params, this, 'options') + + var ssl = typeof this.ssl === 'object' ? this.ssl : this.ssl ? { sslmode: this.ssl } : {} + add(params, ssl, 'sslmode') + add(params, ssl, 'sslca') + add(params, ssl, 'sslkey') + add(params, ssl, 'sslcert') + add(params, ssl, 'sslrootcert') + + if (this.database) { + params.push('dbname=' + quoteParamValue(this.database)) + } + if (this.replication) { + params.push('replication=' + quoteParamValue(this.replication)) + } + if (this.host) { + params.push('host=' + quoteParamValue(this.host)) + } + if (this.isDomainSocket) { + return cb(null, params.join(' ')) + } + if (this.client_encoding) { + params.push('client_encoding=' + quoteParamValue(this.client_encoding)) + } + dns.lookup(this.host, function (err, address) { + if (err) return cb(err, null) + params.push('hostaddr=' + quoteParamValue(address)) + return cb(null, params.join(' ')) + }) + } +} + +module.exports = ConnectionParameters diff --git a/packages/pg/lib/connection.js b/packages/pg/lib/connection.js new file mode 100644 index 0000000..33bed15 --- /dev/null +++ b/packages/pg/lib/connection.js @@ -0,0 +1,213 @@ +'use strict' + +var net = require('net') +var EventEmitter = require('events').EventEmitter + +const { parse, serialize } = require('../../pg-protocol') + +const flushBuffer = serialize.flush() +const syncBuffer = serialize.sync() +const endBuffer = serialize.end() + +// TODO(bmc) support binary mode at some point +class Connection extends EventEmitter { + constructor(config) { + super() + config = config || {} + this.stream = config.stream || new net.Socket() + this._keepAlive = config.keepAlive + this._keepAliveInitialDelayMillis = config.keepAliveInitialDelayMillis + this.lastBuffer = false + this.parsedStatements = {} + this.ssl = config.ssl || false + this._ending = false + this._emitMessage = false + var self = this + this.on('newListener', function (eventName) { + if (eventName === 'message') { + self._emitMessage = true + } + }) + } + + connect(port, host) { + var self = this + + this._connecting = true + this.stream.setNoDelay(true) + this.stream.connect(port, host) + + this.stream.once('connect', function () { + if (self._keepAlive) { + self.stream.setKeepAlive(true, self._keepAliveInitialDelayMillis) + } + self.emit('connect') + }) + + const reportStreamError = function (error) { + // errors about disconnections should be ignored during disconnect + if (self._ending && (error.code === 'ECONNRESET' || error.code === 'EPIPE')) { + return + } + self.emit('error', error) + } + this.stream.on('error', reportStreamError) + + this.stream.on('close', function () { + self.emit('end') + }) + + if (!this.ssl) { + return this.attachListeners(this.stream) + } + + this.stream.once('data', function (buffer) { + var responseCode = buffer.toString('utf8') + switch (responseCode) { + case 'S': // Server supports SSL connections, continue with a secure connection + break + case 'N': // Server does not support SSL connections + self.stream.end() + return self.emit('error', new Error('The server does not support SSL connections')) + default: + // Any other response byte, including 'E' (ErrorResponse) indicating a server error + self.stream.end() + return self.emit('error', new Error('There was an error establishing an SSL connection')) + } + var tls = require('tls') + const options = { + socket: self.stream, + } + + if (self.ssl !== true) { + Object.assign(options, self.ssl) + + if ('key' in self.ssl) { + options.key = self.ssl.key + } + } + + if (net.isIP(host) === 0) { + options.servername = host + } + try { + self.stream = tls.connect(options) + } catch (err) { + return self.emit('error', err) + } + self.attachListeners(self.stream) + self.stream.on('error', reportStreamError) + + self.emit('sslconnect') + }) + } + + attachListeners(stream) { + stream.on('end', () => { + this.emit('end') + }) + parse(stream, (msg) => { + var eventName = msg.name === 'error' ? 'errorMessage' : msg.name + if (this._emitMessage) { + this.emit('message', msg) + } + this.emit(eventName, msg) + }) + } + + requestSsl() { + this.stream.write(serialize.requestSsl()) + } + + startup(config) { + this.stream.write(serialize.startup(config)) + } + + cancel(processID, secretKey) { + this._send(serialize.cancel(processID, secretKey)) + } + + password(password) { + this._send(serialize.password(password)) + } + + sendSASLInitialResponseMessage(mechanism, initialResponse) { + this._send(serialize.sendSASLInitialResponseMessage(mechanism, initialResponse)) + } + + sendSCRAMClientFinalMessage(additionalData) { + this._send(serialize.sendSCRAMClientFinalMessage(additionalData)) + } + + _send(buffer) { + if (!this.stream.writable) { + return false + } + return this.stream.write(buffer) + } + + query(text) { + this._send(serialize.query(text)) + } + + // send parse message + parse(query) { + this._send(serialize.parse(query)) + } + + // send bind message + bind(config) { + this._send(serialize.bind(config)) + } + + // send execute message + execute(config) { + this._send(serialize.execute(config)) + } + + flush() { + if (this.stream.writable) { + this.stream.write(flushBuffer) + } + } + + sync() { + this._ending = true + this._send(flushBuffer) + this._send(syncBuffer) + } + + end() { + // 0x58 = 'X' + this._ending = true + if (!this._connecting || !this.stream.writable) { + this.stream.end() + return + } + return this.stream.write(endBuffer, () => { + this.stream.end() + }) + } + + close(msg) { + this._send(serialize.close(msg)) + } + + describe(msg) { + this._send(serialize.describe(msg)) + } + + sendCopyFromChunk(chunk) { + this._send(serialize.copyData(chunk)) + } + + endCopyFrom() { + this._send(serialize.copyDone()) + } + + sendCopyFail(msg) { + this._send(serialize.copyFail(msg)) + } +} + +module.exports = Connection diff --git a/packages/pg/lib/defaults.js b/packages/pg/lib/defaults.js new file mode 100644 index 0000000..9384e01 --- /dev/null +++ b/packages/pg/lib/defaults.js @@ -0,0 +1,80 @@ +'use strict' + +module.exports = { + // database host. defaults to localhost + host: 'localhost', + + // database user's name + user: process.platform === 'win32' ? process.env.USERNAME : process.env.USER, + + // name of database to connect + database: undefined, + + // database user's password + password: null, + + // a Postgres connection string to be used instead of setting individual connection items + // NOTE: Setting this value will cause it to override any other value (such as database or user) defined + // in the defaults object. + connectionString: undefined, + + // database port + port: 5432, + + // number of rows to return at a time from a prepared statement's + // portal. 0 will return all rows at once + rows: 0, + + // binary result mode + binary: false, + + // Connection pool options - see https://github.com/brianc/node-pg-pool + + // number of connections to use in connection pool + // 0 will disable connection pooling + max: 10, + + // max milliseconds a client can go unused before it is removed + // from the pool and destroyed + idleTimeoutMillis: 30000, + + client_encoding: '', + + ssl: false, + + application_name: undefined, + + fallback_application_name: undefined, + + options: undefined, + + parseInputDatesAsUTC: false, + + // max milliseconds any query using this connection will execute for before timing out in error. + // false=unlimited + statement_timeout: false, + + // Terminate any session with an open transaction that has been idle for longer than the specified duration in milliseconds + // false=unlimited + idle_in_transaction_session_timeout: false, + + // max milliseconds to wait for query to complete (client side) + query_timeout: false, + + connect_timeout: 0, + + keepalives: 1, + + keepalives_idle: 0, +} + +var pgTypes = require('pg-types') +// save default parsers +var parseBigInteger = pgTypes.getTypeParser(20, 'text') +var parseBigIntegerArray = pgTypes.getTypeParser(1016, 'text') + +// parse int8 so you can get your count values as actual numbers +module.exports.__defineSetter__('parseInt8', function (val) { + pgTypes.setTypeParser(20, 'text', val ? pgTypes.getTypeParser(23, 'text') : parseBigInteger) + pgTypes.setTypeParser(1016, 'text', val ? pgTypes.getTypeParser(1007, 'text') : parseBigIntegerArray) +}) diff --git a/packages/pg/lib/index.js b/packages/pg/lib/index.js new file mode 100644 index 0000000..aec626d --- /dev/null +++ b/packages/pg/lib/index.js @@ -0,0 +1,55 @@ +'use strict' + +var Client = require('./client') +var defaults = require('./defaults') +var Connection = require('./connection') +var Pool = require('../../pg-pool') +const { DatabaseError } = require('../../pg-protocol') + +const poolFactory = (Client) => { + return class BoundPool extends Pool { + constructor(options) { + super(options, Client) + } + } +} + +var PG = function (clientConstructor) { + this.defaults = defaults + this.Client = clientConstructor + this.Query = this.Client.Query + this.Pool = poolFactory(this.Client) + this._pools = [] + this.Connection = Connection + this.types = require('pg-types') + this.DatabaseError = DatabaseError +} + +if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') { + module.exports = new PG(require('./native')) +} else { + module.exports = new PG(Client) + + // lazy require native module...the native module may not have installed + Object.defineProperty(module.exports, 'native', { + configurable: true, + enumerable: false, + get() { + var native = null + try { + native = new PG(require('./native')) + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw err + } + } + + // overwrite module.exports.native so that getter is never called again + Object.defineProperty(module.exports, 'native', { + value: native, + }) + + return native + }, + }) +} diff --git a/packages/pg/lib/native/client.js b/packages/pg/lib/native/client.js new file mode 100644 index 0000000..6cf800d --- /dev/null +++ b/packages/pg/lib/native/client.js @@ -0,0 +1,294 @@ +'use strict' + +// eslint-disable-next-line +var Native = require('pg-native') +var TypeOverrides = require('../type-overrides') +var pkg = require('../../package.json') +var EventEmitter = require('events').EventEmitter +var util = require('util') +var ConnectionParameters = require('../connection-parameters') + +var NativeQuery = require('./query') + +var Client = (module.exports = function (config) { + EventEmitter.call(this) + config = config || {} + + this._Promise = config.Promise || global.Promise + this._types = new TypeOverrides(config.types) + + this.native = new Native({ + types: this._types, + }) + + this._queryQueue = [] + this._ending = false + this._connecting = false + this._connected = false + this._queryable = true + + // keep these on the object for legacy reasons + // for the time being. TODO: deprecate all this jazz + var cp = (this.connectionParameters = new ConnectionParameters(config)) + this.user = cp.user + + // "hiding" the password so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this, 'password', { + configurable: true, + enumerable: false, + writable: true, + value: cp.password, + }) + this.database = cp.database + this.host = cp.host + this.port = cp.port + + // a hash to hold named queries + this.namedQueries = {} +}) + +Client.Query = NativeQuery + +util.inherits(Client, EventEmitter) + +Client.prototype._errorAllQueries = function (err) { + const enqueueError = (query) => { + process.nextTick(() => { + query.native = this.native + query.handleError(err) + }) + } + + if (this._hasActiveQuery()) { + enqueueError(this._activeQuery) + this._activeQuery = null + } + + this._queryQueue.forEach(enqueueError) + this._queryQueue.length = 0 +} + +// connect to the backend +// pass an optional callback to be called once connected +// or with an error if there was a connection error +Client.prototype._connect = function (cb) { + var self = this + + if (this._connecting) { + process.nextTick(() => cb(new Error('Client has already been connected. You cannot reuse a client.'))) + return + } + + this._connecting = true + + this.connectionParameters.getLibpqConnectionString(function (err, conString) { + if (err) return cb(err) + self.native.connect(conString, function (err) { + if (err) { + self.native.end() + return cb(err) + } + + // set internal states to connected + self._connected = true + + // handle connection errors from the native layer + self.native.on('error', function (err) { + self._queryable = false + self._errorAllQueries(err) + self.emit('error', err) + }) + + self.native.on('notification', function (msg) { + self.emit('notification', { + channel: msg.relname, + payload: msg.extra, + }) + }) + + // signal we are connected now + self.emit('connect') + self._pulseQueryQueue(true) + + cb() + }) + }) +} + +Client.prototype.connect = function (callback) { + if (callback) { + this._connect(callback) + return + } + + return new this._Promise((resolve, reject) => { + this._connect((error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) + }) +} + +// send a query to the server +// this method is highly overloaded to take +// 1) string query, optional array of parameters, optional function callback +// 2) object query with { +// string query +// optional array values, +// optional function callback instead of as a separate parameter +// optional string name to name & cache the query plan +// optional string rowMode = 'array' for an array of results +// } +Client.prototype.query = function (config, values, callback) { + var query + var result + var readTimeout + var readTimeoutTimer + var queryCallback + + if (config === null || config === undefined) { + throw new TypeError('Client was passed a null or undefined query') + } else if (typeof config.submit === 'function') { + readTimeout = config.query_timeout || this.connectionParameters.query_timeout + result = query = config + // accept query(new Query(...), (err, res) => { }) style + if (typeof values === 'function') { + config.callback = values + } + } else { + readTimeout = this.connectionParameters.query_timeout + query = new NativeQuery(config, values, callback) + if (!query.callback) { + let resolveOut, rejectOut + result = new this._Promise((resolve, reject) => { + resolveOut = resolve + rejectOut = reject + }) + query.callback = (err, res) => (err ? rejectOut(err) : resolveOut(res)) + } + } + + if (readTimeout) { + queryCallback = query.callback + + readTimeoutTimer = setTimeout(() => { + var error = new Error('Query read timeout') + + process.nextTick(() => { + query.handleError(error, this.connection) + }) + + queryCallback(error) + + // we already returned an error, + // just do nothing if query completes + query.callback = () => {} + + // Remove from queue + var index = this._queryQueue.indexOf(query) + if (index > -1) { + this._queryQueue.splice(index, 1) + } + + this._pulseQueryQueue() + }, readTimeout) + + query.callback = (err, res) => { + clearTimeout(readTimeoutTimer) + queryCallback(err, res) + } + } + + if (!this._queryable) { + query.native = this.native + process.nextTick(() => { + query.handleError(new Error('Client has encountered a connection error and is not queryable')) + }) + return result + } + + if (this._ending) { + query.native = this.native + process.nextTick(() => { + query.handleError(new Error('Client was closed and is not queryable')) + }) + return result + } + + this._queryQueue.push(query) + this._pulseQueryQueue() + return result +} + +// disconnect from the backend server +Client.prototype.end = function (cb) { + var self = this + + this._ending = true + + if (!this._connected) { + this.once('connect', this.end.bind(this, cb)) + } + var result + if (!cb) { + result = new this._Promise(function (resolve, reject) { + cb = (err) => (err ? reject(err) : resolve()) + }) + } + this.native.end(function () { + self._errorAllQueries(new Error('Connection terminated')) + + process.nextTick(() => { + self.emit('end') + if (cb) cb() + }) + }) + return result +} + +Client.prototype._hasActiveQuery = function () { + return this._activeQuery && this._activeQuery.state !== 'error' && this._activeQuery.state !== 'end' +} + +Client.prototype._pulseQueryQueue = function (initialConnection) { + if (!this._connected) { + return + } + if (this._hasActiveQuery()) { + return + } + var query = this._queryQueue.shift() + if (!query) { + if (!initialConnection) { + this.emit('drain') + } + return + } + this._activeQuery = query + query.submit(this) + var self = this + query.once('_done', function () { + self._pulseQueryQueue() + }) +} + +// attempt to cancel an in-progress query +Client.prototype.cancel = function (query) { + if (this._activeQuery === query) { + this.native.cancel(function () {}) + } else if (this._queryQueue.indexOf(query) !== -1) { + this._queryQueue.splice(this._queryQueue.indexOf(query), 1) + } +} + +Client.prototype.setTypeParser = function (oid, format, parseFn) { + return this._types.setTypeParser(oid, format, parseFn) +} + +Client.prototype.getTypeParser = function (oid, format) { + return this._types.getTypeParser(oid, format) +} diff --git a/packages/pg/lib/native/index.js b/packages/pg/lib/native/index.js new file mode 100644 index 0000000..eead422 --- /dev/null +++ b/packages/pg/lib/native/index.js @@ -0,0 +1,2 @@ +'use strict' +module.exports = require('./client') diff --git a/packages/pg/lib/native/query.js b/packages/pg/lib/native/query.js new file mode 100644 index 0000000..d06db43 --- /dev/null +++ b/packages/pg/lib/native/query.js @@ -0,0 +1,165 @@ +'use strict' + +var EventEmitter = require('events').EventEmitter +var util = require('util') +var utils = require('../utils') + +var NativeQuery = (module.exports = function (config, values, callback) { + EventEmitter.call(this) + config = utils.normalizeQueryConfig(config, values, callback) + this.text = config.text + this.values = config.values + this.name = config.name + this.callback = config.callback + this.state = 'new' + this._arrayMode = config.rowMode === 'array' + + // if the 'row' event is listened for + // then emit them as they come in + // without setting singleRowMode to true + // this has almost no meaning because libpq + // reads all rows into memory befor returning any + this._emitRowEvents = false + this.on( + 'newListener', + function (event) { + if (event === 'row') this._emitRowEvents = true + }.bind(this) + ) +}) + +util.inherits(NativeQuery, EventEmitter) + +var errorFieldMap = { + /* eslint-disable quote-props */ + sqlState: 'code', + statementPosition: 'position', + messagePrimary: 'message', + context: 'where', + schemaName: 'schema', + tableName: 'table', + columnName: 'column', + dataTypeName: 'dataType', + constraintName: 'constraint', + sourceFile: 'file', + sourceLine: 'line', + sourceFunction: 'routine', +} + +NativeQuery.prototype.handleError = function (err) { + // copy pq error fields into the error object + var fields = this.native.pq.resultErrorFields() + if (fields) { + for (var key in fields) { + var normalizedFieldName = errorFieldMap[key] || key + err[normalizedFieldName] = fields[key] + } + } + if (this.callback) { + this.callback(err) + } else { + this.emit('error', err) + } + this.state = 'error' +} + +NativeQuery.prototype.then = function (onSuccess, onFailure) { + return this._getPromise().then(onSuccess, onFailure) +} + +NativeQuery.prototype.catch = function (callback) { + return this._getPromise().catch(callback) +} + +NativeQuery.prototype._getPromise = function () { + if (this._promise) return this._promise + this._promise = new Promise( + function (resolve, reject) { + this._once('end', resolve) + this._once('error', reject) + }.bind(this) + ) + return this._promise +} + +NativeQuery.prototype.submit = function (client) { + this.state = 'running' + var self = this + this.native = client.native + client.native.arrayMode = this._arrayMode + + var after = function (err, rows, results) { + client.native.arrayMode = false + setImmediate(function () { + self.emit('_done') + }) + + // handle possible query error + if (err) { + return self.handleError(err) + } + + // emit row events for each row in the result + if (self._emitRowEvents) { + if (results.length > 1) { + rows.forEach((rowOfRows, i) => { + rowOfRows.forEach((row) => { + self.emit('row', row, results[i]) + }) + }) + } else { + rows.forEach(function (row) { + self.emit('row', row, results) + }) + } + } + + // handle successful result + self.state = 'end' + self.emit('end', results) + if (self.callback) { + self.callback(null, results) + } + } + + if (process.domain) { + after = process.domain.bind(after) + } + + // named query + if (this.name) { + if (this.name.length > 63) { + /* eslint-disable no-console */ + console.error('Warning! Postgres only supports 63 characters for query names.') + console.error('You supplied %s (%s)', this.name, this.name.length) + console.error('This can cause conflicts and silent errors executing queries') + /* eslint-enable no-console */ + } + var values = (this.values || []).map(utils.prepareValue) + + // check if the client has already executed this named query + // if so...just execute it again - skip the planning phase + if (client.namedQueries[this.name]) { + if (this.text && client.namedQueries[this.name] !== this.text) { + const err = new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`) + return after(err) + } + return client.native.execute(this.name, values, after) + } + // plan the named query the first time, then execute it + return client.native.prepare(this.name, this.text, values.length, function (err) { + if (err) return after(err) + client.namedQueries[self.name] = self.text + return self.native.execute(self.name, values, after) + }) + } else if (this.values) { + if (!Array.isArray(this.values)) { + const err = new Error('Query values must be an array') + return after(err) + } + var vals = this.values.map(utils.prepareValue) + client.native.query(this.text, vals, after) + } else { + client.native.query(this.text, after) + } +} diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js new file mode 100644 index 0000000..c0dfedd --- /dev/null +++ b/packages/pg/lib/query.js @@ -0,0 +1,234 @@ +'use strict' + +const { EventEmitter } = require('events') + +const Result = require('./result') +const utils = require('./utils') + +class Query extends EventEmitter { + constructor(config, values, callback) { + super() + + config = utils.normalizeQueryConfig(config, values, callback) + + this.text = config.text + this.values = config.values + this.rows = config.rows + this.types = config.types + this.name = config.name + this.binary = config.binary + // use unique portal name each time + this.portal = config.portal || '' + this.callback = config.callback + this._rowMode = config.rowMode + if (process.domain && config.callback) { + this.callback = process.domain.bind(config.callback) + } + this._result = new Result(this._rowMode, this.types) + + // potential for multiple results + this._results = this._result + this.isPreparedStatement = false + this._canceledDueToError = false + this._promise = null + } + + requiresPreparation() { + // named queries must always be prepared + if (this.name) { + return true + } + // always prepare if there are max number of rows expected per + // portal execution + if (this.rows) { + return true + } + // don't prepare empty text queries + if (!this.text) { + return false + } + // prepare if there are values + if (!this.values) { + return false + } + return this.values.length > 0 + } + + _checkForMultirow() { + // if we already have a result with a command property + // then we've already executed one query in a multi-statement simple query + // turn our results into an array of results + if (this._result.command) { + if (!Array.isArray(this._results)) { + this._results = [this._result] + } + this._result = new Result(this._rowMode, this.types) + this._results.push(this._result) + } + } + + // associates row metadata from the supplied + // message with this query object + // metadata used when parsing row results + handleRowDescription(msg) { + this._checkForMultirow() + this._result.addFields(msg.fields) + this._accumulateRows = this.callback || !this.listeners('row').length + } + + handleDataRow(msg) { + let row + + if (this._canceledDueToError) { + return + } + + try { + row = this._result.parseRow(msg.fields) + } catch (err) { + this._canceledDueToError = err + return + } + + this.emit('row', row, this._result) + if (this._accumulateRows) { + this._result.addRow(row) + } + } + + handleCommandComplete(msg, connection) { + this._checkForMultirow() + this._result.addCommandComplete(msg) + // need to sync after each command complete of a prepared statement + // if we were using a row count which results in multiple calls to _getRows + if (this.rows) { + connection.sync() + } + } + + // if a named prepared statement is created with empty query text + // the backend will send an emptyQuery message but *not* a command complete message + // since we pipeline sync immediately after execute we don't need to do anything here + // unless we have rows specified, in which case we did not pipeline the intial sync call + handleEmptyQuery(connection) { + if (this.rows) { + connection.sync() + } + } + + handleError(err, connection) { + // need to sync after error during a prepared statement + if (this._canceledDueToError) { + err = this._canceledDueToError + this._canceledDueToError = false + } + // if callback supplied do not emit error event as uncaught error + // events will bubble up to node process + if (this.callback) { + return this.callback(err) + } + this.emit('error', err) + } + + handleReadyForQuery(con) { + if (this._canceledDueToError) { + return this.handleError(this._canceledDueToError, con) + } + if (this.callback) { + this.callback(null, this._results) + } + this.emit('end', this._results) + } + + submit(connection) { + if (typeof this.text !== 'string' && typeof this.name !== 'string') { + return new Error('A query must have either text or a name. Supplying neither is unsupported.') + } + const previous = connection.parsedStatements[this.name] + if (this.text && previous && this.text !== previous) { + return new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`) + } + if (this.values && !Array.isArray(this.values)) { + return new Error('Query values must be an array') + } + if (this.requiresPreparation()) { + this.prepare(connection) + } else { + connection.query(this.text) + } + return null + } + + hasBeenParsed(connection) { + return this.name && connection.parsedStatements[this.name] + } + + handlePortalSuspended(connection) { + this._getRows(connection, this.rows) + } + + _getRows(connection, rows) { + connection.execute({ + portal: this.portal, + rows: rows, + }) + // if we're not reading pages of rows send the sync command + // to indicate the pipeline is finished + if (!rows) { + connection.sync() + } else { + // otherwise flush the call out to read more rows + connection.flush() + } + } + + // http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY + prepare(connection) { + // prepared statements need sync to be called after each command + // complete or when an error is encountered + this.isPreparedStatement = true + + // TODO refactor this poor encapsulation + if (!this.hasBeenParsed(connection)) { + connection.parse({ + text: this.text, + name: this.name, + types: this.types, + }) + } + + // because we're mapping user supplied values to + // postgres wire protocol compatible values it could + // throw an exception, so try/catch this section + try { + connection.bind({ + portal: this.portal, + statement: this.name, + values: this.values, + binary: this.binary, + valueMapper: utils.prepareValue, + }) + } catch (err) { + this.handleError(err, connection) + return + } + + connection.describe({ + type: 'P', + name: this.portal || '', + }) + + this._getRows(connection, this.rows) + } + + handleCopyInResponse(connection) { + connection.sendCopyFail('No source stream defined') + } + + // eslint-disable-next-line no-unused-vars + handleCopyData(msg, connection) { + // noop + } +} + +module.exports = Query diff --git a/packages/pg/lib/result.js b/packages/pg/lib/result.js new file mode 100644 index 0000000..3506097 --- /dev/null +++ b/packages/pg/lib/result.js @@ -0,0 +1,100 @@ +'use strict' + +var types = require('pg-types') + +var matchRegexp = /^([A-Za-z]+)(?: (\d+))?(?: (\d+))?/ + +// result object returned from query +// in the 'end' event and also +// passed as second argument to provided callback +class Result { + constructor(rowMode, types) { + this.command = null + this.rowCount = null + this.oid = null + this.rows = [] + this.fields = [] + this._parsers = undefined + this._types = types + this.RowCtor = null + this.rowAsArray = rowMode === 'array' + if (this.rowAsArray) { + this.parseRow = this._parseRowAsArray + } + } + + // adds a command complete message + addCommandComplete(msg) { + var match + if (msg.text) { + // pure javascript + match = matchRegexp.exec(msg.text) + } else { + // native bindings + match = matchRegexp.exec(msg.command) + } + if (match) { + this.command = match[1] + if (match[3]) { + // COMMMAND OID ROWS + this.oid = parseInt(match[2], 10) + this.rowCount = parseInt(match[3], 10) + } else if (match[2]) { + // COMMAND ROWS + this.rowCount = parseInt(match[2], 10) + } + } + } + + _parseRowAsArray(rowData) { + var row = new Array(rowData.length) + for (var i = 0, len = rowData.length; i < len; i++) { + var rawValue = rowData[i] + if (rawValue !== null) { + row[i] = this._parsers[i](rawValue) + } else { + row[i] = null + } + } + return row + } + + parseRow(rowData) { + var row = {} + for (var i = 0, len = rowData.length; i < len; i++) { + var rawValue = rowData[i] + var field = this.fields[i].name + if (rawValue !== null) { + row[field] = this._parsers[i](rawValue) + } else { + row[field] = null + } + } + return row + } + + addRow(row) { + this.rows.push(row) + } + + addFields(fieldDescriptions) { + // clears field definitions + // multiple query statements in 1 action can result in multiple sets + // of rowDescriptions...eg: 'select NOW(); select 1::int;' + // you need to reset the fields + this.fields = fieldDescriptions + if (this.fields.length) { + this._parsers = new Array(fieldDescriptions.length) + } + for (var i = 0; i < fieldDescriptions.length; i++) { + var desc = fieldDescriptions[i] + if (this._types) { + this._parsers[i] = this._types.getTypeParser(desc.dataTypeID, desc.format || 'text') + } else { + this._parsers[i] = types.getTypeParser(desc.dataTypeID, desc.format || 'text') + } + } + } +} + +module.exports = Result diff --git a/packages/pg/lib/rfc5802.js b/packages/pg/lib/rfc5802.js new file mode 100644 index 0000000..e6ddf8c --- /dev/null +++ b/packages/pg/lib/rfc5802.js @@ -0,0 +1,59 @@ +'use strict' +const crypto = require('crypto') +const { SM3 } = require('gm-crypto') + +function xorBuffers(a, b) { + if (!Buffer.isBuffer(a)) { + throw new TypeError('first argument must be a Buffer') + } + if (!Buffer.isBuffer(b)) { + throw new TypeError('second argument must be a Buffer') + } + if (a.length !== b.length) { + throw new Error('Buffer lengths must match') + } + if (a.length === 0) { + throw new Error('Buffers cannot be empty') + } + return Buffer.from(a.map((_, i) => a[i] ^ b[i])) +} + +function sha256(text) { + return crypto.createHash('sha256').update(text).digest(); +} + +function hmacSha256(key, msg) { + return crypto.createHmac('sha256', key).update(msg).digest(); +} + +// Generate a Key based on openGauss jdbc driver (PBKDF2WithHmacSHA1) +// org.postgresql.util.MD5Digest.generateKFromPBKDF2 +function generateKeyFromPBKDF2(password, random64code, server_iteration) { + var random32code = Buffer.from(random64code,'hex'); + return crypto.pbkdf2Sync(password, random32code, server_iteration, 32, 'sha1'); +} + +// Deprecated function for convert buffer to hex string +// use buffer.toString('hex') instead +function buf2hex(buffer) { + return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); +} + +// Core function to get the password hash for openGauss +const postgresSha256PasswordHash = function (password, random64code, token, server_iteration, isSM3) { + if (typeof password !== 'string') { + throw new Error('RFC5802-Art_Chen: client password must be a string') + } + + var key = generateKeyFromPBKDF2(password, random64code, server_iteration) + var clientKey = hmacSha256(key, 'Client Key') + var storedKey = isSM3 ? Buffer.from(SM3.digest(clientKey)) : sha256(clientKey); + var hmac_result = hmacSha256(storedKey, Buffer.from(token,'hex')); + var h = xorBuffers(hmac_result, clientKey); + + return h.toString('hex'); // We can use toString instead of our buf2hex +} + +module.exports = { + postgresSha256PasswordHash +} diff --git a/packages/pg/lib/sasl.js b/packages/pg/lib/sasl.js new file mode 100644 index 0000000..c618047 --- /dev/null +++ b/packages/pg/lib/sasl.js @@ -0,0 +1,209 @@ +'use strict' +const crypto = require('crypto') + +function startSession(mechanisms) { + if (mechanisms.indexOf('SCRAM-SHA-256') === -1) { + throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported') + } + + const clientNonce = crypto.randomBytes(18).toString('base64') + + return { + mechanism: 'SCRAM-SHA-256', + clientNonce, + response: 'n,,n=*,r=' + clientNonce, + message: 'SASLInitialResponse', + } +} + +function continueSession(session, password, serverData) { + if (session.message !== 'SASLInitialResponse') { + throw new Error('SASL: Last message was not SASLInitialResponse') + } + if (typeof password !== 'string') { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string') + } + if (typeof serverData !== 'string') { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string') + } + + const sv = parseServerFirstMessage(serverData) + + if (!sv.nonce.startsWith(session.clientNonce)) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce') + } else if (sv.nonce.length === session.clientNonce.length) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short') + } + + var saltBytes = Buffer.from(sv.salt, 'base64') + + var saltedPassword = Hi(password, saltBytes, sv.iteration) + + var clientKey = hmacSha256(saltedPassword, 'Client Key') + var storedKey = sha256(clientKey) + + var clientFirstMessageBare = 'n=*,r=' + session.clientNonce + var serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration + + var clientFinalMessageWithoutProof = 'c=biws,r=' + sv.nonce + + var authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof + + var clientSignature = hmacSha256(storedKey, authMessage) + var clientProofBytes = xorBuffers(clientKey, clientSignature) + var clientProof = clientProofBytes.toString('base64') + + var serverKey = hmacSha256(saltedPassword, 'Server Key') + var serverSignatureBytes = hmacSha256(serverKey, authMessage) + + session.message = 'SASLResponse' + session.serverSignature = serverSignatureBytes.toString('base64') + session.response = clientFinalMessageWithoutProof + ',p=' + clientProof +} + +function finalizeSession(session, serverData) { + if (session.message !== 'SASLResponse') { + throw new Error('SASL: Last message was not SASLResponse') + } + if (typeof serverData !== 'string') { + throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a string') + } + + const { serverSignature } = parseServerFinalMessage(serverData) + + if (serverSignature !== session.serverSignature) { + throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match') + } +} + +/** + * printable = %x21-2B / %x2D-7E + * ;; Printable ASCII except ",". + * ;; Note that any "printable" is also + * ;; a valid "value". + */ +function isPrintableChars(text) { + if (typeof text !== 'string') { + throw new TypeError('SASL: text must be a string') + } + return text + .split('') + .map((_, i) => text.charCodeAt(i)) + .every((c) => (c >= 0x21 && c <= 0x2b) || (c >= 0x2d && c <= 0x7e)) +} + +/** + * base64-char = ALPHA / DIGIT / "/" / "+" + * + * base64-4 = 4base64-char + * + * base64-3 = 3base64-char "=" + * + * base64-2 = 2base64-char "==" + * + * base64 = *base64-4 [base64-3 / base64-2] + */ +function isBase64(text) { + return /^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(text) +} + +function parseAttributePairs(text) { + if (typeof text !== 'string') { + throw new TypeError('SASL: attribute pairs text must be a string') + } + + return new Map( + text.split(',').map((attrValue) => { + if (!/^.=/.test(attrValue)) { + throw new Error('SASL: Invalid attribute pair entry') + } + const name = attrValue[0] + const value = attrValue.substring(2) + return [name, value] + }) + ) +} + +function parseServerFirstMessage(data) { + const attrPairs = parseAttributePairs(data) + + const nonce = attrPairs.get('r') + if (!nonce) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing') + } else if (!isPrintableChars(nonce)) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce must only contain printable characters') + } + const salt = attrPairs.get('s') + if (!salt) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing') + } else if (!isBase64(salt)) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64') + } + const iterationText = attrPairs.get('i') + if (!iterationText) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing') + } else if (!/^[1-9][0-9]*$/.test(iterationText)) { + throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count') + } + const iteration = parseInt(iterationText, 10) + + return { + nonce, + salt, + iteration, + } +} + +function parseServerFinalMessage(serverData) { + const attrPairs = parseAttributePairs(serverData) + const serverSignature = attrPairs.get('v') + if (!serverSignature) { + throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing') + } else if (!isBase64(serverSignature)) { + throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64') + } + return { + serverSignature, + } +} + +function xorBuffers(a, b) { + if (!Buffer.isBuffer(a)) { + throw new TypeError('first argument must be a Buffer') + } + if (!Buffer.isBuffer(b)) { + throw new TypeError('second argument must be a Buffer') + } + if (a.length !== b.length) { + throw new Error('Buffer lengths must match') + } + if (a.length === 0) { + throw new Error('Buffers cannot be empty') + } + return Buffer.from(a.map((_, i) => a[i] ^ b[i])) +} + +function sha256(text) { + return crypto.createHash('sha256').update(text).digest() +} + +function hmacSha256(key, msg) { + return crypto.createHmac('sha256', key).update(msg).digest() +} + +function Hi(password, saltBytes, iterations) { + var ui1 = hmacSha256(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])])) + var ui = ui1 + for (var i = 0; i < iterations - 1; i++) { + ui1 = hmacSha256(password, ui1) + ui = xorBuffers(ui, ui1) + } + + return ui +} + +module.exports = { + startSession, + continueSession, + finalizeSession, +} diff --git a/packages/pg/lib/type-overrides.js b/packages/pg/lib/type-overrides.js new file mode 100644 index 0000000..6669348 --- /dev/null +++ b/packages/pg/lib/type-overrides.js @@ -0,0 +1,35 @@ +'use strict' + +var types = require('pg-types') + +function TypeOverrides(userTypes) { + this._types = userTypes || types + this.text = {} + this.binary = {} +} + +TypeOverrides.prototype.getOverrides = function (format) { + switch (format) { + case 'text': + return this.text + case 'binary': + return this.binary + default: + return {} + } +} + +TypeOverrides.prototype.setTypeParser = function (oid, format, parseFn) { + if (typeof format === 'function') { + parseFn = format + format = 'text' + } + this.getOverrides(format)[oid] = parseFn +} + +TypeOverrides.prototype.getTypeParser = function (oid, format) { + format = format || 'text' + return this.getOverrides(format)[oid] || this._types.getTypeParser(oid, format) +} + +module.exports = TypeOverrides diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js new file mode 100644 index 0000000..d63fe68 --- /dev/null +++ b/packages/pg/lib/utils.js @@ -0,0 +1,187 @@ +'use strict' + +const crypto = require('crypto') + +const defaults = require('./defaults') + +function escapeElement(elementRepresentation) { + var escaped = elementRepresentation.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + + return '"' + escaped + '"' +} + +// convert a JS array to a postgres array literal +// uses comma separator so won't work for types like box that use +// a different array separator. +function arrayString(val) { + var result = '{' + for (var i = 0; i < val.length; i++) { + if (i > 0) { + result = result + ',' + } + if (val[i] === null || typeof val[i] === 'undefined') { + result = result + 'NULL' + } else if (Array.isArray(val[i])) { + result = result + arrayString(val[i]) + } else if (val[i] instanceof Buffer) { + result += '\\\\x' + val[i].toString('hex') + } else { + result += escapeElement(prepareValue(val[i])) + } + } + result = result + '}' + return result +} + +// converts values from javascript types +// to their 'raw' counterparts for use as a postgres parameter +// note: you can override this function to provide your own conversion mechanism +// for complex types, etc... +var prepareValue = function (val, seen) { + // null and undefined are both null for postgres + if (val == null) { + return null + } + if (val instanceof Buffer) { + return val + } + if (ArrayBuffer.isView(val)) { + var buf = Buffer.from(val.buffer, val.byteOffset, val.byteLength) + if (buf.length === val.byteLength) { + return buf + } + return buf.slice(val.byteOffset, val.byteOffset + val.byteLength) // Node.js v4 does not support those Buffer.from params + } + if (val instanceof Date) { + if (defaults.parseInputDatesAsUTC) { + return dateToStringUTC(val) + } else { + return dateToString(val) + } + } + if (Array.isArray(val)) { + return arrayString(val) + } + if (typeof val === 'object') { + return prepareObject(val, seen) + } + return val.toString() +} + +function prepareObject(val, seen) { + if (val && typeof val.toPostgres === 'function') { + seen = seen || [] + if (seen.indexOf(val) !== -1) { + throw new Error('circular reference detected while preparing "' + val + '" for query') + } + seen.push(val) + + return prepareValue(val.toPostgres(prepareValue), seen) + } + return JSON.stringify(val) +} + +function pad(number, digits) { + number = '' + number + while (number.length < digits) { + number = '0' + number + } + return number +} + +function dateToString(date) { + var offset = -date.getTimezoneOffset() + + var year = date.getFullYear() + var isBCYear = year < 1 + if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation + + var ret = + pad(year, 4) + + '-' + + pad(date.getMonth() + 1, 2) + + '-' + + pad(date.getDate(), 2) + + 'T' + + pad(date.getHours(), 2) + + ':' + + pad(date.getMinutes(), 2) + + ':' + + pad(date.getSeconds(), 2) + + '.' + + pad(date.getMilliseconds(), 3) + + if (offset < 0) { + ret += '-' + offset *= -1 + } else { + ret += '+' + } + + ret += pad(Math.floor(offset / 60), 2) + ':' + pad(offset % 60, 2) + if (isBCYear) ret += ' BC' + return ret +} + +function dateToStringUTC(date) { + var year = date.getUTCFullYear() + var isBCYear = year < 1 + if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation + + var ret = + pad(year, 4) + + '-' + + pad(date.getUTCMonth() + 1, 2) + + '-' + + pad(date.getUTCDate(), 2) + + 'T' + + pad(date.getUTCHours(), 2) + + ':' + + pad(date.getUTCMinutes(), 2) + + ':' + + pad(date.getUTCSeconds(), 2) + + '.' + + pad(date.getUTCMilliseconds(), 3) + + ret += '+00:00' + if (isBCYear) ret += ' BC' + return ret +} + +function normalizeQueryConfig(config, values, callback) { + // can take in strings or config objects + config = typeof config === 'string' ? { text: config } : config + if (values) { + if (typeof values === 'function') { + config.callback = values + } else { + config.values = values + } + } + if (callback) { + config.callback = callback + } + return config +} + +const md5 = function (string) { + return crypto.createHash('md5').update(string, 'utf-8').digest('hex') +} + +// See AuthenticationMD5Password at https://www.postgresql.org/docs/current/static/protocol-flow.html +const postgresMd5PasswordHash = function (user, password, salt) { + var inner = md5(password + user) + var outer = md5(Buffer.concat([Buffer.from(inner), salt])) + return 'md5' + outer +} + +module.exports = { + prepareValue: function prepareValueWrapper(value) { + // this ensures that extra arguments do not get passed into prepareValue + // by accident, eg: from calling values.map(utils.prepareValue) + return prepareValue(value) + }, + normalizeQueryConfig, + postgresMd5PasswordHash, + md5, +} diff --git a/packages/pg/package.json b/packages/pg/package.json new file mode 100644 index 0000000..1403323 --- /dev/null +++ b/packages/pg/package.json @@ -0,0 +1,57 @@ +{ + "name": "pg", + "version": "8.6.0", + "description": "PostgreSQL client - pure javascript & libpq with the same API", + "keywords": [ + "database", + "libpq", + "pg", + "postgre", + "postgres", + "postgresql", + "rdbms" + ], + "homepage": "https://github.com/brianc/node-postgres", + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg" + }, + "author": "Brian Carlson ", + "main": "./lib", + "dependencies": { + "buffer-writer": "2.0.0", + "gm-crypto": "^0.1.8", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.3.0", + "pg-protocol": "^1.5.0", + "pg-types": "^2.2.0", + "pgpass": "1.x" + }, + "devDependencies": { + "async": "0.9.0", + "bluebird": "3.5.2", + "co": "4.6.0", + "pg-copy-streams": "0.3.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + }, + "scripts": { + "test": "make test-all" + }, + "files": [ + "lib", + "SPONSORS.md" + ], + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + } +} diff --git a/packages/pg/script/create-test-tables.js b/packages/pg/script/create-test-tables.js new file mode 100644 index 0000000..c7b2ff9 --- /dev/null +++ b/packages/pg/script/create-test-tables.js @@ -0,0 +1,71 @@ +'use strict' +var args = require('../test/cli') +var pg = require('../lib') + +var people = [ + { name: 'Aaron', age: 10 }, + { name: 'Brian', age: 20 }, + { name: 'Chris', age: 30 }, + { name: 'David', age: 40 }, + { name: 'Elvis', age: 50 }, + { name: 'Frank', age: 60 }, + { name: 'Grace', age: 70 }, + { name: 'Haley', age: 80 }, + { name: 'Irma', age: 90 }, + { name: 'Jenny', age: 100 }, + { name: 'Kevin', age: 110 }, + { name: 'Larry', age: 120 }, + { name: 'Michelle', age: 130 }, + { name: 'Nancy', age: 140 }, + { name: 'Olivia', age: 150 }, + { name: 'Peter', age: 160 }, + { name: 'Quinn', age: 170 }, + { name: 'Ronda', age: 180 }, + { name: 'Shelley', age: 190 }, + { name: 'Tobias', age: 200 }, + { name: 'Uma', age: 210 }, + { name: 'Veena', age: 220 }, + { name: 'Wanda', age: 230 }, + { name: 'Xavier', age: 240 }, + { name: 'Yoyo', age: 250 }, + { name: 'Zanzabar', age: 260 }, +] + +var con = new pg.Client({ + host: args.host, + port: args.port, + user: args.user, + password: args.password, + database: args.database, +}) + +con.connect((err) => { + if (err) { + throw err + } + + con.query( + 'DROP TABLE IF EXISTS person;' + ' CREATE TABLE person (id serial, name varchar(10), age integer)', + (err) => { + if (err) { + throw err + } + + console.log('Created table person') + console.log('Filling it with people') + + con.query( + 'INSERT INTO person (name, age) VALUES' + + people.map((person) => ` ('${person.name}', ${person.age})`).join(','), + (err, result) => { + if (err) { + throw err + } + + console.log(`Inserted ${result.rowCount} people`) + con.end() + } + ) + } + ) +}) diff --git a/packages/pg/script/dump-db-types.js b/packages/pg/script/dump-db-types.js new file mode 100644 index 0000000..f762494 --- /dev/null +++ b/packages/pg/script/dump-db-types.js @@ -0,0 +1,18 @@ +'use strict' +var pg = require('../lib') +var args = require('../test/cli') + +var queries = ['select CURRENT_TIMESTAMP', "select interval '1 day' + interval '1 hour'", "select TIMESTAMP 'today'"] + +queries.forEach(function (query) { + var client = new pg.Client({ + user: args.user, + database: args.database, + password: args.password, + }) + client.connect() + client.query(query).on('row', function (row) { + console.log(row) + client.end() + }) +}) diff --git a/packages/pg/script/list-db-types.js b/packages/pg/script/list-db-types.js new file mode 100644 index 0000000..df179af --- /dev/null +++ b/packages/pg/script/list-db-types.js @@ -0,0 +1,10 @@ +'use strict' +var helper = require('../test/integration/test-helper') +var pg = helper.pg +pg.connect( + helper.config, + assert.success(function (client) { + var query = client.query("select oid, typname from pg_type where typtype = 'b' order by oid") + query.on('row', console.log) + }) +) diff --git a/packages/pg/test-1.js b/packages/pg/test-1.js new file mode 100644 index 0000000..f3484ea --- /dev/null +++ b/packages/pg/test-1.js @@ -0,0 +1,57 @@ +const { Pool, Client } = require('./lib') +// const isMd5 = false +// const client = true + +const ogConfig = { + user: 'sm3test', + database: 'postgres', + password: 'Aa123456', + host: '10.211.55.22', + port: 5432, +} +const pool = new Pool(ogConfig) + +pool.on('error', (err, result) => { + return console.error('catch error: ', err) +}) + +pool.connect((err, client, release) => { + if (err) { + return console.log('can not connect to openGauss server. err: '+ err); + } + client.query(`CREATE TABLE test + ( + c_customer_sk integer + );`) + client.query('insert into test values(1111)', (err, res) => { + console.log(res) + }) + + client.query('update test set c_customer_sk = 2222', (err, res) => { + console.log(res) + }) + client.query('SELECT * FROM test;', (err, res) => { + console.log(res) + }) + client.query('delete from test where c_customer_sk = 2222', (err, res) => { + console.log(res) + }) + client.query('SELECT * FROM test;', (err, res) => { + console.log(res) + }) + console.log('Dropping table before test stop.') + client.query('DROP TABLE test;') + release() + return console.log('connected to openGauss!') +}) + +function rollback(client) { + //terminating a client connection will + //automatically rollback any uncommitted transactions + //so while it's not technically mandatory to call + //ROLLBACK it is cleaner and more correct + return client + .query('ROLLBACK') + .then(() => client.end()) + .catch(() => client.end()) +} diff --git a/packages/pg/test/buffer-list.js b/packages/pg/test/buffer-list.js new file mode 100644 index 0000000..ec48b6a --- /dev/null +++ b/packages/pg/test/buffer-list.js @@ -0,0 +1,76 @@ +'use strict' + +const BufferList = function () { + this.buffers = [] +} +var p = BufferList.prototype + +p.add = function (buffer, front) { + this.buffers[front ? 'unshift' : 'push'](buffer) + return this +} + +p.addInt16 = function (val, front) { + return this.add(Buffer.from([val >>> 8, val >>> 0]), front) +} + +p.getByteLength = function (initial) { + return this.buffers.reduce(function (previous, current) { + return previous + current.length + }, initial || 0) +} + +p.addInt32 = function (val, first) { + return this.add( + Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]), + first + ) +} + +p.addCString = function (val, front) { + var len = Buffer.byteLength(val) + var buffer = Buffer.alloc(len + 1) + buffer.write(val) + buffer[len] = 0 + return this.add(buffer, front) +} + +p.addString = function (val, front) { + var len = Buffer.byteLength(val) + var buffer = Buffer.alloc(len) + buffer.write(val) + return this.add(buffer, front) +} + +p.addChar = function (char, first) { + return this.add(Buffer.from(char, 'utf8'), first) +} + +p.join = function (appendLength, char) { + var length = this.getByteLength() + if (appendLength) { + this.addInt32(length + 4, true) + return this.join(false, char) + } + if (char) { + this.addChar(char, true) + length++ + } + var result = Buffer.alloc(length) + var index = 0 + this.buffers.forEach(function (buffer) { + buffer.copy(result, index, 0) + index += buffer.length + }) + return result +} + +BufferList.concat = function () { + var total = new BufferList() + for (var i = 0; i < arguments.length; i++) { + total.add(arguments[i]) + } + return total.join() +} + +module.exports = BufferList diff --git a/packages/pg/test/cli.js b/packages/pg/test/cli.js new file mode 100644 index 0000000..03699b9 --- /dev/null +++ b/packages/pg/test/cli.js @@ -0,0 +1,25 @@ +'use strict' +var ConnectionParameters = require('../lib/connection-parameters') +var config = new ConnectionParameters(process.argv[2]) + +for (var i = 0; i < process.argv.length; i++) { + switch (process.argv[i].toLowerCase()) { + case 'native': + config.native = true + break + case 'binary': + config.binary = true + break + case 'down': + config.down = true + break + default: + break + } +} + +if (process.env['PG_TEST_NATIVE']) { + config.native = true +} + +module.exports = config diff --git a/packages/pg/test/integration/client/api-tests.js b/packages/pg/test/integration/client/api-tests.js new file mode 100644 index 0000000..abaab69 --- /dev/null +++ b/packages/pg/test/integration/client/api-tests.js @@ -0,0 +1,259 @@ +'use strict' +var helper = require('../test-helper') +var pg = helper.pg + +var suite = new helper.Suite() + +suite.test('null and undefined are both inserted as NULL', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query('CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)') + client.query('INSERT INTO my_nulls(a,b,c,d,e,f) VALUES ($1,$2,$3,$4,$5,$6)', [ + null, + undefined, + null, + undefined, + null, + undefined, + ]) + client.query( + 'SELECT * FROM my_nulls', + assert.calls(function (err, result) { + console.log(err) + assert.ifError(err) + assert.equal(result.rows.length, 1) + assert.isNull(result.rows[0].a) + assert.isNull(result.rows[0].b) + assert.isNull(result.rows[0].c) + assert.isNull(result.rows[0].d) + assert.isNull(result.rows[0].e) + assert.isNull(result.rows[0].f) + pool.end(done) + release() + }) + ) + }) + ) +}) + +suite.test('pool callback behavior', (done) => { + // test weird callback behavior with node-pool + const pool = new pg.Pool() + pool.connect(function (err) { + assert(!err) + arguments[1].emit('drain') + arguments[2]() + pool.end(done) + }) +}) + +suite.test('query timeout', (cb) => { + const pool = new pg.Pool({ query_timeout: 1000 }) + pool.connect().then((client) => { + client.query( + 'SELECT pg_sleep(2)', + assert.calls(function (err, result) { + assert(err) + assert(err.message === 'Query read timeout') + client.release() + pool.end(cb) + }) + ) + }) +}) + +suite.test('query recover from timeout', (cb) => { + const pool = new pg.Pool({ query_timeout: 1000 }) + pool.connect().then((client) => { + client.query( + 'SELECT pg_sleep(20)', + assert.calls(function (err, result) { + assert(err) + assert(err.message === 'Query read timeout') + client.release(err) + pool.connect().then((client) => { + client.query( + 'SELECT 1', + assert.calls(function (err, result) { + assert(!err) + client.release(err) + pool.end(cb) + }) + ) + }) + }) + ) + }) +}) + +suite.test('query no timeout', (cb) => { + const pool = new pg.Pool({ query_timeout: 10000 }) + pool.connect().then((client) => { + client.query( + 'SELECT pg_sleep(1)', + assert.calls(function (err, result) { + assert(!err) + client.release() + pool.end(cb) + }) + ) + }) +}) + +suite.test('callback API', (done) => { + const client = new helper.Client() + client.query('CREATE TEMP TABLE peep(name text)') + client.query('INSERT INTO peep(name) VALUES ($1)', ['brianc']) + const config = { + text: 'INSERT INTO peep(name) VALUES ($1)', + values: ['brian'], + } + client.query(config) + client.query('INSERT INTO peep(name) VALUES ($1)', ['aaron']) + + client.query('SELECT * FROM peep ORDER BY name COLLATE "C"', (err, res) => { + assert(!err) + assert.equal(res.rowCount, 3) + assert.deepEqual(res.rows, [ + { + name: 'aaron', + }, + { + name: 'brian', + }, + { + name: 'brianc', + }, + ]) + done() + }) + client.connect((err) => { + assert(!err) + client.once('drain', () => client.end()) + }) +}) + +suite.test('executing nested queries', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query( + 'select now as now from NOW()', + assert.calls(function (err, result) { + assert.equal(new Date().getYear(), result.rows[0].now.getYear()) + client.query( + 'select now as now_again FROM NOW()', + assert.calls(function () { + client.query( + 'select * FROM NOW()', + assert.calls(function () { + assert.ok('all queries hit') + release() + pool.end(done) + }) + ) + }) + ) + }) + ) + }) + ) +}) + +suite.test('raises error if cannot connect', function () { + var connectionString = 'pg://sfalsdkf:asdf@localhost/ieieie' + const pool = new pg.Pool({ connectionString: connectionString }) + pool.connect( + assert.calls(function (err, client, done) { + assert.ok(err, 'should have raised an error') + done() + }) + ) +}) + +suite.test('query errors are handled and do not bubble if callback is provided', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query( + 'SELECT OISDJF FROM LEIWLISEJLSE', + assert.calls(function (err, result) { + assert.ok(err) + release() + pool.end(done) + }) + ) + }) + ) +}) + +suite.test('callback is fired once and only once', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query('CREATE TEMP TABLE boom(name varchar(10))') + var callCount = 0 + client.query( + [ + "INSERT INTO boom(name) VALUES('hai')", + "INSERT INTO boom(name) VALUES('boom')", + "INSERT INTO boom(name) VALUES('zoom')", + ].join(';'), + function (err, callback) { + assert.equal(callCount++, 0, 'Call count should be 0. More means this callback fired more than once.') + release() + pool.end(done) + } + ) + }) + ) +}) + +suite.test('can provide callback and config object', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query( + { + name: 'boom', + text: 'select NOW()', + }, + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.rows[0].now.getYear(), new Date().getYear()) + release() + pool.end(done) + }) + ) + }) + ) +}) + +suite.test('can provide callback and config and parameters', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + var config = { + text: 'select $1::text as val', + } + client.query( + config, + ['hi'], + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.rows.length, 1) + assert.equal(result.rows[0].val, 'hi') + release() + pool.end(done) + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/appname-tests.js b/packages/pg/test/integration/client/appname-tests.js new file mode 100644 index 0000000..ab7202a --- /dev/null +++ b/packages/pg/test/integration/client/appname-tests.js @@ -0,0 +1,97 @@ +'use strict' +var helper = require('./test-helper') +var Client = helper.Client + +var suite = new helper.Suite() + +var conInfo = helper.config + +function getConInfo(override) { + return Object.assign({}, conInfo, override) +} + +function getAppName(conf, cb) { + var client = new Client(conf) + client.connect( + assert.success(function () { + client.query( + 'SHOW application_name', + assert.success(function (res) { + var appName = res.rows[0].application_name + cb(appName) + client.end() + }) + ) + }) + ) +} + +suite.test('No default appliation_name ', function (done) { + var conf = getConInfo() + getAppName({}, function (res) { + assert.strictEqual(res, '') + done() + }) +}) + +suite.test('fallback_application_name is used', function (done) { + var fbAppName = 'this is my app' + var conf = getConInfo({ + fallback_application_name: fbAppName, + }) + getAppName(conf, function (res) { + assert.strictEqual(res, fbAppName) + done() + }) +}) + +suite.test('application_name is used', function (done) { + var appName = 'some wired !@#$% application_name' + var conf = getConInfo({ + application_name: appName, + }) + getAppName(conf, function (res) { + assert.strictEqual(res, appName) + done() + }) +}) + +suite.test('application_name has precedence over fallback_application_name', function (done) { + var appName = 'some wired !@#$% application_name' + var fbAppName = 'some other strange $$test$$ appname' + var conf = getConInfo({ + application_name: appName, + fallback_application_name: fbAppName, + }) + getAppName(conf, function (res) { + assert.strictEqual(res, appName) + done() + }) +}) + +suite.test('application_name from connection string', function (done) { + var appName = 'my app' + var conParams = require('../../../lib/connection-parameters') + var conf + if (process.argv[2]) { + conf = new conParams(process.argv[2] + '?application_name=' + appName) + } else { + conf = 'postgres://?application_name=' + appName + } + getAppName(conf, function (res) { + assert.strictEqual(res, appName) + done() + }) +}) + +// TODO: make the test work for native client too +if (!helper.args.native) { + suite.test('application_name is read from the env', function (done) { + var appName = (process.env.PGAPPNAME = 'testest') + getAppName({}, function (res) { + delete process.env.PGAPPNAME + assert.strictEqual(res, appName) + done() + }) + }) +} diff --git a/packages/pg/test/integration/client/array-tests.js b/packages/pg/test/integration/client/array-tests.js new file mode 100644 index 0000000..a321396 --- /dev/null +++ b/packages/pg/test/integration/client/array-tests.js @@ -0,0 +1,231 @@ +'use strict' +var helper = require('./test-helper') +var pg = helper.pg + +var suite = new helper.Suite() + +const pool = new pg.Pool() + +pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + + suite.test('nulls', function (done) { + client.query( + 'SELECT $1::text[] as array', + [[null]], + assert.success(function (result) { + var array = result.rows[0].array + assert.lengthIs(array, 1) + assert.isNull(array[0]) + done() + }) + ) + }) + + suite.test('elements containing JSON-escaped characters', function (done) { + var param = '\\"\\"' + + for (var i = 1; i <= 0x1f; i++) { + param += String.fromCharCode(i) + } + + client.query( + 'SELECT $1::text[] as array', + [[param]], + assert.success(function (result) { + var array = result.rows[0].array + assert.lengthIs(array, 1) + assert.equal(array[0], param) + done() + }) + ) + }) + + suite.test('cleanup', () => release()) + + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query('CREATE TEMP TABLE why(names text[], numbors integer[])') + client + .query(new pg.Query('INSERT INTO why(names, numbors) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\')')) + .on('error', console.log) + suite.test('numbers', function (done) { + // client.connection.on('message', console.log) + client.query( + 'SELECT numbors FROM why', + assert.success(function (result) { + assert.lengthIs(result.rows[0].numbors, 3) + assert.equal(result.rows[0].numbors[0], 1) + assert.equal(result.rows[0].numbors[1], 2) + assert.equal(result.rows[0].numbors[2], 3) + done() + }) + ) + }) + + suite.test('parses string arrays', function (done) { + client.query( + 'SELECT names FROM why', + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 3) + assert.equal(names[0], 'aaron') + assert.equal(names[1], 'brian') + assert.equal(names[2], 'a b c') + done() + }) + ) + }) + + suite.test('empty array', function (done) { + client.query( + "SELECT '{}'::text[] as names", + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 0) + done() + }) + ) + }) + + suite.test('element containing comma', function (done) { + client.query( + 'SELECT \'{"joe,bob",jim}\'::text[] as names', + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 2) + assert.equal(names[0], 'joe,bob') + assert.equal(names[1], 'jim') + done() + }) + ) + }) + + suite.test('bracket in quotes', function (done) { + client.query( + 'SELECT \'{"{","}"}\'::text[] as names', + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 2) + assert.equal(names[0], '{') + assert.equal(names[1], '}') + done() + }) + ) + }) + + suite.test('null value', function (done) { + client.query( + 'SELECT \'{joe,null,bob,"NULL"}\'::text[] as names', + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 4) + assert.equal(names[0], 'joe') + assert.equal(names[1], null) + assert.equal(names[2], 'bob') + assert.equal(names[3], 'NULL') + done() + }) + ) + }) + + suite.test('element containing quote char', function (done) { + client.query( + "SELECT ARRAY['joe''', 'jim', 'bob\"'] AS names", + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 3) + assert.equal(names[0], "joe'") + assert.equal(names[1], 'jim') + assert.equal(names[2], 'bob"') + done() + }) + ) + }) + + suite.test('nested array', function (done) { + client.query( + "SELECT '{{1,joe},{2,bob}}'::text[] as names", + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 2) + + assert.lengthIs(names[0], 2) + assert.equal(names[0][0], '1') + assert.equal(names[0][1], 'joe') + + assert.lengthIs(names[1], 2) + assert.equal(names[1][0], '2') + assert.equal(names[1][1], 'bob') + done() + }) + ) + }) + + suite.test('integer array', function (done) { + client.query( + "SELECT '{1,2,3}'::integer[] as names", + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 3) + assert.equal(names[0], 1) + assert.equal(names[1], 2) + assert.equal(names[2], 3) + done() + }) + ) + }) + + suite.test('integer nested array', function (done) { + client.query( + "SELECT '{{1,100},{2,100},{3,100}}'::integer[] as names", + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 3) + assert.equal(names[0][0], 1) + assert.equal(names[0][1], 100) + + assert.equal(names[1][0], 2) + assert.equal(names[1][1], 100) + + assert.equal(names[2][0], 3) + assert.equal(names[2][1], 100) + done() + }) + ) + }) + + suite.test('JS array parameter', function (done) { + client.query( + 'SELECT $1::integer[] as names', + [ + [ + [1, 100], + [2, 100], + [3, 100], + ], + ], + assert.success(function (result) { + var names = result.rows[0].names + assert.lengthIs(names, 3) + assert.equal(names[0][0], 1) + assert.equal(names[0][1], 100) + + assert.equal(names[1][0], 2) + assert.equal(names[1][1], 100) + + assert.equal(names[2][0], 3) + assert.equal(names[2][1], 100) + release() + pool.end(() => { + done() + }) + }) + ) + }) + }) + ) + }) +) diff --git a/packages/pg/test/integration/client/big-simple-query-tests.js b/packages/pg/test/integration/client/big-simple-query-tests.js new file mode 100644 index 0000000..b0dc252 --- /dev/null +++ b/packages/pg/test/integration/client/big-simple-query-tests.js @@ -0,0 +1,121 @@ +'use strict' +var helper = require('./test-helper') +var Query = helper.pg.Query + +const suite = new helper.Suite() + +/* + Test to trigger a bug. + + I really dont know what's wrong but it seams that its triggered by multable big queryies with supplied values. + The test bellow can trigger the bug. +*/ + +// Big query with a where clouse from supplied value +var big_query_rows_1 = [] +var big_query_rows_2 = [] +var big_query_rows_3 = [] + +// Works +suite.test('big simple query 1', function (done) { + var client = helper.client() + client + .query( + new Query( + "select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = '' or 1 = 1" + ) + ) + .on('row', function (row) { + big_query_rows_1.push(row) + }) + .on('error', function (error) { + console.log('big simple query 1 error') + console.log(error) + }) + client.on('drain', () => { + client.end() + done() + }) +}) + +// Works +suite.test('big simple query 2', function (done) { + var client = helper.client() + client + .query( + new Query( + "select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = $1 or 1 = 1", + [''] + ) + ) + .on('row', function (row) { + big_query_rows_2.push(row) + }) + .on('error', function (error) { + console.log('big simple query 2 error') + console.log(error) + }) + client.on('drain', () => { + client.end() + done() + }) +}) + +// Fails most of the time with 'invalid byte sequence for encoding "UTF8": 0xb9' or 'insufficient data left in message' +// If test 1 and 2 are commented out it works +suite.test('big simple query 3', function (done) { + var client = helper.client() + client + .query( + new Query( + "select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = $1 or 1 = 1", + [''] + ) + ) + .on('row', function (row) { + big_query_rows_3.push(row) + }) + .on('error', function (error) { + console.log('big simple query 3 error') + console.log(error) + }) + client.on('drain', () => { + client.end() + done() + }) +}) + +process.on('exit', function () { + assert.equal(big_query_rows_1.length, 26, 'big simple query 1 should return 26 rows') + assert.equal(big_query_rows_2.length, 26, 'big simple query 2 should return 26 rows') + assert.equal(big_query_rows_3.length, 26, 'big simple query 3 should return 26 rows') +}) + +var runBigQuery = function (client) { + var rows = [] + var q = client.query( + "select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = $1 or 1 = 1", + [''], + function (err, result) { + if (err != null) { + console.log(err) + throw Err + } + assert.lengthIs(result.rows, 26) + } + ) +} + +suite.test('many times', function (done) { + var client = helper.client() + for (var i = 0; i < 20; i++) { + runBigQuery(client) + } + client.on('drain', function () { + client.end() + setTimeout(function () { + done() + // let client disconnect fully + }, 100) + }) +}) diff --git a/packages/pg/test/integration/client/configuration-tests.js b/packages/pg/test/integration/client/configuration-tests.js new file mode 100644 index 0000000..0737a79 --- /dev/null +++ b/packages/pg/test/integration/client/configuration-tests.js @@ -0,0 +1,84 @@ +'use strict' +var helper = require('./test-helper') +var pg = helper.pg + +var suite = new helper.Suite() + +// clear process.env +var realEnv = {} +for (var key in process.env) { + realEnv[key] = process.env[key] + if (!key.indexOf('PG')) delete process.env[key] +} + +suite.test('default values are used in new clients', function () { + assert.same(pg.defaults, { + user: process.env.USER, + database: undefined, + password: null, + port: 5432, + rows: 0, + max: 10, + binary: false, + idleTimeoutMillis: 30000, + client_encoding: '', + ssl: false, + application_name: undefined, + fallback_application_name: undefined, + parseInputDatesAsUTC: false, + }) + + var client = new pg.Client() + assert.same(client, { + user: process.env.USER, + database: process.env.USER, + password: null, + port: 5432, + }) +}) + +suite.test('modified values are passed to created clients', function () { + pg.defaults.user = 'boom' + pg.defaults.password = 'zap' + pg.defaults.database = 'pow' + pg.defaults.port = 1234 + pg.defaults.host = 'blam' + + var client = new Client() + assert.same(client, { + user: 'boom', + password: 'zap', + database: 'pow', + port: 1234, + host: 'blam', + }) +}) + +suite.test('database defaults to user when user is non-default', () => { + { + pg.defaults.database = undefined + + const client = new Client({ + user: 'foo', + }) + + assert.strictEqual(client.database, 'foo') + } + + { + pg.defaults.database = 'bar' + + const client = new Client({ + user: 'foo', + }) + + assert.strictEqual(client.database, 'bar') + } +}) + +suite.test('cleanup', () => { + // restore process.env + for (var key in realEnv) { + process.env[key] = realEnv[key] + } +}) diff --git a/packages/pg/test/integration/client/connection-parameter-tests.js b/packages/pg/test/integration/client/connection-parameter-tests.js new file mode 100644 index 0000000..45b5eba --- /dev/null +++ b/packages/pg/test/integration/client/connection-parameter-tests.js @@ -0,0 +1,15 @@ +const assert = require('assert') +const helper = require('../test-helper') +const suite = new helper.Suite() +const { Client } = helper.pg + +suite.test('it sends options', async () => { + const client = new Client({ + options: '--default_transaction_isolation=serializable', + }) + await client.connect() + const { rows } = await client.query('SHOW default_transaction_isolation') + assert.strictEqual(rows.length, 1) + assert.strictEqual(rows[0].default_transaction_isolation, 'serializable') + await client.end() +}) diff --git a/packages/pg/test/integration/client/connection-timeout-tests.js b/packages/pg/test/integration/client/connection-timeout-tests.js new file mode 100644 index 0000000..843fa95 --- /dev/null +++ b/packages/pg/test/integration/client/connection-timeout-tests.js @@ -0,0 +1,87 @@ +'use strict' +const net = require('net') +const buffers = require('../../test-buffers') +const helper = require('./test-helper') + +const suite = new helper.Suite() + +const options = { + host: 'localhost', + port: 54321, + connectionTimeoutMillis: 2000, + user: 'not', + database: 'existing', +} + +const serverWithConnectionTimeout = (timeout, callback) => { + const sockets = new Set() + + const server = net.createServer((socket) => { + sockets.add(socket) + socket.once('end', () => sockets.delete(socket)) + + socket.on('data', (data) => { + // deny request for SSL + if (data.length === 8) { + socket.write(Buffer.from('N', 'utf8')) + // consider all authentication requests as good + } else if (!data[0]) { + socket.write(buffers.authenticationOk()) + // send ReadyForQuery `timeout` ms after authentication + setTimeout(() => socket.write(buffers.readyForQuery()), timeout).unref() + // respond with our canned response + } else { + socket.write(buffers.readyForQuery()) + } + }) + }) + + let closing = false + const closeServer = (done) => { + if (closing) return + closing = true + + server.close(done) + for (const socket of sockets) { + socket.destroy() + } + } + + server.listen(options.port, options.host, () => callback(closeServer)) +} + +suite.test('successful connection', (done) => { + serverWithConnectionTimeout(0, (closeServer) => { + const timeoutId = setTimeout(() => { + throw new Error('Client should have connected successfully but it did not.') + }, 3000) + + const client = new helper.Client(options) + client + .connect() + .then(() => client.end()) + .then(() => closeServer(done)) + .catch((err) => closeServer(() => done(err))) + .then(() => clearTimeout(timeoutId)) + }) +}) + +suite.test('expired connection timeout', (done) => { + serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, (closeServer) => { + const timeoutId = setTimeout(() => { + throw new Error('Client should have emitted an error but it did not.') + }, 3000) + + const client = new helper.Client(options) + client + .connect() + .then(() => client.end()) + .then(() => closeServer(() => done(new Error('Connection timeout should have expired but it did not.')))) + .catch((err) => { + assert(err instanceof Error) + assert(/timeout expired\s*/.test(err.message)) + closeServer(done) + }) + .then(() => clearTimeout(timeoutId)) + }) +}) diff --git a/packages/pg/test/integration/client/custom-types-tests.js b/packages/pg/test/integration/client/custom-types-tests.js new file mode 100644 index 0000000..d1dd2ee --- /dev/null +++ b/packages/pg/test/integration/client/custom-types-tests.js @@ -0,0 +1,42 @@ +'use strict' +const helper = require('./test-helper') +const Client = helper.pg.Client +const suite = new helper.Suite() + +const customTypes = { + getTypeParser: () => () => 'okay!', +} + +suite.test('custom type parser in client config', (done) => { + const client = new Client({ types: customTypes }) + + client.connect().then(() => { + client.query( + 'SELECT NOW() as val', + assert.success(function (res) { + assert.equal(res.rows[0].val, 'okay!') + client.end().then(done) + }) + ) + }) +}) + +// Custom type-parsers per query are not supported in native +if (!helper.args.native) { + suite.test('custom type parser in query', (done) => { + const client = new Client() + + client.connect().then(() => { + client.query( + { + text: 'SELECT NOW() as val', + types: customTypes, + }, + assert.success(function (res) { + assert.equal(res.rows[0].val, 'okay!') + client.end().then(done) + }) + ) + }) + }) +} diff --git a/packages/pg/test/integration/client/empty-query-tests.js b/packages/pg/test/integration/client/empty-query-tests.js new file mode 100644 index 0000000..d887885 --- /dev/null +++ b/packages/pg/test/integration/client/empty-query-tests.js @@ -0,0 +1,20 @@ +'use strict' +var helper = require('./test-helper') +const suite = new helper.Suite() + +suite.test('empty query message handling', function (done) { + const client = helper.client() + assert.emits(client, 'drain', function () { + client.end(done) + }) + client.query({ text: '' }) +}) + +suite.test('callback supported', function (done) { + const client = helper.client() + client.query('', function (err, result) { + assert(!err) + assert.empty(result.rows) + client.end(done) + }) +}) diff --git a/packages/pg/test/integration/client/error-handling-tests.js b/packages/pg/test/integration/client/error-handling-tests.js new file mode 100644 index 0000000..4e879c9 --- /dev/null +++ b/packages/pg/test/integration/client/error-handling-tests.js @@ -0,0 +1,259 @@ +'use strict' + +var helper = require('./test-helper') +var util = require('util') + +var pg = helper.pg +const Client = pg.Client +const DatabaseError = pg.DatabaseError + +var createErorrClient = function () { + var client = helper.client() + client.once('error', function (err) { + assert.fail('Client shoud not throw error during query execution') + }) + client.on('drain', client.end.bind(client)) + return client +} + +const suite = new helper.Suite('error handling') + +suite.test('sending non-array argument as values causes an error callback', (done) => { + const client = new Client() + client.connect((err) => { + if (err) { + return done(err) + } + client.query('select $1::text as name', 'foo', (err) => { + assert(err instanceof Error) + client.query('SELECT $1::text as name', ['foo'], (err, res) => { + assert.equal(res.rows[0].name, 'foo') + client.end(done) + }) + }) + }) +}) + +suite.test('re-using connections results in error callback', (done) => { + const client = new Client() + client.connect((err) => { + if (err) { + return done(err) + } + client.connect((err) => { + assert(err instanceof Error) + client.end(done) + }) + }) +}) + +suite.testAsync('re-using connections results in promise rejection', () => { + const client = new Client() + return client.connect().then(() => { + return helper.rejection(client.connect()).then((err) => { + assert(err instanceof Error) + return client.end() + }) + }) +}) + +suite.test('using a client after closing it results in error', (done) => { + const client = new Client() + client.connect((err) => { + if (err) { + return done(err) + } + client.end( + assert.calls(() => { + client.query( + 'SELECT 1', + assert.calls((err) => { + assert.equal(err.message, 'Client was closed and is not queryable') + done() + }) + ) + }) + ) + }) +}) + +suite.test('query receives error on client shutdown', function (done) { + var client = new Client() + client.connect( + assert.success(function () { + const config = { + text: 'select pg_sleep(5)', + name: 'foobar', + } + let queryError + client.query( + new pg.Query(config), + assert.calls(function (err, res) { + assert(err instanceof Error) + queryError = err + }) + ) + setTimeout(() => client.end(), 50) + client.once('end', () => { + assert(queryError instanceof Error) + done() + }) + }) + ) +}) + +var ensureFuture = function (testClient, done) { + var goodQuery = testClient.query(new pg.Query('select age from boom')) + assert.emits(goodQuery, 'row', function (row) { + assert.equal(row.age, 28) + done() + }) +} + +suite.test('when query is parsing', (done) => { + var client = createErorrClient() + + var q = client.query({ text: 'CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);' }) + + // this query wont parse since there isn't a table named bang + var query = client.query( + new pg.Query({ + text: 'select * from bang where name = $1', + values: ['0'], + }) + ) + + assert.emits(query, 'error', function (err) { + ensureFuture(client, done) + }) +}) + +suite.test('when a query is binding', function (done) { + var client = createErorrClient() + + var q = client.query({ text: 'CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);' }) + + var query = client.query( + new pg.Query({ + text: 'select * from boom where age = $1', + values: ['asldkfjasdf'], + }) + ) + + assert.emits(query, 'error', function (err) { + if (!helper.config.native) { + assert(err instanceof DatabaseError) + } + assert.equal(err.severity, 'ERROR') + ensureFuture(client, done) + }) +}) + +suite.test('non-query error with callback', function (done) { + var client = new Client({ + user: 'asldkfjsadlfkj', + }) + client.connect( + assert.calls(function (error, client) { + assert(error instanceof Error) + done() + }) + ) +}) + +suite.test('non-error calls supplied callback', function (done) { + var client = new Client({ + user: helper.args.user, + password: helper.args.password, + host: helper.args.host, + port: helper.args.port, + database: helper.args.database, + }) + + client.connect( + assert.calls(function (err) { + assert.ifError(err) + client.end(done) + }) + ) +}) + +suite.test('when connecting to an invalid host with callback', function (done) { + var client = new Client({ + user: 'very invalid username', + }) + client.on('error', () => { + assert.fail('unexpected error event when connecting') + }) + client.connect(function (error, client) { + assert(error instanceof Error) + done() + }) +}) + +suite.test('when connecting to invalid host with promise', function (done) { + var client = new Client({ + user: 'very invalid username', + }) + client.on('error', () => { + assert.fail('unexpected error event when connecting') + }) + client.connect().catch((e) => done()) +}) + +suite.test('non-query error', function (done) { + var client = new Client({ + user: 'asldkfjsadlfkj', + }) + client.connect().catch((e) => { + assert(e instanceof Error) + done() + }) +}) + +suite.test('within a simple query', (done) => { + var client = createErorrClient() + + var query = client.query(new pg.Query("select eeeee from yodas_dsflsd where pixistix = 'zoiks!!!'")) + + assert.emits(query, 'error', function (error) { + if (!helper.config.native) { + assert(error instanceof DatabaseError) + } + assert.equal(error.severity, 'ERROR') + done() + }) +}) + +suite.test('connected, idle client error', (done) => { + const client = new Client() + client.connect((err) => { + if (err) { + throw new Error('Should not receive error callback after connection') + } + setImmediate(() => { + ;(client.connection || client.native).emit('error', new Error('expected')) + }) + }) + client.on('error', (err) => { + assert.equal(err.message, 'expected') + client.end(done) + }) +}) + +suite.test('cannot pass non-string values to query as text', (done) => { + const client = new Client() + client.connect((err) => { + if (err) { + return done(err) + } + client.query({ text: {} }, (err) => { + assert(err) + client.query({}, (err) => { + client.on('drain', () => { + client.end(done) + }) + }) + }) + }) +}) diff --git a/packages/pg/test/integration/client/field-name-escape-tests.js b/packages/pg/test/integration/client/field-name-escape-tests.js new file mode 100644 index 0000000..146ad1b --- /dev/null +++ b/packages/pg/test/integration/client/field-name-escape-tests.js @@ -0,0 +1,10 @@ +var pg = require('./test-helper').pg + +var sql = 'SELECT 1 AS "\\\'/*", 2 AS "\\\'*/\n + process.exit(-1)] = null;\n//"' + +var client = new pg.Client() +client.connect() +client.query(sql, function (err, res) { + if (err) throw err + client.end() +}) diff --git a/packages/pg/test/integration/client/huge-numeric-tests.js b/packages/pg/test/integration/client/huge-numeric-tests.js new file mode 100644 index 0000000..bdbfac2 --- /dev/null +++ b/packages/pg/test/integration/client/huge-numeric-tests.js @@ -0,0 +1,27 @@ +'use strict' +var helper = require('./test-helper') +const pool = new helper.pg.Pool() + +pool.connect( + assert.success(function (client, done) { + var types = require('pg-types') + // 1231 = numericOID + types.setTypeParser(1700, function () { + return 'yes' + }) + types.setTypeParser(1700, 'binary', function () { + return 'yes' + }) + var bignum = '294733346389144765940638005275322203805' + client.query('CREATE TEMP TABLE bignumz(id numeric)') + client.query('INSERT INTO bignumz(id) VALUES ($1)', [bignum]) + client.query( + 'SELECT * FROM bignumz', + assert.success(function (result) { + assert.equal(result.rows[0].id, 'yes') + done() + pool.end() + }) + ) + }) +) diff --git a/packages/pg/test/integration/client/idle_in_transaction_session_timeout-tests.js b/packages/pg/test/integration/client/idle_in_transaction_session_timeout-tests.js new file mode 100644 index 0000000..f970faa --- /dev/null +++ b/packages/pg/test/integration/client/idle_in_transaction_session_timeout-tests.js @@ -0,0 +1,95 @@ +'use strict' +var helper = require('./test-helper') +var Client = helper.Client + +var suite = new helper.Suite() + +var conInfo = helper.config + +function getConInfo(override) { + return Object.assign({}, conInfo, override) +} + +function testClientVersion(cb) { + var client = new Client({}) + client.connect( + assert.success(function () { + helper.versionGTE( + client, + 100000, + assert.success(function (isGreater) { + return client.end( + assert.success(function () { + if (!isGreater) { + console.log( + 'skip idle_in_transaction_session_timeout at client-level is only available in v10 and above' + ) + return + } + cb() + }) + ) + }) + ) + }) + ) +} + +function getIdleTransactionSessionTimeout(conf, cb) { + var client = new Client(conf) + client.connect( + assert.success(function () { + client.query( + 'SHOW idle_in_transaction_session_timeout', + assert.success(function (res) { + var timeout = res.rows[0].idle_in_transaction_session_timeout + cb(timeout) + client.end() + }) + ) + }) + ) +} + +if (!helper.args.native) { + // idle_in_transaction_session_timeout is not supported with the native client + testClientVersion(function () { + suite.test('No default idle_in_transaction_session_timeout ', function (done) { + getConInfo() + getIdleTransactionSessionTimeout({}, function (res) { + assert.strictEqual(res, '0') // 0 = no timeout + done() + }) + }) + + suite.test('idle_in_transaction_session_timeout integer is used', function (done) { + var conf = getConInfo({ + idle_in_transaction_session_timeout: 3000, + }) + getIdleTransactionSessionTimeout(conf, function (res) { + assert.strictEqual(res, '3s') + done() + }) + }) + + suite.test('idle_in_transaction_session_timeout float is used', function (done) { + var conf = getConInfo({ + idle_in_transaction_session_timeout: 3000.7, + }) + getIdleTransactionSessionTimeout(conf, function (res) { + assert.strictEqual(res, '3s') + done() + }) + }) + + suite.test('idle_in_transaction_session_timeout string is used', function (done) { + var conf = getConInfo({ + idle_in_transaction_session_timeout: '3000', + }) + getIdleTransactionSessionTimeout(conf, function (res) { + assert.strictEqual(res, '3s') + done() + }) + }) + }) +} diff --git a/packages/pg/test/integration/client/json-type-parsing-tests.js b/packages/pg/test/integration/client/json-type-parsing-tests.js new file mode 100644 index 0000000..ba76960 --- /dev/null +++ b/packages/pg/test/integration/client/json-type-parsing-tests.js @@ -0,0 +1,37 @@ +'use strict' +var helper = require('./test-helper') +var assert = require('assert') + +const pool = new helper.pg.Pool() +pool.connect( + assert.success(function (client, done) { + helper.versionGTE( + client, + 90200, + assert.success(function (jsonSupported) { + if (!jsonSupported) { + console.log('skip json test on older versions of postgres') + done() + return pool.end() + } + client.query('CREATE TEMP TABLE stuff(id SERIAL PRIMARY KEY, data JSON)') + var value = { name: 'Brian', age: 250, alive: true, now: new Date() } + client.query('INSERT INTO stuff (data) VALUES ($1)', [value]) + client.query( + 'SELECT * FROM stuff', + assert.success(function (result) { + assert.equal(result.rows.length, 1) + assert.equal(typeof result.rows[0].data, 'object') + var row = result.rows[0].data + assert.strictEqual(row.name, value.name) + assert.strictEqual(row.age, value.age) + assert.strictEqual(row.alive, value.alive) + assert.equal(JSON.stringify(row.now), JSON.stringify(value.now)) + done() + pool.end() + }) + ) + }) + ) + }) +) diff --git a/packages/pg/test/integration/client/multiple-results-tests.js b/packages/pg/test/integration/client/multiple-results-tests.js new file mode 100644 index 0000000..addca9b --- /dev/null +++ b/packages/pg/test/integration/client/multiple-results-tests.js @@ -0,0 +1,78 @@ +'use strict' +const assert = require('assert') +const co = require('co') + +const helper = require('./test-helper') + +const suite = new helper.Suite('multiple result sets') + +suite.test( + 'two select results work', + co.wrap(function* () { + const client = new helper.Client() + yield client.connect() + + const results = yield client.query(`SELECT 'foo'::text as name; SELECT 'bar'::text as baz`) + assert(Array.isArray(results)) + + assert.equal(results[0].fields[0].name, 'name') + assert.deepEqual(results[0].rows, [{ name: 'foo' }]) + + assert.equal(results[1].fields[0].name, 'baz') + assert.deepEqual(results[1].rows, [{ baz: 'bar' }]) + + return client.end() + }) +) + +suite.test( + 'multiple selects work', + co.wrap(function* () { + const client = new helper.Client() + yield client.connect() + + const text = ` + SELECT * FROM generate_series(2, 4) as foo; + SELECT * FROM generate_series(8, 10) as bar; + SELECT * FROM generate_series(20, 22) as baz; + ` + + const results = yield client.query(text) + assert(Array.isArray(results)) + + assert.equal(results[0].fields[0].name, 'foo') + assert.deepEqual(results[0].rows, [{ foo: 2 }, { foo: 3 }, { foo: 4 }]) + + assert.equal(results[1].fields[0].name, 'bar') + assert.deepEqual(results[1].rows, [{ bar: 8 }, { bar: 9 }, { bar: 10 }]) + + assert.equal(results[2].fields[0].name, 'baz') + assert.deepEqual(results[2].rows, [{ baz: 20 }, { baz: 21 }, { baz: 22 }]) + + assert.equal(results.length, 3) + + return client.end() + }) +) + +suite.test( + 'mixed queries and statements', + co.wrap(function* () { + const client = new helper.Client() + yield client.connect() + + const text = ` + CREATE TEMP TABLE weather(type text); + INSERT INTO weather(type) VALUES ('rain'); + SELECT * FROM weather; + ` + + const results = yield client.query(text) + assert(Array.isArray(results)) + assert.equal(results[0].command, 'CREATE') + assert.equal(results[1].command, 'INSERT') + assert.equal(results[2].command, 'SELECT') + + return client.end() + }) +) diff --git a/packages/pg/test/integration/client/network-partition-tests.js b/packages/pg/test/integration/client/network-partition-tests.js new file mode 100644 index 0000000..9933964 --- /dev/null +++ b/packages/pg/test/integration/client/network-partition-tests.js @@ -0,0 +1,94 @@ +'use strict' +var buffers = require('../../test-buffers') +var helper = require('./test-helper') +var suite = new helper.Suite() + +var net = require('net') + +var Server = function (response) { + this.server = undefined + this.socket = undefined + this.response = response +} + +Server.prototype.start = function (cb) { + // this is our fake postgres server + // it responds with our specified response immediatley after receiving every buffer + // this is sufficient into convincing the client its connectet to a valid backend + // if we respond with a readyForQuery message + this.server = net.createServer( + function (socket) { + this.socket = socket + if (this.response) { + this.socket.on( + 'data', + function (data) { + // deny request for SSL + if (data.length == 8) { + this.socket.write(Buffer.from('N', 'utf8')) + // consider all authentication requests as good + } else if (!data[0]) { + this.socket.write(buffers.authenticationOk()) + // respond with our canned response + } else { + this.socket.write(this.response) + } + }.bind(this) + ) + } + }.bind(this) + ) + + var port = 54321 + + var options = { + host: 'localhost', + port: port, + } + this.server.listen(options.port, options.host, function () { + cb(options) + }) +} + +Server.prototype.drop = function () { + this.socket.destroy() +} + +Server.prototype.close = function (cb) { + this.server.close(cb) +} + +var testServer = function (server, cb) { + // wait for our server to start + server.start(function (options) { + // connect a client to it + var client = new helper.Client(options) + client.connect().catch((err) => { + assert(err instanceof Error) + clearTimeout(timeoutId) + server.close(cb) + }) + + server.server.on('connection', () => { + // after 50 milliseconds, drop the client + setTimeout(function () { + server.drop() + }, 50) + }) + + // blow up if we don't receive an error + var timeoutId = setTimeout(function () { + throw new Error('Client should have emitted an error but it did not.') + }, 5000) + }) +} + +suite.test('readyForQuery server', (done) => { + const respondingServer = new Server(buffers.readyForQuery()) + testServer(respondingServer, done) +}) + +suite.test('silent server', (done) => { + const silentServer = new Server() + testServer(silentServer, done) +}) diff --git a/packages/pg/test/integration/client/no-data-tests.js b/packages/pg/test/integration/client/no-data-tests.js new file mode 100644 index 0000000..ad0f22b --- /dev/null +++ b/packages/pg/test/integration/client/no-data-tests.js @@ -0,0 +1,45 @@ +'use strict' +var helper = require('./test-helper') +const suite = new helper.Suite() + +suite.test('noData message handling', function () { + var client = helper.client() + + var q = client.query({ + name: 'boom', + text: 'create temp table boom(id serial, size integer)', + }) + + client.query( + { + name: 'insert', + text: 'insert into boom(size) values($1)', + values: [100], + }, + function (err, result) { + if (err) { + console.log(err) + throw err + } + } + ) + + client.query({ + name: 'insert', + values: [101], + }) + + var query = client.query( + { + name: 'fetch', + text: 'select size from boom where size < $1', + values: [101], + }, + (err, res) => { + var row = res.rows[0] + assert.strictEqual(row.size, 100) + } + ) + + client.on('drain', client.end.bind(client)) +}) diff --git a/packages/pg/test/integration/client/no-row-result-tests.js b/packages/pg/test/integration/client/no-row-result-tests.js new file mode 100644 index 0000000..6e8f52c --- /dev/null +++ b/packages/pg/test/integration/client/no-row-result-tests.js @@ -0,0 +1,30 @@ +'use strict' +var helper = require('./test-helper') +var pg = helper.pg +const suite = new helper.Suite() +const pool = new pg.Pool() + +suite.test('can access results when no rows are returned', function (done) { + var checkResult = function (result) { + assert(result.fields, 'should have fields definition') + assert.equal(result.fields.length, 1) + assert.equal(result.fields[0].name, 'val') + assert.equal(result.fields[0].dataTypeID, 25) + } + + pool.connect( + assert.success(function (client, release) { + const q = new pg.Query('select $1::text as val limit 0', ['hi']) + var query = client.query( + q, + assert.success(function (result) { + checkResult(result) + release() + pool.end(done) + }) + ) + + assert.emits(query, 'end', checkResult) + }) + ) +}) diff --git a/packages/pg/test/integration/client/notice-tests.js b/packages/pg/test/integration/client/notice-tests.js new file mode 100644 index 0000000..b5d4f3d --- /dev/null +++ b/packages/pg/test/integration/client/notice-tests.js @@ -0,0 +1,75 @@ +'use strict' +const helper = require('./test-helper') +const assert = require('assert') +const suite = new helper.Suite() + +suite.test('emits notify message', function (done) { + const client = helper.client() + client.query( + 'LISTEN boom', + assert.calls(function () { + const otherClient = helper.client() + let bothEmitted = -1 + otherClient.query( + 'LISTEN boom', + assert.calls(function () { + assert.emits(client, 'notification', function (msg) { + // make sure PQfreemem doesn't invalidate string pointers + setTimeout(function () { + assert.equal(msg.channel, 'boom') + assert.ok( + msg.payload == 'omg!' /* 9.x */ || msg.payload == '' /* 8.x */, + 'expected blank payload or correct payload but got ' + msg.message + ) + client.end(++bothEmitted ? done : undefined) + }, 100) + }) + assert.emits(otherClient, 'notification', function (msg) { + assert.equal(msg.channel, 'boom') + otherClient.end(++bothEmitted ? done : undefined) + }) + + client.query("NOTIFY boom, 'omg!'", function (err, q) { + if (err) { + // notify not supported with payload on 8.x + client.query('NOTIFY boom') + } + }) + }) + ) + }) + ) +}) + +// this test fails on travis due to their config +suite.test('emits notice message', function (done) { + if (helper.args.native) { + console.error('notice messages do not work curreintly with node-libpq') + return done() + } + + const client = helper.client() + const text = ` +DO language plpgsql $$ +BEGIN + RAISE NOTICE 'hello, world!' USING ERRCODE = '23505', DETAIL = 'this is a test'; +END +$$; + ` + client.query('SET SESSION client_min_messages=notice', (err) => { + assert.ifError(err) + client.query(text, () => { + client.end() + }) + }) + assert.emits(client, 'notice', function (notice) { + assert.ok(notice != null) + // notice messages should not be error instances + assert(notice instanceof Error === false) + assert.strictEqual(notice.name, 'notice') + assert.strictEqual(notice.message, 'hello, world!') + assert.strictEqual(notice.detail, 'this is a test') + assert.strictEqual(notice.code, '23505') + done() + }) +}) diff --git a/packages/pg/test/integration/client/parse-int-8-tests.js b/packages/pg/test/integration/client/parse-int-8-tests.js new file mode 100644 index 0000000..9f251de --- /dev/null +++ b/packages/pg/test/integration/client/parse-int-8-tests.js @@ -0,0 +1,37 @@ +'use strict' + +var helper = require('../test-helper') +var pg = helper.pg +const suite = new helper.Suite() + +const pool = new pg.Pool(helper.config) +suite.test('ability to turn on and off parser', function () { + if (helper.args.binary) return false + pool.connect( + assert.success(function (client, done) { + pg.defaults.parseInt8 = true + client.query('CREATE TEMP TABLE asdf(id SERIAL PRIMARY KEY)') + client.query( + 'SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf', + assert.success(function (res) { + assert.strictEqual(0, res.rows[0].count) + assert.strictEqual(1, res.rows[0].array[0]) + assert.strictEqual(2, res.rows[0].array[1]) + assert.strictEqual(3, res.rows[0].array[2]) + pg.defaults.parseInt8 = false + client.query( + 'SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf', + assert.success(function (res) { + done() + assert.strictEqual('0', res.rows[0].count) + assert.strictEqual('1', res.rows[0].array[0]) + assert.strictEqual('2', res.rows[0].array[1]) + assert.strictEqual('3', res.rows[0].array[2]) + pool.end() + }) + ) + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/prepared-statement-tests.js b/packages/pg/test/integration/client/prepared-statement-tests.js new file mode 100644 index 0000000..ebc1f73 --- /dev/null +++ b/packages/pg/test/integration/client/prepared-statement-tests.js @@ -0,0 +1,188 @@ +'use strict' +var helper = require('./test-helper') +var Query = helper.pg.Query + +var suite = new helper.Suite() + +;(function () { + var client = helper.client() + client.on('drain', client.end.bind(client)) + + var queryName = 'user by age and like name' + var parseCount = 0 + + suite.test('first named prepared statement', function (done) { + var query = client.query( + new Query({ + text: 'select name from person where age <= $1 and name LIKE $2', + values: [20, 'Bri%'], + name: queryName, + }) + ) + + assert.emits(query, 'row', function (row) { + assert.equal(row.name, 'Brian') + }) + + query.on('end', () => done()) + }) + + suite.test('second named prepared statement with same name & text', function (done) { + var cachedQuery = client.query( + new Query({ + text: 'select name from person where age <= $1 and name LIKE $2', + name: queryName, + values: [10, 'A%'], + }) + ) + + assert.emits(cachedQuery, 'row', function (row) { + assert.equal(row.name, 'Aaron') + }) + + cachedQuery.on('end', () => done()) + }) + + suite.test('with same name, but without query text', function (done) { + var q = client.query( + new Query({ + name: queryName, + values: [30, '%n%'], + }) + ) + + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Aaron') + + // test second row is emitted as well + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Brian') + }) + }) + + q.on('end', () => done()) + }) + + suite.test('with same name, but with different text', function (done) { + client.query( + new Query({ + text: 'select name from person where age >= $1 and name LIKE $2', + name: queryName, + values: [30, '%n%'], + }), + assert.calls((err) => { + assert.equal( + err.message, + `Prepared statements must be unique - '${queryName}' was used for a different statement` + ) + done() + }) + ) + }) +})() +;(function () { + var statementName = 'differ' + var statement1 = 'select count(*)::int4 as count from person' + var statement2 = 'select count(*)::int4 as count from person where age < $1' + + var client1 = helper.client() + var client2 = helper.client() + + suite.test('client 1 execution', function (done) { + var query = client1.query( + { + name: statementName, + text: statement1, + }, + (err, res) => { + assert(!err) + assert.equal(res.rows[0].count, 26) + done() + } + ) + }) + + suite.test('client 2 execution', function (done) { + var query = client2.query( + new Query({ + name: statementName, + text: statement2, + values: [11], + }) + ) + + assert.emits(query, 'row', function (row) { + assert.equal(row.count, 1) + }) + + assert.emits(query, 'end', function () { + done() + }) + }) + + suite.test('clean up clients', () => { + return client1.end().then(() => client2.end()) + }) +})() +;(function () { + var client = helper.client() + client.query('CREATE TEMP TABLE zoom(name varchar(100));') + client.query("INSERT INTO zoom (name) VALUES ('zed')") + client.query("INSERT INTO zoom (name) VALUES ('postgres')") + client.query("INSERT INTO zoom (name) VALUES ('node postgres')") + + var checkForResults = function (q) { + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'node postgres') + + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'postgres') + + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'zed') + }) + }) + }) + } + + suite.test('with small row count', function (done) { + var query = client.query( + new Query( + { + name: 'get names', + text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"', + rows: 1, + }, + done + ) + ) + + checkForResults(query) + }) + + suite.test('with large row count', function (done) { + var query = client.query( + new Query( + { + name: 'get names', + text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"', + rows: 1000, + }, + done + ) + ) + checkForResults(query) + }) + + suite.testAsync('with no data response and rows', async function () { + const result = await client.query({ + name: 'some insert', + text: '', + values: [], + rows: 1, + }) + assert.equal(result.rows.length, 0) + }) + + suite.test('cleanup', () => client.end()) +})() diff --git a/packages/pg/test/integration/client/promise-api-tests.js b/packages/pg/test/integration/client/promise-api-tests.js new file mode 100644 index 0000000..d8128cf --- /dev/null +++ b/packages/pg/test/integration/client/promise-api-tests.js @@ -0,0 +1,43 @@ +'use strict' + +const helper = require('./test-helper') +const pg = helper.pg + +const suite = new helper.Suite() + +suite.test('valid connection completes promise', () => { + const client = new pg.Client() + return client.connect().then(() => { + return client.end().then(() => {}) + }) +}) + +suite.test('valid connection completes promise', () => { + const client = new pg.Client() + return client.connect().then(() => { + return client.end().then(() => {}) + }) +}) + +suite.test('invalid connection rejects promise', (done) => { + const client = new pg.Client({ host: 'alksdjflaskdfj', port: 1234 }) + return client.connect().catch((e) => { + assert(e instanceof Error) + done() + }) +}) + +suite.test('connected client does not reject promise after connection', (done) => { + const client = new pg.Client() + return client.connect().then(() => { + setTimeout(() => { + client.on('error', (e) => { + assert(e instanceof Error) + client.end() + done() + }) + // manually kill the connection + client.emit('error', new Error('something bad happened...but not really')) + }, 50) + }) +}) diff --git a/packages/pg/test/integration/client/query-as-promise-tests.js b/packages/pg/test/integration/client/query-as-promise-tests.js new file mode 100644 index 0000000..30c106f --- /dev/null +++ b/packages/pg/test/integration/client/query-as-promise-tests.js @@ -0,0 +1,54 @@ +'use strict' +var bluebird = require('bluebird') +var helper = require('../test-helper') +var pg = helper.pg + +process.on('unhandledRejection', function (e) { + console.error(e, e.stack) + process.exit(1) +}) + +const suite = new helper.Suite() + +suite.test('promise API', (cb) => { + const pool = new pg.Pool() + pool.connect().then((client) => { + client + .query('SELECT $1::text as name', ['foo']) + .then(function (result) { + assert.equal(result.rows[0].name, 'foo') + return client + }) + .then(function (client) { + client.query('ALKJSDF').catch(function (e) { + assert(e instanceof Error) + client.query('SELECT 1 as num').then(function (result) { + assert.equal(result.rows[0].num, 1) + client.release() + pool.end(cb) + }) + }) + }) + }) +}) + +suite.test('promise API with configurable promise type', (cb) => { + const client = new pg.Client({ Promise: bluebird }) + const connectPromise = client.connect() + assert(connectPromise instanceof bluebird, 'Client connect() returns configured promise') + + connectPromise + .then(() => { + const queryPromise = client.query('SELECT 1') + assert(queryPromise instanceof bluebird, 'Client query() returns configured promise') + + return queryPromise.then(() => { + client.end(cb) + }) + }) + .catch((error) => { + process.nextTick(() => { + throw error + }) + }) +}) diff --git a/packages/pg/test/integration/client/query-column-names-tests.js b/packages/pg/test/integration/client/query-column-names-tests.js new file mode 100644 index 0000000..a109209 --- /dev/null +++ b/packages/pg/test/integration/client/query-column-names-tests.js @@ -0,0 +1,20 @@ +'use strict' +var helper = require('../test-helper') +var pg = helper.pg + +new helper.Suite().test('support for complex column names', function () { + const pool = new pg.Pool() + pool.connect( + assert.success(function (client, done) { + client.query('CREATE TEMP TABLE t ( "complex\'\'column" TEXT )') + client.query( + 'SELECT * FROM t', + assert.success(function (res) { + done() + assert.strictEqual(res.fields[0].name, "complex''column") + pool.end() + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/query-error-handling-prepared-statement-tests.js b/packages/pg/test/integration/client/query-error-handling-prepared-statement-tests.js new file mode 100644 index 0000000..adef58d --- /dev/null +++ b/packages/pg/test/integration/client/query-error-handling-prepared-statement-tests.js @@ -0,0 +1,125 @@ +'use strict' +var helper = require('./test-helper') +var Query = helper.pg.Query +var util = require('util') + +var suite = new helper.Suite() + +suite.test('client end during query execution of prepared statement', function (done) { + var client = new Client() + client.connect( + assert.success(function () { + var sleepQuery = 'select pg_sleep($1)' + + var queryConfig = { + name: 'sleep query', + text: sleepQuery, + values: [5], + } + + var queryInstance = new Query( + queryConfig, + assert.calls(function (err, result) { + assert.equal(err.message, 'Connection terminated') + done() + }) + ) + + var query1 = client.query(queryInstance) + + query1.on('error', function (err) { + assert.fail('Prepared statement should not emit error') + }) + + query1.on('row', function (row) { + assert.fail('Prepared statement should not emit row') + }) + + query1.on('end', function (err) { + assert.fail('Prepared statement when executed should not return before being killed') + }) + + client.end() + }) + ) +}) + +function killIdleQuery(targetQuery, cb) { + var client2 = new Client(helper.args) + var pidColName = 'procpid' + var queryColName = 'current_query' + client2.connect( + assert.success(function () { + helper.versionGTE( + client2, + 90200, + assert.success(function (isGreater) { + if (isGreater) { + pidColName = 'pid' + queryColName = 'query' + } + var killIdleQuery = + 'SELECT ' + + pidColName + + ', (SELECT pg_terminate_backend(' + + pidColName + + ')) AS killed FROM pg_stat_activity WHERE ' + + queryColName + + ' = $1' + client2.query( + killIdleQuery, + [targetQuery], + assert.calls(function (err, res) { + assert.ifError(err) + assert.equal(res.rows.length, 1) + client2.end(cb) + assert.emits(client2, 'end') + }) + ) + }) + ) + }) + ) +} + +suite.test('query killed during query execution of prepared statement', function (done) { + if (helper.args.native) { + return done() + } + var client = new Client(helper.args) + client.connect( + assert.success(function () { + var sleepQuery = 'select pg_sleep($1)' + + const queryConfig = { + name: 'sleep query', + text: sleepQuery, + values: [5], + } + + // client should emit an error because it is unexpectedly disconnected + assert.emits(client, 'error') + + var query1 = client.query( + new Query(queryConfig), + assert.calls(function (err, result) { + assert.equal(err.message, 'terminating connection due to administrator command') + }) + ) + + query1.on('error', function (err) { + assert.fail('Prepared statement should not emit error') + }) + + query1.on('row', function (row) { + assert.fail('Prepared statement should not emit row') + }) + + query1.on('end', function (err) { + assert.fail('Prepared statement when executed should not return before being killed') + }) + + killIdleQuery(sleepQuery, done) + }) + ) +}) diff --git a/packages/pg/test/integration/client/query-error-handling-tests.js b/packages/pg/test/integration/client/query-error-handling-tests.js new file mode 100644 index 0000000..3ede5d9 --- /dev/null +++ b/packages/pg/test/integration/client/query-error-handling-tests.js @@ -0,0 +1,122 @@ +'use strict' +var helper = require('./test-helper') +var util = require('util') +var Query = helper.pg.Query +var DatabaseError = helper.pg.DatabaseError + +test('error during query execution', function () { + var client = new Client(helper.args) + client.connect( + assert.success(function () { + var queryText = 'select pg_sleep(10)' + var sleepQuery = new Query(queryText) + var pidColName = 'procpid' + var queryColName = 'current_query' + helper.versionGTE( + client, + 90200, + assert.success(function (isGreater) { + if (isGreater) { + pidColName = 'pid' + queryColName = 'query' + } + var query1 = client.query( + sleepQuery, + assert.calls(function (err, result) { + assert(err) + client.end() + }) + ) + //ensure query1 does not emit an 'end' event + //because it was killed and received an error + //https://github.com/brianc/node-postgres/issues/547 + query1.on('end', function () { + assert.fail('Query with an error should not emit "end" event') + }) + setTimeout(function () { + var client2 = new Client(helper.args) + client2.connect( + assert.success(function () { + var killIdleQuery = `SELECT ${pidColName}, (SELECT pg_cancel_backend(${pidColName})) AS killed FROM pg_stat_activity WHERE ${queryColName} LIKE $1` + client2.query( + killIdleQuery, + [queryText], + assert.calls(function (err, res) { + assert.ifError(err) + assert(res.rows.length > 0) + client2.end() + assert.emits(client2, 'end') + }) + ) + }) + ) + }, 300) + }) + ) + }) + ) +}) + +if (helper.config.native) { + return +} + +test('9.3 column error fields', function () { + var client = new Client(helper.args) + client.connect( + assert.success(function () { + helper.versionGTE( + client, + 90300, + assert.success(function (isGreater) { + if (!isGreater) { + return client.end() + } + + client.query('CREATE TEMP TABLE column_err_test(a int NOT NULL)') + client.query('INSERT INTO column_err_test(a) VALUES (NULL)', function (err) { + if (!helper.config.native) { + assert(err instanceof DatabaseError) + } + assert.equal(err.severity, 'ERROR') + assert.equal(err.code, '23502') + assert.equal(err.table, 'column_err_test') + assert.equal(err.column, 'a') + return client.end() + }) + }) + ) + }) + ) +}) + +test('9.3 constraint error fields', function () { + var client = new Client(helper.args) + client.connect( + assert.success(function () { + helper.versionGTE( + client, + 90300, + assert.success(function (isGreater) { + if (!isGreater) { + console.log('skip 9.3 error field on older versions of postgres') + return client.end() + } + + client.query('CREATE TEMP TABLE constraint_err_test(a int PRIMARY KEY)') + client.query('INSERT INTO constraint_err_test(a) VALUES (1)') + client.query('INSERT INTO constraint_err_test(a) VALUES (1)', function (err) { + if (!helper.config.native) { + assert(err instanceof DatabaseError) + } + assert.equal(err.severity, 'ERROR') + assert.equal(err.code, '23505') + assert.equal(err.table, 'constraint_err_test') + assert.equal(err.constraint, 'constraint_err_test_pkey') + return client.end() + }) + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/quick-disconnect-tests.js b/packages/pg/test/integration/client/quick-disconnect-tests.js new file mode 100644 index 0000000..066411f --- /dev/null +++ b/packages/pg/test/integration/client/quick-disconnect-tests.js @@ -0,0 +1,8 @@ +'use strict' +// test for issue #320 +// +var helper = require('./test-helper') + +var client = new helper.pg.Client(helper.config) +client.connect() +client.end() diff --git a/packages/pg/test/integration/client/result-metadata-tests.js b/packages/pg/test/integration/client/result-metadata-tests.js new file mode 100644 index 0000000..66d9ac4 --- /dev/null +++ b/packages/pg/test/integration/client/result-metadata-tests.js @@ -0,0 +1,47 @@ +'use strict' +var helper = require('./test-helper') +var pg = helper.pg + +const pool = new pg.Pool() +new helper.Suite().test('should return insert metadata', function () { + pool.connect( + assert.calls(function (err, client, done) { + assert(!err) + + helper.versionGTE( + client, + 90000, + assert.success(function (hasRowCount) { + client.query( + 'CREATE TEMP TABLE zugzug(name varchar(10))', + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.oid, null) + assert.equal(result.command, 'CREATE') + + var q = client.query( + "INSERT INTO zugzug(name) VALUES('more work?')", + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.command, 'INSERT') + assert.equal(result.rowCount, 1) + + client.query( + 'SELECT * FROM zugzug', + assert.calls(function (err, result) { + assert(!err) + if (hasRowCount) assert.equal(result.rowCount, 1) + assert.equal(result.command, 'SELECT') + done() + process.nextTick(pool.end.bind(pool)) + }) + ) + }) + ) + }) + ) + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/results-as-array-tests.js b/packages/pg/test/integration/client/results-as-array-tests.js new file mode 100644 index 0000000..5ebb2a9 --- /dev/null +++ b/packages/pg/test/integration/client/results-as-array-tests.js @@ -0,0 +1,36 @@ +'use strict' +var util = require('util') +var helper = require('./test-helper') + +var Client = helper.Client + +var conInfo = helper.config + +test('returns results as array', function () { + var client = new Client(conInfo) + var checkRow = function (row) { + assert(util.isArray(row), 'row should be an array') + assert.equal(row.length, 4) + assert.equal(row[0].getFullYear(), new Date().getFullYear()) + assert.strictEqual(row[1], 1) + assert.strictEqual(row[2], 'hai') + assert.strictEqual(row[3], null) + } + client.connect( + assert.success(function () { + var config = { + text: 'SELECT NOW(), 1::int, $1::text, null', + values: ['hai'], + rowMode: 'array', + } + var query = client.query( + config, + assert.success(function (result) { + assert.equal(result.rows.length, 1) + checkRow(result.rows[0]) + client.end() + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/row-description-on-results-tests.js b/packages/pg/test/integration/client/row-description-on-results-tests.js new file mode 100644 index 0000000..688b96e --- /dev/null +++ b/packages/pg/test/integration/client/row-description-on-results-tests.js @@ -0,0 +1,50 @@ +'use strict' +var helper = require('./test-helper') + +var Client = helper.Client + +var conInfo = helper.config + +var checkResult = function (result) { + assert(result.fields) + assert.equal(result.fields.length, 3) + var fields = result.fields + assert.equal(fields[0].name, 'now') + assert.equal(fields[1].name, 'num') + assert.equal(fields[2].name, 'texty') + assert.equal(fields[0].dataTypeID, 1184) + assert.equal(fields[1].dataTypeID, 23) + assert.equal(fields[2].dataTypeID, 25) +} + +test('row descriptions on result object', function () { + var client = new Client(conInfo) + client.connect( + assert.success(function () { + client.query( + 'SELECT NOW() as now, 1::int as num, $1::text as texty', + ['hello'], + assert.success(function (result) { + checkResult(result) + client.end() + }) + ) + }) + ) +}) + +test('row description on no rows', function () { + var client = new Client(conInfo) + client.connect( + assert.success(function () { + client.query( + 'SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0', + ['hello'], + assert.success(function (result) { + checkResult(result) + client.end() + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/sasl-scram-tests.js b/packages/pg/test/integration/client/sasl-scram-tests.js new file mode 100644 index 0000000..debc286 --- /dev/null +++ b/packages/pg/test/integration/client/sasl-scram-tests.js @@ -0,0 +1,75 @@ +'use strict' +const helper = require('./../test-helper') +const pg = helper.pg +const suite = new helper.Suite() +const { native } = helper.args + +/** + * This test only executes if the env variables SCRAM_TEST_PGUSER and + * SCRAM_TEST_PGPASSWORD are defined. You can override additional values + * for the host, port and database with other SCRAM_TEST_ prefixed vars. + * If the variables are not defined the test will be skipped. + * + * SQL to create test role: + * + * SET password_encryption = 'scram-sha-256'; + * CREATE ROLE scram_test login password 'test4scram'; + * + * Add the following entries to pg_hba.conf: + * + * host all scram_test ::1/128 scram-sha-256 + * host all scram_test 0.0.0.0/0 scram-sha-256 + * + * Then run this file with after exporting: + * + * SCRAM_TEST_PGUSER=scram_test + * SCRAM_TEST_PGPASSWORD=test4scram + */ + +// Base config for SCRAM tests +const config = { + user: process.env.SCRAM_TEST_PGUSER, + password: process.env.SCRAM_TEST_PGPASSWORD, + host: process.env.SCRAM_TEST_PGHOST, // optional + port: process.env.SCRAM_TEST_PGPORT, // optional + database: process.env.SCRAM_TEST_PGDATABASE, // optional +} + +if (native) { + suite.testAsync('skipping SCRAM tests (on native)', () => {}) + return +} +if (!config.user || !config.password) { + suite.testAsync('skipping SCRAM tests (missing env)', () => {}) + return +} + +suite.testAsync('can connect using sasl/scram', async () => { + const client = new pg.Client(config) + let usingSasl = false + client.connection.once('authenticationSASL', () => { + usingSasl = true + }) + await client.connect() + assert.ok(usingSasl, 'Should be using SASL for authentication') + await client.end() +}) + +suite.testAsync('sasl/scram fails when password is wrong', async () => { + const client = new pg.Client({ + ...config, + password: config.password + 'append-something-to-make-it-bad', + }) + let usingSasl = false + client.connection.once('authenticationSASL', () => { + usingSasl = true + }) + await assert.rejects( + () => client.connect(), + { + code: '28P01', + }, + 'Error code should be for a password error' + ) + assert.ok(usingSasl, 'Should be using SASL for authentication') +}) diff --git a/packages/pg/test/integration/client/simple-query-tests.js b/packages/pg/test/integration/client/simple-query-tests.js new file mode 100644 index 0000000..d22d747 --- /dev/null +++ b/packages/pg/test/integration/client/simple-query-tests.js @@ -0,0 +1,98 @@ +'use strict' +var helper = require('./test-helper') +var Query = helper.pg.Query + +// before running this test make sure you run the script create-test-tables +test('simple query interface', function () { + var client = helper.client() + + var query = client.query(new Query('select name from person order by name collate "C"')) + + client.on('drain', client.end.bind(client)) + + var rows = [] + query.on('row', function (row, result) { + assert.ok(result) + rows.push(row['name']) + }) + query.once('row', function (row) { + test('Can iterate through columns', function () { + var columnCount = 0 + for (var column in row) { + columnCount++ + } + if ('length' in row) { + assert.lengthIs( + row, + columnCount, + 'Iterating through the columns gives a different length from calling .length.' + ) + } + }) + }) + + assert.emits(query, 'end', function () { + test('returned right number of rows', function () { + assert.lengthIs(rows, 26) + }) + test('row ordering', function () { + assert.equal(rows[0], 'Aaron') + assert.equal(rows[25], 'Zanzabar') + }) + }) +}) + +test('prepared statements do not mutate params', function () { + var client = helper.client() + + var params = [1] + + var query = client.query(new Query('select name from person where $1 = 1 order by name collate "C"', params)) + + assert.deepEqual(params, [1]) + + client.on('drain', client.end.bind(client)) + + const rows = [] + query.on('row', function (row, result) { + assert.ok(result) + rows.push(row) + }) + + query.on('end', function (result) { + assert.lengthIs(rows, 26, 'result returned wrong number of rows') + assert.lengthIs(rows, result.rowCount) + assert.equal(rows[0].name, 'Aaron') + assert.equal(rows[25].name, 'Zanzabar') + }) +}) + +test('multiple simple queries', function () { + var client = helper.client() + client.query({ text: "create temp table bang(id serial, name varchar(5));insert into bang(name) VALUES('boom');" }) + client.query("insert into bang(name) VALUES ('yes');") + var query = client.query(new Query('select name from bang')) + assert.emits(query, 'row', function (row) { + assert.equal(row['name'], 'boom') + assert.emits(query, 'row', function (row) { + assert.equal(row['name'], 'yes') + }) + }) + client.on('drain', client.end.bind(client)) +}) + +test('multiple select statements', function () { + var client = helper.client() + client.query( + 'create temp table boom(age integer); insert into boom(age) values(1); insert into boom(age) values(2); insert into boom(age) values(3)' + ) + client.query({ text: "create temp table bang(name varchar(5)); insert into bang(name) values('zoom');" }) + var result = client.query(new Query({ text: 'select age from boom where age < 2; select name from bang' })) + assert.emits(result, 'row', function (row) { + assert.strictEqual(row['age'], 1) + assert.emits(result, 'row', function (row) { + assert.strictEqual(row['name'], 'zoom') + }) + }) + client.on('drain', client.end.bind(client)) +}) diff --git a/packages/pg/test/integration/client/ssl-tests.js b/packages/pg/test/integration/client/ssl-tests.js new file mode 100644 index 0000000..97aa594 --- /dev/null +++ b/packages/pg/test/integration/client/ssl-tests.js @@ -0,0 +1,21 @@ +'use strict' +var pg = require('../../../lib') +var config = require('./test-helper').config +test('can connect with ssl', function () { + return false + config.ssl = { + rejectUnauthorized: false, + } + pg.connect( + config, + assert.success(function (client) { + return false + client.query( + 'SELECT NOW()', + assert.success(function () { + pg.end() + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/client/statement_timeout-tests.js b/packages/pg/test/integration/client/statement_timeout-tests.js new file mode 100644 index 0000000..e0898cc --- /dev/null +++ b/packages/pg/test/integration/client/statement_timeout-tests.js @@ -0,0 +1,84 @@ +'use strict' +var helper = require('./test-helper') +var Client = helper.Client + +var suite = new helper.Suite() + +var conInfo = helper.config + +function getConInfo(override) { + return Object.assign({}, conInfo, override) +} + +function getStatementTimeout(conf, cb) { + var client = new Client(conf) + client.connect( + assert.success(function () { + client.query( + 'SHOW statement_timeout', + assert.success(function (res) { + var statementTimeout = res.rows[0].statement_timeout + cb(statementTimeout) + client.end() + }) + ) + }) + ) +} + +if (!helper.args.native) { + // statement_timeout is not supported with the native client + suite.test('No default statement_timeout ', function (done) { + getConInfo() + getStatementTimeout({}, function (res) { + assert.strictEqual(res, '0') // 0 = no timeout + done() + }) + }) + + suite.test('statement_timeout integer is used', function (done) { + var conf = getConInfo({ + statement_timeout: 3000, + }) + getStatementTimeout(conf, function (res) { + assert.strictEqual(res, '3s') + done() + }) + }) + + suite.test('statement_timeout float is used', function (done) { + var conf = getConInfo({ + statement_timeout: 3000.7, + }) + getStatementTimeout(conf, function (res) { + assert.strictEqual(res, '3s') + done() + }) + }) + + suite.test('statement_timeout string is used', function (done) { + var conf = getConInfo({ + statement_timeout: '3000', + }) + getStatementTimeout(conf, function (res) { + assert.strictEqual(res, '3s') + done() + }) + }) + + suite.test('statement_timeout actually cancels long running queries', function (done) { + var conf = getConInfo({ + statement_timeout: '10', // 10ms to keep tests running fast + }) + var client = new Client(conf) + client.connect( + assert.success(function () { + client.query('SELECT pg_sleep( 1 )', function (error) { + client.end() + assert.strictEqual(error.code, '57014') // query_cancelled + done() + }) + }) + ) + }) +} diff --git a/packages/pg/test/integration/client/test-helper.js b/packages/pg/test/integration/client/test-helper.js new file mode 100644 index 0000000..14f8134 --- /dev/null +++ b/packages/pg/test/integration/client/test-helper.js @@ -0,0 +1,4 @@ +'use strict' +var helper = require('./../test-helper') + +module.exports = helper diff --git a/packages/pg/test/integration/client/timezone-tests.js b/packages/pg/test/integration/client/timezone-tests.js new file mode 100644 index 0000000..c9f6a8c --- /dev/null +++ b/packages/pg/test/integration/client/timezone-tests.js @@ -0,0 +1,34 @@ +'use strict' +var helper = require('./../test-helper') +var exec = require('child_process').exec + +var oldTz = process.env.TZ +process.env.TZ = 'Europe/Berlin' + +var date = new Date() + +const pool = new helper.pg.Pool() +const suite = new helper.Suite() + +pool.connect(function (err, client, done) { + assert(!err) + + suite.test('timestamp without time zone', function (cb) { + client.query('SELECT CAST($1 AS TIMESTAMP WITHOUT TIME ZONE) AS "val"', [date], function (err, result) { + assert(!err) + assert.equal(result.rows[0].val.getTime(), date.getTime()) + cb() + }) + }) + + suite.test('timestamp with time zone', function (cb) { + client.query('SELECT CAST($1 AS TIMESTAMP WITH TIME ZONE) AS "val"', [date], function (err, result) { + assert(!err) + assert.equal(result.rows[0].val.getTime(), date.getTime()) + + done() + pool.end(cb) + process.env.TZ = oldTz + }) + }) +}) diff --git a/packages/pg/test/integration/client/transaction-tests.js b/packages/pg/test/integration/client/transaction-tests.js new file mode 100644 index 0000000..18f8ff0 --- /dev/null +++ b/packages/pg/test/integration/client/transaction-tests.js @@ -0,0 +1,99 @@ +'use strict' +var helper = require('./test-helper') +const suite = new helper.Suite() +const pg = helper.pg + +const client = new pg.Client() +client.connect( + assert.success(function () { + client.query('begin') + + var getZed = { + text: 'SELECT * FROM person WHERE name = $1', + values: ['Zed'], + } + + suite.test('name should not exist in the database', function (done) { + client.query( + getZed, + assert.calls(function (err, result) { + assert(!err) + assert.empty(result.rows) + done() + }) + ) + }) + + suite.test('can insert name', (done) => { + client.query( + 'INSERT INTO person(name, age) VALUES($1, $2)', + ['Zed', 270], + assert.calls(function (err, result) { + assert(!err) + done() + }) + ) + }) + + suite.test('name should exist in the database', function (done) { + client.query( + getZed, + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.rows[0].name, 'Zed') + done() + }) + ) + }) + + suite.test('rollback', (done) => { + client.query('rollback', done) + }) + + suite.test('name should not exist in the database', function (done) { + client.query( + getZed, + assert.calls(function (err, result) { + assert(!err) + assert.empty(result.rows) + client.end(done) + }) + ) + }) + }) +) + +suite.test('gh#36', function (cb) { + const pool = new pg.Pool() + pool.connect( + assert.success(function (client, done) { + client.query('BEGIN') + client.query( + { + name: 'X', + text: 'SELECT $1::INTEGER', + values: [0], + }, + assert.calls(function (err, result) { + if (err) throw err + assert.equal(result.rows.length, 1) + }) + ) + client.query( + { + name: 'X', + text: 'SELECT $1::INTEGER', + values: [0], + }, + assert.calls(function (err, result) { + if (err) throw err + assert.equal(result.rows.length, 1) + }) + ) + client.query('COMMIT', function () { + done() + pool.end(cb) + }) + }) + ) +}) diff --git a/packages/pg/test/integration/client/type-coercion-tests.js b/packages/pg/test/integration/client/type-coercion-tests.js new file mode 100644 index 0000000..33249a9 --- /dev/null +++ b/packages/pg/test/integration/client/type-coercion-tests.js @@ -0,0 +1,243 @@ +'use strict' +var helper = require('./test-helper') +var pg = helper.pg +var sink +const suite = new helper.Suite() + +var testForTypeCoercion = function (type) { + const pool = new pg.Pool() + suite.test(`test type coercion ${type.name}`, (cb) => { + pool.connect(function (err, client, done) { + assert(!err) + client.query( + 'create temp table test_type(col ' + type.name + ')', + assert.calls(function (err, result) { + assert(!err) + + type.values.forEach(function (val) { + var insertQuery = client.query( + 'insert into test_type(col) VALUES($1)', + [val], + assert.calls(function (err, result) { + assert(!err) + }) + ) + + var query = client.query( + new pg.Query({ + name: 'get type ' + type.name, + text: 'select col from test_type', + }) + ) + + query.on('error', function (err) { + console.log(err) + throw err + }) + + assert.emits( + query, + 'row', + function (row) { + var expected = val + ' (' + typeof val + ')' + var returned = row.col + ' (' + typeof row.col + ')' + assert.strictEqual(row.col, val, 'expected ' + type.name + ' of ' + expected + ' but got ' + returned) + }, + 'row should have been called for ' + type.name + ' of ' + val + ) + + client.query('delete from test_type') + }) + + client.query('drop table test_type', function () { + done() + pool.end(cb) + }) + }) + ) + }) + }) +} + +var types = [ + { + name: 'integer', + values: [-2147483648, -1, 0, 1, 2147483647, null], + }, + { + name: 'smallint', + values: [-32768, -1, 0, 1, 32767, null], + }, + { + name: 'bigint', + values: [ + '-9223372036854775808', + '-9007199254740992', + '0', + '9007199254740992', + '72057594037928030', + '9223372036854775807', + null, + ], + }, + { + name: 'varchar(5)', + values: ['yo', '', 'zomg!', null], + }, + { + name: 'oid', + values: [0, 204410, null], + }, + { + name: 'bool', + values: [true, false, null], + }, + { + name: 'numeric', + values: [ + '-12.34', + '0', + '12.34', + '-3141592653589793238462643383279502.1618033988749894848204586834365638', + '3141592653589793238462643383279502.1618033988749894848204586834365638', + null, + ], + }, + { + name: 'real', + values: [-101.3, -1.2, 0, 1.2, 101.1, null], + }, + { + name: 'double precision', + values: [-101.3, -1.2, 0, 1.2, 101.1, null], + }, + { + name: 'timestamptz', + values: [null], + }, + { + name: 'timestamp', + values: [null], + }, + { + name: 'timetz', + values: ['13:11:12.1234-05:30', null], + }, + { + name: 'time', + values: ['13:12:12.321', null], + }, +] + +// ignore some tests in binary mode +if (helper.config.binary) { + types = types.filter(function (type) { + return !(type.name in { real: 1, timetz: 1, time: 1, numeric: 1, bigint: 1 }) + }) +} + +var valueCount = 0 + +types.forEach(function (type) { + testForTypeCoercion(type) +}) + +suite.test('timestampz round trip', function (cb) { + var now = new Date() + var client = helper.client() + client.query('create temp table date_tests(name varchar(10), tstz timestamptz(3))') + client.query({ + text: 'insert into date_tests(name, tstz)VALUES($1, $2)', + name: 'add date', + values: ['now', now], + }) + var result = client.query( + new pg.Query({ + name: 'get date', + text: 'select * from date_tests where name = $1', + values: ['now'], + }) + ) + + assert.emits(result, 'row', function (row) { + var date = row.tstz + assert.equal(date.getYear(), now.getYear()) + assert.equal(date.getMonth(), now.getMonth()) + assert.equal(date.getDate(), now.getDate()) + assert.equal(date.getHours(), now.getHours()) + assert.equal(date.getMinutes(), now.getMinutes()) + assert.equal(date.getSeconds(), now.getSeconds()) + assert.equal(date.getMilliseconds(), now.getMilliseconds()) + }) + + client.on('drain', () => { + client.end(cb) + }) +}) + +suite.test('selecting nulls', (cb) => { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, done) { + assert.ifError(err) + client.query( + 'select null as res;', + assert.calls(function (err, res) { + assert(!err) + assert.strictEqual(res.rows[0].res, null) + }) + ) + client.query('select 7 <> $1 as res;', [null], function (err, res) { + assert(!err) + assert.strictEqual(res.rows[0].res, null) + done() + pool.end(cb) + }) + }) + ) +}) + +suite.test('date range extremes', function (done) { + var client = helper.client() + + // Set the server timeszone to the same as used for the test, + // otherwise (if server's timezone is ahead of GMT) in + // textParsers.js::parseDate() the timezone offest is added to the date; + // in the case of "275760-09-13 00:00:00 GMT" the timevalue overflows. + client.query( + 'SET TIMEZONE TO GMT', + assert.success(function (res) { + // PostgreSQL supports date range of 4713 BCE to 294276 CE + // http://www.postgresql.org/docs/9.2/static/datatype-datetime.html + // ECMAScript supports date range of Apr 20 271821 BCE to Sep 13 275760 CE + // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 + client.query( + 'SELECT $1::TIMESTAMPTZ as when', + ['275760-09-13 00:00:00 GMT'], + assert.success(function (res) { + assert.equal(res.rows[0].when.getFullYear(), 275760) + }) + ) + + client.query( + 'SELECT $1::TIMESTAMPTZ as when', + ['4713-12-31 12:31:59 BC GMT'], + assert.success(function (res) { + assert.equal(res.rows[0].when.getFullYear(), -4712) + }) + ) + + client.query( + 'SELECT $1::TIMESTAMPTZ as when', + ['275760-09-13 00:00:00 -15:00'], + assert.success(function (res) { + assert(isNaN(res.rows[0].when.getTime())) + }) + ) + + client.on('drain', () => { + client.end(done) + }) + }) + ) +}) diff --git a/packages/pg/test/integration/client/type-parser-override-tests.js b/packages/pg/test/integration/client/type-parser-override-tests.js new file mode 100644 index 0000000..42c3daf --- /dev/null +++ b/packages/pg/test/integration/client/type-parser-override-tests.js @@ -0,0 +1,44 @@ +'use strict' +var helper = require('./test-helper') + +function testTypeParser(client, expectedResult, done) { + var boolValue = true + client.query('CREATE TEMP TABLE parserOverrideTest(id bool)') + client.query('INSERT INTO parserOverrideTest(id) VALUES ($1)', [boolValue]) + client.query( + 'SELECT * FROM parserOverrideTest', + assert.success(function (result) { + assert.equal(result.rows[0].id, expectedResult) + done() + }) + ) +} + +const pool = new helper.pg.Pool(helper.config) +pool.connect( + assert.success(function (client1, done1) { + pool.connect( + assert.success(function (client2, done2) { + var boolTypeOID = 16 + client1.setTypeParser(boolTypeOID, function () { + return 'first client' + }) + client2.setTypeParser(boolTypeOID, function () { + return 'second client' + }) + + client1.setTypeParser(boolTypeOID, 'binary', function () { + return 'first client binary' + }) + client2.setTypeParser(boolTypeOID, 'binary', function () { + return 'second client binary' + }) + + testTypeParser(client1, 'first client', () => { + done1() + testTypeParser(client2, 'second client', () => done2(), pool.end()) + }) + }) + ) + }) +) diff --git a/packages/pg/test/integration/connection-pool/connection-pool-size-tests.js b/packages/pg/test/integration/connection-pool/connection-pool-size-tests.js new file mode 100644 index 0000000..da281a1 --- /dev/null +++ b/packages/pg/test/integration/connection-pool/connection-pool-size-tests.js @@ -0,0 +1,10 @@ +'use strict' +var helper = require('./test-helper') + +helper.testPoolSize(1) + +helper.testPoolSize(2) + +helper.testPoolSize(40) + +helper.testPoolSize(200) diff --git a/packages/pg/test/integration/connection-pool/error-tests.js b/packages/pg/test/integration/connection-pool/error-tests.js new file mode 100644 index 0000000..f3f9cdc --- /dev/null +++ b/packages/pg/test/integration/connection-pool/error-tests.js @@ -0,0 +1,166 @@ +'use strict' +var helper = require('./test-helper') +const pg = helper.pg +const native = helper.args.native + +const suite = new helper.Suite() +suite.test('connecting to invalid port', (cb) => { + const pool = new pg.Pool({ port: 13801 }) + pool.connect().catch((e) => cb()) +}) + +suite.test('errors emitted on checked-out clients', (cb) => { + // make pool hold 2 clients + const pool = new pg.Pool({ max: 2 }) + // get first client + pool.connect( + assert.success(function (client, done) { + client.query('SELECT NOW()', function () { + pool.connect( + assert.success(function (client2, done2) { + var pidColName = 'procpid' + helper.versionGTE( + client2, + 90200, + assert.success(function (isGreater) { + var killIdleQuery = + 'SELECT pid, (SELECT pg_terminate_backend(pid)) AS killed FROM pg_stat_activity WHERE state = $1' + var params = ['idle'] + if (!isGreater) { + killIdleQuery = + 'SELECT procpid, (SELECT pg_terminate_backend(procpid)) AS killed FROM pg_stat_activity WHERE current_query LIKE $1' + params = ['%IDLE%'] + } + + client.once('error', (err) => { + client.on('error', (err) => {}) + done(err) + cb() + }) + + // kill the connection from client + client2.query( + killIdleQuery, + params, + assert.success(function (res) { + // check to make sure client connection actually was killed + // return client2 to the pool + done2() + pool.end() + }) + ) + }) + ) + }) + ) + }) + }) + ) +}) + +suite.test('connection-level errors cause queued queries to fail', (cb) => { + const pool = new pg.Pool() + pool.connect( + assert.success((client, done) => { + client.query( + 'SELECT pg_terminate_backend(pg_backend_pid())', + assert.calls((err) => { + if (helper.args.native) { + assert.ok(err) + } else { + assert.equal(err.code, '57P01') + } + }) + ) + + client.once( + 'error', + assert.calls((err) => { + client.on('error', (err) => {}) + }) + ) + + client.query( + 'SELECT 1', + assert.calls((err) => { + if (helper.args.native) { + assert.equal(err.message, 'terminating connection due to administrator command') + } else { + assert.equal(err.message, 'Connection terminated unexpectedly') + } + + done(err) + pool.end() + cb() + }) + ) + }) + ) +}) + +suite.test('connection-level errors cause future queries to fail', (cb) => { + const pool = new pg.Pool() + pool.connect( + assert.success((client, done) => { + client.query( + 'SELECT pg_terminate_backend(pg_backend_pid())', + assert.calls((err) => { + if (helper.args.native) { + assert.ok(err) + } else { + assert.equal(err.code, '57P01') + } + }) + ) + + client.once( + 'error', + assert.calls((err) => { + client.on('error', (err) => {}) + client.query( + 'SELECT 1', + assert.calls((err) => { + if (helper.args.native) { + assert.equal(err.message, 'terminating connection due to administrator command') + } else { + assert.equal(err.message, 'Client has encountered a connection error and is not queryable') + } + + done(err) + pool.end() + cb() + }) + ) + }) + ) + }) + ) +}) + +suite.test('handles socket error during pool.query and destroys it immediately', (cb) => { + const pool = new pg.Pool({ max: 1 }) + + if (native) { + pool.query('SELECT pg_sleep(10)', [], (err) => { + assert.equal(err.message, 'canceling statement due to user request') + cb() + }) + + setTimeout(() => { + pool._clients[0].native.cancel((err) => { + assert.ifError(err) + }) + }, 100) + } else { + pool.query('SELECT pg_sleep(10)', [], (err) => { + assert.equal(err.message, 'network issue') + assert.equal(stream.destroyed, true) + cb() + }) + + const stream = pool._clients[0].connection.stream + setTimeout(() => { + stream.emit('error', new Error('network issue')) + }, 100) + } +}) diff --git a/packages/pg/test/integration/connection-pool/idle-timeout-tests.js b/packages/pg/test/integration/connection-pool/idle-timeout-tests.js new file mode 100644 index 0000000..f36b693 --- /dev/null +++ b/packages/pg/test/integration/connection-pool/idle-timeout-tests.js @@ -0,0 +1,14 @@ +'use strict' +var helper = require('./test-helper') + +new helper.Suite().test('idle timeout', function () { + const config = Object.assign({}, helper.config, { idleTimeoutMillis: 50 }) + const pool = new helper.pg.Pool(config) + pool.connect( + assert.calls(function (err, client, done) { + assert(!err) + client.query('SELECT NOW()') + done() + }) + ) +}) diff --git a/packages/pg/test/integration/connection-pool/native-instance-tests.js b/packages/pg/test/integration/connection-pool/native-instance-tests.js new file mode 100644 index 0000000..a981503 --- /dev/null +++ b/packages/pg/test/integration/connection-pool/native-instance-tests.js @@ -0,0 +1,18 @@ +'use strict' +var helper = require('./../test-helper') +var pg = helper.pg +var native = helper.args.native + +var pool = new pg.Pool() + +pool.connect( + assert.calls(function (err, client, done) { + if (native) { + assert(client.native) + } else { + assert(!client.native) + } + done() + pool.end() + }) +) diff --git a/packages/pg/test/integration/connection-pool/test-helper.js b/packages/pg/test/integration/connection-pool/test-helper.js new file mode 100644 index 0000000..97a177a --- /dev/null +++ b/packages/pg/test/integration/connection-pool/test-helper.js @@ -0,0 +1,31 @@ +'use strict' +var helper = require('./../test-helper') + +const suite = new helper.Suite() + +helper.testPoolSize = function (max) { + suite.test(`test ${max} queries executed on a pool rapidly`, (cb) => { + const pool = new helper.pg.Pool({ max: 10 }) + + var sink = new helper.Sink(max, function () { + pool.end(cb) + }) + + for (var i = 0; i < max; i++) { + pool.connect(function (err, client, done) { + assert(!err) + client.query('SELECT * FROM NOW()') + client.query('select generate_series(0, 25)', function (err, result) { + assert.equal(result.rows.length, 26) + }) + var query = client.query('SELECT * FROM NOW()', (err) => { + assert(!err) + sink.add() + done() + }) + }) + } + }) +} + +module.exports = Object.assign({}, helper, { suite: suite }) diff --git a/packages/pg/test/integration/connection-pool/tls-tests.js b/packages/pg/test/integration/connection-pool/tls-tests.js new file mode 100644 index 0000000..f85941d --- /dev/null +++ b/packages/pg/test/integration/connection-pool/tls-tests.js @@ -0,0 +1,23 @@ +'use strict' + +const fs = require('fs') + +const helper = require('./test-helper') +const pg = helper.pg + +const suite = new helper.Suite() + +if (process.env.PG_CLIENT_CERT_TEST) { + suite.testAsync('client certificate', async () => { + const pool = new pg.Pool({ + ssl: { + ca: fs.readFileSync(process.env.PGSSLROOTCERT), + cert: fs.readFileSync(process.env.PGSSLCERT), + key: fs.readFileSync(process.env.PGSSLKEY), + }, + }) + + await pool.query('SELECT 1') + await pool.end() + }) +} diff --git a/packages/pg/test/integration/connection-pool/yield-support-tests.js b/packages/pg/test/integration/connection-pool/yield-support-tests.js new file mode 100644 index 0000000..00508f5 --- /dev/null +++ b/packages/pg/test/integration/connection-pool/yield-support-tests.js @@ -0,0 +1,23 @@ +'use strict' +var helper = require('./test-helper') +var co = require('co') + +const pool = new helper.pg.Pool() +new helper.Suite().test( + 'using coroutines works with promises', + co.wrap(function* () { + var client = yield pool.connect() + var res = yield client.query('SELECT $1::text as name', ['foo']) + assert.equal(res.rows[0].name, 'foo') + + var threw = false + try { + yield client.query('SELECT LKDSJDSLKFJ') + } catch (e) { + threw = true + } + assert(threw) + client.release() + yield pool.end() + }) +) diff --git a/packages/pg/test/integration/connection/bound-command-tests.js b/packages/pg/test/integration/connection/bound-command-tests.js new file mode 100644 index 0000000..15f4f79 --- /dev/null +++ b/packages/pg/test/integration/connection/bound-command-tests.js @@ -0,0 +1,58 @@ +'use strict' +var helper = require('./test-helper') +// http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY + +test('flushing once', function () { + helper.connect(function (con) { + con.parse({ + text: 'select * from ids', + }) + + con.bind() + con.execute() + con.flush() + + assert.emits(con, 'parseComplete') + assert.emits(con, 'bindComplete') + assert.emits(con, 'dataRow') + assert.emits(con, 'commandComplete', function () { + con.sync() + }) + assert.emits(con, 'readyForQuery', function () { + con.end() + }) + }) +}) + +test('sending many flushes', function () { + helper.connect(function (con) { + assert.emits(con, 'parseComplete', function () { + con.bind() + con.flush() + }) + + assert.emits(con, 'bindComplete', function () { + con.execute() + con.flush() + }) + + assert.emits(con, 'dataRow', function (msg) { + assert.equal(msg.fields[0], 1) + assert.emits(con, 'dataRow', function (msg) { + assert.equal(msg.fields[0], 2) + assert.emits(con, 'commandComplete', function () { + con.sync() + }) + assert.emits(con, 'readyForQuery', function () { + con.end() + }) + }) + }) + + con.parse({ + text: 'select * from ids order by id', + }) + + con.flush() + }) +}) diff --git a/packages/pg/test/integration/connection/copy-tests.js b/packages/pg/test/integration/connection/copy-tests.js new file mode 100644 index 0000000..177009d --- /dev/null +++ b/packages/pg/test/integration/connection/copy-tests.js @@ -0,0 +1,43 @@ +'use strict' +var helper = require('./test-helper') +var assert = require('assert') + +test('COPY FROM events check', function () { + helper.connect(function (con) { + var stdinStream = con.query('COPY person FROM STDIN') + con.on('copyInResponse', function () { + con.endCopyFrom() + }) + assert.emits( + con, + 'copyInResponse', + function () { + con.endCopyFrom() + }, + 'backend should emit copyInResponse after COPY FROM query' + ) + assert.emits( + con, + 'commandComplete', + function () { + con.end() + }, + 'backend should emit commandComplete after COPY FROM stream ends' + ) + }) +}) +test('COPY TO events check', function () { + helper.connect(function (con) { + var stdoutStream = con.query('COPY person TO STDOUT') + assert.emits(con, 'copyOutResponse', function () {}, 'backend should emit copyOutResponse after COPY TO query') + assert.emits(con, 'copyData', function () {}, 'backend should emit copyData on every data row') + assert.emits( + con, + 'copyDone', + function () { + con.end() + }, + 'backend should emit copyDone after all data rows' + ) + }) +}) diff --git a/packages/pg/test/integration/connection/dynamic-password-tests.js b/packages/pg/test/integration/connection/dynamic-password-tests.js new file mode 100644 index 0000000..3ab39d0 --- /dev/null +++ b/packages/pg/test/integration/connection/dynamic-password-tests.js @@ -0,0 +1,119 @@ +'use strict' +const assert = require('assert') +const helper = require('./../test-helper') +const suite = new helper.Suite() +const pg = require('../../../lib/index') +const Client = pg.Client + +const password = process.env.PGPASSWORD || null +const sleep = (millis) => new Promise((resolve) => setTimeout(resolve, millis)) + +if (!password) { + // skip these tests; no password will be requested + return +} + +suite.testAsync('Get password from a sync function', () => { + let wasCalled = false + function getPassword() { + wasCalled = true + return password + } + const client = new Client({ + password: getPassword, + }) + return client.connect().then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + return client.end() + }) +}) + +suite.testAsync('Throw error from a sync function', () => { + let wasCalled = false + const myError = new Error('Oops!') + function getPassword() { + wasCalled = true + throw myError + } + const client = new Client({ + password: getPassword, + }) + let wasThrown = false + return client + .connect() + .catch((err) => { + assert.equal(err, myError, 'Our sync error should have been thrown') + wasThrown = true + }) + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + assert.ok(wasThrown, 'Our error should have been thrown') + return client.end() + }) +}) + +suite.testAsync('Get password from a function asynchronously', () => { + let wasCalled = false + function getPassword() { + wasCalled = true + return sleep(100).then(() => password) + } + const client = new Client({ + password: getPassword, + }) + return client.connect().then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + return client.end() + }) +}) + +suite.testAsync('Throw error from an async function', () => { + let wasCalled = false + const myError = new Error('Oops!') + function getPassword() { + wasCalled = true + return sleep(100).then(() => { + throw myError + }) + } + const client = new Client({ + password: getPassword, + }) + let wasThrown = false + return client + .connect() + .catch((err) => { + assert.equal(err, myError, 'Our async error should have been thrown') + wasThrown = true + }) + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + assert.ok(wasThrown, 'Our error should have been thrown') + return client.end() + }) +}) + +suite.testAsync('Password function must return a string', () => { + let wasCalled = false + function getPassword() { + wasCalled = true + // Return a password that is not a string + return 12345 + } + const client = new Client({ + password: getPassword, + }) + let wasThrown = false + return client + .connect() + .catch((err) => { + assert.ok(err instanceof TypeError, 'A TypeError should have been thrown') + assert.equal(err.message, 'Password must be a string') + wasThrown = true + }) + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + assert.ok(wasThrown, 'Our error should have been thrown') + return client.end() + }) +}) diff --git a/packages/pg/test/integration/connection/notification-tests.js b/packages/pg/test/integration/connection/notification-tests.js new file mode 100644 index 0000000..534106d --- /dev/null +++ b/packages/pg/test/integration/connection/notification-tests.js @@ -0,0 +1,16 @@ +'use strict' +var helper = require('./test-helper') +// http://www.postgresql.org/docs/8.3/static/libpq-notify.html +test('recieves notification from same connection with no payload', function () { + helper.connect(function (con) { + con.query('LISTEN boom') + assert.emits(con, 'readyForQuery', function () { + con.query('NOTIFY boom') + assert.emits(con, 'notification', function (msg) { + assert.equal(msg.payload, '') + assert.equal(msg.channel, 'boom') + con.end() + }) + }) + }) +}) diff --git a/packages/pg/test/integration/connection/query-tests.js b/packages/pg/test/integration/connection/query-tests.js new file mode 100644 index 0000000..4105bb7 --- /dev/null +++ b/packages/pg/test/integration/connection/query-tests.js @@ -0,0 +1,26 @@ +'use strict' +var helper = require('./test-helper') +var assert = require('assert') + +var rows = [] +// testing the low level 1-1 mapping api of client to postgres messages +// it's cumbersome to use the api this way +test('simple query', function () { + helper.connect(function (con) { + con.query('select * from ids') + assert.emits(con, 'dataRow') + con.on('dataRow', function (msg) { + rows.push(msg.fields) + }) + assert.emits(con, 'readyForQuery', function () { + con.end() + }) + }) +}) + +process.on('exit', function () { + assert.equal(rows.length, 2) + assert.equal(rows[0].length, 1) + assert.strictEqual(String(rows[0][0]), '1') + assert.strictEqual(String(rows[1][0]), '2') +}) diff --git a/packages/pg/test/integration/connection/test-helper.js b/packages/pg/test/integration/connection/test-helper.js new file mode 100644 index 0000000..a94c64b --- /dev/null +++ b/packages/pg/test/integration/connection/test-helper.js @@ -0,0 +1,40 @@ +'use strict' +var net = require('net') +var helper = require('../test-helper') +var Connection = require('../../../lib/connection') +var utils = require('../../../lib/utils') +var connect = function (callback) { + var username = helper.args.user + var database = helper.args.database + var con = new Connection({ stream: new net.Stream() }) + con.on('error', function (error) { + console.log(error) + throw new Error('Connection error') + }) + con.connect(helper.args.port || '5432', helper.args.host || 'localhost') + con.once('connect', function () { + con.startup({ + user: username, + database: database, + }) + con.once('authenticationCleartextPassword', function () { + con.password(helper.args.password) + }) + con.once('authenticationMD5Password', function (msg) { + con.password(utils.postgresMd5PasswordHash(helper.args.user, helper.args.password, msg.salt)) + }) + con.once('readyForQuery', function () { + con.query('create temp table ids(id integer)') + con.once('readyForQuery', function () { + con.query('insert into ids(id) values(1); insert into ids(id) values(2);') + con.once('readyForQuery', function () { + callback(con) + }) + }) + }) + }) +} + +module.exports = { + connect: connect, +} diff --git a/packages/pg/test/integration/domain-tests.js b/packages/pg/test/integration/domain-tests.js new file mode 100644 index 0000000..ce46eb8 --- /dev/null +++ b/packages/pg/test/integration/domain-tests.js @@ -0,0 +1,62 @@ +'use strict' +var async = require('async') + +var helper = require('./test-helper') +var Query = helper.pg.Query +var suite = new helper.Suite() + +const Pool = helper.pg.Pool + +suite.test('no domain', function (cb) { + assert(!process.domain) + const pool = new Pool() + pool.connect( + assert.success(function (client, done) { + assert(!process.domain) + done() + pool.end(cb) + }) + ) +}) + +suite.test('with domain', function (cb) { + assert(!process.domain) + const pool = new Pool() + var domain = require('domain').create() + domain.run(function () { + var startingDomain = process.domain + assert(startingDomain) + pool.connect( + assert.success(function (client, done) { + assert(process.domain, 'no domain exists in connect callback') + assert.equal(startingDomain, process.domain, 'domain was lost when checking out a client') + var query = client.query( + 'SELECT NOW()', + assert.success(function () { + assert(process.domain, 'no domain exists in query callback') + assert.equal(startingDomain, process.domain, 'domain was lost when checking out a client') + done(true) + process.domain.exit() + pool.end(cb) + }) + ) + }) + ) + }) +}) + +suite.test('error on domain', function (cb) { + var domain = require('domain').create() + const pool = new Pool() + domain.on('error', function () { + pool.end(cb) + }) + domain.run(function () { + pool.connect( + assert.success(function (client, done) { + client.query(new Query('SELECT SLDKJFLSKDJF')) + client.on('drain', done) + }) + ) + }) +}) diff --git a/packages/pg/test/integration/gh-issues/1105-tests.js b/packages/pg/test/integration/gh-issues/1105-tests.js new file mode 100644 index 0000000..d9885f8 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/1105-tests.js @@ -0,0 +1,20 @@ +const pg = require('../../../lib') +const helper = require('../test-helper') +const suite = new helper.Suite() + +suite.testAsync('timeout causing query crashes', async () => { + const client = new helper.Client() + await client.connect() + await client.query('CREATE TEMP TABLE foobar( name TEXT NOT NULL, id SERIAL)') + await client.query('BEGIN') + await client.query("SET LOCAL statement_timeout TO '1ms'") + let count = 0 + while (count++ < 5000) { + try { + await client.query('INSERT INTO foobar(name) VALUES ($1)', [Math.random() * 1000 + '']) + } catch (e) { + await client.query('ROLLBACK') + } + } + await client.end() +}) diff --git a/packages/pg/test/integration/gh-issues/130-tests.js b/packages/pg/test/integration/gh-issues/130-tests.js new file mode 100644 index 0000000..fb86b5b --- /dev/null +++ b/packages/pg/test/integration/gh-issues/130-tests.js @@ -0,0 +1,28 @@ +'use strict' +var helper = require('../test-helper') +var exec = require('child_process').exec + +helper.pg.defaults.poolIdleTimeout = 1000 + +const pool = new helper.pg.Pool() +pool.connect(function (err, client, done) { + assert.ifError(err) + client.once('error', function (err) { + client.on('error', (err) => {}) + done(err) + }) + client.query('SELECT pg_backend_pid()', function (err, result) { + assert.ifError(err) + var pid = result.rows[0].pg_backend_pid + var psql = 'psql' + if (helper.args.host) psql = psql + ' -h ' + helper.args.host + if (helper.args.port) psql = psql + ' -p ' + helper.args.port + if (helper.args.user) psql = psql + ' -U ' + helper.args.user + exec( + psql + ' -c "select pg_terminate_backend(' + pid + ')" template1', + assert.calls(function (error, stdout, stderr) { + assert.ifError(error) + }) + ) + }) +}) diff --git a/packages/pg/test/integration/gh-issues/131-tests.js b/packages/pg/test/integration/gh-issues/131-tests.js new file mode 100644 index 0000000..5838067 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/131-tests.js @@ -0,0 +1,33 @@ +'use strict' +var helper = require('../test-helper') +var pg = helper.pg + +var suite = new helper.Suite() + +suite.test('parsing array decimal results', function (done) { + const pool = new pg.Pool() + pool.connect( + assert.calls(function (err, client, release) { + assert(!err) + client.query('CREATE TEMP TABLE why(names text[], numbors integer[], decimals double precision[])') + client + .query( + new pg.Query( + 'INSERT INTO why(names, numbors, decimals) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\', \'{.1, 0.05, 3.654}\')' + ) + ) + .on('error', console.log) + client.query( + 'SELECT decimals FROM why', + assert.success(function (result) { + assert.lengthIs(result.rows[0].decimals, 3) + assert.equal(result.rows[0].decimals[0], 0.1) + assert.equal(result.rows[0].decimals[1], 0.05) + assert.equal(result.rows[0].decimals[2], 3.654) + release() + pool.end(done) + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/gh-issues/1382-tests.js b/packages/pg/test/integration/gh-issues/1382-tests.js new file mode 100644 index 0000000..e80924c --- /dev/null +++ b/packages/pg/test/integration/gh-issues/1382-tests.js @@ -0,0 +1,34 @@ +'use strict' +var helper = require('./../test-helper') + +const suite = new helper.Suite() + +suite.test('calling end during active query should return a promise', (done) => { + const client = new helper.pg.Client() + let callCount = 0 + // ensure both the query rejects and the end promise resolves + const after = () => { + if (++callCount > 1) { + done() + } + } + client.connect().then(() => { + client.query('SELECT NOW()').catch(after) + client.end().then(after) + }) +}) + +suite.test('calling end during an active query should call end callback', (done) => { + const client = new helper.pg.Client() + let callCount = 0 + // ensure both the query rejects and the end callback fires + const after = () => { + if (++callCount > 1) { + done() + } + } + client.connect().then(() => { + client.query('SELECT NOW()').catch(after) + client.end(after) + }) +}) diff --git a/packages/pg/test/integration/gh-issues/1542-tests.js b/packages/pg/test/integration/gh-issues/1542-tests.js new file mode 100644 index 0000000..f65aa3f --- /dev/null +++ b/packages/pg/test/integration/gh-issues/1542-tests.js @@ -0,0 +1,22 @@ +'use strict' +const helper = require('./../test-helper') +const assert = require('assert') + +const suite = new helper.Suite() + +suite.testAsync('BoundPool can be subclassed', async () => { + const Pool = helper.pg.Pool + class SubPool extends Pool {} + const subPool = new SubPool() + const client = await subPool.connect() + client.release() + await subPool.end() + assert(subPool instanceof helper.pg.Pool) +}) + +suite.test('calling pg.Pool without new throws', () => { + const Pool = helper.pg.Pool + assert.throws(() => { + const pool = Pool() + }) +}) diff --git a/packages/pg/test/integration/gh-issues/1854-tests.js b/packages/pg/test/integration/gh-issues/1854-tests.js new file mode 100644 index 0000000..92ac6ec --- /dev/null +++ b/packages/pg/test/integration/gh-issues/1854-tests.js @@ -0,0 +1,33 @@ +'use strict' +var helper = require('./../test-helper') + +const suite = new helper.Suite() + +suite.test('Parameter serialization errors should not cause query to hang', (done) => { + if (helper.config.native) { + // pg-native cannot handle non-string parameters so skip this entirely + return done() + } + const client = new helper.pg.Client() + const expectedErr = new Error('Serialization error') + client + .connect() + .then(() => { + const obj = { + toPostgres: function () { + throw expectedErr + }, + } + return client.query('SELECT $1::text', [obj]).then(() => { + throw new Error('Expected a serialization error to be thrown but no error was thrown') + }) + }) + .catch((err) => { + client.end(() => {}) + if (err !== expectedErr) { + done(new Error('Expected a serialization error to be thrown but instead caught: ' + err)) + return + } + done() + }) +}) diff --git a/packages/pg/test/integration/gh-issues/199-tests.js b/packages/pg/test/integration/gh-issues/199-tests.js new file mode 100644 index 0000000..2710020 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/199-tests.js @@ -0,0 +1,23 @@ +'use strict' +var helper = require('../test-helper') +var client = helper.client() + +client.query('CREATE TEMP TABLE arrtest (n integer, s varchar)') +client.query("INSERT INTO arrtest VALUES (4, 'foo'), (5, 'bar'), (6, 'baz');") + +var qText = + "SELECT \ +ARRAY[1, 2, 3] AS b,\ +ARRAY['xx', 'yy', 'zz'] AS c,\ +ARRAY(SELECT n FROM arrtest) AS d,\ +ARRAY(SELECT s FROM arrtest) AS e;" + +client.query(qText, function (err, result) { + if (err) throw err + var row = result.rows[0] + for (var key in row) { + assert.equal(typeof row[key], 'object') + assert.equal(row[key].length, 3) + } + client.end() +}) diff --git a/packages/pg/test/integration/gh-issues/1992-tests.js b/packages/pg/test/integration/gh-issues/1992-tests.js new file mode 100644 index 0000000..abb2167 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/1992-tests.js @@ -0,0 +1,10 @@ +'use strict' +const helper = require('./../test-helper') +const assert = require('assert') + +const suite = new helper.Suite() + +suite.test('Native should not be enumerable', () => { + const keys = Object.keys(helper.pg) + assert.strictEqual(keys.indexOf('native'), -1) +}) diff --git a/packages/pg/test/integration/gh-issues/2056-tests.js b/packages/pg/test/integration/gh-issues/2056-tests.js new file mode 100644 index 0000000..2a12678 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2056-tests.js @@ -0,0 +1,21 @@ +'use strict' +var helper = require('./../test-helper') +var assert = require('assert') + +const suite = new helper.Suite() + +suite.test('All queries should return a result array', (done) => { + const client = new helper.pg.Client() + client.connect() + const promises = [] + promises.push(client.query('CREATE TEMP TABLE foo(bar TEXT)')) + promises.push(client.query('INSERT INTO foo(bar) VALUES($1)', ['qux'])) + promises.push(client.query('SELECT * FROM foo WHERE bar = $1', ['foo'])) + Promise.all(promises).then((results) => { + results.forEach((res) => { + assert(Array.isArray(res.fields)) + assert(Array.isArray(res.rows)) + }) + client.end(done) + }) +}) diff --git a/packages/pg/test/integration/gh-issues/2064-tests.js b/packages/pg/test/integration/gh-issues/2064-tests.js new file mode 100644 index 0000000..6118ca2 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2064-tests.js @@ -0,0 +1,31 @@ +'use strict' +const helper = require('./../test-helper') +const assert = require('assert') +const util = require('util') + +const suite = new helper.Suite() + +const password = 'FAIL THIS TEST' + +suite.test('Password should not exist in toString() output', () => { + const pool = new helper.pg.Pool({ password }) + const client = new helper.pg.Client({ password }) + assert(pool.toString().indexOf(password) === -1) + assert(client.toString().indexOf(password) === -1) +}) + +suite.test('Password should not exist in util.inspect output', () => { + const pool = new helper.pg.Pool({ password }) + const client = new helper.pg.Client({ password }) + const depth = 20 + assert(util.inspect(pool, { depth }).indexOf(password) === -1) + assert(util.inspect(client, { depth }).indexOf(password) === -1) +}) + +suite.test('Password should not exist in json.stringfy output', () => { + const pool = new helper.pg.Pool({ password }) + const client = new helper.pg.Client({ password }) + const depth = 20 + assert(JSON.stringify(pool).indexOf(password) === -1) + assert(JSON.stringify(client).indexOf(password) === -1) +}) diff --git a/packages/pg/test/integration/gh-issues/2079-tests.js b/packages/pg/test/integration/gh-issues/2079-tests.js new file mode 100644 index 0000000..ad1c82a --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2079-tests.js @@ -0,0 +1,53 @@ +'use strict' +var helper = require('./../test-helper') +var assert = require('assert') + +const suite = new helper.Suite() + +// makes a backend server that responds with a non 'S' ssl response buffer +let makeTerminatingBackend = (byte) => { + const { createServer } = require('net') + + const server = createServer((socket) => { + // attach a listener so the socket can drain + // https://www.postgresql.org/docs/9.3/protocol-message-formats.html + socket.on('data', (buff) => { + const code = buff.readInt32BE(4) + // I don't see anything in the docs about 80877104 + // but libpq is sending it... + if (code === 80877103 || code === 80877104) { + const packet = Buffer.from(byte, 'utf-8') + socket.write(packet) + } + }) + socket.on('close', () => { + server.close() + }) + }) + + server.listen() + const { port } = server.address() + return port +} + +suite.test('SSL connection error allows event loop to exit', (done) => { + const port = makeTerminatingBackend('N') + const client = new helper.pg.Client({ ssl: 'require', port, host: 'localhost' }) + // since there was a connection error the client's socket should be closed + // and the event loop will have no refs and exit cleanly + client.connect((err) => { + assert(err instanceof Error) + done() + }) +}) + +suite.test('Non "S" response code allows event loop to exit', (done) => { + const port = makeTerminatingBackend('X') + const client = new helper.pg.Client({ ssl: 'require', host: 'localhost', port }) + // since there was a connection error the client's socket should be closed + // and the event loop will have no refs and exit cleanly + client.connect((err) => { + assert(err instanceof Error) + done() + }) +}) diff --git a/packages/pg/test/integration/gh-issues/2085-tests.js b/packages/pg/test/integration/gh-issues/2085-tests.js new file mode 100644 index 0000000..2536bba --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2085-tests.js @@ -0,0 +1,35 @@ +'use strict' +var helper = require('./../test-helper') +var assert = require('assert') + +const suite = new helper.Suite() + +// allow skipping of this test via env var for +// local testing when you don't have SSL set up +if (process.env.PGTESTNOSSL) { + return +} + +suite.testAsync('it should connect over ssl', async () => { + const ssl = helper.args.native + ? 'require' + : { + rejectUnauthorized: false, + } + const client = new helper.pg.Client({ ssl }) + await client.connect() + const { rows } = await client.query('SELECT NOW()') + assert.strictEqual(rows.length, 1) + await client.end() +}) + +suite.testAsync('it should fail with self-signed cert error w/o rejectUnauthorized being passed', async () => { + const ssl = helper.args.native ? 'verify-ca' : {} + const client = new helper.pg.Client({ ssl }) + try { + await client.connect() + } catch (e) { + return + } + throw new Error('this test should have thrown an error due to self-signed cert') +}) diff --git a/packages/pg/test/integration/gh-issues/2108-tests.js b/packages/pg/test/integration/gh-issues/2108-tests.js new file mode 100644 index 0000000..cbf2caa --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2108-tests.js @@ -0,0 +1,13 @@ +'use strict' +var helper = require('./../test-helper') +const suite = new helper.Suite() + +suite.test('Closing an unconnected client calls callback', (done) => { + const client = new helper.pg.Client() + client.end(done) +}) + +suite.testAsync('Closing an unconnected client resolves promise', () => { + const client = new helper.pg.Client() + return client.end() +}) diff --git a/packages/pg/test/integration/gh-issues/2303-tests.js b/packages/pg/test/integration/gh-issues/2303-tests.js new file mode 100644 index 0000000..7496a6f --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2303-tests.js @@ -0,0 +1,47 @@ +'use strict' +const helper = require('./../test-helper') +const assert = require('assert') +const util = require('util') + +const suite = new helper.Suite() + +const secret_value = 'FAIL THIS TEST' + +suite.test('SSL Key should not exist in toString() output', () => { + const pool = new helper.pg.Pool({ ssl: { key: secret_value } }) + const client = new helper.pg.Client({ ssl: { key: secret_value } }) + assert(pool.toString().indexOf(secret_value) === -1) + assert(client.toString().indexOf(secret_value) === -1) +}) + +suite.test('SSL Key should not exist in util.inspect output', () => { + const pool = new helper.pg.Pool({ ssl: { key: secret_value } }) + const client = new helper.pg.Client({ ssl: { key: secret_value } }) + const depth = 20 + assert(util.inspect(pool, { depth }).indexOf(secret_value) === -1) + assert(util.inspect(client, { depth }).indexOf(secret_value) === -1) +}) + +suite.test('SSL Key should not exist in json.stringfy output', () => { + const pool = new helper.pg.Pool({ ssl: { key: secret_value } }) + const client = new helper.pg.Client({ ssl: { key: secret_value } }) + const depth = 20 + assert(JSON.stringify(pool).indexOf(secret_value) === -1) + assert(JSON.stringify(client).indexOf(secret_value) === -1) +}) + +suite.test('SSL Key should exist for direct access', () => { + const pool = new helper.pg.Pool({ ssl: { key: secret_value } }) + const client = new helper.pg.Client({ ssl: { key: secret_value } }) + assert(pool.options.ssl.key === secret_value) + assert(client.connectionParameters.ssl.key === secret_value) +}) + +suite.test('SSL Key should exist for direct access even when non-enumerable custom config', () => { + const config = { ssl: { key: secret_value } } + Object.defineProperty(config.ssl, 'key', { enumerable: false }) + const pool = new helper.pg.Pool(config) + const client = new helper.pg.Client(config) + assert(pool.options.ssl.key === secret_value) + assert(client.connectionParameters.ssl.key === secret_value) +}) diff --git a/packages/pg/test/integration/gh-issues/2307-tests.js b/packages/pg/test/integration/gh-issues/2307-tests.js new file mode 100644 index 0000000..d5f7c05 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/2307-tests.js @@ -0,0 +1,24 @@ +'use strict' + +const pg = require('../../../lib') +const helper = require('../test-helper') + +const suite = new helper.Suite() + +suite.test('bad ssl credentials do not cause crash', (done) => { + const config = { + ssl: { + ca: 'invalid_value', + key: 'invalid_value', + cert: 'invalid_value', + }, + } + + const client = new pg.Client(config) + + client.connect((err) => { + assert(err) + client.end() + done() + }) +}) diff --git a/packages/pg/test/integration/gh-issues/507-tests.js b/packages/pg/test/integration/gh-issues/507-tests.js new file mode 100644 index 0000000..f77d1f8 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/507-tests.js @@ -0,0 +1,19 @@ +'use strict' +var helper = require('../test-helper') +var pg = helper.pg + +new helper.Suite().test('parsing array results', function (cb) { + const pool = new pg.Pool() + pool.connect( + assert.success(function (client, done) { + client.query('CREATE TEMP TABLE test_table(bar integer, "baz\'s" integer)') + client.query('INSERT INTO test_table(bar, "baz\'s") VALUES(1, 1), (2, 2)') + client.query('SELECT * FROM test_table', function (err, res) { + assert.equal(res.rows[0]["baz's"], 1) + assert.equal(res.rows[1]["baz's"], 2) + done() + pool.end(cb) + }) + }) + ) +}) diff --git a/packages/pg/test/integration/gh-issues/600-tests.js b/packages/pg/test/integration/gh-issues/600-tests.js new file mode 100644 index 0000000..af679ee --- /dev/null +++ b/packages/pg/test/integration/gh-issues/600-tests.js @@ -0,0 +1,90 @@ +'use strict' +var async = require('async') +var helper = require('../test-helper') +const suite = new helper.Suite() + +var db = helper.client() + +function createTableFoo(callback) { + db.query('create temp table foo(column1 int, column2 int)', callback) +} + +function createTableBar(callback) { + db.query('create temp table bar(column1 text, column2 text)', callback) +} + +function insertDataFoo(callback) { + db.query( + { + name: 'insertFoo', + text: 'insert into foo values($1,$2)', + values: ['one', 'two'], + }, + callback + ) +} + +function insertDataBar(callback) { + db.query( + { + name: 'insertBar', + text: 'insert into bar values($1,$2)', + values: ['one', 'two'], + }, + callback + ) +} + +function startTransaction(callback) { + db.query('BEGIN', callback) +} +function endTransaction(callback) { + db.query('COMMIT', callback) +} + +function doTransaction(callback) { + // The transaction runs startTransaction, then all queries, then endTransaction, + // no matter if there has been an error in a query in the middle. + startTransaction(function () { + insertDataFoo(function () { + insertDataBar(function () { + endTransaction(callback) + }) + }) + }) +} + +var steps = [createTableFoo, createTableBar, doTransaction, insertDataBar] + +suite.test('test if query fails', function (done) { + async.series( + steps, + assert.success(function () { + db.end() + done() + }) + ) +}) + +suite.test('test if prepare works but bind fails', function (done) { + var client = helper.client() + var q = { + text: 'SELECT $1::int as name', + values: ['brian'], + name: 'test', + } + client.query( + q, + assert.calls(function (err, res) { + q.values = [1] + client.query( + q, + assert.calls(function (err, res) { + assert.ifError(err) + client.end() + done() + }) + ) + }) + ) +}) diff --git a/packages/pg/test/integration/gh-issues/675-tests.js b/packages/pg/test/integration/gh-issues/675-tests.js new file mode 100644 index 0000000..2e281ec --- /dev/null +++ b/packages/pg/test/integration/gh-issues/675-tests.js @@ -0,0 +1,30 @@ +'use strict' +var helper = require('../test-helper') +var assert = require('assert') + +const pool = new helper.pg.Pool() +pool.connect(function (err, client, done) { + if (err) throw err + + var c = 'CREATE TEMP TABLE posts (body TEXT)' + + client.query(c, function (err) { + if (err) throw err + + c = 'INSERT INTO posts (body) VALUES ($1) RETURNING *' + + var body = Buffer.from('foo') + client.query(c, [body], function (err) { + if (err) throw err + + body = Buffer.from([]) + client.query(c, [body], function (err, res) { + done() + + if (err) throw err + assert.equal(res.rows[0].body, '') + pool.end() + }) + }) + }) +}) diff --git a/packages/pg/test/integration/gh-issues/699-tests.js b/packages/pg/test/integration/gh-issues/699-tests.js new file mode 100644 index 0000000..c9be63b --- /dev/null +++ b/packages/pg/test/integration/gh-issues/699-tests.js @@ -0,0 +1,31 @@ +'use strict' +var helper = require('../test-helper') +var assert = require('assert') +var copyFrom = require('pg-copy-streams').from + +if (helper.args.native) return + +const pool = new helper.pg.Pool() +pool.connect(function (err, client, done) { + if (err) throw err + + var c = 'CREATE TEMP TABLE employee (id integer, fname varchar(400), lname varchar(400))' + + client.query(c, function (err) { + if (err) throw err + + var stream = client.query(copyFrom('COPY employee FROM STDIN')) + stream.on('end', function () { + done() + setTimeout(() => { + pool.end() + }, 50) + }) + + for (var i = 1; i <= 5; i++) { + var line = ['1\ttest', i, '\tuser', i, '\n'] + stream.write(line.join('')) + } + stream.end() + }) +}) diff --git a/packages/pg/test/integration/gh-issues/787-tests.js b/packages/pg/test/integration/gh-issues/787-tests.js new file mode 100644 index 0000000..9a3198f --- /dev/null +++ b/packages/pg/test/integration/gh-issues/787-tests.js @@ -0,0 +1,14 @@ +'use strict' +var helper = require('../test-helper') +const pool = new helper.pg.Pool() + +pool.connect(function (err, client) { + var q = { + name: + 'This is a super long query name just so I can test that an error message is properly spit out to console.error without throwing an exception or anything', + text: 'SELECT NOW()', + } + client.query(q, function () { + client.end() + }) +}) diff --git a/packages/pg/test/integration/gh-issues/882-tests.js b/packages/pg/test/integration/gh-issues/882-tests.js new file mode 100644 index 0000000..4a8ef64 --- /dev/null +++ b/packages/pg/test/integration/gh-issues/882-tests.js @@ -0,0 +1,9 @@ +'use strict' +// client should not hang on an empty query +var helper = require('../test-helper') +var client = helper.client() +client.query({ name: 'foo1', text: null }) +client.query({ name: 'foo2', text: ' ' }) +client.query({ name: 'foo3', text: '' }, function (err, res) { + client.end() +}) diff --git a/packages/pg/test/integration/gh-issues/981-tests.js b/packages/pg/test/integration/gh-issues/981-tests.js new file mode 100644 index 0000000..998adea --- /dev/null +++ b/packages/pg/test/integration/gh-issues/981-tests.js @@ -0,0 +1,37 @@ +'use strict' +var helper = require('./../test-helper') + +//native bindings are only installed for native tests +if (!helper.args.native) { + return +} + +var assert = require('assert') +var pg = require('../../../lib') +var native = require('../../../lib').native + +var JsClient = require('../../../lib/client') +var NativeClient = require('../../../lib/native') + +assert(pg.Client === JsClient) +assert(native.Client === NativeClient) + +const jsPool = new pg.Pool() +const nativePool = new native.Pool() + +const suite = new helper.Suite() +suite.test('js pool returns js client', (cb) => { + jsPool.connect(function (err, client, done) { + assert(client instanceof JsClient) + done() + jsPool.end(cb) + }) +}) + +suite.test('native pool returns native client', (cb) => { + nativePool.connect(function (err, client, done) { + assert(client instanceof NativeClient) + done() + nativePool.end(cb) + }) +}) diff --git a/packages/pg/test/integration/test-helper.js b/packages/pg/test/integration/test-helper.js new file mode 100644 index 0000000..9b8b58c --- /dev/null +++ b/packages/pg/test/integration/test-helper.js @@ -0,0 +1,29 @@ +'use strict' +var helper = require('./../test-helper') + +if (helper.args.native) { + Client = require('./../../lib/native') + helper.Client = Client + helper.pg = helper.pg.native +} + +// creates a client from cli parameters +helper.client = function (cb) { + var client = new Client() + client.connect(cb) + return client +} + +helper.versionGTE = function (client, testVersion, callback) { + client.query( + 'SHOW server_version_num', + assert.calls(function (err, result) { + if (err) return callback(err) + var version = parseInt(result.rows[0].server_version_num, 10) + return callback(null, version >= testVersion) + }) + ) +} + +// export parent helper stuffs +module.exports = helper diff --git a/packages/pg/test/native/callback-api-tests.js b/packages/pg/test/native/callback-api-tests.js new file mode 100644 index 0000000..80fdcdf --- /dev/null +++ b/packages/pg/test/native/callback-api-tests.js @@ -0,0 +1,41 @@ +'use strict' +var domain = require('domain') +var helper = require('./../test-helper') +var Client = require('./../../lib/native') +const suite = new helper.Suite() + +suite.test('fires callback with results', function (done) { + var client = new Client(helper.config) + client.connect() + client.query( + 'SELECT 1 as num', + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.rows[0].num, 1) + assert.strictEqual(result.rowCount, 1) + client.query( + 'SELECT * FROM person WHERE name = $1', + ['Brian'], + assert.calls(function (err, result) { + assert(!err) + assert.equal(result.rows[0].name, 'Brian') + client.end(done) + }) + ) + }) + ) +}) + +suite.test('preserves domain', function (done) { + var dom = domain.create() + + dom.run(function () { + var client = new Client(helper.config) + assert.ok(dom === require('domain').active, 'domain is active') + client.connect() + client.query('select 1', function () { + assert.ok(dom === require('domain').active, 'domain is still active') + client.end(done) + }) + }) +}) diff --git a/packages/pg/test/native/evented-api-tests.js b/packages/pg/test/native/evented-api-tests.js new file mode 100644 index 0000000..ba0496e --- /dev/null +++ b/packages/pg/test/native/evented-api-tests.js @@ -0,0 +1,94 @@ +'use strict' +var helper = require('../test-helper') +var Client = require('../../lib/native') +var Query = Client.Query + +var setupClient = function () { + var client = new Client(helper.config) + client.connect() + client.query('CREATE TEMP TABLE boom(name varchar(10), age integer)') + client.query("INSERT INTO boom(name, age) VALUES('Aaron', 26)") + client.query("INSERT INTO boom(name, age) VALUES('Brian', 28)") + return client +} + +test('multiple results', function () { + test('queued queries', function () { + var client = setupClient() + var q = client.query(new Query('SELECT name FROM BOOM')) + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Aaron') + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Brian') + }) + }) + assert.emits(q, 'end', function () { + test('query with config', function () { + var q2 = client.query(new Query({ text: 'SELECT 1 as num' })) + assert.emits(q2, 'row', function (row) { + assert.strictEqual(row.num, 1) + assert.emits(q2, 'end', function () { + client.end() + }) + }) + }) + }) + }) +}) + +test('parameterized queries', function () { + test('with a single string param', function () { + var client = setupClient() + var q = client.query(new Query('SELECT * FROM boom WHERE name = $1', ['Aaron'])) + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Aaron') + }) + assert.emits(q, 'end', function () { + client.end() + }) + }) + + test('with object config for query', function () { + var client = setupClient() + var q = client.query( + new Query({ + text: 'SELECT name FROM boom WHERE name = $1', + values: ['Brian'], + }) + ) + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Brian') + }) + assert.emits(q, 'end', function () { + client.end() + }) + }) + + test('multiple parameters', function () { + var client = setupClient() + var q = client.query( + new Query('SELECT name FROM boom WHERE name = $1 or name = $2 ORDER BY name COLLATE "C"', ['Aaron', 'Brian']) + ) + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Aaron') + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Brian') + assert.emits(q, 'end', function () { + client.end() + }) + }) + }) + }) + + test('integer parameters', function () { + var client = setupClient() + var q = client.query(new Query('SELECT * FROM boom WHERE age > $1', [27])) + assert.emits(q, 'row', function (row) { + assert.equal(row.name, 'Brian') + assert.equal(row.age, 28) + }) + assert.emits(q, 'end', function () { + client.end() + }) + }) +}) diff --git a/packages/pg/test/native/missing-native.js b/packages/pg/test/native/missing-native.js new file mode 100644 index 0000000..35dad3f --- /dev/null +++ b/packages/pg/test/native/missing-native.js @@ -0,0 +1,7 @@ +'use strict' +// this test assumes it has been run from the Makefile +// and that node_modules/pg-native has been deleted + +var assert = require('assert') + +assert.equal(require('../../lib').native, null) diff --git a/packages/pg/test/native/native-vs-js-error-tests.js b/packages/pg/test/native/native-vs-js-error-tests.js new file mode 100644 index 0000000..842e01a --- /dev/null +++ b/packages/pg/test/native/native-vs-js-error-tests.js @@ -0,0 +1,21 @@ +'use strict' +var assert = require('assert') +var Client = require('../../lib/client') +var NativeClient = require('../../lib/native') + +var client = new Client() +var nativeClient = new NativeClient() + +client.connect() +nativeClient.connect((err) => { + client.query('SELECT alsdkfj', (err) => { + client.end() + + nativeClient.query('SELECT lkdasjfasd', (nativeErr) => { + for (var key in nativeErr) { + assert.equal(err[key], nativeErr[key], `Expected err.${key} to equal nativeErr.${key}`) + } + nativeClient.end() + }) + }) +}) diff --git a/packages/pg/test/native/stress-tests.js b/packages/pg/test/native/stress-tests.js new file mode 100644 index 0000000..9d12877 --- /dev/null +++ b/packages/pg/test/native/stress-tests.js @@ -0,0 +1,51 @@ +'use strict' +var helper = require('../test-helper') +var Client = require('../../lib/native') +var Query = Client.Query + +test('many rows', function () { + var client = new Client(helper.config) + client.connect() + var q = client.query(new Query('SELECT * FROM person')) + var rows = [] + q.on('row', function (row) { + rows.push(row) + }) + assert.emits(q, 'end', function () { + client.end() + assert.lengthIs(rows, 26) + }) +}) + +test('many queries', function () { + var client = new Client(helper.config) + client.connect() + var count = 0 + var expected = 100 + for (var i = 0; i < expected; i++) { + var q = client.query(new Query('SELECT * FROM person')) + assert.emits(q, 'end', function () { + count++ + }) + } + assert.emits(client, 'drain', function () { + client.end() + assert.equal(count, expected) + }) +}) + +test('many clients', function () { + var clients = [] + for (var i = 0; i < 10; i++) { + clients.push(new Client(helper.config)) + } + clients.forEach(function (client) { + client.connect() + for (var i = 0; i < 20; i++) { + client.query('SELECT * FROM person') + } + assert.emits(client, 'drain', function () { + client.end() + }) + }) +}) diff --git a/packages/pg/test/suite.js b/packages/pg/test/suite.js new file mode 100644 index 0000000..7d19edb --- /dev/null +++ b/packages/pg/test/suite.js @@ -0,0 +1,96 @@ +'use strict' + +const async = require('async') + +class Test { + constructor(name, cb) { + this.name = name + this.action = cb + this.timeout = 5000 + } + + run(cb) { + try { + this._run(cb) + } catch (e) { + cb(e) + } + } + + _run(cb) { + if (!this.action) { + console.log(`${this.name} skipped`) + return cb() + } + if (!this.action.length) { + const result = this.action.call(this) + if (!(result || 0).then) { + return cb() + } + result.then(() => cb()).catch((err) => cb(err || new Error('Unhandled promise rejection'))) + } else { + this.action.call(this, cb) + } + } +} + +class Suite { + constructor(name) { + console.log('') + this._queue = async.queue(this.run.bind(this), 1) + this._queue.drain = () => {} + } + + run(test, cb) { + process.stdout.write(' ' + test.name + ' ') + if (!test.action) { + process.stdout.write('? - SKIPPED\n') + return cb() + } + + const tid = setTimeout(() => { + const err = Error(`test: ${test.name} did not complete withint ${test.timeout}ms`) + console.log('\n' + err.stack) + process.exit(-1) + }, test.timeout) + + test.run((err) => { + clearTimeout(tid) + if (err) { + process.stdout.write(`FAILED!\n\n${err.stack}\n`) + process.exit(-1) + } else { + process.stdout.write('✔\n') + cb() + } + }) + } + + test(name, cb) { + const test = new Test(name, cb) + this._queue.push(test) + } + + /** + * Run an async test that can return a Promise. If the Promise resolves + * successfully then the test will pass. If the Promise rejects with an + * error then the test will be considered failed. + */ + testAsync(name, action) { + const test = new Test(name, (cb) => { + Promise.resolve() + .then(action) + .then(() => cb(null), cb) + }) + this._queue.push(test) + } +} + +process.on('unhandledRejection', (e) => { + setImmediate(() => { + console.error('Unhandled promise rejection') + throw e + }) +}) + +module.exports = Suite diff --git a/packages/pg/test/test-buffers.js b/packages/pg/test/test-buffers.js new file mode 100644 index 0000000..2989434 --- /dev/null +++ b/packages/pg/test/test-buffers.js @@ -0,0 +1,121 @@ +'use strict' +require('./test-helper') +const BufferList = require('./buffer-list') +// http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html + +var buffers = {} +buffers.readyForQuery = function () { + return new BufferList().add(Buffer.from('I')).join(true, 'Z') +} + +buffers.authenticationOk = function () { + return new BufferList().addInt32(0).join(true, 'R') +} + +buffers.authenticationCleartextPassword = function () { + return new BufferList().addInt32(3).join(true, 'R') +} + +buffers.authenticationMD5Password = function () { + return new BufferList() + .addInt32(5) + .add(Buffer.from([1, 2, 3, 4])) + .join(true, 'R') +} + +buffers.authenticationSASL = function () { + return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R') +} + +buffers.authenticationSASLContinue = function () { + return new BufferList().addInt32(11).addString('data').join(true, 'R') +} + +buffers.authenticationSASLFinal = function () { + return new BufferList().addInt32(12).addString('data').join(true, 'R') +} + +buffers.parameterStatus = function (name, value) { + return new BufferList().addCString(name).addCString(value).join(true, 'S') +} + +buffers.backendKeyData = function (processID, secretKey) { + return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K') +} + +buffers.commandComplete = function (string) { + return new BufferList().addCString(string).join(true, 'C') +} + +buffers.rowDescription = function (fields) { + fields = fields || [] + var buf = new BufferList() + buf.addInt16(fields.length) + fields.forEach(function (field) { + buf + .addCString(field.name) + .addInt32(field.tableID || 0) + .addInt16(field.attributeNumber || 0) + .addInt32(field.dataTypeID || 0) + .addInt16(field.dataTypeSize || 0) + .addInt32(field.typeModifier || 0) + .addInt16(field.formatCode || 0) + }) + return buf.join(true, 'T') +} + +buffers.dataRow = function (columns) { + columns = columns || [] + var buf = new BufferList() + buf.addInt16(columns.length) + columns.forEach(function (col) { + if (col == null) { + buf.addInt32(-1) + } else { + var strBuf = Buffer.from(col, 'utf8') + buf.addInt32(strBuf.length) + buf.add(strBuf) + } + }) + return buf.join(true, 'D') +} + +buffers.error = function (fields) { + return errorOrNotice(fields).join(true, 'E') +} + +buffers.notice = function (fields) { + return errorOrNotice(fields).join(true, 'N') +} + +var errorOrNotice = function (fields) { + fields = fields || [] + var buf = new BufferList() + fields.forEach(function (field) { + buf.addChar(field.type) + buf.addCString(field.value) + }) + return buf.add(Buffer.from([0])) // terminator +} + +buffers.parseComplete = function () { + return new BufferList().join(true, '1') +} + +buffers.bindComplete = function () { + return new BufferList().join(true, '2') +} + +buffers.notification = function (id, channel, payload) { + return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A') +} + +buffers.emptyQuery = function () { + return new BufferList().join(true, 'I') +} + +buffers.portalSuspended = function () { + return new BufferList().join(true, 's') +} + +module.exports = buffers diff --git a/packages/pg/test/test-helper.js b/packages/pg/test/test-helper.js new file mode 100644 index 0000000..5999ea9 --- /dev/null +++ b/packages/pg/test/test-helper.js @@ -0,0 +1,244 @@ +'use strict' +// make assert a global... +global.assert = require('assert') +var sys = require('util') + +const Suite = require('./suite') +const args = require('./cli') + +global.Client = require('./../lib').Client + +process.on('uncaughtException', function (d) { + if ('stack' in d && 'message' in d) { + console.log('Message: ' + d.message) + console.log(d.stack) + } else { + console.log(d) + } + process.exit(-1) +}) + +assert.same = function (actual, expected) { + for (var key in expected) { + assert.equal(actual[key], expected[key]) + } +} + +assert.emits = function (item, eventName, callback, message) { + var called = false + var id = setTimeout(function () { + test("Should have called '" + eventName + "' event", function () { + assert.ok(called, message || "Expected '" + eventName + "' to be called.") + }) + }, 5000) + + item.once(eventName, function () { + if (eventName === 'error') { + // belt and braces test to ensure all error events return an error + assert.ok( + arguments[0] instanceof Error, + 'Expected error events to throw instances of Error but found: ' + sys.inspect(arguments[0]) + ) + } + called = true + clearTimeout(id) + assert.ok(true) + if (callback) { + callback.apply(item, arguments) + } + }) +} + +assert.UTCDate = function (actual, year, month, day, hours, min, sec, milisecond) { + var actualYear = actual.getUTCFullYear() + assert.equal(actualYear, year, 'expected year ' + year + ' but got ' + actualYear) + + var actualMonth = actual.getUTCMonth() + assert.equal(actualMonth, month, 'expected month ' + month + ' but got ' + actualMonth) + + var actualDate = actual.getUTCDate() + assert.equal(actualDate, day, 'expected day ' + day + ' but got ' + actualDate) + + var actualHours = actual.getUTCHours() + assert.equal(actualHours, hours, 'expected hours ' + hours + ' but got ' + actualHours) + + var actualMin = actual.getUTCMinutes() + assert.equal(actualMin, min, 'expected min ' + min + ' but got ' + actualMin) + + var actualSec = actual.getUTCSeconds() + assert.equal(actualSec, sec, 'expected sec ' + sec + ' but got ' + actualSec) + + var actualMili = actual.getUTCMilliseconds() + assert.equal(actualMili, milisecond, 'expected milisecond ' + milisecond + ' but got ' + actualMili) +} + +assert.equalBuffers = function (actual, expected) { + if (actual.length != expected.length) { + spit(actual, expected) + assert.equal(actual.length, expected.length) + } + for (var i = 0; i < actual.length; i++) { + if (actual[i] != expected[i]) { + spit(actual, expected) + } + assert.equal(actual[i], expected[i]) + } +} + +assert.empty = function (actual) { + assert.lengthIs(actual, 0) +} + +assert.success = function (callback) { + if (callback.length === 1 || callback.length === 0) { + return assert.calls(function (err, arg) { + if (err) { + console.log(err) + } + assert(!err) + callback(arg) + }) + } else if (callback.length === 2) { + return assert.calls(function (err, arg1, arg2) { + if (err) { + console.log(err) + } + assert(!err) + callback(arg1, arg2) + }) + } else { + throw new Error('need to preserve arrity of wrapped function') + } +} + +assert.lengthIs = function (actual, expectedLength) { + assert.equal(actual.length, expectedLength) +} + +var expect = function (callback, timeout) { + var executed = false + timeout = timeout || parseInt(process.env.TEST_TIMEOUT) || 5000 + var id = setTimeout(function () { + assert.ok( + executed, + 'Expected execution of function to be fired within ' + + timeout + + ' milliseconds ' + + ' (hint: export TEST_TIMEOUT=' + + ' to change timeout globally)' + + callback.toString() + ) + }, timeout) + + if (callback.length < 3) { + return function (err, queryResult) { + clearTimeout(id) + if (err) { + assert.ok(err instanceof Error, 'Expected errors to be instances of Error: ' + sys.inspect(err)) + } + callback.apply(this, arguments) + } + } else if (callback.length == 3) { + return function (err, arg1, arg2) { + clearTimeout(id) + if (err) { + assert.ok(err instanceof Error, 'Expected errors to be instances of Error: ' + sys.inspect(err)) + } + callback.apply(this, arguments) + } + } else { + throw new Error('Unsupported arrity ' + callback.length) + } +} +assert.calls = expect + +assert.isNull = function (item, message) { + message = message || 'expected ' + item + ' to be null' + assert.ok(item === null, message) +} + +global.test = function (name, action) { + test.testCount++ + test[name] = action + var result = test[name]() + if (result === false) { + process.stdout.write('?') + } else { + process.stdout.write('.') + } +} + +// print out the filename +process.stdout.write(require('path').basename(process.argv[1])) +if (args.binary) process.stdout.write(' (binary)') +if (args.native) process.stdout.write(' (native)') + +process.on('exit', function () { + console.log('') +}) + +process.on('uncaughtException', function (err) { + console.error('\n %s', err.stack || err.toString()) + // causes xargs to abort right away + process.exit(255) +}) + +var Sink = function (expected, timeout, callback) { + var defaultTimeout = 5000 + if (typeof timeout === 'function') { + callback = timeout + timeout = defaultTimeout + } + timeout = timeout || defaultTimeout + var internalCount = 0 + var kill = function () { + assert.ok(false, 'Did not reach expected ' + expected + ' with an idle timeout of ' + timeout) + } + var killTimeout = setTimeout(kill, timeout) + return { + add: function (count) { + count = count || 1 + internalCount += count + clearTimeout(killTimeout) + if (internalCount < expected) { + killTimeout = setTimeout(kill, timeout) + } else { + assert.equal(internalCount, expected) + callback() + } + }, + } +} + +var getTimezoneOffset = Date.prototype.getTimezoneOffset + +var setTimezoneOffset = function (minutesOffset) { + Date.prototype.getTimezoneOffset = function () { + return minutesOffset + } +} + +var resetTimezoneOffset = function () { + Date.prototype.getTimezoneOffset = getTimezoneOffset +} + +const rejection = (promise) => + promise.then( + (value) => { + throw new Error(`Promise resolved when rejection was expected; value: ${sys.inspect(value)}`) + }, + (error) => error + ) + +module.exports = { + Sink: Sink, + Suite: Suite, + pg: require('./../lib/'), + args: args, + config: args, + sys: sys, + Client: Client, + setTimezoneOffset: setTimezoneOffset, + resetTimezoneOffset: resetTimezoneOffset, + rejection: rejection, +} diff --git a/packages/pg/test/tls/GNUmakefile b/packages/pg/test/tls/GNUmakefile new file mode 100644 index 0000000..12d8f49 --- /dev/null +++ b/packages/pg/test/tls/GNUmakefile @@ -0,0 +1,71 @@ +DESTDIR ::= /var/lib/postgres/data +POSTGRES_USER ::= postgres +POSTGRES_GROUP ::= postgres +DATABASE_HOST ::= localhost +DATABASE_USER ::= postgres + +all: \ + test-server-ca.crt \ + test-client-ca.crt \ + test-server.key \ + test-server.crt \ + test-client.key \ + test-client.crt + +clean: + rm -f \ + test-server-ca.key \ + test-client-ca.key \ + test-server-ca.crt \ + test-client-ca.crt \ + test-server.key \ + test-server.crt \ + test-client.key \ + test-client.crt + +install: test-server.crt test-server.key test-client-ca.crt + install \ + --owner=$(POSTGRES_USER) \ + --group=$(POSTGRES_GROUP) \ + --mode=0600 \ + -t $(DESTDIR) \ + $^ + +test-%-ca.crt: test-%-ca.key + openssl req -new -x509 \ + -subj '/CN=node-postgres test $* CA' \ + -days 3650 \ + -key $< \ + -out $@ + +test-server.csr: test-server.key + openssl req -new \ + -subj '/CN=$(DATABASE_HOST)' \ + -key $< \ + -out $@ + +test-client.csr: test-client.key + openssl req -new \ + -subj '/CN=$(DATABASE_USER)' \ + -key $< \ + -out $@ + +test-%.crt: test-%.csr test-%-ca.crt test-%-ca.key + openssl x509 -req \ + -CA test-$*-ca.crt \ + -CAkey test-$*-ca.key \ + -set_serial 1 \ + -days 3650 \ + -in $< \ + -out $@ + +%.key: + openssl genpkey \ + -algorithm EC \ + -pkeyopt ec_paramgen_curve:prime256v1 \ + -out $@ + +.PHONY: all clean install +.SECONDARY: test-server-ca.key test-client-ca.key +.INTERMEDIATE: test-server.csr test-client.csr +.POSIX: diff --git a/packages/pg/test/tls/test-client-ca.crt b/packages/pg/test/tls/test-client-ca.crt new file mode 100644 index 0000000..c2c5c04 --- /dev/null +++ b/packages/pg/test/tls/test-client-ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUmgAwIBAgIUNYMF06PrmjsMR6x+C8k5YZn9heAwCgYIKoZIzj0EAwIw +JzElMCMGA1UEAwwcbm9kZS1wb3N0Z3JlcyB0ZXN0IGNsaWVudCBDQTAeFw0yMDEw +MzExOTI1NDdaFw0zMDEwMjkxOTI1NDdaMCcxJTAjBgNVBAMMHG5vZGUtcG9zdGdy +ZXMgdGVzdCBjbGllbnQgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASI/Efx +Pq0P54VKPkTUOTwBH1iuYbnLpd4kAGjb1E334/p9CEBbDREVSqDjYjWswFybxKIF +ooKXtMpEMJfymJAUo1MwUTAdBgNVHQ4EFgQU/b/FRwYZ5/VMjdesIolksiqNYK4w +HwYDVR0jBBgwFoAU/b/FRwYZ5/VMjdesIolksiqNYK4wDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAgNIADBFAiEApHFCAWGbRGqYkyiBO+gMyX6gF5oFJywUupZP +LfgIRDACIDBZotzPe6+BIl2fU9Xgm7CxV6cCoX8bPEJKveKMnOaN +-----END CERTIFICATE----- diff --git a/packages/pg/test/tls/test-client-ca.key b/packages/pg/test/tls/test-client-ca.key new file mode 100644 index 0000000..86a4cb4 --- /dev/null +++ b/packages/pg/test/tls/test-client-ca.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKsipfQWM+41FriF7 +kRxVaiNi8qY1fzLx6Dp/gUQQPG6hRANCAASI/EfxPq0P54VKPkTUOTwBH1iuYbnL +pd4kAGjb1E334/p9CEBbDREVSqDjYjWswFybxKIFooKXtMpEMJfymJAU +-----END PRIVATE KEY----- diff --git a/packages/pg/test/tls/test-client.crt b/packages/pg/test/tls/test-client.crt new file mode 100644 index 0000000..2d2a899 --- /dev/null +++ b/packages/pg/test/tls/test-client.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBITCByAIBATAKBggqhkjOPQQDAjAnMSUwIwYDVQQDDBxub2RlLXBvc3RncmVz +IHRlc3QgY2xpZW50IENBMB4XDTIwMTAzMTE5MjU0N1oXDTMwMTAyOTE5MjU0N1ow +EzERMA8GA1UEAwwIcG9zdGdyZXMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARY +4j5AgTLi/O/UTB8l1mX+nD9u3SW9RwN1mekcqEZqCpOPMsQEQ/HLxaKnoSTD6w/G +NqrBnHlbMGPwEdKvV96bMAoGCCqGSM49BAMCA0gAMEUCIQDzfjm+BzmjrsIO4QRu +Et0ShHBK3Kley3oqnzoJHCUSmAIgdF5gELQ5mlJVX3bAI8h1cKiC/L6awwg7eBDU +S1gBTaI= +-----END CERTIFICATE----- diff --git a/packages/pg/test/tls/test-client.key b/packages/pg/test/tls/test-client.key new file mode 100644 index 0000000..662f355 --- /dev/null +++ b/packages/pg/test/tls/test-client.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgL9jW07+fXy/74Ub3 +579RXm0Xpo7lnNnQleSzkTEXCrmhRANCAARY4j5AgTLi/O/UTB8l1mX+nD9u3SW9 +RwN1mekcqEZqCpOPMsQEQ/HLxaKnoSTD6w/GNqrBnHlbMGPwEdKvV96b +-----END PRIVATE KEY----- diff --git a/packages/pg/test/tls/test-server-ca.crt b/packages/pg/test/tls/test-server-ca.crt new file mode 100644 index 0000000..ac34275 --- /dev/null +++ b/packages/pg/test/tls/test-server-ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUmgAwIBAgIUD582G2ou0Lg9q7AJeAMpiQVaiPQwCgYIKoZIzj0EAwIw +JzElMCMGA1UEAwwcbm9kZS1wb3N0Z3JlcyB0ZXN0IHNlcnZlciBDQTAeFw0yMDEw +MzExOTI1NDdaFw0zMDEwMjkxOTI1NDdaMCcxJTAjBgNVBAMMHG5vZGUtcG9zdGdy +ZXMgdGVzdCBzZXJ2ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/jGRh +FiZu96o0hfgIkep4PusTwI6P1ASFh8LgnUu2bMcIlYakQK0ap2XvCaSl9675+Lu9 +yNZaSZVA5LpFICXto1MwUTAdBgNVHQ4EFgQUHI1BK+6u7r9r1XhighuP2/eGcQUw +HwYDVR0jBBgwFoAUHI1BK+6u7r9r1XhighuP2/eGcQUwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAgNIADBFAiALwBWN9pRpaGQ12G9ERACn8/6RtAoO4lI5RmaR +rsTHtAIhAJxMfzNIgBAgX7vBSjHaqA08CozIctDSVag/rDlAzgy0 +-----END CERTIFICATE----- diff --git a/packages/pg/test/tls/test-server-ca.key b/packages/pg/test/tls/test-server-ca.key new file mode 100644 index 0000000..bfc4925 --- /dev/null +++ b/packages/pg/test/tls/test-server-ca.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyUd4vHDNrEFzfttP +z+AFp3Tbyui+b3i9YDW7VqpMOIKhRANCAAT/jGRhFiZu96o0hfgIkep4PusTwI6P +1ASFh8LgnUu2bMcIlYakQK0ap2XvCaSl9675+Lu9yNZaSZVA5LpFICXt +-----END PRIVATE KEY----- diff --git a/packages/pg/test/tls/test-server.crt b/packages/pg/test/tls/test-server.crt new file mode 100644 index 0000000..171700d --- /dev/null +++ b/packages/pg/test/tls/test-server.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBITCByQIBATAKBggqhkjOPQQDAjAnMSUwIwYDVQQDDBxub2RlLXBvc3RncmVz +IHRlc3Qgc2VydmVyIENBMB4XDTIwMTAzMTE5MjU0N1oXDTMwMTAyOTE5MjU0N1ow +FDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +4Mwi6dHeWRZ2QU19a5ykq6gJfIVJDEaJqNlWXk/5/laiGy8ScBV0YAlvk9xsfAyU +YDxcQTjQkeC0bbzhdEPjNjAKBggqhkjOPQQDAgNHADBEAiB+DW/8Kg3tuoovAE+8 +1Pv/8OkF3MD4A1ztULkW3KJ4PwIgMn7ea3HrEQJoeSKFe1kKIgNrHftdC5kZQYj5 +uNXYpLo= +-----END CERTIFICATE----- diff --git a/packages/pg/test/tls/test-server.key b/packages/pg/test/tls/test-server.key new file mode 100644 index 0000000..1ce884e --- /dev/null +++ b/packages/pg/test/tls/test-server.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgBoW9xxWBH2tHiPFk +9ajPALHyw0lHAY1DF8WvHQNodx2hRANCAATgzCLp0d5ZFnZBTX1rnKSrqAl8hUkM +Romo2VZeT/n+VqIbLxJwFXRgCW+T3Gx8DJRgPFxBONCR4LRtvOF0Q+M2 +-----END PRIVATE KEY----- diff --git a/packages/pg/test/unit/client/cleartext-password-tests.js b/packages/pg/test/unit/client/cleartext-password-tests.js new file mode 100644 index 0000000..49db22d --- /dev/null +++ b/packages/pg/test/unit/client/cleartext-password-tests.js @@ -0,0 +1,30 @@ +'use strict' + +const helper = require('./test-helper') +const createClient = require('./test-helper').createClient + +test('cleartext password authentication', function () { + test('responds with password', function () { + var client = createClient() + client.password = '!' + client.connection.stream.packets = [] + client.connection.emit('authenticationCleartextPassword') + var packets = client.connection.stream.packets + assert.lengthIs(packets, 1) + var packet = packets[0] + assert.equalBuffers(packet, [0x70, 0, 0, 0, 6, 33, 0]) + }) + + test('does not crash with null password using pg-pass', function () { + process.env.PGPASSFILE = `${__dirname}/pgpass.file` + var client = new helper.Client({ + host: 'foo', + port: 5432, + database: 'bar', + user: 'baz', + stream: new MemoryStream(), + }) + client.connect() + client.connection.emit('authenticationCleartextPassword') + }) +}) diff --git a/packages/pg/test/unit/client/configuration-tests.js b/packages/pg/test/unit/client/configuration-tests.js new file mode 100644 index 0000000..19a1da8 --- /dev/null +++ b/packages/pg/test/unit/client/configuration-tests.js @@ -0,0 +1,168 @@ +'use strict' +require('./test-helper') +var assert = require('assert') + +var pguser = process.env['PGUSER'] || process.env.USER +var pgdatabase = process.env['PGDATABASE'] || process.env.USER +var pgport = process.env['PGPORT'] || 5432 + +test('client settings', function () { + test('defaults', function () { + var client = new Client() + assert.equal(client.user, pguser) + assert.equal(client.database, pgdatabase) + assert.equal(client.port, pgport) + assert.equal(client.ssl, false) + }) + + test('custom', function () { + var user = 'brian' + var database = 'pgjstest' + var password = 'boom' + var client = new Client({ + user: user, + database: database, + port: 321, + password: password, + ssl: true, + }) + + assert.equal(client.user, user) + assert.equal(client.database, database) + assert.equal(client.port, 321) + assert.equal(client.password, password) + assert.equal(client.ssl, true) + }) + + test('custom ssl default on', function () { + var old = process.env.PGSSLMODE + process.env.PGSSLMODE = 'prefer' + + var client = new Client() + process.env.PGSSLMODE = old + + assert.equal(client.ssl, true) + }) + + test('custom ssl force off', function () { + var old = process.env.PGSSLMODE + process.env.PGSSLMODE = 'prefer' + + var client = new Client({ + ssl: false, + }) + process.env.PGSSLMODE = old + + assert.equal(client.ssl, false) + }) +}) + +test('initializing from a config string', function () { + test('uses connectionString property', function () { + var client = new Client({ + connectionString: 'postgres://brian:pass@host1:333/databasename', + }) + assert.equal(client.user, 'brian') + assert.equal(client.password, 'pass') + assert.equal(client.host, 'host1') + assert.equal(client.port, 333) + assert.equal(client.database, 'databasename') + }) + + test('uses the correct values from the config string', function () { + var client = new Client('postgres://brian:pass@host1:333/databasename') + assert.equal(client.user, 'brian') + assert.equal(client.password, 'pass') + assert.equal(client.host, 'host1') + assert.equal(client.port, 333) + assert.equal(client.database, 'databasename') + }) + + test('uses the correct values from the config string with space in password', function () { + var client = new Client('postgres://brian:pass word@host1:333/databasename') + assert.equal(client.user, 'brian') + assert.equal(client.password, 'pass word') + assert.equal(client.host, 'host1') + assert.equal(client.port, 333) + assert.equal(client.database, 'databasename') + }) + + test('when not including all values the defaults are used', function () { + var client = new Client('postgres://host1') + assert.equal(client.user, process.env['PGUSER'] || process.env.USER) + assert.equal(client.password, process.env['PGPASSWORD'] || null) + assert.equal(client.host, 'host1') + assert.equal(client.port, process.env['PGPORT'] || 5432) + assert.equal(client.database, process.env['PGDATABASE'] || process.env.USER) + }) + + test('when not including all values the environment variables are used', function () { + var envUserDefined = process.env['PGUSER'] !== undefined + var envPasswordDefined = process.env['PGPASSWORD'] !== undefined + var envDBDefined = process.env['PGDATABASE'] !== undefined + var envHostDefined = process.env['PGHOST'] !== undefined + var envPortDefined = process.env['PGPORT'] !== undefined + + var savedEnvUser = process.env['PGUSER'] + var savedEnvPassword = process.env['PGPASSWORD'] + var savedEnvDB = process.env['PGDATABASE'] + var savedEnvHost = process.env['PGHOST'] + var savedEnvPort = process.env['PGPORT'] + + process.env['PGUSER'] = 'utUser1' + process.env['PGPASSWORD'] = 'utPass1' + process.env['PGDATABASE'] = 'utDB1' + process.env['PGHOST'] = 'utHost1' + process.env['PGPORT'] = 5464 + + var client = new Client('postgres://host1') + assert.equal(client.user, process.env['PGUSER']) + assert.equal(client.password, process.env['PGPASSWORD']) + assert.equal(client.host, 'host1') + assert.equal(client.port, process.env['PGPORT']) + assert.equal(client.database, process.env['PGDATABASE']) + + if (envUserDefined) { + process.env['PGUSER'] = savedEnvUser + } else { + delete process.env['PGUSER'] + } + + if (envPasswordDefined) { + process.env['PGPASSWORD'] = savedEnvPassword + } else { + delete process.env['PGPASSWORD'] + } + + if (envDBDefined) { + process.env['PGDATABASE'] = savedEnvDB + } else { + delete process.env['PGDATABASE'] + } + + if (envHostDefined) { + process.env['PGHOST'] = savedEnvHost + } else { + delete process.env['PGHOST'] + } + + if (envPortDefined) { + process.env['PGPORT'] = savedEnvPort + } else { + delete process.env['PGPORT'] + } + }) +}) + +test('calls connect correctly on connection', function () { + var client = new Client('/tmp') + var usedPort = '' + var usedHost = '' + client.connection.connect = function (port, host) { + usedPort = port + usedHost = host + } + client.connect() + assert.equal(usedPort, '/tmp/.s.PGSQL.' + pgport) + assert.strictEqual(usedHost, undefined) +}) diff --git a/packages/pg/test/unit/client/early-disconnect-tests.js b/packages/pg/test/unit/client/early-disconnect-tests.js new file mode 100644 index 0000000..4944828 --- /dev/null +++ b/packages/pg/test/unit/client/early-disconnect-tests.js @@ -0,0 +1,19 @@ +'use strict' +var helper = require('./test-helper') +var net = require('net') +var pg = require('../../../lib/index.js') + +/* console.log() messages show up in `make test` output. TODO: fix it. */ +var server = net.createServer(function (c) { + c.destroy() + server.close() +}) + +server.listen(7777, function () { + var client = new pg.Client('postgres://localhost:7777') + client.connect( + assert.calls(function (err) { + assert(err) + }) + ) +}) diff --git a/packages/pg/test/unit/client/escape-tests.js b/packages/pg/test/unit/client/escape-tests.js new file mode 100644 index 0000000..721b04b --- /dev/null +++ b/packages/pg/test/unit/client/escape-tests.js @@ -0,0 +1,65 @@ +'use strict' +var helper = require('./test-helper') + +function createClient(callback) { + var client = new Client(helper.config) + client.connect(function (err) { + return callback(client) + }) +} + +var testLit = function (testName, input, expected) { + test(testName, function () { + var client = new Client(helper.config) + var actual = client.escapeLiteral(input) + assert.equal(expected, actual) + }) +} + +var testIdent = function (testName, input, expected) { + test(testName, function () { + var client = new Client(helper.config) + var actual = client.escapeIdentifier(input) + assert.equal(expected, actual) + }) +} + +testLit('escapeLiteral: no special characters', 'hello world', "'hello world'") + +testLit('escapeLiteral: contains double quotes only', 'hello " world', "'hello \" world'") + +testLit('escapeLiteral: contains single quotes only', "hello ' world", "'hello '' world'") + +testLit('escapeLiteral: contains backslashes only', 'hello \\ world', " E'hello \\\\ world'") + +testLit('escapeLiteral: contains single quotes and double quotes', 'hello \' " world', "'hello '' \" world'") + +testLit('escapeLiteral: contains double quotes and backslashes', 'hello \\ " world', " E'hello \\\\ \" world'") + +testLit('escapeLiteral: contains single quotes and backslashes', "hello \\ ' world", " E'hello \\\\ '' world'") + +testLit( + 'escapeLiteral: contains single quotes, double quotes, and backslashes', + 'hello \\ \' " world', + " E'hello \\\\ '' \" world'" +) + +testIdent('escapeIdentifier: no special characters', 'hello world', '"hello world"') + +testIdent('escapeIdentifier: contains double quotes only', 'hello " world', '"hello "" world"') + +testIdent('escapeIdentifier: contains single quotes only', "hello ' world", '"hello \' world"') + +testIdent('escapeIdentifier: contains backslashes only', 'hello \\ world', '"hello \\ world"') + +testIdent('escapeIdentifier: contains single quotes and double quotes', 'hello \' " world', '"hello \' "" world"') + +testIdent('escapeIdentifier: contains double quotes and backslashes', 'hello \\ " world', '"hello \\ "" world"') + +testIdent('escapeIdentifier: contains single quotes and backslashes', "hello \\ ' world", '"hello \\ \' world"') + +testIdent( + 'escapeIdentifier: contains single quotes, double quotes, and backslashes', + 'hello \\ \' " world', + '"hello \\ \' "" world"' +) diff --git a/packages/pg/test/unit/client/md5-password-tests.js b/packages/pg/test/unit/client/md5-password-tests.js new file mode 100644 index 0000000..71f5020 --- /dev/null +++ b/packages/pg/test/unit/client/md5-password-tests.js @@ -0,0 +1,24 @@ +'use strict' +var helper = require('./test-helper') +const BufferList = require('../../buffer-list') +var utils = require('../../../lib/utils') + +test('md5 authentication', function () { + var client = helper.createClient() + client.password = '!' + var salt = Buffer.from([1, 2, 3, 4]) + client.connection.emit('authenticationMD5Password', { salt: salt }) + + test('responds', function () { + assert.lengthIs(client.connection.stream.packets, 1) + test('should have correct encrypted data', function () { + var password = utils.postgresMd5PasswordHash(client.user, client.password, salt) + // how do we want to test this? + assert.equalBuffers(client.connection.stream.packets[0], new BufferList().addCString(password).join(true, 'p')) + }) + }) +}) + +test('md5 of utf-8 strings', function () { + assert.equal(utils.md5('😊'), '5deda34cd95f304948d2bc1b4a62c11e') +}) diff --git a/packages/pg/test/unit/client/notification-tests.js b/packages/pg/test/unit/client/notification-tests.js new file mode 100644 index 0000000..7143aca --- /dev/null +++ b/packages/pg/test/unit/client/notification-tests.js @@ -0,0 +1,10 @@ +'use strict' +var helper = require('./test-helper') + +test('passes connection notification', function () { + var client = helper.client() + assert.emits(client, 'notice', function (msg) { + assert.equal(msg, 'HAY!!') + }) + client.connection.emit('notice', 'HAY!!') +}) diff --git a/packages/pg/test/unit/client/pgpass.file b/packages/pg/test/unit/client/pgpass.file new file mode 100644 index 0000000..fa0cd41 --- /dev/null +++ b/packages/pg/test/unit/client/pgpass.file @@ -0,0 +1 @@ +foo:5432:bar:baz:quz diff --git a/packages/pg/test/unit/client/prepared-statement-tests.js b/packages/pg/test/unit/client/prepared-statement-tests.js new file mode 100644 index 0000000..2499808 --- /dev/null +++ b/packages/pg/test/unit/client/prepared-statement-tests.js @@ -0,0 +1,157 @@ +'use strict' +var helper = require('./test-helper') +var Query = require('../../../lib/query') + +var client = helper.client() +var con = client.connection +var parseArg = null +con.parse = function (arg) { + parseArg = arg + process.nextTick(function () { + con.emit('parseComplete') + }) +} + +var bindArg = null +con.bind = function (arg) { + bindArg = arg + process.nextTick(function () { + con.emit('bindComplete') + }) +} + +var executeArg = null +con.execute = function (arg) { + executeArg = arg + process.nextTick(function () { + con.emit('rowData', { fields: [] }) + con.emit('commandComplete', { text: '' }) + }) +} + +var describeArg = null +con.describe = function (arg) { + describeArg = arg + process.nextTick(function () { + con.emit('rowDescription', { fields: [] }) + }) +} + +var syncCalled = false +con.flush = function () {} +con.sync = function () { + syncCalled = true + process.nextTick(function () { + con.emit('readyForQuery') + }) +} + +test('bound command', function () { + test('simple, unnamed bound command', function () { + assert.ok(client.connection.emit('readyForQuery')) + + var query = client.query( + new Query({ + text: 'select * from X where name = $1', + values: ['hi'], + }) + ) + + assert.emits(query, 'end', function () { + test('parse argument', function () { + assert.equal(parseArg.name, null) + assert.equal(parseArg.text, 'select * from X where name = $1') + assert.equal(parseArg.types, null) + }) + + test('bind argument', function () { + assert.equal(bindArg.statement, null) + assert.equal(bindArg.portal, '') + assert.lengthIs(bindArg.values, 1) + assert.equal(bindArg.values[0], 'hi') + }) + + test('describe argument', function () { + assert.equal(describeArg.type, 'P') + assert.equal(describeArg.name, '') + }) + + test('execute argument', function () { + assert.equal(executeArg.portal, '') + assert.equal(executeArg.rows, null) + }) + + test('sync called', function () { + assert.ok(syncCalled) + }) + }) + }) +}) + +var portalClient = helper.client() +var portalCon = portalClient.connection +var portalParseArg = null +portalCon.parse = function (arg) { + portalParseArg = arg + process.nextTick(function () { + portalCon.emit('parseComplete') + }) +} + +var portalBindArg = null +portalCon.bind = function (arg) { + portalBindArg = arg + process.nextTick(function () { + portalCon.emit('bindComplete') + }) +} + +var portalExecuteArg = null +portalCon.execute = function (arg) { + portalExecuteArg = arg + process.nextTick(function () { + portalCon.emit('rowData', { fields: [] }) + portalCon.emit('commandComplete', { text: '' }) + }) +} + +var portalDescribeArg = null +portalCon.describe = function (arg) { + portalDescribeArg = arg + process.nextTick(function () { + portalCon.emit('rowDescription', { fields: [] }) + }) +} + +portalCon.flush = function () {} +portalCon.sync = function () { + process.nextTick(function () { + portalCon.emit('readyForQuery') + }) +} + +test('prepared statement with explicit portal', function () { + assert.ok(portalClient.connection.emit('readyForQuery')) + + var query = portalClient.query( + new Query({ + text: 'select * from X where name = $1', + portal: 'myportal', + values: ['hi'], + }) + ) + + assert.emits(query, 'end', function () { + test('bind argument', function () { + assert.equal(portalBindArg.portal, 'myportal') + }) + + test('describe argument', function () { + assert.equal(portalDescribeArg.name, 'myportal') + }) + + test('execute argument', function () { + assert.equal(portalExecuteArg.portal, 'myportal') + }) + }) +}) diff --git a/packages/pg/test/unit/client/query-queue-tests.js b/packages/pg/test/unit/client/query-queue-tests.js new file mode 100644 index 0000000..0b5eaa5 --- /dev/null +++ b/packages/pg/test/unit/client/query-queue-tests.js @@ -0,0 +1,52 @@ +'use strict' +var helper = require('./test-helper') +var Connection = require('../../../lib/connection') + +test('drain', function () { + var con = new Connection({ stream: 'NO' }) + var client = new Client({ connection: con }) + con.connect = function () { + con.emit('connect') + } + con.query = function () {} + client.connect() + + var raisedDrain = false + client.on('drain', function () { + raisedDrain = true + }) + + client.query('hello') + client.query('sup') + client.query('boom') + + test('with pending queries', function () { + test('does not emit drain', function () { + assert.equal(raisedDrain, false) + }) + }) + + test('after some queries executed', function () { + con.emit('readyForQuery') + test('does not emit drain', function () { + assert.equal(raisedDrain, false) + }) + }) + + test('when all queries are sent', function () { + con.emit('readyForQuery') + con.emit('readyForQuery') + test('does not emit drain', function () { + assert.equal(raisedDrain, false) + }) + }) + + test('after last query finishes', function () { + con.emit('readyForQuery') + test('emits drain', function () { + process.nextTick(function () { + assert.ok(raisedDrain) + }) + }) + }) +}) diff --git a/packages/pg/test/unit/client/result-metadata-tests.js b/packages/pg/test/unit/client/result-metadata-tests.js new file mode 100644 index 0000000..a5e6542 --- /dev/null +++ b/packages/pg/test/unit/client/result-metadata-tests.js @@ -0,0 +1,42 @@ +'use strict' +var helper = require('./test-helper') + +var testForTag = function (tagText, callback) { + test('includes command tag data for tag ' + tagText, function () { + var client = helper.client() + client.connection.emit('readyForQuery') + + var query = client.query( + 'whatever', + assert.calls((err, result) => { + assert.ok(result != null, 'should pass something to this event') + callback(result) + }) + ) + assert.lengthIs(client.connection.queries, 1) + + client.connection.emit('commandComplete', { + text: tagText, + }) + + client.connection.emit('readyForQuery') + }) +} + +var check = function (oid, rowCount, command) { + return function (result) { + if (oid != null) { + assert.equal(result.oid, oid) + } + assert.equal(result.rowCount, rowCount) + assert.equal(result.command, command) + } +} + +testForTag('INSERT 0 3', check(0, 3, 'INSERT')) +testForTag('INSERT 841 1', check(841, 1, 'INSERT')) +testForTag('DELETE 10', check(null, 10, 'DELETE')) +testForTag('UPDATE 11', check(null, 11, 'UPDATE')) +testForTag('SELECT 20', check(null, 20, 'SELECT')) +testForTag('COPY', check(null, null, 'COPY')) +testForTag('COPY 12345', check(null, 12345, 'COPY')) diff --git a/packages/pg/test/unit/client/sasl-scram-tests.js b/packages/pg/test/unit/client/sasl-scram-tests.js new file mode 100644 index 0000000..e53448b --- /dev/null +++ b/packages/pg/test/unit/client/sasl-scram-tests.js @@ -0,0 +1,189 @@ +'use strict' +require('./test-helper') + +var sasl = require('../../../lib/sasl') + +test('sasl/scram', function () { + test('startSession', function () { + test('fails when mechanisms does not include SCRAM-SHA-256', function () { + assert.throws( + function () { + sasl.startSession([]) + }, + { + message: 'SASL: Only mechanism SCRAM-SHA-256 is currently supported', + } + ) + }) + + test('returns expected session data', function () { + const session = sasl.startSession(['SCRAM-SHA-256']) + + assert.equal(session.mechanism, 'SCRAM-SHA-256') + assert.equal(String(session.clientNonce).length, 24) + assert.equal(session.message, 'SASLInitialResponse') + + assert(session.response.match(/^n,,n=\*,r=.{24}/)) + }) + + test('creates random nonces', function () { + const session1 = sasl.startSession(['SCRAM-SHA-256']) + const session2 = sasl.startSession(['SCRAM-SHA-256']) + + assert(session1.clientNonce != session2.clientNonce) + }) + }) + + test('continueSession', function () { + test('fails when last session message was not SASLInitialResponse', function () { + assert.throws( + function () { + sasl.continueSession({}, '', '') + }, + { + message: 'SASL: Last message was not SASLInitialResponse', + } + ) + }) + + test('fails when nonce is missing in server message', function () { + assert.throws( + function () { + sasl.continueSession( + { + message: 'SASLInitialResponse', + }, + 'bad-password', + 's=1,i=1' + ) + }, + { + message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing', + } + ) + }) + + test('fails when salt is missing in server message', function () { + assert.throws( + function () { + sasl.continueSession( + { + message: 'SASLInitialResponse', + }, + 'bad-password', + 'r=1,i=1' + ) + }, + { + message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing', + } + ) + }) + + test('fails when iteration is missing in server message', function () { + assert.throws( + function () { + sasl.continueSession( + { + message: 'SASLInitialResponse', + }, + 'bad-password', + 'r=1,s=abcd' + ) + }, + { + message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing', + } + ) + }) + + test('fails when server nonce does not start with client nonce', function () { + assert.throws( + function () { + sasl.continueSession( + { + message: 'SASLInitialResponse', + clientNonce: '2', + }, + 'bad-password', + 'r=1,s=abcd,i=1' + ) + }, + { + message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce', + } + ) + }) + + test('sets expected session data', function () { + const session = { + message: 'SASLInitialResponse', + clientNonce: 'a', + } + + sasl.continueSession(session, 'password', 'r=ab,s=abcd,i=1') + + assert.equal(session.message, 'SASLResponse') + assert.equal(session.serverSignature, 'jwt97IHWFn7FEqHykPTxsoQrKGOMXJl/PJyJ1JXTBKc=') + + assert.equal(session.response, 'c=biws,r=ab,p=mU8grLfTjDrJer9ITsdHk0igMRDejG10EJPFbIBL3D0=') + }) + }) + + test('continueSession', function () { + test('fails when last session message was not SASLResponse', function () { + assert.throws( + function () { + sasl.finalizeSession({}) + }, + { + message: 'SASL: Last message was not SASLResponse', + } + ) + }) + + test('fails when server signature is not valid base64', function () { + assert.throws( + function () { + sasl.finalizeSession( + { + message: 'SASLResponse', + serverSignature: 'abcd', + }, + 'v=x1' // Purposefully invalid base64 + ) + }, + { + message: 'SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64', + } + ) + }) + + test('fails when server signature does not match', function () { + assert.throws( + function () { + sasl.finalizeSession( + { + message: 'SASLResponse', + serverSignature: 'abcd', + }, + 'v=xyzq' + ) + }, + { + message: 'SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match', + } + ) + }) + + test('does not fail when eveything is ok', function () { + sasl.finalizeSession( + { + message: 'SASLResponse', + serverSignature: 'abcd', + }, + 'v=abcd' + ) + }) + }) +}) diff --git a/packages/pg/test/unit/client/set-keepalives-tests.js b/packages/pg/test/unit/client/set-keepalives-tests.js new file mode 100644 index 0000000..3fef0c0 --- /dev/null +++ b/packages/pg/test/unit/client/set-keepalives-tests.js @@ -0,0 +1,32 @@ +'use strict' +const net = require('net') +const pg = require('../../../lib/index.js') +const helper = require('./test-helper') + +const suite = new helper.Suite() + +suite.test('setting keep alive', (done) => { + const server = net.createServer((c) => { + c.destroy() + server.close() + }) + + server.listen(7777, () => { + const stream = new net.Socket() + stream.setKeepAlive = (enable, initialDelay) => { + assert(enable === true) + assert(initialDelay === 10000) + done() + } + + const client = new pg.Client({ + host: 'localhost', + port: 7777, + keepAlive: true, + keepAliveInitialDelayMillis: 10000, + stream, + }) + + client.connect().catch(() => {}) + }) +}) diff --git a/packages/pg/test/unit/client/simple-query-tests.js b/packages/pg/test/unit/client/simple-query-tests.js new file mode 100644 index 0000000..2c3ea5e --- /dev/null +++ b/packages/pg/test/unit/client/simple-query-tests.js @@ -0,0 +1,153 @@ +'use strict' +var helper = require('./test-helper') +var Query = require('../../../lib/query') + +test('executing query', function () { + test('queing query', function () { + test('when connection is ready', function () { + var client = helper.client() + assert.empty(client.connection.queries) + client.connection.emit('readyForQuery') + client.query('yes') + assert.lengthIs(client.connection.queries, 1) + assert.equal(client.connection.queries, 'yes') + }) + + test('when connection is not ready', function () { + var client = helper.client() + + test('query is not sent', function () { + client.query('boom') + assert.empty(client.connection.queries) + }) + + test('sends query to connection once ready', function () { + assert.ok(client.connection.emit('readyForQuery')) + assert.lengthIs(client.connection.queries, 1) + assert.equal(client.connection.queries[0], 'boom') + }) + }) + + test('multiple in the queue', function () { + var client = helper.client() + var connection = client.connection + var queries = connection.queries + client.query('one') + client.query('two') + client.query('three') + assert.empty(queries) + + test('after one ready for query', function () { + connection.emit('readyForQuery') + assert.lengthIs(queries, 1) + assert.equal(queries[0], 'one') + }) + + test('after two ready for query', function () { + connection.emit('readyForQuery') + assert.lengthIs(queries, 2) + }) + + test('after a bunch more', function () { + connection.emit('readyForQuery') + connection.emit('readyForQuery') + connection.emit('readyForQuery') + assert.lengthIs(queries, 3) + assert.equal(queries[0], 'one') + assert.equal(queries[1], 'two') + assert.equal(queries[2], 'three') + }) + }) + }) + + test('query event binding and flow', function () { + var client = helper.client() + var con = client.connection + var query = client.query(new Query('whatever')) + + test('has no queries sent before ready', function () { + assert.empty(con.queries) + }) + + test('sends query on readyForQuery event', function () { + con.emit('readyForQuery') + assert.lengthIs(con.queries, 1) + assert.equal(con.queries[0], 'whatever') + }) + + test('handles rowDescription message', function () { + var handled = con.emit('rowDescription', { + fields: [ + { + name: 'boom', + }, + ], + }) + assert.ok(handled, 'should have handlded rowDescription') + }) + + test('handles dataRow messages', function () { + assert.emits(query, 'row', function (row) { + assert.equal(row['boom'], 'hi') + }) + + var handled = con.emit('dataRow', { fields: ['hi'] }) + assert.ok(handled, 'should have handled first data row message') + + assert.emits(query, 'row', function (row) { + assert.equal(row['boom'], 'bye') + }) + + var handledAgain = con.emit('dataRow', { fields: ['bye'] }) + assert.ok(handledAgain, 'should have handled seciond data row message') + }) + + // multiple command complete messages will be sent + // when multiple queries are in a simple command + test('handles command complete messages', function () { + con.emit('commandComplete', { + text: 'INSERT 31 1', + }) + }) + + test('removes itself after another readyForQuery message', function () { + return false + assert.emits(query, 'end', function (msg) { + // TODO do we want to check the complete messages? + }) + con.emit('readyForQuery') + // this would never actually happen + ;['dataRow', 'rowDescription', 'commandComplete'].forEach(function (msg) { + assert.equal(con.emit(msg), false, "Should no longer be picking up '" + msg + "' messages") + }) + }) + }) + + test('handles errors', function () { + var client = helper.client() + + test('throws an error when config is null', function () { + try { + client.query(null, undefined) + } catch (error) { + assert.equal( + error.message, + 'Client was passed a null or undefined query', + 'Should have thrown an Error for null queries' + ) + } + }) + + test('throws an error when config is undefined', function () { + try { + client.query() + } catch (error) { + assert.equal( + error.message, + 'Client was passed a null or undefined query', + 'Should have thrown an Error for null queries' + ) + } + }) + }) +}) diff --git a/packages/pg/test/unit/client/stream-and-query-error-interaction-tests.js b/packages/pg/test/unit/client/stream-and-query-error-interaction-tests.js new file mode 100644 index 0000000..892d2e8 --- /dev/null +++ b/packages/pg/test/unit/client/stream-and-query-error-interaction-tests.js @@ -0,0 +1,38 @@ +'use strict' +var helper = require('./test-helper') +var Connection = require('../../../lib/connection') +var Client = require('../../../lib/client') + +test('emits end when not in query', function () { + var stream = new (require('events').EventEmitter)() + stream.setNoDelay = () => {} + stream.connect = function () { + // NOOP + } + stream.write = function () { + // NOOP + } + + var client = new Client({ connection: new Connection({ stream: stream }) }) + client.connect( + assert.calls(function () { + client.query( + 'SELECT NOW()', + assert.calls(function (err, result) { + assert(err) + }) + ) + }) + ) + assert.emits(client, 'error') + assert.emits(client, 'end') + client.connection.emit('connect') + process.nextTick(function () { + client.connection.emit('readyForQuery') + assert.equal(client.queryQueue.length, 0) + assert(client.activeQuery, 'client should have issued query') + process.nextTick(function () { + stream.emit('close') + }) + }) +}) diff --git a/packages/pg/test/unit/client/test-helper.js b/packages/pg/test/unit/client/test-helper.js new file mode 100644 index 0000000..8d18590 --- /dev/null +++ b/packages/pg/test/unit/client/test-helper.js @@ -0,0 +1,24 @@ +'use strict' +var helper = require('../test-helper') +var Connection = require('../../../lib/connection') + +var makeClient = function () { + var connection = new Connection({ stream: 'no' }) + connection.startup = function () {} + connection.connect = function () {} + connection.query = function (text) { + this.queries.push(text) + } + connection.queries = [] + var client = new Client({ connection: connection }) + client.connect() + client.connection.emit('connect') + return client +} + +module.exports = Object.assign( + { + client: makeClient, + }, + helper +) diff --git a/packages/pg/test/unit/client/throw-in-type-parser-tests.js b/packages/pg/test/unit/client/throw-in-type-parser-tests.js new file mode 100644 index 0000000..8f71fdc --- /dev/null +++ b/packages/pg/test/unit/client/throw-in-type-parser-tests.js @@ -0,0 +1,69 @@ +'use strict' +var helper = require('./test-helper') +var Query = require('../../../lib/query') +var types = require('pg-types') + +const suite = new helper.Suite() + +var typeParserError = new Error('TEST: Throw in type parsers') + +types.setTypeParser('special oid that will throw', function () { + throw typeParserError +}) + +const emitFakeEvents = (con) => { + setImmediate(() => { + con.emit('readyForQuery') + + con.emit('rowDescription', { + fields: [ + { + name: 'boom', + dataTypeID: 'special oid that will throw', + }, + ], + }) + + con.emit('dataRow', { fields: ['hi'] }) + con.emit('dataRow', { fields: ['hi'] }) + con.emit('commandComplete', { text: 'INSERT 31 1' }) + con.emit('readyForQuery') + }) +} + +suite.test('emits error', function (done) { + var handled + var client = helper.client() + var con = client.connection + var query = client.query(new Query('whatever')) + emitFakeEvents(con) + + assert.emits(query, 'error', function (err) { + assert.equal(err, typeParserError) + done() + }) +}) + +suite.test('calls callback with error', function (done) { + var handled + + var callbackCalled = 0 + + var client = helper.client() + var con = client.connection + emitFakeEvents(con) + var query = client.query('whatever', function (err) { + assert.equal(err, typeParserError) + done() + }) +}) + +suite.test('rejects promise with error', function (done) { + var client = helper.client() + var con = client.connection + emitFakeEvents(con) + client.query('whatever').catch((err) => { + assert.equal(err, typeParserError) + done() + }) +}) diff --git a/packages/pg/test/unit/connection-parameters/creation-tests.js b/packages/pg/test/unit/connection-parameters/creation-tests.js new file mode 100644 index 0000000..633b0ea --- /dev/null +++ b/packages/pg/test/unit/connection-parameters/creation-tests.js @@ -0,0 +1,345 @@ +'use strict' +var helper = require('../test-helper') +var assert = require('assert') +var ConnectionParameters = require('../../../lib/connection-parameters') +var defaults = require('../../../lib').defaults + +// clear process.env +for (var key in process.env) { + delete process.env[key] +} + +test('ConnectionParameters construction', function () { + assert.ok(new ConnectionParameters(), 'with null config') + assert.ok(new ConnectionParameters({ user: 'asdf' }), 'with config object') + assert.ok(new ConnectionParameters('postgres://localhost/postgres'), 'with connection string') +}) + +var compare = function (actual, expected, type) { + const expectedDatabase = expected.database === undefined ? expected.user : expected.database + + assert.equal(actual.user, expected.user, type + ' user') + assert.equal(actual.database, expectedDatabase, type + ' database') + assert.equal(actual.port, expected.port, type + ' port') + assert.equal(actual.host, expected.host, type + ' host') + assert.equal(actual.password, expected.password, type + ' password') + assert.equal(actual.binary, expected.binary, type + ' binary') + assert.equal(actual.statement_timeout, expected.statement_timeout, type + ' statement_timeout') + assert.equal(actual.options, expected.options, type + ' options') + assert.equal( + actual.idle_in_transaction_session_timeout, + expected.idle_in_transaction_session_timeout, + type + ' idle_in_transaction_session_timeout' + ) +} + +test('ConnectionParameters initializing from defaults', function () { + var subject = new ConnectionParameters() + compare(subject, defaults, 'defaults') + assert.ok(subject.isDomainSocket === false) +}) + +test('ConnectionParameters initializing from defaults with connectionString set', function () { + var config = { + user: 'brians-are-the-best', + database: 'scoobysnacks', + port: 7777, + password: 'mypassword', + host: 'foo.bar.net', + binary: defaults.binary, + statement_timeout: false, + idle_in_transaction_session_timeout: false, + options: '-c geqo=off', + } + + var original_value = defaults.connectionString + // Just changing this here doesn't actually work because it's no longer in scope when viewed inside of + // of ConnectionParameters() so we have to pass in the defaults explicitly to test it + defaults.connectionString = + 'postgres://brians-are-the-best:mypassword@foo.bar.net:7777/scoobysnacks?options=-c geqo=off' + var subject = new ConnectionParameters(defaults) + defaults.connectionString = original_value + compare(subject, config, 'defaults-connectionString') +}) + +test('ConnectionParameters initializing from config', function () { + var config = { + user: 'brian', + database: 'home', + port: 7777, + password: 'pizza', + binary: true, + encoding: 'utf8', + host: 'yo', + ssl: { + asdf: 'blah', + }, + statement_timeout: 15000, + idle_in_transaction_session_timeout: 15000, + options: '-c geqo=off', + } + var subject = new ConnectionParameters(config) + compare(subject, config, 'config') + assert.ok(subject.isDomainSocket === false) +}) + +test('ConnectionParameters initializing from config and config.connectionString', function () { + var subject1 = new ConnectionParameters({ + connectionString: 'postgres://test@host/db', + }) + var subject2 = new ConnectionParameters({ + connectionString: 'postgres://test@host/db?ssl=1', + }) + var subject3 = new ConnectionParameters({ + connectionString: 'postgres://test@host/db', + ssl: true, + }) + var subject4 = new ConnectionParameters({ + connectionString: 'postgres://test@host/db?ssl=1', + ssl: false, + }) + + assert.equal(subject1.ssl, false) + assert.equal(subject2.ssl, true) + assert.equal(subject3.ssl, true) + assert.equal(subject4.ssl, true) +}) + +test('escape spaces if present', function () { + var subject = new ConnectionParameters('postgres://localhost/post gres') + assert.equal(subject.database, 'post gres') +}) + +test('do not double escape spaces', function () { + var subject = new ConnectionParameters('postgres://localhost/post%20gres') + assert.equal(subject.database, 'post gres') +}) + +test('initializing with unix domain socket', function () { + var subject = new ConnectionParameters('/var/run/') + assert.ok(subject.isDomainSocket) + assert.equal(subject.host, '/var/run/') + assert.equal(subject.database, defaults.user) +}) + +test('initializing with unix domain socket and a specific database, the simple way', function () { + var subject = new ConnectionParameters('/var/run/ mydb') + assert.ok(subject.isDomainSocket) + assert.equal(subject.host, '/var/run/') + assert.equal(subject.database, 'mydb') +}) + +test('initializing with unix domain socket, the health way', function () { + var subject = new ConnectionParameters('socket:/some path/?db=my[db]&encoding=utf8') + assert.ok(subject.isDomainSocket) + assert.equal(subject.host, '/some path/') + assert.equal(subject.database, 'my[db]', 'must to be escaped and unescaped trough "my%5Bdb%5D"') + assert.equal(subject.client_encoding, 'utf8') +}) + +test('initializing with unix domain socket, the escaped health way', function () { + var subject = new ConnectionParameters('socket:/some%20path/?db=my%2Bdb&encoding=utf8') + assert.ok(subject.isDomainSocket) + assert.equal(subject.host, '/some path/') + assert.equal(subject.database, 'my+db') + assert.equal(subject.client_encoding, 'utf8') +}) + +test('libpq connection string building', function () { + var checkForPart = function (array, part) { + assert.ok(array.indexOf(part) > -1, array.join(' ') + ' did not contain ' + part) + } + + test('builds simple string', function () { + var config = { + user: 'brian', + password: 'xyz', + port: 888, + host: 'localhost', + database: 'bam', + } + var subject = new ConnectionParameters(config) + subject.getLibpqConnectionString( + assert.calls(function (err, constring) { + assert(!err) + var parts = constring.split(' ') + checkForPart(parts, "user='brian'") + checkForPart(parts, "password='xyz'") + checkForPart(parts, "port='888'") + checkForPart(parts, "hostaddr='127.0.0.1'") + checkForPart(parts, "dbname='bam'") + }) + ) + }) + + test('builds dns string', function () { + var config = { + user: 'brian', + password: 'asdf', + port: 5432, + host: 'localhost', + } + var subject = new ConnectionParameters(config) + subject.getLibpqConnectionString( + assert.calls(function (err, constring) { + assert(!err) + var parts = constring.split(' ') + checkForPart(parts, "user='brian'") + checkForPart(parts, "hostaddr='127.0.0.1'") + }) + ) + }) + + test('error when dns fails', function () { + var config = { + user: 'brian', + password: 'asf', + port: 5432, + host: 'asdlfkjasldfkksfd#!$!!!!..com', + } + var subject = new ConnectionParameters(config) + subject.getLibpqConnectionString( + assert.calls(function (err, constring) { + assert.ok(err) + assert.isNull(constring) + }) + ) + }) + + test('connecting to unix domain socket', function () { + var config = { + user: 'brian', + password: 'asf', + port: 5432, + host: '/tmp/', + } + var subject = new ConnectionParameters(config) + subject.getLibpqConnectionString( + assert.calls(function (err, constring) { + assert(!err) + var parts = constring.split(' ') + checkForPart(parts, "user='brian'") + checkForPart(parts, "host='/tmp/'") + }) + ) + }) + + test('config contains quotes and backslashes', function () { + var config = { + user: 'not\\brian', + password: "bad'chars", + port: 5432, + host: '/tmp/', + } + var subject = new ConnectionParameters(config) + subject.getLibpqConnectionString( + assert.calls(function (err, constring) { + assert(!err) + var parts = constring.split(' ') + checkForPart(parts, "user='not\\\\brian'") + checkForPart(parts, "password='bad\\'chars'") + }) + ) + }) + + test('encoding can be specified by config', function () { + var config = { + client_encoding: 'utf-8', + } + var subject = new ConnectionParameters(config) + subject.getLibpqConnectionString( + assert.calls(function (err, constring) { + assert(!err) + var parts = constring.split(' ') + checkForPart(parts, "client_encoding='utf-8'") + }) + ) + }) + + test('password contains < and/or > characters', function () { + var sourceConfig = { + user: 'brian', + password: 'helloe', + port: 5432, + host: 'localhost', + database: 'postgres', + } + var connectionString = + 'postgres://' + + sourceConfig.user + + ':' + + sourceConfig.password + + '@' + + sourceConfig.host + + ':' + + sourceConfig.port + + '/' + + sourceConfig.database + var subject = new ConnectionParameters(connectionString) + assert.equal(subject.password, sourceConfig.password) + }) + + test('username or password contains weird characters', function () { + var defaults = require('../../../lib/defaults') + defaults.ssl = true + var strang = 'pg://my f%irst name:is&%awesome!@localhost:9000' + var subject = new ConnectionParameters(strang) + assert.equal(subject.user, 'my f%irst name') + assert.equal(subject.password, 'is&%awesome!') + assert.equal(subject.host, 'localhost') + assert.equal(subject.ssl, true) + }) + + test('url is properly encoded', function () { + var encoded = 'pg://bi%25na%25%25ry%20:s%40f%23@localhost/%20u%2520rl' + var subject = new ConnectionParameters(encoded) + assert.equal(subject.user, 'bi%na%%ry ') + assert.equal(subject.password, 's@f#') + assert.equal(subject.host, 'localhost') + assert.equal(subject.database, ' u%20rl') + }) + + test('ssl is set on client', function () { + var Client = require('../../../lib/client') + var defaults = require('../../../lib/defaults') + defaults.ssl = true + var c = new Client('postgres://user@password:host/database') + assert(c.ssl, 'Client should have ssl enabled via defaults') + }) + + test('coercing string "true" to boolean', function () { + const subject = new ConnectionParameters({ ssl: 'true' }) + assert.strictEqual(subject.ssl, true) + }) + + test('ssl is set on client', function () { + var sourceConfig = { + user: 'brian', + password: 'helloe', + port: 5432, + host: 'localhost', + database: 'postgres', + ssl: { + sslmode: 'verify-ca', + sslca: '/path/ca.pem', + sslkey: '/path/cert.key', + sslcert: '/path/cert.crt', + sslrootcert: '/path/root.crt', + }, + } + var Client = require('../../../lib/client') + var defaults = require('../../../lib/defaults') + defaults.ssl = true + var c = new ConnectionParameters(sourceConfig) + c.getLibpqConnectionString( + assert.calls(function (err, pgCString) { + assert(!err) + assert.equal( + pgCString.indexOf("sslrootcert='/path/root.crt'") !== -1, + true, + 'libpqConnectionString should contain sslrootcert' + ) + }) + ) + }) +}) diff --git a/packages/pg/test/unit/connection-parameters/environment-variable-tests.js b/packages/pg/test/unit/connection-parameters/environment-variable-tests.js new file mode 100644 index 0000000..b20a793 --- /dev/null +++ b/packages/pg/test/unit/connection-parameters/environment-variable-tests.js @@ -0,0 +1,128 @@ +'use strict' +var helper = require('../test-helper') +const Suite = require('../../suite') + +var assert = require('assert') +var ConnectionParameters = require('../../../lib/connection-parameters') +var defaults = require('../../../lib').defaults + +// clear process.env +var realEnv = {} +for (var key in process.env) { + realEnv[key] = process.env[key] + delete process.env[key] +} + +const suite = new Suite('ConnectionParameters') + +const clearEnv = () => { + // clear process.env + for (var key in process.env) { + delete process.env[key] + } +} + +suite.test('ConnectionParameters initialized from environment variables', function () { + clearEnv() + process.env['PGHOST'] = 'local' + process.env['PGUSER'] = 'bmc2' + process.env['PGPORT'] = 7890 + process.env['PGDATABASE'] = 'allyerbase' + process.env['PGPASSWORD'] = 'open' + + var subject = new ConnectionParameters() + assert.equal(subject.host, 'local', 'env host') + assert.equal(subject.user, 'bmc2', 'env user') + assert.equal(subject.port, 7890, 'env port') + assert.equal(subject.database, 'allyerbase', 'env database') + assert.equal(subject.password, 'open', 'env password') +}) + +suite.test('ConnectionParameters initialized from mix', function () { + clearEnv() + process.env['PGHOST'] = 'local' + process.env['PGUSER'] = 'bmc2' + process.env['PGPORT'] = 7890 + process.env['PGDATABASE'] = 'allyerbase' + process.env['PGPASSWORD'] = 'open' + delete process.env['PGPASSWORD'] + delete process.env['PGDATABASE'] + var subject = new ConnectionParameters({ + user: 'testing', + database: 'zugzug', + }) + assert.equal(subject.host, 'local', 'env host') + assert.equal(subject.user, 'testing', 'config user') + assert.equal(subject.port, 7890, 'env port') + assert.equal(subject.database, 'zugzug', 'config database') + assert.equal(subject.password, defaults.password, 'defaults password') +}) + +suite.test('connection string parsing', function () { + clearEnv() + var string = 'postgres://brian:pw@boom:381/lala' + var subject = new ConnectionParameters(string) + assert.equal(subject.host, 'boom', 'string host') + assert.equal(subject.user, 'brian', 'string user') + assert.equal(subject.password, 'pw', 'string password') + assert.equal(subject.port, 381, 'string port') + assert.equal(subject.database, 'lala', 'string database') +}) + +suite.test('connection string parsing - ssl', function () { + // clear process.env + clearEnv() + + var string = 'postgres://brian:pw@boom:381/lala?ssl=true' + var subject = new ConnectionParameters(string) + assert.equal(subject.ssl, true, 'ssl') + + string = 'postgres://brian:pw@boom:381/lala?ssl=1' + subject = new ConnectionParameters(string) + assert.equal(subject.ssl, true, 'ssl') + + string = 'postgres://brian:pw@boom:381/lala?other&ssl=true' + subject = new ConnectionParameters(string) + assert.equal(subject.ssl, true, 'ssl') + + string = 'postgres://brian:pw@boom:381/lala?ssl=0' + subject = new ConnectionParameters(string) + assert.equal(!!subject.ssl, false, 'ssl') + + string = 'postgres://brian:pw@boom:381/lala' + subject = new ConnectionParameters(string) + assert.equal(!!subject.ssl, false, 'ssl') + + string = 'postgres://brian:pw@boom:381/lala?ssl=no-verify' + subject = new ConnectionParameters(string) + assert.deepStrictEqual(subject.ssl, { rejectUnauthorized: false }, 'ssl') +}) + +suite.test('ssl is false by default', function () { + clearEnv() + var subject = new ConnectionParameters() + assert.equal(subject.ssl, false) +}) + +var testVal = function (mode, expected) { + suite.test('ssl is ' + expected + ' when $PGSSLMODE=' + mode, function () { + clearEnv() + process.env.PGSSLMODE = mode + var subject = new ConnectionParameters() + assert.deepStrictEqual(subject.ssl, expected) + }) +} + +testVal('', false) +testVal('disable', false) +testVal('allow', false) +testVal('prefer', true) +testVal('require', true) +testVal('verify-ca', true) +testVal('verify-full', true) +testVal('no-verify', { rejectUnauthorized: false }) + +// restore process.env +for (var key in realEnv) { + process.env[key] = realEnv[key] +} diff --git a/packages/pg/test/unit/connection-pool/configuration-tests.js b/packages/pg/test/unit/connection-pool/configuration-tests.js new file mode 100644 index 0000000..10c9918 --- /dev/null +++ b/packages/pg/test/unit/connection-pool/configuration-tests.js @@ -0,0 +1,14 @@ +'use strict' + +const assert = require('assert') +const helper = require('../test-helper') + +test('pool with copied settings includes password', () => { + const original = new helper.pg.Pool({ + password: 'original', + }) + + const copy = new helper.pg.Pool(original.options) + + assert.equal(copy.options.password, 'original') +}) diff --git a/packages/pg/test/unit/connection/error-tests.js b/packages/pg/test/unit/connection/error-tests.js new file mode 100644 index 0000000..091c13e --- /dev/null +++ b/packages/pg/test/unit/connection/error-tests.js @@ -0,0 +1,87 @@ +'use strict' +var helper = require('./test-helper') +var Connection = require('../../../lib/connection') +var net = require('net') + +const suite = new helper.Suite() + +suite.test('connection emits stream errors', function (done) { + var con = new Connection({ stream: new MemoryStream() }) + assert.emits(con, 'error', function (err) { + assert.equal(err.message, 'OMG!') + done() + }) + con.connect() + con.stream.emit('error', new Error('OMG!')) +}) + +suite.test('connection emits ECONNRESET errors during normal operation', function (done) { + var con = new Connection({ stream: new MemoryStream() }) + con.connect() + assert.emits(con, 'error', function (err) { + assert.equal(err.code, 'ECONNRESET') + done() + }) + var e = new Error('Connection Reset') + e.code = 'ECONNRESET' + con.stream.emit('error', e) +}) + +suite.test('connection does not emit ECONNRESET errors during disconnect', function (done) { + var con = new Connection({ stream: new MemoryStream() }) + con.connect() + var e = new Error('Connection Reset') + e.code = 'ECONNRESET' + con.end() + con.stream.emit('error', e) + done() +}) + +var SSLNegotiationPacketTests = [ + { + testName: 'connection does not emit ECONNRESET errors during disconnect also when using SSL', + errorMessage: null, + response: 'S', + responseType: 'sslconnect', + }, + { + testName: 'connection emits an error when SSL is not supported', + errorMessage: 'The server does not support SSL connections', + response: 'N', + responseType: 'error', + }, + { + testName: 'connection emits an error when postmaster responds to SSL negotiation packet', + errorMessage: 'There was an error establishing an SSL connection', + response: 'E', + responseType: 'error', + }, +] + +for (const tc of SSLNegotiationPacketTests) { + suite.test(tc.testName, function (done) { + // our fake postgres server + var socket + var server = net.createServer(function (c) { + socket = c + c.once('data', function (data) { + c.write(Buffer.from(tc.response)) + }) + }) + + server.listen(7778, function () { + var con = new Connection({ ssl: true }) + con.connect(7778, 'localhost') + assert.emits(con, tc.responseType, function (err) { + if (tc.errorMessage !== null || err) { + assert.equal(err.message, tc.errorMessage) + } + con.end() + socket.destroy() + server.close() + done() + }) + con.requestSsl() + }) + }) +} diff --git a/packages/pg/test/unit/connection/inbound-parser-tests.js b/packages/pg/test/unit/connection/inbound-parser-tests.js new file mode 100644 index 0000000..af9385c --- /dev/null +++ b/packages/pg/test/unit/connection/inbound-parser-tests.js @@ -0,0 +1,510 @@ +'use strict' +require('./test-helper') +const BufferList = require('../../buffer-list') +var Connection = require('../../../lib/connection') +var buffers = require('../../test-buffers') +var PARSE = function (buffer) { + return new Parser(buffer).parse() +} + +var authOkBuffer = buffers.authenticationOk() +var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8') +var readyForQueryBuffer = buffers.readyForQuery() +var backendKeyDataBuffer = buffers.backendKeyData(1, 2) +var commandCompleteBuffer = buffers.commandComplete('SELECT 3') +var parseCompleteBuffer = buffers.parseComplete() +var bindCompleteBuffer = buffers.bindComplete() +var portalSuspendedBuffer = buffers.portalSuspended() + +var addRow = function (bufferList, name, offset) { + return bufferList + .addCString(name) // field name + .addInt32(offset++) // table id + .addInt16(offset++) // attribute of column number + .addInt32(offset++) // objectId of field's data type + .addInt16(offset++) // datatype size + .addInt32(offset++) // type modifier + .addInt16(0) // format code, 0 => text +} + +var row1 = { + name: 'id', + tableID: 1, + attributeNumber: 2, + dataTypeID: 3, + dataTypeSize: 4, + typeModifier: 5, + formatCode: 0, +} +var oneRowDescBuff = new buffers.rowDescription([row1]) +row1.name = 'bang' + +var twoRowBuf = new buffers.rowDescription([ + row1, + { + name: 'whoah', + tableID: 10, + attributeNumber: 11, + dataTypeID: 12, + dataTypeSize: 13, + typeModifier: 14, + formatCode: 0, + }, +]) + +var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D') + +var emptyRowFieldBuf = buffers.dataRow() + +var oneFieldBuf = new BufferList() + .addInt16(1) // number of fields + .addInt32(5) // length of bytes of fields + .addCString('test') + .join(true, 'D') + +var oneFieldBuf = buffers.dataRow(['test']) + +var expectedAuthenticationOkayMessage = { + name: 'authenticationOk', + length: 8, +} + +var expectedParameterStatusMessage = { + name: 'parameterStatus', + parameterName: 'client_encoding', + parameterValue: 'UTF8', + length: 25, +} + +var expectedBackendKeyDataMessage = { + name: 'backendKeyData', + processID: 1, + secretKey: 2, +} + +var expectedReadyForQueryMessage = { + name: 'readyForQuery', + length: 5, + status: 'I', +} + +var expectedCommandCompleteMessage = { + length: 13, + text: 'SELECT 3', +} +var emptyRowDescriptionBuffer = new BufferList() + .addInt16(0) // number of fields + .join(true, 'T') + +var expectedEmptyRowDescriptionMessage = { + name: 'rowDescription', + length: 6, + fieldCount: 0, +} +var expectedOneRowMessage = { + name: 'rowDescription', + length: 27, + fieldCount: 1, +} + +var expectedTwoRowMessage = { + name: 'rowDescription', + length: 53, + fieldCount: 2, +} + +var testForMessage = function (buffer, expectedMessage) { + var lastMessage = {} + test('recieves and parses ' + expectedMessage.name, function () { + var stream = new MemoryStream() + var client = new Connection({ + stream: stream, + }) + client.connect() + + client.on('message', function (msg) { + lastMessage = msg + }) + + client.on(expectedMessage.name, function () { + client.removeAllListeners(expectedMessage.name) + }) + + stream.emit('data', buffer) + assert.same(lastMessage, expectedMessage) + }) + return lastMessage +} + +var plainPasswordBuffer = buffers.authenticationCleartextPassword() +var md5PasswordBuffer = buffers.authenticationMD5Password() +var SASLBuffer = buffers.authenticationSASL() +var SASLContinueBuffer = buffers.authenticationSASLContinue() +var SASLFinalBuffer = buffers.authenticationSASLFinal() + +var expectedPlainPasswordMessage = { + name: 'authenticationCleartextPassword', +} + +var expectedMD5PasswordMessage = { + name: 'authenticationMD5Password', +} + +var expectedSASLMessage = { + name: 'authenticationSASL', +} + +var expectedSASLContinueMessage = { + name: 'authenticationSASLContinue', + data: 'data', +} + +var expectedSASLFinalMessage = { + name: 'authenticationSASLFinal', + data: 'data', +} + +var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom') +var expectedNotificationResponseMessage = { + name: 'notification', + processId: 4, + channel: 'hi', + payload: 'boom', +} + +test('Connection', function () { + testForMessage(authOkBuffer, expectedAuthenticationOkayMessage) + testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage) + var msgMD5 = testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage) + test('md5 has right salt', function () { + assert.equalBuffers(msgMD5.salt, Buffer.from([1, 2, 3, 4])) + }) + + var msgSASL = testForMessage(SASLBuffer, expectedSASLMessage) + test('SASL has the right mechanisms', function () { + assert.deepStrictEqual(msgSASL.mechanisms, ['SCRAM-SHA-256']) + }) + testForMessage(SASLContinueBuffer, expectedSASLContinueMessage) + testForMessage(SASLFinalBuffer, expectedSASLFinalMessage) + + testForMessage(paramStatusBuffer, expectedParameterStatusMessage) + testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage) + testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage) + testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage) + testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage) + test('empty row message', function () { + var message = testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage) + test('has no fields', function () { + assert.equal(message.fields.length, 0) + }) + }) + + test('no data message', function () { + testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), { + name: 'noData', + }) + }) + + test('one row message', function () { + var message = testForMessage(oneRowDescBuff, expectedOneRowMessage) + test('has one field', function () { + assert.equal(message.fields.length, 1) + }) + test('has correct field info', function () { + assert.same(message.fields[0], { + name: 'id', + tableID: 1, + columnID: 2, + dataTypeID: 3, + dataTypeSize: 4, + dataTypeModifier: 5, + format: 'text', + }) + }) + }) + + test('two row message', function () { + var message = testForMessage(twoRowBuf, expectedTwoRowMessage) + test('has two fields', function () { + assert.equal(message.fields.length, 2) + }) + test('has correct first field', function () { + assert.same(message.fields[0], { + name: 'bang', + tableID: 1, + columnID: 2, + dataTypeID: 3, + dataTypeSize: 4, + dataTypeModifier: 5, + format: 'text', + }) + }) + test('has correct second field', function () { + assert.same(message.fields[1], { + name: 'whoah', + tableID: 10, + columnID: 11, + dataTypeID: 12, + dataTypeSize: 13, + dataTypeModifier: 14, + format: 'text', + }) + }) + }) + + test('parsing rows', function () { + test('parsing empty row', function () { + var message = testForMessage(emptyRowFieldBuf, { + name: 'dataRow', + fieldCount: 0, + }) + test('has 0 fields', function () { + assert.equal(message.fields.length, 0) + }) + }) + + test('parsing data row with fields', function () { + var message = testForMessage(oneFieldBuf, { + name: 'dataRow', + fieldCount: 1, + }) + test('has 1 field', function () { + assert.equal(message.fields.length, 1) + }) + + test('field is correct', function () { + assert.equal(message.fields[0], 'test') + }) + }) + }) + + test('notice message', function () { + // this uses the same logic as error message + var buff = buffers.notice([{ type: 'C', value: 'code' }]) + testForMessage(buff, { + name: 'notice', + code: 'code', + }) + }) + + test('error messages', function () { + test('with no fields', function () { + var msg = testForMessage(buffers.error(), { + name: 'error', + }) + }) + + test('with all the fields', function () { + var buffer = buffers.error([ + { + type: 'S', + value: 'ERROR', + }, + { + type: 'C', + value: 'code', + }, + { + type: 'M', + value: 'message', + }, + { + type: 'D', + value: 'details', + }, + { + type: 'H', + value: 'hint', + }, + { + type: 'P', + value: '100', + }, + { + type: 'p', + value: '101', + }, + { + type: 'q', + value: 'query', + }, + { + type: 'W', + value: 'where', + }, + { + type: 'F', + value: 'file', + }, + { + type: 'L', + value: 'line', + }, + { + type: 'R', + value: 'routine', + }, + { + type: 'Z', // ignored + value: 'alsdkf', + }, + ]) + + testForMessage(buffer, { + name: 'error', + severity: 'ERROR', + code: 'code', + message: 'message', + detail: 'details', + hint: 'hint', + position: '100', + internalPosition: '101', + internalQuery: 'query', + where: 'where', + file: 'file', + line: 'line', + routine: 'routine', + }) + }) + }) + + test('parses parse complete command', function () { + testForMessage(parseCompleteBuffer, { + name: 'parseComplete', + }) + }) + + test('parses bind complete command', function () { + testForMessage(bindCompleteBuffer, { + name: 'bindComplete', + }) + }) + + test('parses portal suspended message', function () { + testForMessage(portalSuspendedBuffer, { + name: 'portalSuspended', + }) + }) + + test('parses replication start message', function () { + testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), { + name: 'replicationStart', + length: 4, + }) + }) +}) + +// since the data message on a stream can randomly divide the incomming +// tcp packets anywhere, we need to make sure we can parse every single +// split on a tcp message +test('split buffer, single message parsing', function () { + var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!']) + var stream = new MemoryStream() + var client = new Connection({ + stream: stream, + }) + client.connect() + var message = null + client.on('message', function (msg) { + message = msg + }) + + test('parses when full buffer comes in', function () { + stream.emit('data', fullBuffer) + assert.lengthIs(message.fields, 5) + assert.equal(message.fields[0], null) + assert.equal(message.fields[1], 'bang') + assert.equal(message.fields[2], 'zug zug') + assert.equal(message.fields[3], null) + assert.equal(message.fields[4], '!') + }) + + var testMessageRecievedAfterSpiltAt = function (split) { + var firstBuffer = Buffer.alloc(fullBuffer.length - split) + var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length) + fullBuffer.copy(firstBuffer, 0, 0) + fullBuffer.copy(secondBuffer, 0, firstBuffer.length) + stream.emit('data', firstBuffer) + stream.emit('data', secondBuffer) + assert.lengthIs(message.fields, 5) + assert.equal(message.fields[0], null) + assert.equal(message.fields[1], 'bang') + assert.equal(message.fields[2], 'zug zug') + assert.equal(message.fields[3], null) + assert.equal(message.fields[4], '!') + } + + test('parses when split in the middle', function () { + testMessageRecievedAfterSpiltAt(6) + }) + + test('parses when split at end', function () { + testMessageRecievedAfterSpiltAt(2) + }) + + test('parses when split at beginning', function () { + testMessageRecievedAfterSpiltAt(fullBuffer.length - 2) + testMessageRecievedAfterSpiltAt(fullBuffer.length - 1) + testMessageRecievedAfterSpiltAt(fullBuffer.length - 5) + }) +}) + +test('split buffer, multiple message parsing', function () { + var dataRowBuffer = buffers.dataRow(['!']) + var readyForQueryBuffer = buffers.readyForQuery() + var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length) + dataRowBuffer.copy(fullBuffer, 0, 0) + readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0) + + var messages = [] + var stream = new MemoryStream() + var client = new Connection({ + stream: stream, + }) + client.connect() + client.on('message', function (msg) { + messages.push(msg) + }) + + var verifyMessages = function () { + assert.lengthIs(messages, 2) + assert.same(messages[0], { + name: 'dataRow', + fieldCount: 1, + }) + assert.equal(messages[0].fields[0], '!') + assert.same(messages[1], { + name: 'readyForQuery', + }) + messages = [] + } + // sanity check + test('recieves both messages when packet is not split', function () { + stream.emit('data', fullBuffer) + verifyMessages() + }) + var splitAndVerifyTwoMessages = function (split) { + var firstBuffer = Buffer.alloc(fullBuffer.length - split) + var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length) + fullBuffer.copy(firstBuffer, 0, 0) + fullBuffer.copy(secondBuffer, 0, firstBuffer.length) + stream.emit('data', firstBuffer) + stream.emit('data', secondBuffer) + } + + test('recieves both messages when packet is split', function () { + test('in the middle', function () { + splitAndVerifyTwoMessages(11) + }) + test('at the front', function () { + splitAndVerifyTwoMessages(fullBuffer.length - 1) + splitAndVerifyTwoMessages(fullBuffer.length - 4) + splitAndVerifyTwoMessages(fullBuffer.length - 6) + }) + + test('at the end', function () { + splitAndVerifyTwoMessages(8) + splitAndVerifyTwoMessages(1) + }) + }) +}) diff --git a/packages/pg/test/unit/connection/startup-tests.js b/packages/pg/test/unit/connection/startup-tests.js new file mode 100644 index 0000000..e2eb6ee --- /dev/null +++ b/packages/pg/test/unit/connection/startup-tests.js @@ -0,0 +1,66 @@ +'use strict' +require('./test-helper') +var Connection = require('../../../lib/connection') +test('connection can take existing stream', function () { + var stream = new MemoryStream() + var con = new Connection({ stream: stream }) + assert.equal(con.stream, stream) +}) + +test('using any stream', function () { + var makeStream = function () { + var stream = new MemoryStream() + stream.connect = function (port, host) { + this.connectCalled = true + this.port = port + this.host = host + } + return stream + } + + var stream = makeStream() + + var con = new Connection({ stream: stream }) + + con.connect(1234, 'bang') + + test('makes stream connect', function () { + assert.equal(stream.connectCalled, true) + }) + + test('uses configured port', function () { + assert.equal(stream.port, 1234) + }) + + test('uses configured host', function () { + assert.equal(stream.host, 'bang') + }) + + test('after stream connects client emits connected event', function () { + var hit = false + + con.once('connect', function () { + hit = true + }) + + assert.ok(stream.emit('connect')) + assert.ok(hit) + }) + + test('after stream emits connected event init TCP-keepalive', function () { + var stream = makeStream() + var con = new Connection({ stream: stream, keepAlive: true }) + con.connect(123, 'test') + + var res = false + + stream.setKeepAlive = function (bit) { + res = bit + } + + assert.ok(stream.emit('connect')) + setTimeout(function () { + assert.equal(res, true) + }) + }) +}) diff --git a/packages/pg/test/unit/connection/test-helper.js b/packages/pg/test/unit/connection/test-helper.js new file mode 100644 index 0000000..0cc83dc --- /dev/null +++ b/packages/pg/test/unit/connection/test-helper.js @@ -0,0 +1,2 @@ +'use strict' +module.exports = require('../test-helper') diff --git a/packages/pg/test/unit/test-helper.js b/packages/pg/test/unit/test-helper.js new file mode 100644 index 0000000..407dbf2 --- /dev/null +++ b/packages/pg/test/unit/test-helper.js @@ -0,0 +1,48 @@ +'use strict' +var EventEmitter = require('events').EventEmitter + +var helper = require('../test-helper') +var Connection = require('../../lib/connection') + +global.MemoryStream = function () { + EventEmitter.call(this) + this.packets = [] +} + +helper.sys.inherits(MemoryStream, EventEmitter) + +var p = MemoryStream.prototype + +p.connect = function () { + // NOOP +} + +p.setNoDelay = () => {} + +p.write = function (packet, cb) { + this.packets.push(packet) + if (cb) { + cb() + } +} + +p.end = function () { + p.closed = true +} + +p.setKeepAlive = function () {} +p.closed = false +p.writable = true + +const createClient = function () { + var stream = new MemoryStream() + var client = new Client({ + connection: new Connection({ stream: stream }), + }) + client.connect() + return client +} + +module.exports = Object.assign({}, helper, { + createClient: createClient, +}) diff --git a/packages/pg/test/unit/utils-tests.js b/packages/pg/test/unit/utils-tests.js new file mode 100644 index 0000000..3d087ad --- /dev/null +++ b/packages/pg/test/unit/utils-tests.js @@ -0,0 +1,241 @@ +'use strict' +var helper = require('./test-helper') +var utils = require('./../../lib/utils') +var defaults = require('./../../lib').defaults + +test('ensure types is exported on root object', function () { + var pg = require('../../lib') + assert(pg.types) + assert(pg.types.getTypeParser) + assert(pg.types.setTypeParser) +}) + +// this tests the monkey patching +// to ensure comptability with older +// versions of node +test('EventEmitter.once', function (t) { + // an event emitter + var stream = new MemoryStream() + + var callCount = 0 + stream.once('single', function () { + callCount++ + }) + + stream.emit('single') + stream.emit('single') + assert.equal(callCount, 1) +}) + +test('normalizing query configs', function () { + var config + var callback = function () {} + + config = utils.normalizeQueryConfig({ text: 'TEXT' }) + assert.same(config, { text: 'TEXT' }) + + config = utils.normalizeQueryConfig({ text: 'TEXT' }, [10]) + assert.deepEqual(config, { text: 'TEXT', values: [10] }) + + config = utils.normalizeQueryConfig({ text: 'TEXT', values: [10] }) + assert.deepEqual(config, { text: 'TEXT', values: [10] }) + + config = utils.normalizeQueryConfig('TEXT', [10], callback) + assert.deepEqual(config, { text: 'TEXT', values: [10], callback: callback }) + + config = utils.normalizeQueryConfig({ text: 'TEXT', values: [10] }, callback) + assert.deepEqual(config, { text: 'TEXT', values: [10], callback: callback }) +}) + +test('prepareValues: buffer prepared properly', function () { + var buf = Buffer.from('quack') + var out = utils.prepareValue(buf) + assert.strictEqual(buf, out) +}) + +test('prepareValues: Uint8Array prepared properly', function () { + var buf = new Uint8Array([1, 2, 3]).subarray(1, 2) + var out = utils.prepareValue(buf) + assert.ok(Buffer.isBuffer(out)) + assert.equal(out.length, 1) + assert.deepEqual(out[0], 2) +}) + +test('prepareValues: date prepared properly', function () { + helper.setTimezoneOffset(-330) + + var date = new Date(2014, 1, 1, 11, 11, 1, 7) + var out = utils.prepareValue(date) + assert.strictEqual(out, '2014-02-01T11:11:01.007+05:30') + + helper.resetTimezoneOffset() +}) + +test('prepareValues: date prepared properly as UTC', function () { + defaults.parseInputDatesAsUTC = true + + // make a date in the local timezone that represents a specific UTC point in time + var date = new Date(Date.UTC(2014, 1, 1, 11, 11, 1, 7)) + var out = utils.prepareValue(date) + assert.strictEqual(out, '2014-02-01T11:11:01.007+00:00') + + defaults.parseInputDatesAsUTC = false +}) + +test('prepareValues: BC date prepared properly', function () { + helper.setTimezoneOffset(-330) + + var date = new Date(-3245, 1, 1, 11, 11, 1, 7) + var out = utils.prepareValue(date) + assert.strictEqual(out, '3246-02-01T11:11:01.007+05:30 BC') + + helper.resetTimezoneOffset() +}) + +test('prepareValues: 1 BC date prepared properly', function () { + helper.setTimezoneOffset(-330) + + // can't use the multi-argument constructor as year 0 would be interpreted as 1900 + var date = new Date('0000-02-01T11:11:01.007') + var out = utils.prepareValue(date) + assert.strictEqual(out, '0001-02-01T11:11:01.007+05:30 BC') + + helper.resetTimezoneOffset() +}) + +test('prepareValues: undefined prepared properly', function () { + var out = utils.prepareValue(void 0) + assert.strictEqual(out, null) +}) + +test('prepareValue: null prepared properly', function () { + var out = utils.prepareValue(null) + assert.strictEqual(out, null) +}) + +test('prepareValue: true prepared properly', function () { + var out = utils.prepareValue(true) + assert.strictEqual(out, 'true') +}) + +test('prepareValue: false prepared properly', function () { + var out = utils.prepareValue(false) + assert.strictEqual(out, 'false') +}) + +test('prepareValue: number prepared properly', function () { + var out = utils.prepareValue(3.042) + assert.strictEqual(out, '3.042') +}) + +test('prepareValue: string prepared properly', function () { + var out = utils.prepareValue('big bad wolf') + assert.strictEqual(out, 'big bad wolf') +}) + +test('prepareValue: simple array prepared properly', function () { + var out = utils.prepareValue([1, null, 3, undefined, [5, 6, 'squ,awk']]) + assert.strictEqual(out, '{"1",NULL,"3",NULL,{"5","6","squ,awk"}}') +}) + +test('prepareValue: complex array prepared properly', function () { + var out = utils.prepareValue([{ x: 42 }, { y: 84 }]) + assert.strictEqual(out, '{"{\\"x\\":42}","{\\"y\\":84}"}') +}) + +test('prepareValue: date array prepared properly', function () { + helper.setTimezoneOffset(-330) + + var date = new Date(2014, 1, 1, 11, 11, 1, 7) + var out = utils.prepareValue([date]) + assert.strictEqual(out, '{"2014-02-01T11:11:01.007+05:30"}') + + helper.resetTimezoneOffset() +}) + +test('prepareValue: arbitrary objects prepared properly', function () { + var out = utils.prepareValue({ x: 42 }) + assert.strictEqual(out, '{"x":42}') +}) + +test('prepareValue: objects with simple toPostgres prepared properly', function () { + var customType = { + toPostgres: function () { + return 'zomgcustom!' + }, + } + var out = utils.prepareValue(customType) + assert.strictEqual(out, 'zomgcustom!') +}) + +test('prepareValue: buffer array prepared properly', function () { + var buffer1 = Buffer.from('dead', 'hex') + var buffer2 = Buffer.from('beef', 'hex') + var out = utils.prepareValue([buffer1, buffer2]) + assert.strictEqual(out, '{\\\\xdead,\\\\xbeef}') +}) + +test('prepareValue: objects with complex toPostgres prepared properly', function () { + var buf = Buffer.from('zomgcustom!') + var customType = { + toPostgres: function () { + return [1, 2] + }, + } + var out = utils.prepareValue(customType) + assert.strictEqual(out, '{"1","2"}') +}) + +test('prepareValue: objects with toPostgres receive prepareValue', function () { + var customRange = { + lower: { + toPostgres: function () { + return 5 + }, + }, + upper: { + toPostgres: function () { + return 10 + }, + }, + toPostgres: function (prepare) { + return '[' + prepare(this.lower) + ',' + prepare(this.upper) + ']' + }, + } + var out = utils.prepareValue(customRange) + assert.strictEqual(out, '[5,10]') +}) + +test('prepareValue: objects with circular toPostgres rejected', function () { + var buf = Buffer.from('zomgcustom!') + var customType = { + toPostgres: function () { + return { + toPostgres: function () { + return customType + }, + } + }, + } + + // can't use `assert.throws` since we need to distinguish circular reference + // errors from call stack exceeded errors + try { + utils.prepareValue(customType) + } catch (e) { + assert.ok(e.message.match(/circular/), 'Expected circular reference error but got ' + e) + return + } + throw new Error('Expected prepareValue to throw exception') +}) + +test('prepareValue: can safely be used to map an array of values including those with toPostgres functions', function () { + var customType = { + toPostgres: function () { + return 'zomgcustom!' + }, + } + var values = [1, 'test', customType] + var out = values.map(utils.prepareValue) + assert.deepEqual(out, [1, 'test', 'zomgcustom!']) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..53fb70c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "strict": true, + "incremental": true, + "composite": true + }, + "include": [], + "references": [ + {"path": "./packages/pg-query-stream"}, + {"path": "./packages/pg-protocol"} + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..f2724a8 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,6770 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + "integrity" "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==" + "resolved" "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz" + "version" "7.10.4" + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + "integrity" "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + "resolved" "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz" + "version" "7.10.4" + +"@babel/highlight@^7.10.4": + "integrity" "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==" + "resolved" "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz" + "version" "7.10.4" + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + "chalk" "^2.0.0" + "js-tokens" "^4.0.0" + +"@eslint/eslintrc@^0.1.3": + "integrity" "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==" + "resolved" "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz" + "version" "0.1.3" + dependencies: + "ajv" "^6.12.4" + "debug" "^4.1.1" + "espree" "^7.3.0" + "globals" "^12.1.0" + "ignore" "^4.0.6" + "import-fresh" "^3.2.1" + "js-yaml" "^3.13.1" + "lodash" "^4.17.19" + "minimatch" "^3.0.4" + "strip-json-comments" "^3.1.1" + +"@evocateur/libnpmaccess@^3.1.2": + "integrity" "sha512-KSCAHwNWro0CF2ukxufCitT9K5LjL/KuMmNzSu8wuwN2rjyKHD8+cmOsiybK+W5hdnwc5M1SmRlVCaMHQo+3rg==" + "resolved" "https://registry.npmjs.org/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz" + "version" "3.1.2" + dependencies: + "@evocateur/npm-registry-fetch" "^4.0.0" + "aproba" "^2.0.0" + "figgy-pudding" "^3.5.1" + "get-stream" "^4.0.0" + "npm-package-arg" "^6.1.0" + +"@evocateur/libnpmpublish@^1.2.2": + "integrity" "sha512-MJrrk9ct1FeY9zRlyeoyMieBjGDG9ihyyD9/Ft6MMrTxql9NyoEx2hw9casTIP4CdqEVu+3nQ2nXxoJ8RCXyFg==" + "resolved" "https://registry.npmjs.org/@evocateur/libnpmpublish/-/libnpmpublish-1.2.2.tgz" + "version" "1.2.2" + dependencies: + "@evocateur/npm-registry-fetch" "^4.0.0" + "aproba" "^2.0.0" + "figgy-pudding" "^3.5.1" + "get-stream" "^4.0.0" + "lodash.clonedeep" "^4.5.0" + "normalize-package-data" "^2.4.0" + "npm-package-arg" "^6.1.0" + "semver" "^5.5.1" + "ssri" "^6.0.1" + +"@evocateur/npm-registry-fetch@^4.0.0": + "integrity" "sha512-k1WGfKRQyhJpIr+P17O5vLIo2ko1PFLKwoetatdduUSt/aQ4J2sJrJwwatdI5Z3SiYk/mRH9S3JpdmMFd/IK4g==" + "resolved" "https://registry.npmjs.org/@evocateur/npm-registry-fetch/-/npm-registry-fetch-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "bluebird" "^3.5.1" + "figgy-pudding" "^3.4.1" + "JSONStream" "^1.3.4" + "lru-cache" "^5.1.1" + "make-fetch-happen" "^5.0.0" + "npm-package-arg" "^6.1.0" + "safe-buffer" "^5.1.2" + +"@evocateur/pacote@^9.6.3": + "integrity" "sha512-EI552lf0aG2nOV8NnZpTxNo2PcXKPmDbF9K8eCBFQdIZwHNGN/mi815fxtmUMa2wTa1yndotICIDt/V0vpEx2w==" + "resolved" "https://registry.npmjs.org/@evocateur/pacote/-/pacote-9.6.5.tgz" + "version" "9.6.5" + dependencies: + "@evocateur/npm-registry-fetch" "^4.0.0" + "bluebird" "^3.5.3" + "cacache" "^12.0.3" + "chownr" "^1.1.2" + "figgy-pudding" "^3.5.1" + "get-stream" "^4.1.0" + "glob" "^7.1.4" + "infer-owner" "^1.0.4" + "lru-cache" "^5.1.1" + "make-fetch-happen" "^5.0.0" + "minimatch" "^3.0.4" + "minipass" "^2.3.5" + "mississippi" "^3.0.0" + "mkdirp" "^0.5.1" + "normalize-package-data" "^2.5.0" + "npm-package-arg" "^6.1.0" + "npm-packlist" "^1.4.4" + "npm-pick-manifest" "^3.0.0" + "osenv" "^0.1.5" + "promise-inflight" "^1.0.1" + "promise-retry" "^1.1.1" + "protoduck" "^5.0.1" + "rimraf" "^2.6.3" + "safe-buffer" "^5.2.0" + "semver" "^5.7.0" + "ssri" "^6.0.1" + "tar" "^4.4.10" + "unique-filename" "^1.1.1" + "which" "^1.3.1" + +"@lerna/add@3.21.0": + "integrity" "sha512-vhUXXF6SpufBE1EkNEXwz1VLW03f177G9uMOFMQkp6OJ30/PWg4Ekifuz9/3YfgB2/GH8Tu4Lk3O51P2Hskg/A==" + "resolved" "https://registry.npmjs.org/@lerna/add/-/add-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@evocateur/pacote" "^9.6.3" + "@lerna/bootstrap" "3.21.0" + "@lerna/command" "3.21.0" + "@lerna/filter-options" "3.20.0" + "@lerna/npm-conf" "3.16.0" + "@lerna/validation-error" "3.13.0" + "dedent" "^0.7.0" + "npm-package-arg" "^6.1.0" + "p-map" "^2.1.0" + "semver" "^6.2.0" + +"@lerna/bootstrap@3.21.0": + "integrity" "sha512-mtNHlXpmvJn6JTu0KcuTTPl2jLsDNud0QacV/h++qsaKbhAaJr/FElNZ5s7MwZFUM3XaDmvWzHKaszeBMHIbBw==" + "resolved" "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/command" "3.21.0" + "@lerna/filter-options" "3.20.0" + "@lerna/has-npm-version" "3.16.5" + "@lerna/npm-install" "3.16.5" + "@lerna/package-graph" "3.18.5" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/rimraf-dir" "3.16.5" + "@lerna/run-lifecycle" "3.16.2" + "@lerna/run-topologically" "3.18.5" + "@lerna/symlink-binary" "3.17.0" + "@lerna/symlink-dependencies" "3.17.0" + "@lerna/validation-error" "3.13.0" + "dedent" "^0.7.0" + "get-port" "^4.2.0" + "multimatch" "^3.0.0" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + "p-finally" "^1.0.0" + "p-map" "^2.1.0" + "p-map-series" "^1.0.0" + "p-waterfall" "^1.0.0" + "read-package-tree" "^5.1.6" + "semver" "^6.2.0" + +"@lerna/changed@3.21.0": + "integrity" "sha512-hzqoyf8MSHVjZp0gfJ7G8jaz+++mgXYiNs9iViQGA8JlN/dnWLI5sWDptEH3/B30Izo+fdVz0S0s7ydVE3pWIw==" + "resolved" "https://registry.npmjs.org/@lerna/changed/-/changed-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/collect-updates" "3.20.0" + "@lerna/command" "3.21.0" + "@lerna/listable" "3.18.5" + "@lerna/output" "3.13.0" + +"@lerna/check-working-tree@3.16.5": + "integrity" "sha512-xWjVBcuhvB8+UmCSb5tKVLB5OuzSpw96WEhS2uz6hkWVa/Euh1A0/HJwn2cemyK47wUrCQXtczBUiqnq9yX5VQ==" + "resolved" "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/collect-uncommitted" "3.16.5" + "@lerna/describe-ref" "3.16.5" + "@lerna/validation-error" "3.13.0" + +"@lerna/child-process@3.16.5": + "integrity" "sha512-vdcI7mzei9ERRV4oO8Y1LHBZ3A5+ampRKg1wq5nutLsUA4mEBN6H7JqjWOMY9xZemv6+kATm2ofjJ3lW5TszQg==" + "resolved" "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "chalk" "^2.3.1" + "execa" "^1.0.0" + "strong-log-transformer" "^2.0.0" + +"@lerna/clean@3.21.0": + "integrity" "sha512-b/L9l+MDgE/7oGbrav6rG8RTQvRiZLO1zTcG17zgJAAuhlsPxJExMlh2DFwJEVi2les70vMhHfST3Ue1IMMjpg==" + "resolved" "https://registry.npmjs.org/@lerna/clean/-/clean-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/command" "3.21.0" + "@lerna/filter-options" "3.20.0" + "@lerna/prompt" "3.18.5" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/rimraf-dir" "3.16.5" + "p-map" "^2.1.0" + "p-map-series" "^1.0.0" + "p-waterfall" "^1.0.0" + +"@lerna/cli@3.18.5": + "integrity" "sha512-erkbxkj9jfc89vVs/jBLY/fM0I80oLmJkFUV3Q3wk9J3miYhP14zgVEBsPZY68IZlEjT6T3Xlq2xO1AVaatHsA==" + "resolved" "https://registry.npmjs.org/@lerna/cli/-/cli-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@lerna/global-options" "3.13.0" + "dedent" "^0.7.0" + "npmlog" "^4.1.2" + "yargs" "^14.2.2" + +"@lerna/collect-uncommitted@3.16.5": + "integrity" "sha512-ZgqnGwpDZiWyzIQVZtQaj9tRizsL4dUOhuOStWgTAw1EMe47cvAY2kL709DzxFhjr6JpJSjXV5rZEAeU3VE0Hg==" + "resolved" "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/child-process" "3.16.5" + "chalk" "^2.3.1" + "figgy-pudding" "^3.5.1" + "npmlog" "^4.1.2" + +"@lerna/collect-updates@3.20.0": + "integrity" "sha512-qBTVT5g4fupVhBFuY4nI/3FSJtQVcDh7/gEPOpRxoXB/yCSnT38MFHXWl+y4einLciCjt/+0x6/4AG80fjay2Q==" + "resolved" "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.20.0.tgz" + "version" "3.20.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/describe-ref" "3.16.5" + "minimatch" "^3.0.4" + "npmlog" "^4.1.2" + "slash" "^2.0.0" + +"@lerna/command@3.21.0": + "integrity" "sha512-T2bu6R8R3KkH5YoCKdutKv123iUgUbW8efVjdGCDnCMthAQzoentOJfDeodBwn0P2OqCl3ohsiNVtSn9h78fyQ==" + "resolved" "https://registry.npmjs.org/@lerna/command/-/command-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/package-graph" "3.18.5" + "@lerna/project" "3.21.0" + "@lerna/validation-error" "3.13.0" + "@lerna/write-log-file" "3.13.0" + "clone-deep" "^4.0.1" + "dedent" "^0.7.0" + "execa" "^1.0.0" + "is-ci" "^2.0.0" + "npmlog" "^4.1.2" + +"@lerna/conventional-commits@3.22.0": + "integrity" "sha512-z4ZZk1e8Mhz7+IS8NxHr64wyklHctCJyWpJKEZZPJiLFJ8yKto/x38O80R10pIzC0rr8Sy/OsjSH4bl0TbbgqA==" + "resolved" "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.22.0.tgz" + "version" "3.22.0" + dependencies: + "@lerna/validation-error" "3.13.0" + "conventional-changelog-angular" "^5.0.3" + "conventional-changelog-core" "^3.1.6" + "conventional-recommended-bump" "^5.0.0" + "fs-extra" "^8.1.0" + "get-stream" "^4.0.0" + "lodash.template" "^4.5.0" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + "pify" "^4.0.1" + "semver" "^6.2.0" + +"@lerna/create-symlink@3.16.2": + "integrity" "sha512-pzXIJp6av15P325sgiIRpsPXLFmkisLhMBCy4764d+7yjf2bzrJ4gkWVMhsv4AdF0NN3OyZ5jjzzTtLNqfR+Jw==" + "resolved" "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.16.2.tgz" + "version" "3.16.2" + dependencies: + "@zkochan/cmd-shim" "^3.1.0" + "fs-extra" "^8.1.0" + "npmlog" "^4.1.2" + +"@lerna/create@3.22.0": + "integrity" "sha512-MdiQQzCcB4E9fBF1TyMOaAEz9lUjIHp1Ju9H7f3lXze5JK6Fl5NYkouAvsLgY6YSIhXMY8AHW2zzXeBDY4yWkw==" + "resolved" "https://registry.npmjs.org/@lerna/create/-/create-3.22.0.tgz" + "version" "3.22.0" + dependencies: + "@evocateur/pacote" "^9.6.3" + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.21.0" + "@lerna/npm-conf" "3.16.0" + "@lerna/validation-error" "3.13.0" + "camelcase" "^5.0.0" + "dedent" "^0.7.0" + "fs-extra" "^8.1.0" + "globby" "^9.2.0" + "init-package-json" "^1.10.3" + "npm-package-arg" "^6.1.0" + "p-reduce" "^1.0.0" + "pify" "^4.0.1" + "semver" "^6.2.0" + "slash" "^2.0.0" + "validate-npm-package-license" "^3.0.3" + "validate-npm-package-name" "^3.0.0" + "whatwg-url" "^7.0.0" + +"@lerna/describe-ref@3.16.5": + "integrity" "sha512-c01+4gUF0saOOtDBzbLMFOTJDHTKbDFNErEY6q6i9QaXuzy9LNN62z+Hw4acAAZuJQhrVWncVathcmkkjvSVGw==" + "resolved" "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/child-process" "3.16.5" + "npmlog" "^4.1.2" + +"@lerna/diff@3.21.0": + "integrity" "sha512-5viTR33QV3S7O+bjruo1SaR40m7F2aUHJaDAC7fL9Ca6xji+aw1KFkpCtVlISS0G8vikUREGMJh+c/VMSc8Usw==" + "resolved" "https://registry.npmjs.org/@lerna/diff/-/diff-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.21.0" + "@lerna/validation-error" "3.13.0" + "npmlog" "^4.1.2" + +"@lerna/exec@3.21.0": + "integrity" "sha512-iLvDBrIE6rpdd4GIKTY9mkXyhwsJ2RvQdB9ZU+/NhR3okXfqKc6py/24tV111jqpXTtZUW6HNydT4dMao2hi1Q==" + "resolved" "https://registry.npmjs.org/@lerna/exec/-/exec-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.21.0" + "@lerna/filter-options" "3.20.0" + "@lerna/profiler" "3.20.0" + "@lerna/run-topologically" "3.18.5" + "@lerna/validation-error" "3.13.0" + "p-map" "^2.1.0" + +"@lerna/filter-options@3.20.0": + "integrity" "sha512-bmcHtvxn7SIl/R9gpiNMVG7yjx7WyT0HSGw34YVZ9B+3xF/83N3r5Rgtjh4hheLZ+Q91Or0Jyu5O3Nr+AwZe2g==" + "resolved" "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.20.0.tgz" + "version" "3.20.0" + dependencies: + "@lerna/collect-updates" "3.20.0" + "@lerna/filter-packages" "3.18.0" + "dedent" "^0.7.0" + "figgy-pudding" "^3.5.1" + "npmlog" "^4.1.2" + +"@lerna/filter-packages@3.18.0": + "integrity" "sha512-6/0pMM04bCHNATIOkouuYmPg6KH3VkPCIgTfQmdkPJTullERyEQfNUKikrefjxo1vHOoCACDpy65JYyKiAbdwQ==" + "resolved" "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.18.0.tgz" + "version" "3.18.0" + dependencies: + "@lerna/validation-error" "3.13.0" + "multimatch" "^3.0.0" + "npmlog" "^4.1.2" + +"@lerna/get-npm-exec-opts@3.13.0": + "integrity" "sha512-Y0xWL0rg3boVyJk6An/vurKzubyJKtrxYv2sj4bB8Mc5zZ3tqtv0ccbOkmkXKqbzvNNF7VeUt1OJ3DRgtC/QZw==" + "resolved" "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.13.0.tgz" + "version" "3.13.0" + dependencies: + "npmlog" "^4.1.2" + +"@lerna/get-packed@3.16.0": + "integrity" "sha512-AjsFiaJzo1GCPnJUJZiTW6J1EihrPkc2y3nMu6m3uWFxoleklsSCyImumzVZJssxMi3CPpztj8LmADLedl9kXw==" + "resolved" "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.16.0.tgz" + "version" "3.16.0" + dependencies: + "fs-extra" "^8.1.0" + "ssri" "^6.0.1" + "tar" "^4.4.8" + +"@lerna/github-client@3.22.0": + "integrity" "sha512-O/GwPW+Gzr3Eb5bk+nTzTJ3uv+jh5jGho9BOqKlajXaOkMYGBELEAqV5+uARNGWZFvYAiF4PgqHb6aCUu7XdXg==" + "resolved" "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.22.0.tgz" + "version" "3.22.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@octokit/plugin-enterprise-rest" "^6.0.1" + "@octokit/rest" "^16.28.4" + "git-url-parse" "^11.1.2" + "npmlog" "^4.1.2" + +"@lerna/gitlab-client@3.15.0": + "integrity" "sha512-OsBvRSejHXUBMgwWQqNoioB8sgzL/Pf1pOUhHKtkiMl6aAWjklaaq5HPMvTIsZPfS6DJ9L5OK2GGZuooP/5c8Q==" + "resolved" "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-3.15.0.tgz" + "version" "3.15.0" + dependencies: + "node-fetch" "^2.5.0" + "npmlog" "^4.1.2" + "whatwg-url" "^7.0.0" + +"@lerna/global-options@3.13.0": + "integrity" "sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ==" + "resolved" "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.13.0.tgz" + "version" "3.13.0" + +"@lerna/has-npm-version@3.16.5": + "integrity" "sha512-WL7LycR9bkftyqbYop5rEGJ9sRFIV55tSGmbN1HLrF9idwOCD7CLrT64t235t3t4O5gehDnwKI5h2U3oxTrF8Q==" + "resolved" "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/child-process" "3.16.5" + "semver" "^6.2.0" + +"@lerna/import@3.22.0": + "integrity" "sha512-uWOlexasM5XR6tXi4YehODtH9Y3OZrFht3mGUFFT3OIl2s+V85xIGFfqFGMTipMPAGb2oF1UBLL48kR43hRsOg==" + "resolved" "https://registry.npmjs.org/@lerna/import/-/import-3.22.0.tgz" + "version" "3.22.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.21.0" + "@lerna/prompt" "3.18.5" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/validation-error" "3.13.0" + "dedent" "^0.7.0" + "fs-extra" "^8.1.0" + "p-map-series" "^1.0.0" + +"@lerna/info@3.21.0": + "integrity" "sha512-0XDqGYVBgWxUquFaIptW2bYSIu6jOs1BtkvRTWDDhw4zyEdp6q4eaMvqdSap1CG+7wM5jeLCi6z94wS0AuiuwA==" + "resolved" "https://registry.npmjs.org/@lerna/info/-/info-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/command" "3.21.0" + "@lerna/output" "3.13.0" + "envinfo" "^7.3.1" + +"@lerna/init@3.21.0": + "integrity" "sha512-6CM0z+EFUkFfurwdJCR+LQQF6MqHbYDCBPyhu/d086LRf58GtYZYj49J8mKG9ktayp/TOIxL/pKKjgLD8QBPOg==" + "resolved" "https://registry.npmjs.org/@lerna/init/-/init-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.21.0" + "fs-extra" "^8.1.0" + "p-map" "^2.1.0" + "write-json-file" "^3.2.0" + +"@lerna/link@3.21.0": + "integrity" "sha512-tGu9GxrX7Ivs+Wl3w1+jrLi1nQ36kNI32dcOssij6bg0oZ2M2MDEFI9UF2gmoypTaN9uO5TSsjCFS7aR79HbdQ==" + "resolved" "https://registry.npmjs.org/@lerna/link/-/link-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/command" "3.21.0" + "@lerna/package-graph" "3.18.5" + "@lerna/symlink-dependencies" "3.17.0" + "p-map" "^2.1.0" + "slash" "^2.0.0" + +"@lerna/list@3.21.0": + "integrity" "sha512-KehRjE83B1VaAbRRkRy6jLX1Cin8ltsrQ7FHf2bhwhRHK0S54YuA6LOoBnY/NtA8bHDX/Z+G5sMY78X30NS9tg==" + "resolved" "https://registry.npmjs.org/@lerna/list/-/list-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/command" "3.21.0" + "@lerna/filter-options" "3.20.0" + "@lerna/listable" "3.18.5" + "@lerna/output" "3.13.0" + +"@lerna/listable@3.18.5": + "integrity" "sha512-Sdr3pVyaEv5A7ZkGGYR7zN+tTl2iDcinryBPvtuv20VJrXBE8wYcOks1edBTcOWsPjCE/rMP4bo1pseyk3UTsg==" + "resolved" "https://registry.npmjs.org/@lerna/listable/-/listable-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@lerna/query-graph" "3.18.5" + "chalk" "^2.3.1" + "columnify" "^1.5.4" + +"@lerna/log-packed@3.16.0": + "integrity" "sha512-Fp+McSNBV/P2mnLUYTaSlG8GSmpXM7krKWcllqElGxvAqv6chk2K3c2k80MeVB4WvJ9tRjUUf+i7HUTiQ9/ckQ==" + "resolved" "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.16.0.tgz" + "version" "3.16.0" + dependencies: + "byte-size" "^5.0.1" + "columnify" "^1.5.4" + "has-unicode" "^2.0.1" + "npmlog" "^4.1.2" + +"@lerna/npm-conf@3.16.0": + "integrity" "sha512-HbO3DUrTkCAn2iQ9+FF/eisDpWY5POQAOF1m7q//CZjdC2HSW3UYbKEGsSisFxSfaF9Z4jtrV+F/wX6qWs3CuA==" + "resolved" "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.16.0.tgz" + "version" "3.16.0" + dependencies: + "config-chain" "^1.1.11" + "pify" "^4.0.1" + +"@lerna/npm-dist-tag@3.18.5": + "integrity" "sha512-xw0HDoIG6HreVsJND9/dGls1c+lf6vhu7yJoo56Sz5bvncTloYGLUppIfDHQr4ZvmPCK8rsh0euCVh2giPxzKQ==" + "resolved" "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@evocateur/npm-registry-fetch" "^4.0.0" + "@lerna/otplease" "3.18.5" + "figgy-pudding" "^3.5.1" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + +"@lerna/npm-install@3.16.5": + "integrity" "sha512-hfiKk8Eku6rB9uApqsalHHTHY+mOrrHeWEs+gtg7+meQZMTS3kzv4oVp5cBZigndQr3knTLjwthT/FX4KvseFg==" + "resolved" "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/get-npm-exec-opts" "3.13.0" + "fs-extra" "^8.1.0" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + "signal-exit" "^3.0.2" + "write-pkg" "^3.1.0" + +"@lerna/npm-publish@3.18.5": + "integrity" "sha512-3etLT9+2L8JAx5F8uf7qp6iAtOLSMj+ZYWY6oUgozPi/uLqU0/gsMsEXh3F0+YVW33q0M61RpduBoAlOOZnaTg==" + "resolved" "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@evocateur/libnpmpublish" "^1.2.2" + "@lerna/otplease" "3.18.5" + "@lerna/run-lifecycle" "3.16.2" + "figgy-pudding" "^3.5.1" + "fs-extra" "^8.1.0" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + "pify" "^4.0.1" + "read-package-json" "^2.0.13" + +"@lerna/npm-run-script@3.16.5": + "integrity" "sha512-1asRi+LjmVn3pMjEdpqKJZFT/3ZNpb+VVeJMwrJaV/3DivdNg7XlPK9LTrORuKU4PSvhdEZvJmSlxCKyDpiXsQ==" + "resolved" "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/child-process" "3.16.5" + "@lerna/get-npm-exec-opts" "3.13.0" + "npmlog" "^4.1.2" + +"@lerna/otplease@3.18.5": + "integrity" "sha512-S+SldXAbcXTEDhzdxYLU0ZBKuYyURP/ND2/dK6IpKgLxQYh/z4ScljPDMyKymmEvgiEJmBsPZAAPfmNPEzxjog==" + "resolved" "https://registry.npmjs.org/@lerna/otplease/-/otplease-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@lerna/prompt" "3.18.5" + "figgy-pudding" "^3.5.1" + +"@lerna/output@3.13.0": + "integrity" "sha512-7ZnQ9nvUDu/WD+bNsypmPG5MwZBwu86iRoiW6C1WBuXXDxM5cnIAC1m2WxHeFnjyMrYlRXM9PzOQ9VDD+C15Rg==" + "resolved" "https://registry.npmjs.org/@lerna/output/-/output-3.13.0.tgz" + "version" "3.13.0" + dependencies: + "npmlog" "^4.1.2" + +"@lerna/pack-directory@3.16.4": + "integrity" "sha512-uxSF0HZeGyKaaVHz5FroDY9A5NDDiCibrbYR6+khmrhZtY0Bgn6hWq8Gswl9iIlymA+VzCbshWIMX4o2O8C8ng==" + "resolved" "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.16.4.tgz" + "version" "3.16.4" + dependencies: + "@lerna/get-packed" "3.16.0" + "@lerna/package" "3.16.0" + "@lerna/run-lifecycle" "3.16.2" + "figgy-pudding" "^3.5.1" + "npm-packlist" "^1.4.4" + "npmlog" "^4.1.2" + "tar" "^4.4.10" + "temp-write" "^3.4.0" + +"@lerna/package-graph@3.18.5": + "integrity" "sha512-8QDrR9T+dBegjeLr+n9WZTVxUYUhIUjUgZ0gvNxUBN8S1WB9r6H5Yk56/MVaB64tA3oGAN9IIxX6w0WvTfFudA==" + "resolved" "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@lerna/prerelease-id-from-version" "3.16.0" + "@lerna/validation-error" "3.13.0" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + "semver" "^6.2.0" + +"@lerna/package@3.16.0": + "integrity" "sha512-2lHBWpaxcBoiNVbtyLtPUuTYEaB/Z+eEqRS9duxpZs6D+mTTZMNy6/5vpEVSCBmzvdYpyqhqaYjjSLvjjr5Riw==" + "resolved" "https://registry.npmjs.org/@lerna/package/-/package-3.16.0.tgz" + "version" "3.16.0" + dependencies: + "load-json-file" "^5.3.0" + "npm-package-arg" "^6.1.0" + "write-pkg" "^3.1.0" + +"@lerna/prerelease-id-from-version@3.16.0": + "integrity" "sha512-qZyeUyrE59uOK8rKdGn7jQz+9uOpAaF/3hbslJVFL1NqF9ELDTqjCPXivuejMX/lN4OgD6BugTO4cR7UTq/sZA==" + "resolved" "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.16.0.tgz" + "version" "3.16.0" + dependencies: + "semver" "^6.2.0" + +"@lerna/profiler@3.20.0": + "integrity" "sha512-bh8hKxAlm6yu8WEOvbLENm42i2v9SsR4WbrCWSbsmOElx3foRnMlYk7NkGECa+U5c3K4C6GeBbwgqs54PP7Ljg==" + "resolved" "https://registry.npmjs.org/@lerna/profiler/-/profiler-3.20.0.tgz" + "version" "3.20.0" + dependencies: + "figgy-pudding" "^3.5.1" + "fs-extra" "^8.1.0" + "npmlog" "^4.1.2" + "upath" "^1.2.0" + +"@lerna/project@3.21.0": + "integrity" "sha512-xT1mrpET2BF11CY32uypV2GPtPVm6Hgtha7D81GQP9iAitk9EccrdNjYGt5UBYASl4CIDXBRxwmTTVGfrCx82A==" + "resolved" "https://registry.npmjs.org/@lerna/project/-/project-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/package" "3.16.0" + "@lerna/validation-error" "3.13.0" + "cosmiconfig" "^5.1.0" + "dedent" "^0.7.0" + "dot-prop" "^4.2.0" + "glob-parent" "^5.0.0" + "globby" "^9.2.0" + "load-json-file" "^5.3.0" + "npmlog" "^4.1.2" + "p-map" "^2.1.0" + "resolve-from" "^4.0.0" + "write-json-file" "^3.2.0" + +"@lerna/prompt@3.18.5": + "integrity" "sha512-rkKj4nm1twSbBEb69+Em/2jAERK8htUuV8/xSjN0NPC+6UjzAwY52/x9n5cfmpa9lyKf/uItp7chCI7eDmNTKQ==" + "resolved" "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "inquirer" "^6.2.0" + "npmlog" "^4.1.2" + +"@lerna/publish@3.22.1": + "integrity" "sha512-PG9CM9HUYDreb1FbJwFg90TCBQooGjj+n/pb3gw/eH5mEDq0p8wKdLFe0qkiqUkm/Ub5C8DbVFertIo0Vd0zcw==" + "resolved" "https://registry.npmjs.org/@lerna/publish/-/publish-3.22.1.tgz" + "version" "3.22.1" + dependencies: + "@evocateur/libnpmaccess" "^3.1.2" + "@evocateur/npm-registry-fetch" "^4.0.0" + "@evocateur/pacote" "^9.6.3" + "@lerna/check-working-tree" "3.16.5" + "@lerna/child-process" "3.16.5" + "@lerna/collect-updates" "3.20.0" + "@lerna/command" "3.21.0" + "@lerna/describe-ref" "3.16.5" + "@lerna/log-packed" "3.16.0" + "@lerna/npm-conf" "3.16.0" + "@lerna/npm-dist-tag" "3.18.5" + "@lerna/npm-publish" "3.18.5" + "@lerna/otplease" "3.18.5" + "@lerna/output" "3.13.0" + "@lerna/pack-directory" "3.16.4" + "@lerna/prerelease-id-from-version" "3.16.0" + "@lerna/prompt" "3.18.5" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/run-lifecycle" "3.16.2" + "@lerna/run-topologically" "3.18.5" + "@lerna/validation-error" "3.13.0" + "@lerna/version" "3.22.1" + "figgy-pudding" "^3.5.1" + "fs-extra" "^8.1.0" + "npm-package-arg" "^6.1.0" + "npmlog" "^4.1.2" + "p-finally" "^1.0.0" + "p-map" "^2.1.0" + "p-pipe" "^1.2.0" + "semver" "^6.2.0" + +"@lerna/pulse-till-done@3.13.0": + "integrity" "sha512-1SOHpy7ZNTPulzIbargrgaJX387csN7cF1cLOGZiJQA6VqnS5eWs2CIrG8i8wmaUavj2QlQ5oEbRMVVXSsGrzA==" + "resolved" "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-3.13.0.tgz" + "version" "3.13.0" + dependencies: + "npmlog" "^4.1.2" + +"@lerna/query-graph@3.18.5": + "integrity" "sha512-50Lf4uuMpMWvJ306be3oQDHrWV42nai9gbIVByPBYJuVW8dT8O8pA3EzitNYBUdLL9/qEVbrR0ry1HD7EXwtRA==" + "resolved" "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@lerna/package-graph" "3.18.5" + "figgy-pudding" "^3.5.1" + +"@lerna/resolve-symlink@3.16.0": + "integrity" "sha512-Ibj5e7njVHNJ/NOqT4HlEgPFPtPLWsO7iu59AM5bJDcAJcR96mLZ7KGVIsS2tvaO7akMEJvt2P+ErwCdloG3jQ==" + "resolved" "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.16.0.tgz" + "version" "3.16.0" + dependencies: + "fs-extra" "^8.1.0" + "npmlog" "^4.1.2" + "read-cmd-shim" "^1.0.1" + +"@lerna/rimraf-dir@3.16.5": + "integrity" "sha512-bQlKmO0pXUsXoF8lOLknhyQjOZsCc0bosQDoX4lujBXSWxHVTg1VxURtWf2lUjz/ACsJVDfvHZbDm8kyBk5okA==" + "resolved" "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.16.5.tgz" + "version" "3.16.5" + dependencies: + "@lerna/child-process" "3.16.5" + "npmlog" "^4.1.2" + "path-exists" "^3.0.0" + "rimraf" "^2.6.2" + +"@lerna/run-lifecycle@3.16.2": + "integrity" "sha512-RqFoznE8rDpyyF0rOJy3+KjZCeTkO8y/OB9orPauR7G2xQ7PTdCpgo7EO6ZNdz3Al+k1BydClZz/j78gNCmL2A==" + "resolved" "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.16.2.tgz" + "version" "3.16.2" + dependencies: + "@lerna/npm-conf" "3.16.0" + "figgy-pudding" "^3.5.1" + "npm-lifecycle" "^3.1.2" + "npmlog" "^4.1.2" + +"@lerna/run-topologically@3.18.5": + "integrity" "sha512-6N1I+6wf4hLOnPW+XDZqwufyIQ6gqoPfHZFkfWlvTQ+Ue7CuF8qIVQ1Eddw5HKQMkxqN10thKOFfq/9NQZ4NUg==" + "resolved" "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-3.18.5.tgz" + "version" "3.18.5" + dependencies: + "@lerna/query-graph" "3.18.5" + "figgy-pudding" "^3.5.1" + "p-queue" "^4.0.0" + +"@lerna/run@3.21.0": + "integrity" "sha512-fJF68rT3veh+hkToFsBmUJ9MHc9yGXA7LSDvhziAojzOb0AI/jBDp6cEcDQyJ7dbnplba2Lj02IH61QUf9oW0Q==" + "resolved" "https://registry.npmjs.org/@lerna/run/-/run-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "@lerna/command" "3.21.0" + "@lerna/filter-options" "3.20.0" + "@lerna/npm-run-script" "3.16.5" + "@lerna/output" "3.13.0" + "@lerna/profiler" "3.20.0" + "@lerna/run-topologically" "3.18.5" + "@lerna/timer" "3.13.0" + "@lerna/validation-error" "3.13.0" + "p-map" "^2.1.0" + +"@lerna/symlink-binary@3.17.0": + "integrity" "sha512-RLpy9UY6+3nT5J+5jkM5MZyMmjNHxZIZvXLV+Q3MXrf7Eaa1hNqyynyj4RO95fxbS+EZc4XVSk25DGFQbcRNSQ==" + "resolved" "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.17.0.tgz" + "version" "3.17.0" + dependencies: + "@lerna/create-symlink" "3.16.2" + "@lerna/package" "3.16.0" + "fs-extra" "^8.1.0" + "p-map" "^2.1.0" + +"@lerna/symlink-dependencies@3.17.0": + "integrity" "sha512-KmjU5YT1bpt6coOmdFueTJ7DFJL4H1w5eF8yAQ2zsGNTtZ+i5SGFBWpb9AQaw168dydc3s4eu0W0Sirda+F59Q==" + "resolved" "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.17.0.tgz" + "version" "3.17.0" + dependencies: + "@lerna/create-symlink" "3.16.2" + "@lerna/resolve-symlink" "3.16.0" + "@lerna/symlink-binary" "3.17.0" + "fs-extra" "^8.1.0" + "p-finally" "^1.0.0" + "p-map" "^2.1.0" + "p-map-series" "^1.0.0" + +"@lerna/timer@3.13.0": + "integrity" "sha512-RHWrDl8U4XNPqY5MQHkToWS9jHPnkLZEt5VD+uunCKTfzlxGnRCr3/zVr8VGy/uENMYpVP3wJa4RKGY6M0vkRw==" + "resolved" "https://registry.npmjs.org/@lerna/timer/-/timer-3.13.0.tgz" + "version" "3.13.0" + +"@lerna/validation-error@3.13.0": + "integrity" "sha512-SiJP75nwB8GhgwLKQfdkSnDufAaCbkZWJqEDlKOUPUvVOplRGnfL+BPQZH5nvq2BYSRXsksXWZ4UHVnQZI/HYA==" + "resolved" "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.13.0.tgz" + "version" "3.13.0" + dependencies: + "npmlog" "^4.1.2" + +"@lerna/version@3.22.1": + "integrity" "sha512-PSGt/K1hVqreAFoi3zjD0VEDupQ2WZVlVIwesrE5GbrL2BjXowjCsTDPqblahDUPy0hp6h7E2kG855yLTp62+g==" + "resolved" "https://registry.npmjs.org/@lerna/version/-/version-3.22.1.tgz" + "version" "3.22.1" + dependencies: + "@lerna/check-working-tree" "3.16.5" + "@lerna/child-process" "3.16.5" + "@lerna/collect-updates" "3.20.0" + "@lerna/command" "3.21.0" + "@lerna/conventional-commits" "3.22.0" + "@lerna/github-client" "3.22.0" + "@lerna/gitlab-client" "3.15.0" + "@lerna/output" "3.13.0" + "@lerna/prerelease-id-from-version" "3.16.0" + "@lerna/prompt" "3.18.5" + "@lerna/run-lifecycle" "3.16.2" + "@lerna/run-topologically" "3.18.5" + "@lerna/validation-error" "3.13.0" + "chalk" "^2.3.1" + "dedent" "^0.7.0" + "load-json-file" "^5.3.0" + "minimatch" "^3.0.4" + "npmlog" "^4.1.2" + "p-map" "^2.1.0" + "p-pipe" "^1.2.0" + "p-reduce" "^1.0.0" + "p-waterfall" "^1.0.0" + "semver" "^6.2.0" + "slash" "^2.0.0" + "temp-write" "^3.4.0" + "write-json-file" "^3.2.0" + +"@lerna/write-log-file@3.13.0": + "integrity" "sha512-RibeMnDPvlL8bFYW5C8cs4mbI3AHfQef73tnJCQ/SgrXZHehmHnsyWUiE7qDQCAo+B1RfTapvSyFF69iPj326A==" + "resolved" "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.13.0.tgz" + "version" "3.13.0" + dependencies: + "npmlog" "^4.1.2" + "write-file-atomic" "^2.3.0" + +"@mrmlnc/readdir-enhanced@^2.2.1": + "integrity" "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==" + "resolved" "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz" + "version" "2.2.1" + dependencies: + "call-me-maybe" "^1.0.1" + "glob-to-regexp" "^0.3.0" + +"@nodelib/fs.scandir@2.1.3": + "integrity" "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz" + "version" "2.1.3" + dependencies: + "@nodelib/fs.stat" "2.0.3" + "run-parallel" "^1.1.9" + +"@nodelib/fs.stat@^1.1.2": + "integrity" "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz" + "version" "1.1.3" + +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.3": + "integrity" "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz" + "version" "2.0.3" + +"@nodelib/fs.walk@^1.2.3": + "integrity" "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz" + "version" "1.2.4" + dependencies: + "@nodelib/fs.scandir" "2.1.3" + "fastq" "^1.6.0" + +"@octokit/auth-token@^2.4.0": + "integrity" "sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ==" + "resolved" "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "@octokit/types" "^5.0.0" + +"@octokit/endpoint@^6.0.1": + "integrity" "sha512-MuRrgv+bM4Q+e9uEvxAB/Kf+Sj0O2JAOBA131uo1o6lgdq1iS8ejKwtqHgdfY91V3rN9R/hdGKFiQYMzVzVBEQ==" + "resolved" "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.8.tgz" + "version" "6.0.8" + dependencies: + "@octokit/types" "^5.0.0" + "is-plain-object" "^5.0.0" + "universal-user-agent" "^6.0.0" + +"@octokit/plugin-enterprise-rest@^6.0.1": + "integrity" "sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==" + "resolved" "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz" + "version" "6.0.1" + +"@octokit/plugin-paginate-rest@^1.1.1": + "integrity" "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==" + "resolved" "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz" + "version" "1.1.2" + dependencies: + "@octokit/types" "^2.0.1" + +"@octokit/plugin-request-log@^1.0.0": + "integrity" "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==" + "resolved" "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz" + "version" "1.0.0" + +"@octokit/plugin-rest-endpoint-methods@2.4.0": + "integrity" "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==" + "resolved" "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz" + "version" "2.4.0" + dependencies: + "@octokit/types" "^2.0.1" + "deprecation" "^2.3.1" + +"@octokit/request-error@^1.0.2": + "integrity" "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==" + "resolved" "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz" + "version" "1.2.1" + dependencies: + "@octokit/types" "^2.0.0" + "deprecation" "^2.0.0" + "once" "^1.4.0" + +"@octokit/request-error@^2.0.0": + "integrity" "sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw==" + "resolved" "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "@octokit/types" "^5.0.1" + "deprecation" "^2.0.0" + "once" "^1.4.0" + +"@octokit/request@^5.2.0": + "integrity" "sha512-CzwVvRyimIM1h2n9pLVYfTDmX9m+KHSgCpqPsY8F1NdEK8IaWqXhSBXsdjOBFZSpEcxNEeg4p0UO9cQ8EnOCLA==" + "resolved" "https://registry.npmjs.org/@octokit/request/-/request-5.4.9.tgz" + "version" "5.4.9" + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.0.0" + "@octokit/types" "^5.0.0" + "deprecation" "^2.0.0" + "is-plain-object" "^5.0.0" + "node-fetch" "^2.6.1" + "once" "^1.4.0" + "universal-user-agent" "^6.0.0" + +"@octokit/rest@^16.28.4": + "integrity" "sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==" + "resolved" "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz" + "version" "16.43.2" + dependencies: + "@octokit/auth-token" "^2.4.0" + "@octokit/plugin-paginate-rest" "^1.1.1" + "@octokit/plugin-request-log" "^1.0.0" + "@octokit/plugin-rest-endpoint-methods" "2.4.0" + "@octokit/request" "^5.2.0" + "@octokit/request-error" "^1.0.2" + "atob-lite" "^2.0.0" + "before-after-hook" "^2.0.0" + "btoa-lite" "^1.0.0" + "deprecation" "^2.0.0" + "lodash.get" "^4.4.2" + "lodash.set" "^4.3.2" + "lodash.uniq" "^4.5.0" + "octokit-pagination-methods" "^1.1.0" + "once" "^1.4.0" + "universal-user-agent" "^4.0.0" + +"@octokit/types@^2.0.0": + "integrity" "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==" + "resolved" "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz" + "version" "2.16.2" + dependencies: + "@types/node" ">= 8" + +"@octokit/types@^2.0.1": + "integrity" "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==" + "resolved" "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz" + "version" "2.16.2" + dependencies: + "@types/node" ">= 8" + +"@octokit/types@^5.0.0", "@octokit/types@^5.0.1": + "integrity" "sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ==" + "resolved" "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz" + "version" "5.5.0" + dependencies: + "@types/node" ">= 8" + +"@types/chai@^4.2.13", "@types/chai@^4.2.7": + "integrity" "sha512-o3SGYRlOpvLFpwJA6Sl1UPOwKFEvE4FxTEB/c9XHI2whdnd4kmPVkNLL8gY4vWGBxWWDumzLbKsAhEH5SKn37Q==" + "resolved" "https://registry.npmjs.org/@types/chai/-/chai-4.2.13.tgz" + "version" "4.2.13" + +"@types/glob@^7.1.1": + "integrity" "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==" + "resolved" "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz" + "version" "7.1.3" + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/json-schema@^7.0.3": + "integrity" "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + "resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz" + "version" "7.0.6" + +"@types/minimatch@*": + "integrity" "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "resolved" "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz" + "version" "3.0.3" + +"@types/minimist@^1.2.0": + "integrity" "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=" + "resolved" "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz" + "version" "1.2.0" + +"@types/mocha@^5.2.7": + "integrity" "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==" + "resolved" "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz" + "version" "5.2.7" + +"@types/mocha@^8.0.3": + "integrity" "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==" + "resolved" "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz" + "version" "8.0.3" + +"@types/node@*", "@types/node@^12.12.21", "@types/node@>= 8": + "integrity" "sha512-8sRGhbpU+ck1n0PGAUgVrWrWdjSW2aqNeyC15W88GRsMpSwzv6RJGlLhE7s2RhVSOdyDmxbqlWSeThq4/7xqlA==" + "resolved" "https://registry.npmjs.org/@types/node/-/node-12.12.21.tgz" + "version" "12.12.21" + +"@types/node@^14.0.0": + "integrity" "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==" + "resolved" "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz" + "version" "14.11.8" + +"@types/normalize-package-data@^2.4.0": + "integrity" "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" + "resolved" "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz" + "version" "2.4.0" + +"@types/pg-types@*": + "integrity" "sha512-L8ogeT6vDzT1vxlW3KITTCt+BVXXVkLXfZ/XNm6UqbcJgxf+KPO7yjWx7dQQE8RW07KopL10x2gNMs41+IkMGQ==" + "resolved" "https://registry.npmjs.org/@types/pg-types/-/pg-types-1.11.5.tgz" + "version" "1.11.5" + +"@types/pg@^7.14.5": + "integrity" "sha512-wqTKZmqkqXd1YiVRBT2poRrMIojwEi2bKTAAjUX6nEbzr98jc3cfR/7o7ZtubhH5xT7YJ6LRdRr1GZOgs8OUjg==" + "resolved" "https://registry.npmjs.org/@types/pg/-/pg-7.14.5.tgz" + "version" "7.14.5" + dependencies: + "@types/node" "*" + "@types/pg-types" "*" + +"@typescript-eslint/eslint-plugin@^4.4.0": + "integrity" "sha512-RVt5wU9H/2H+N/ZrCasTXdGbUTkbf7Hfi9eLiA8vPQkzUJ/bLDCC3CsoZioPrNcnoyN8r0gT153dC++A4hKBQQ==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "@typescript-eslint/experimental-utils" "4.4.0" + "@typescript-eslint/scope-manager" "4.4.0" + "debug" "^4.1.1" + "functional-red-black-tree" "^1.0.1" + "regexpp" "^3.0.0" + "semver" "^7.3.2" + "tsutils" "^3.17.1" + +"@typescript-eslint/experimental-utils@4.4.0": + "integrity" "sha512-01+OtK/oWeSJTjQcyzDztfLF1YjvKpLFo+JZmurK/qjSRcyObpIecJ4rckDoRCSh5Etw+jKfdSzVEHevh9gJ1w==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.4.0" + "@typescript-eslint/types" "4.4.0" + "@typescript-eslint/typescript-estree" "4.4.0" + "eslint-scope" "^5.0.0" + "eslint-utils" "^2.0.0" + +"@typescript-eslint/parser@^4.0.0", "@typescript-eslint/parser@^4.4.0": + "integrity" "sha512-yc14iEItCxoGb7W4Nx30FlTyGpU9r+j+n1LUK/exlq2eJeFxczrz/xFRZUk2f6yzWfK+pr1DOTyQnmDkcC4TnA==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "@typescript-eslint/scope-manager" "4.4.0" + "@typescript-eslint/types" "4.4.0" + "@typescript-eslint/typescript-estree" "4.4.0" + "debug" "^4.1.1" + +"@typescript-eslint/scope-manager@4.4.0": + "integrity" "sha512-r2FIeeU1lmW4K3CxgOAt8djI5c6Q/5ULAgdVo9AF3hPMpu0B14WznBAtxrmB/qFVbVIB6fSx2a+EVXuhSVMEyA==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "@typescript-eslint/types" "4.4.0" + "@typescript-eslint/visitor-keys" "4.4.0" + +"@typescript-eslint/types@4.4.0": + "integrity" "sha512-nU0VUpzanFw3jjX+50OTQy6MehVvf8pkqFcURPAE06xFNFenMj1GPEI6IESvp7UOHAnq+n/brMirZdR+7rCrlA==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.0.tgz" + "version" "4.4.0" + +"@typescript-eslint/typescript-estree@4.4.0": + "integrity" "sha512-Fh85feshKXwki4nZ1uhCJHmqKJqCMba+8ZicQIhNi5d5jSQFteWiGeF96DTjO8br7fn+prTP+t3Cz/a/3yOKqw==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "@typescript-eslint/types" "4.4.0" + "@typescript-eslint/visitor-keys" "4.4.0" + "debug" "^4.1.1" + "globby" "^11.0.1" + "is-glob" "^4.0.1" + "lodash" "^4.17.15" + "semver" "^7.3.2" + "tsutils" "^3.17.1" + +"@typescript-eslint/visitor-keys@4.4.0": + "integrity" "sha512-oBWeroUZCVsHLiWRdcTXJB7s1nB3taFY8WGvS23tiAlT6jXVvsdAV4rs581bgdEjOhn43q6ro7NkOiLKu6kFqA==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "@typescript-eslint/types" "4.4.0" + "eslint-visitor-keys" "^2.0.0" + +"@zkochan/cmd-shim@^3.1.0": + "integrity" "sha512-o8l0+x7C7sMZU3v9GuJIAU10qQLtwR1dtRQIOmlNMtyaqhmpXOzx1HWiYoWfmmf9HHZoAkXpc9TM9PQYF9d4Jg==" + "resolved" "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "is-windows" "^1.0.0" + "mkdirp-promise" "^5.0.1" + "mz" "^2.5.0" + +"abbrev@1.0.x": + "integrity" "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" + "resolved" "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" + "version" "1.0.9" + +"abbrev@1": + "integrity" "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "resolved" "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + "version" "1.1.1" + +"acorn-jsx@^5.2.0": + "integrity" "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" + "resolved" "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz" + "version" "5.3.1" + +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", "acorn@^7.4.0": + "integrity" "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + "resolved" "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" + "version" "7.4.1" + +"agent-base@^4.3.0", "agent-base@4": + "integrity" "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==" + "resolved" "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "es6-promisify" "^5.0.0" + +"agent-base@~4.2.1": + "integrity" "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==" + "resolved" "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz" + "version" "4.2.1" + dependencies: + "es6-promisify" "^5.0.0" + +"agentkeepalive@^3.4.1": + "integrity" "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==" + "resolved" "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz" + "version" "3.5.2" + dependencies: + "humanize-ms" "^1.2.1" + +"ajv@^6.10.0", "ajv@^6.10.2", "ajv@^6.12.3", "ajv@^6.12.4": + "integrity" "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==" + "resolved" "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + "version" "6.12.6" + dependencies: + "fast-deep-equal" "^3.1.1" + "fast-json-stable-stringify" "^2.0.0" + "json-schema-traverse" "^0.4.1" + "uri-js" "^4.2.2" + +"amdefine@>=0.0.4": + "integrity" "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "resolved" "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" + "version" "1.0.1" + +"ansi-colors@^4.1.1": + "integrity" "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + "resolved" "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" + "version" "4.1.1" + +"ansi-colors@3.2.3": + "integrity" "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" + "resolved" "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz" + "version" "3.2.3" + +"ansi-escapes@^3.2.0": + "integrity" "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "resolved" "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz" + "version" "3.2.0" + +"ansi-regex@^2.0.0": + "integrity" "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + "version" "2.1.1" + +"ansi-regex@^3.0.0": + "integrity" "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz" + "version" "3.0.0" + +"ansi-regex@^4.1.0": + "integrity" "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz" + "version" "4.1.0" + +"ansi-regex@^5.0.0": + "integrity" "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz" + "version" "5.0.0" + +"ansi-styles@^3.2.0": + "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + "version" "3.2.1" + dependencies: + "color-convert" "^1.9.0" + +"ansi-styles@^3.2.1": + "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + "version" "3.2.1" + dependencies: + "color-convert" "^1.9.0" + +"ansi-styles@^4.1.0": + "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "color-convert" "^2.0.1" + +"any-promise@^1.0.0": + "integrity" "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + "resolved" "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + "version" "1.3.0" + +"anymatch@~3.1.1": + "integrity" "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==" + "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz" + "version" "3.1.1" + dependencies: + "normalize-path" "^3.0.0" + "picomatch" "^2.0.4" + +"aproba@^1.0.3": + "integrity" "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "resolved" "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" + "version" "1.2.0" + +"aproba@^1.1.1": + "integrity" "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "resolved" "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" + "version" "1.2.0" + +"aproba@^2.0.0": + "integrity" "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "resolved" "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" + "version" "2.0.0" + +"are-we-there-yet@~1.1.2": + "integrity" "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==" + "resolved" "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz" + "version" "1.1.5" + dependencies: + "delegates" "^1.0.0" + "readable-stream" "^2.0.6" + +"arg@^4.1.0": + "integrity" "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "resolved" "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + "version" "4.1.3" + +"argparse@^1.0.7": + "integrity" "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + "version" "1.0.10" + dependencies: + "sprintf-js" "~1.0.2" + +"arr-diff@^4.0.0": + "integrity" "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "resolved" "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz" + "version" "4.0.0" + +"arr-flatten@^1.1.0": + "integrity" "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "resolved" "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz" + "version" "1.1.0" + +"arr-union@^3.1.0": + "integrity" "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + "resolved" "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" + "version" "3.1.0" + +"array-differ@^2.0.3": + "integrity" "sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==" + "resolved" "https://registry.npmjs.org/array-differ/-/array-differ-2.1.0.tgz" + "version" "2.1.0" + +"array-find-index@^1.0.1": + "integrity" "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + "resolved" "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz" + "version" "1.0.2" + +"array-ify@^1.0.0": + "integrity" "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" + "resolved" "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz" + "version" "1.0.0" + +"array-union@^1.0.2": + "integrity" "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=" + "resolved" "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "array-uniq" "^1.0.1" + +"array-union@^2.1.0": + "integrity" "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "resolved" "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + "version" "2.1.0" + +"array-uniq@^1.0.1": + "integrity" "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "resolved" "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz" + "version" "1.0.3" + +"array-unique@^0.3.2": + "integrity" "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + "resolved" "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz" + "version" "0.3.2" + +"arrify@^1.0.1": + "integrity" "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "resolved" "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + "version" "1.0.1" + +"asap@^2.0.0": + "integrity" "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "resolved" "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" + "version" "2.0.6" + +"asn1@~0.2.3": + "integrity" "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==" + "resolved" "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" + "version" "0.2.4" + dependencies: + "safer-buffer" "~2.1.0" + +"assert-plus@^1.0.0", "assert-plus@1.0.0": + "integrity" "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "resolved" "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + "version" "1.0.0" + +"assertion-error@^1.1.0": + "integrity" "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + "resolved" "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz" + "version" "1.1.0" + +"assertions@~2.3.0": + "integrity" "sha1-qUM87R/OV8yZmvCWXRAI6WwnluY=" + "resolved" "https://registry.npmjs.org/assertions/-/assertions-2.3.4.tgz" + "version" "2.3.4" + dependencies: + "fomatto" "git://github.com/BonsaiDen/Fomatto.git#468666f600b46f9067e3da7200fd9df428923ea6" + "render" "0.1" + "traverser" "1" + +"assign-symbols@^1.0.0": + "integrity" "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "resolved" "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz" + "version" "1.0.0" + +"astral-regex@^1.0.0": + "integrity" "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + "resolved" "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz" + "version" "1.0.0" + +"async@0.9.0": + "integrity" "sha1-rDYTsdqb7RtHUQu0ZRuJMeRxRsc=" + "resolved" "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + "version" "0.9.0" + +"async@1.x": + "integrity" "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "resolved" "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + "version" "1.5.2" + +"asynckit@^0.4.0": + "integrity" "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + "version" "0.4.0" + +"atob-lite@^2.0.0": + "integrity" "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=" + "resolved" "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz" + "version" "2.0.0" + +"atob@^2.1.2": + "integrity" "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + "resolved" "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz" + "version" "2.1.2" + +"aws-sign2@~0.7.0": + "integrity" "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "resolved" "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" + "version" "0.7.0" + +"aws4@^1.8.0": + "integrity" "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + "resolved" "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz" + "version" "1.10.1" + +"balanced-match@^1.0.0": + "integrity" "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" + "version" "1.0.0" + +"base@^0.11.1": + "integrity" "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==" + "resolved" "https://registry.npmjs.org/base/-/base-0.11.2.tgz" + "version" "0.11.2" + dependencies: + "cache-base" "^1.0.1" + "class-utils" "^0.3.5" + "component-emitter" "^1.2.1" + "define-property" "^1.0.0" + "isobject" "^3.0.1" + "mixin-deep" "^1.2.0" + "pascalcase" "^0.1.1" + +"base64-js@0.0.2": + "integrity" "sha1-Ak8Pcq+iW3X5wO5zzU9V7Bvtl4Q=" + "resolved" "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz" + "version" "0.0.2" + +"bcrypt-pbkdf@^1.0.0": + "integrity" "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=" + "resolved" "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "tweetnacl" "^0.14.3" + +"before-after-hook@^2.0.0": + "integrity" "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==" + "resolved" "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz" + "version" "2.1.0" + +"binary-extensions@^2.0.0": + "integrity" "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" + "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz" + "version" "2.1.0" + +"bluebird@^3.5.1", "bluebird@^3.5.3", "bluebird@^3.5.5": + "integrity" "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" + "version" "3.7.2" + +"bluebird@3.4.1": + "integrity" "sha1-tzHd9I4t077awudeEhWhG8uR+gc=" + "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.4.1.tgz" + "version" "3.4.1" + +"bluebird@3.5.2": + "integrity" "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==" + "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz" + "version" "3.5.2" + +"bops@0.0.6": + "integrity" "sha1-CC0dVfoB5g29wuvC26N/ZZVUzzo=" + "resolved" "https://registry.npmjs.org/bops/-/bops-0.0.6.tgz" + "version" "0.0.6" + dependencies: + "base64-js" "0.0.2" + "to-utf8" "0.0.1" + +"brace-expansion@^1.1.7": + "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" + "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + "version" "1.1.11" + dependencies: + "balanced-match" "^1.0.0" + "concat-map" "0.0.1" + +"braces@^2.3.1": + "integrity" "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==" + "resolved" "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz" + "version" "2.3.2" + dependencies: + "arr-flatten" "^1.1.0" + "array-unique" "^0.3.2" + "extend-shallow" "^2.0.1" + "fill-range" "^4.0.0" + "isobject" "^3.0.1" + "repeat-element" "^1.1.2" + "snapdragon" "^0.8.1" + "snapdragon-node" "^2.0.1" + "split-string" "^3.0.2" + "to-regex" "^3.0.1" + +"braces@^3.0.1", "braces@~3.0.2": + "integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" + "resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "fill-range" "^7.0.1" + +"browser-stdout@1.3.1": + "integrity" "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + "resolved" "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" + "version" "1.3.1" + +"btoa-lite@^1.0.0": + "integrity" "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=" + "resolved" "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz" + "version" "1.0.0" + +"buffer-from@^1.0.0": + "integrity" "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" + "version" "1.1.1" + +"buffer-writer@2.0.0": + "integrity" "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + "resolved" "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz" + "version" "2.0.0" + +"builtins@^1.0.3": + "integrity" "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" + "resolved" "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz" + "version" "1.0.3" + +"byline@^5.0.0": + "integrity" "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" + "resolved" "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz" + "version" "5.0.0" + +"byte-size@^5.0.1": + "integrity" "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==" + "resolved" "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz" + "version" "5.0.1" + +"cacache@^12.0.0", "cacache@^12.0.3": + "integrity" "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==" + "resolved" "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz" + "version" "12.0.4" + dependencies: + "bluebird" "^3.5.5" + "chownr" "^1.1.1" + "figgy-pudding" "^3.5.1" + "glob" "^7.1.4" + "graceful-fs" "^4.1.15" + "infer-owner" "^1.0.3" + "lru-cache" "^5.1.1" + "mississippi" "^3.0.0" + "mkdirp" "^0.5.1" + "move-concurrently" "^1.0.1" + "promise-inflight" "^1.0.1" + "rimraf" "^2.6.3" + "ssri" "^6.0.1" + "unique-filename" "^1.1.1" + "y18n" "^4.0.0" + +"cache-base@^1.0.1": + "integrity" "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==" + "resolved" "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "collection-visit" "^1.0.0" + "component-emitter" "^1.2.1" + "get-value" "^2.0.6" + "has-value" "^1.0.0" + "isobject" "^3.0.1" + "set-value" "^2.0.0" + "to-object-path" "^0.3.0" + "union-value" "^1.0.0" + "unset-value" "^1.0.0" + +"call-me-maybe@^1.0.1": + "integrity" "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + "resolved" "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz" + "version" "1.0.1" + +"caller-callsite@^2.0.0": + "integrity" "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=" + "resolved" "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "callsites" "^2.0.0" + +"caller-path@^2.0.0": + "integrity" "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=" + "resolved" "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "caller-callsite" "^2.0.0" + +"callsites@^2.0.0": + "integrity" "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + "resolved" "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz" + "version" "2.0.0" + +"callsites@^3.0.0": + "integrity" "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "resolved" "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + "version" "3.1.0" + +"camelcase-keys@^2.0.0": + "integrity" "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=" + "resolved" "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "camelcase" "^2.0.0" + "map-obj" "^1.0.0" + +"camelcase-keys@^4.0.0": + "integrity" "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=" + "resolved" "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "camelcase" "^4.1.0" + "map-obj" "^2.0.0" + "quick-lru" "^1.0.0" + +"camelcase-keys@^6.2.2": + "integrity" "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==" + "resolved" "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz" + "version" "6.2.2" + dependencies: + "camelcase" "^5.3.1" + "map-obj" "^4.0.0" + "quick-lru" "^4.0.1" + +"camelcase@^2.0.0": + "integrity" "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz" + "version" "2.1.1" + +"camelcase@^4.1.0": + "integrity" "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz" + "version" "4.1.0" + +"camelcase@^5.0.0", "camelcase@^5.3.1": + "integrity" "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + "version" "5.3.1" + +"caseless@~0.12.0": + "integrity" "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "resolved" "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" + "version" "0.12.0" + +"chai@^4.1.1", "chai@^4.2.0": + "integrity" "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==" + "resolved" "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "assertion-error" "^1.1.0" + "check-error" "^1.0.2" + "deep-eql" "^3.0.1" + "get-func-name" "^2.0.0" + "pathval" "^1.1.0" + "type-detect" "^4.0.5" + +"chalk@^2.0.0": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" + +"chalk@^2.3.1": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" + +"chalk@^2.4.2": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" + +"chalk@^4.0.0": + "integrity" "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "ansi-styles" "^4.1.0" + "supports-color" "^7.1.0" + +"chardet@^0.7.0": + "integrity" "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + "resolved" "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" + "version" "0.7.0" + +"check-error@^1.0.2": + "integrity" "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + "resolved" "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz" + "version" "1.0.2" + +"chokidar@3.3.0": + "integrity" "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==" + "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz" + "version" "3.3.0" + dependencies: + "anymatch" "~3.1.1" + "braces" "~3.0.2" + "glob-parent" "~5.1.0" + "is-binary-path" "~2.1.0" + "is-glob" "~4.0.1" + "normalize-path" "~3.0.0" + "readdirp" "~3.2.0" + optionalDependencies: + "fsevents" "~2.1.1" + +"chownr@^1.1.1", "chownr@^1.1.2": + "integrity" "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "resolved" "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" + "version" "1.1.4" + +"chunky@^0.0.0": + "integrity" "sha1-HnWAojwIOJfSrWYkWefv2EZfYIo=" + "resolved" "https://registry.npmjs.org/chunky/-/chunky-0.0.0.tgz" + "version" "0.0.0" + +"ci-info@^2.0.0": + "integrity" "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "resolved" "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" + "version" "2.0.0" + +"class-utils@^0.3.5": + "integrity" "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==" + "resolved" "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz" + "version" "0.3.6" + dependencies: + "arr-union" "^3.1.0" + "define-property" "^0.2.5" + "isobject" "^3.0.0" + "static-extend" "^0.1.1" + +"cli-cursor@^2.1.0": + "integrity" "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=" + "resolved" "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "restore-cursor" "^2.0.0" + +"cli-width@^2.0.0": + "integrity" "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + "resolved" "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz" + "version" "2.2.1" + +"cliui@^5.0.0": + "integrity" "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==" + "resolved" "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "string-width" "^3.1.0" + "strip-ansi" "^5.2.0" + "wrap-ansi" "^5.1.0" + +"clone-deep@^4.0.1": + "integrity" "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==" + "resolved" "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" + "version" "4.0.1" + dependencies: + "is-plain-object" "^2.0.4" + "kind-of" "^6.0.2" + "shallow-clone" "^3.0.0" + +"clone@^1.0.2": + "integrity" "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + "resolved" "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" + "version" "1.0.4" + +"co@4.6.0": + "integrity" "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "resolved" "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + "version" "4.6.0" + +"code-point-at@^1.0.0": + "integrity" "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "resolved" "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" + "version" "1.1.0" + +"collection-visit@^1.0.0": + "integrity" "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=" + "resolved" "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "map-visit" "^1.0.0" + "object-visit" "^1.0.0" + +"color-convert@^1.9.0": + "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + "version" "1.9.3" + dependencies: + "color-name" "1.1.3" + +"color-convert@^2.0.1": + "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "color-name" "~1.1.4" + +"color-name@~1.1.4": + "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + "version" "1.1.4" + +"color-name@1.1.3": + "integrity" "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + "version" "1.1.3" + +"columnify@^1.5.4": + "integrity" "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=" + "resolved" "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz" + "version" "1.5.4" + dependencies: + "strip-ansi" "^3.0.0" + "wcwidth" "^1.0.0" + +"combined-stream@^1.0.6", "combined-stream@~1.0.6": + "integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==" + "resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + "version" "1.0.8" + dependencies: + "delayed-stream" "~1.0.0" + +"compare-func@^2.0.0": + "integrity" "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==" + "resolved" "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "array-ify" "^1.0.0" + "dot-prop" "^5.1.0" + +"component-emitter@^1.2.1": + "integrity" "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + "resolved" "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" + "version" "1.3.0" + +"concat-map@0.0.1": + "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + "version" "0.0.1" + +"concat-stream@^1.5.0": + "integrity" "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==" + "resolved" "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" + "version" "1.6.2" + dependencies: + "buffer-from" "^1.0.0" + "inherits" "^2.0.3" + "readable-stream" "^2.2.2" + "typedarray" "^0.0.6" + +"concat-stream@^2.0.0": + "integrity" "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==" + "resolved" "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "buffer-from" "^1.0.0" + "inherits" "^2.0.3" + "readable-stream" "^3.0.2" + "typedarray" "^0.0.6" + +"concat-stream@~1.0.1": + "integrity" "sha1-AYsYvBx9BzotyCqkhEI0GixN158=" + "resolved" "https://registry.npmjs.org/concat-stream/-/concat-stream-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "bops" "0.0.6" + +"config-chain@^1.1.11": + "integrity" "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==" + "resolved" "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz" + "version" "1.1.12" + dependencies: + "ini" "^1.3.4" + "proto-list" "~1.2.1" + +"console-control-strings@^1.0.0", "console-control-strings@~1.1.0": + "integrity" "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "resolved" "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + "version" "1.1.0" + +"conventional-changelog-angular@^5.0.3": + "integrity" "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==" + "resolved" "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz" + "version" "5.0.11" + dependencies: + "compare-func" "^2.0.0" + "q" "^1.5.1" + +"conventional-changelog-core@^3.1.6": + "integrity" "sha512-LMMX1JlxPIq/Ez5aYAYS5CpuwbOk6QFp8O4HLAcZxe3vxoCtABkhfjetk8IYdRB9CDQGwJFLR3Dr55Za6XKgUQ==" + "resolved" "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.2.3.tgz" + "version" "3.2.3" + dependencies: + "conventional-changelog-writer" "^4.0.6" + "conventional-commits-parser" "^3.0.3" + "dateformat" "^3.0.0" + "get-pkg-repo" "^1.0.0" + "git-raw-commits" "2.0.0" + "git-remote-origin-url" "^2.0.0" + "git-semver-tags" "^2.0.3" + "lodash" "^4.2.1" + "normalize-package-data" "^2.3.5" + "q" "^1.5.1" + "read-pkg" "^3.0.0" + "read-pkg-up" "^3.0.0" + "through2" "^3.0.0" + +"conventional-changelog-preset-loader@^2.1.1": + "integrity" "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==" + "resolved" "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz" + "version" "2.3.4" + +"conventional-changelog-writer@^4.0.6": + "integrity" "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==" + "resolved" "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz" + "version" "4.0.17" + dependencies: + "compare-func" "^2.0.0" + "conventional-commits-filter" "^2.0.6" + "dateformat" "^3.0.0" + "handlebars" "^4.7.6" + "json-stringify-safe" "^5.0.1" + "lodash" "^4.17.15" + "meow" "^7.0.0" + "semver" "^6.0.0" + "split" "^1.0.0" + "through2" "^3.0.0" + +"conventional-commits-filter@^2.0.2", "conventional-commits-filter@^2.0.6": + "integrity" "sha512-4g+sw8+KA50/Qwzfr0hL5k5NWxqtrOVw4DDk3/h6L85a9Gz0/Eqp3oP+CWCNfesBvZZZEFHF7OTEbRe+yYSyKw==" + "resolved" "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz" + "version" "2.0.6" + dependencies: + "lodash.ismatch" "^4.4.0" + "modify-values" "^1.0.0" + +"conventional-commits-parser@^3.0.3": + "integrity" "sha512-RSo5S0WIwXZiRxUGTPuYFbqvrR4vpJ1BDdTlthFgvHt5kEdnd1+pdvwWphWn57/oIl4V72NMmOocFqqJ8mFFhA==" + "resolved" "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "is-text-path" "^1.0.1" + "JSONStream" "^1.0.4" + "lodash" "^4.17.15" + "meow" "^7.0.0" + "split2" "^2.0.0" + "through2" "^3.0.0" + "trim-off-newlines" "^1.0.0" + +"conventional-recommended-bump@^5.0.0": + "integrity" "sha512-RVdt0elRcCxL90IrNP0fYCpq1uGt2MALko0eyeQ+zQuDVWtMGAy9ng6yYn3kax42lCj9+XBxQ8ZN6S9bdKxDhQ==" + "resolved" "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "concat-stream" "^2.0.0" + "conventional-changelog-preset-loader" "^2.1.1" + "conventional-commits-filter" "^2.0.2" + "conventional-commits-parser" "^3.0.3" + "git-raw-commits" "2.0.0" + "git-semver-tags" "^2.0.3" + "meow" "^4.0.0" + "q" "^1.5.1" + +"copy-concurrently@^1.0.0": + "integrity" "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==" + "resolved" "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz" + "version" "1.0.5" + dependencies: + "aproba" "^1.1.1" + "fs-write-stream-atomic" "^1.0.8" + "iferr" "^0.1.5" + "mkdirp" "^0.5.1" + "rimraf" "^2.5.4" + "run-queue" "^1.0.0" + +"copy-descriptor@^0.1.0": + "integrity" "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + "resolved" "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz" + "version" "0.1.1" + +"core-util-is@~1.0.0", "core-util-is@1.0.2": + "integrity" "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "resolved" "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + "version" "1.0.2" + +"cosmiconfig@^5.1.0": + "integrity" "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==" + "resolved" "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz" + "version" "5.2.1" + dependencies: + "import-fresh" "^2.0.0" + "is-directory" "^0.3.1" + "js-yaml" "^3.13.1" + "parse-json" "^4.0.0" + +"coveralls@^3.0.4": + "integrity" "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==" + "resolved" "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "js-yaml" "^3.13.1" + "lcov-parse" "^1.0.0" + "log-driver" "^1.2.7" + "minimist" "^1.2.5" + "request" "^2.88.2" + +"cross-spawn@^6.0.0": + "integrity" "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==" + "resolved" "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" + "version" "6.0.5" + dependencies: + "nice-try" "^1.0.4" + "path-key" "^2.0.1" + "semver" "^5.5.0" + "shebang-command" "^1.2.0" + "which" "^1.2.9" + +"cross-spawn@^7.0.2": + "integrity" "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==" + "resolved" "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + "version" "7.0.3" + dependencies: + "path-key" "^3.1.0" + "shebang-command" "^2.0.0" + "which" "^2.0.1" + +"currently-unhandled@^0.4.1": + "integrity" "sha1-mI3zP+qxke95mmE2nddsF635V+o=" + "resolved" "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz" + "version" "0.4.1" + dependencies: + "array-find-index" "^1.0.1" + +"curry@0.0.x": + "integrity" "sha1-F1DVGNkZxE89N/9E7caT3h8NX8s=" + "resolved" "https://registry.npmjs.org/curry/-/curry-0.0.4.tgz" + "version" "0.0.4" + +"cyclist@^1.0.1": + "integrity" "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + "resolved" "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz" + "version" "1.0.1" + +"dargs@^4.0.1": + "integrity" "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=" + "resolved" "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "number-is-nan" "^1.0.0" + +"dashdash@^1.12.0": + "integrity" "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=" + "resolved" "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" + "version" "1.14.1" + dependencies: + "assert-plus" "^1.0.0" + +"dateformat@^3.0.0": + "integrity" "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + "resolved" "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" + "version" "3.0.3" + +"debug@^2.2.0": + "integrity" "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" + "resolved" "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + "version" "2.6.9" + dependencies: + "ms" "2.0.0" + +"debug@^2.3.3": + "integrity" "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" + "resolved" "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + "version" "2.6.9" + dependencies: + "ms" "2.0.0" + +"debug@^3.1.0": + "integrity" "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==" + "resolved" "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "version" "3.2.6" + dependencies: + "ms" "^2.1.1" + +"debug@^4.0.1", "debug@^4.1.1": + "integrity" "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==" + "resolved" "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "ms" "2.1.2" + +"debug@3.1.0": + "integrity" "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==" + "resolved" "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "ms" "2.0.0" + +"debug@3.2.6": + "integrity" "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==" + "resolved" "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "version" "3.2.6" + dependencies: + "ms" "^2.1.1" + +"debuglog@^1.0.1": + "integrity" "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" + "resolved" "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz" + "version" "1.0.1" + +"decamelize-keys@^1.0.0", "decamelize-keys@^1.1.0": + "integrity" "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=" + "resolved" "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "decamelize" "^1.1.0" + "map-obj" "^1.0.0" + +"decamelize@^1.1.0", "decamelize@^1.1.2", "decamelize@^1.2.0": + "integrity" "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "resolved" "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + "version" "1.2.0" + +"decode-uri-component@^0.2.0": + "integrity" "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "resolved" "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" + "version" "0.2.0" + +"dedent@^0.7.0": + "integrity" "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" + "resolved" "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" + "version" "0.7.0" + +"deep-eql@^3.0.1": + "integrity" "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==" + "resolved" "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "type-detect" "^4.0.0" + +"deep-is@^0.1.3", "deep-is@~0.1.3": + "integrity" "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "resolved" "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" + "version" "0.1.3" + +"defaults@^1.0.3": + "integrity" "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=" + "resolved" "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "clone" "^1.0.2" + +"define-properties@^1.1.2", "define-properties@^1.1.3": + "integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" + "version" "1.1.3" + dependencies: + "object-keys" "^1.0.12" + +"define-property@^0.2.5": + "integrity" "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=" + "resolved" "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz" + "version" "0.2.5" + dependencies: + "is-descriptor" "^0.1.0" + +"define-property@^1.0.0": + "integrity" "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=" + "resolved" "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "is-descriptor" "^1.0.0" + +"define-property@^2.0.2": + "integrity" "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==" + "resolved" "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "is-descriptor" "^1.0.2" + "isobject" "^3.0.1" + +"delayed-stream@~1.0.0": + "integrity" "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + "version" "1.0.0" + +"delegates@^1.0.0": + "integrity" "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "resolved" "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + "version" "1.0.0" + +"deprecation@^2.0.0", "deprecation@^2.3.1": + "integrity" "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + "resolved" "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" + "version" "2.3.1" + +"detect-indent@^5.0.0": + "integrity" "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" + "resolved" "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz" + "version" "5.0.0" + +"dezalgo@^1.0.0": + "integrity" "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=" + "resolved" "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "asap" "^2.0.0" + "wrappy" "1" + +"diff@^4.0.1": + "integrity" "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "resolved" "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + "version" "4.0.2" + +"diff@3.5.0": + "integrity" "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "resolved" "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + "version" "3.5.0" + +"dir-glob@^2.2.2": + "integrity" "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==" + "resolved" "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz" + "version" "2.2.2" + dependencies: + "path-type" "^3.0.0" + +"dir-glob@^3.0.1": + "integrity" "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==" + "resolved" "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "path-type" "^4.0.0" + +"doctrine@^3.0.0": + "integrity" "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==" + "resolved" "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "esutils" "^2.0.2" + +"dot-prop@^4.2.0": + "integrity" "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==" + "resolved" "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz" + "version" "4.2.1" + dependencies: + "is-obj" "^1.0.0" + +"dot-prop@^5.1.0": + "integrity" "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==" + "resolved" "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" + "version" "5.3.0" + dependencies: + "is-obj" "^2.0.0" + +"duplexer@^0.1.1": + "integrity" "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + "resolved" "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" + "version" "0.1.2" + +"duplexify@^3.4.2", "duplexify@^3.6.0": + "integrity" "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==" + "resolved" "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz" + "version" "3.7.1" + dependencies: + "end-of-stream" "^1.0.0" + "inherits" "^2.0.1" + "readable-stream" "^2.0.0" + "stream-shift" "^1.0.0" + +"ecc-jsbn@~0.1.1": + "integrity" "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=" + "resolved" "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" + "version" "0.1.2" + dependencies: + "jsbn" "~0.1.0" + "safer-buffer" "^2.1.0" + +"emoji-regex@^7.0.1": + "integrity" "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz" + "version" "7.0.3" + +"encoding@^0.1.11": + "integrity" "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==" + "resolved" "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" + "version" "0.1.13" + dependencies: + "iconv-lite" "^0.6.2" + +"end-of-stream@^1.0.0", "end-of-stream@^1.1.0": + "integrity" "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==" + "resolved" "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" + "version" "1.4.4" + dependencies: + "once" "^1.4.0" + +"enquirer@^2.3.5": + "integrity" "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==" + "resolved" "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" + "version" "2.3.6" + dependencies: + "ansi-colors" "^4.1.1" + +"env-paths@^2.2.0": + "integrity" "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" + "resolved" "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz" + "version" "2.2.0" + +"envinfo@^7.3.1": + "integrity" "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==" + "resolved" "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz" + "version" "7.7.3" + +"err-code@^1.0.0": + "integrity" "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" + "resolved" "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz" + "version" "1.1.2" + +"error-ex@^1.2.0", "error-ex@^1.3.1": + "integrity" "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==" + "resolved" "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "is-arrayish" "^0.2.1" + +"es-abstract@^1.17.0-next.1", "es-abstract@^1.17.5": + "integrity" "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==" + "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz" + "version" "1.17.7" + dependencies: + "es-to-primitive" "^1.2.1" + "function-bind" "^1.1.1" + "has" "^1.0.3" + "has-symbols" "^1.0.1" + "is-callable" "^1.2.2" + "is-regex" "^1.1.1" + "object-inspect" "^1.8.0" + "object-keys" "^1.1.1" + "object.assign" "^4.1.1" + "string.prototype.trimend" "^1.0.1" + "string.prototype.trimstart" "^1.0.1" + +"es-abstract@^1.18.0-next.0": + "integrity" "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==" + "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz" + "version" "1.18.0-next.1" + dependencies: + "es-to-primitive" "^1.2.1" + "function-bind" "^1.1.1" + "has" "^1.0.3" + "has-symbols" "^1.0.1" + "is-callable" "^1.2.2" + "is-negative-zero" "^2.0.0" + "is-regex" "^1.1.1" + "object-inspect" "^1.8.0" + "object-keys" "^1.1.1" + "object.assign" "^4.1.1" + "string.prototype.trimend" "^1.0.1" + "string.prototype.trimstart" "^1.0.1" + +"es-to-primitive@^1.2.1": + "integrity" "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" + "resolved" "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + "version" "1.2.1" + dependencies: + "is-callable" "^1.1.4" + "is-date-object" "^1.0.1" + "is-symbol" "^1.0.2" + +"es6-promise@^4.0.3": + "integrity" "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + "resolved" "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz" + "version" "4.2.8" + +"es6-promisify@^5.0.0": + "integrity" "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=" + "resolved" "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "es6-promise" "^4.0.3" + +"escape-string-regexp@^1.0.5", "escape-string-regexp@1.0.5": + "integrity" "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + "version" "1.0.5" + +"escodegen@1.8.x": + "integrity" "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=" + "resolved" "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz" + "version" "1.8.1" + dependencies: + "esprima" "^2.7.1" + "estraverse" "^1.9.1" + "esutils" "^2.0.2" + "optionator" "^0.8.1" + optionalDependencies: + "source-map" "~0.2.0" + +"eslint-config-prettier@^6.12.0": + "integrity" "sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw==" + "resolved" "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz" + "version" "6.12.0" + dependencies: + "get-stdin" "^6.0.0" + +"eslint-plugin-es@^3.0.0": + "integrity" "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==" + "resolved" "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "eslint-utils" "^2.0.0" + "regexpp" "^3.0.0" + +"eslint-plugin-node@^11.1.0": + "integrity" "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==" + "resolved" "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz" + "version" "11.1.0" + dependencies: + "eslint-plugin-es" "^3.0.0" + "eslint-utils" "^2.0.0" + "ignore" "^5.1.1" + "minimatch" "^3.0.4" + "resolve" "^1.10.1" + "semver" "^6.1.0" + +"eslint-plugin-prettier@^3.1.4": + "integrity" "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==" + "resolved" "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz" + "version" "3.1.4" + dependencies: + "prettier-linter-helpers" "^1.0.0" + +"eslint-plugin-promise@^3.5.0": + "integrity" "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==" + "resolved" "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz" + "version" "3.8.0" + +"eslint-scope@^5.0.0", "eslint-scope@^5.1.1": + "integrity" "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==" + "resolved" "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + "version" "5.1.1" + dependencies: + "esrecurse" "^4.3.0" + "estraverse" "^4.1.1" + +"eslint-utils@^2.0.0", "eslint-utils@^2.1.0": + "integrity" "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==" + "resolved" "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "eslint-visitor-keys" "^1.1.0" + +"eslint-visitor-keys@^1.1.0": + "integrity" "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" + "version" "1.3.0" + +"eslint-visitor-keys@^1.3.0": + "integrity" "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" + "version" "1.3.0" + +"eslint-visitor-keys@^2.0.0": + "integrity" "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==" + "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz" + "version" "2.0.0" + +"eslint@*", "eslint@^5.0.0 || ^6.0.0 || ^7.0.0", "eslint@^7.11.0", "eslint@>=3.14.1", "eslint@>=4.19.1", "eslint@>=5.0.0", "eslint@>=5.16.0": + "integrity" "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==" + "resolved" "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz" + "version" "7.11.0" + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.1.3" + "ajv" "^6.10.0" + "chalk" "^4.0.0" + "cross-spawn" "^7.0.2" + "debug" "^4.0.1" + "doctrine" "^3.0.0" + "enquirer" "^2.3.5" + "eslint-scope" "^5.1.1" + "eslint-utils" "^2.1.0" + "eslint-visitor-keys" "^2.0.0" + "espree" "^7.3.0" + "esquery" "^1.2.0" + "esutils" "^2.0.2" + "file-entry-cache" "^5.0.1" + "functional-red-black-tree" "^1.0.1" + "glob-parent" "^5.0.0" + "globals" "^12.1.0" + "ignore" "^4.0.6" + "import-fresh" "^3.0.0" + "imurmurhash" "^0.1.4" + "is-glob" "^4.0.0" + "js-yaml" "^3.13.1" + "json-stable-stringify-without-jsonify" "^1.0.1" + "levn" "^0.4.1" + "lodash" "^4.17.19" + "minimatch" "^3.0.4" + "natural-compare" "^1.4.0" + "optionator" "^0.9.1" + "progress" "^2.0.0" + "regexpp" "^3.1.0" + "semver" "^7.2.1" + "strip-ansi" "^6.0.0" + "strip-json-comments" "^3.1.0" + "table" "^5.2.3" + "text-table" "^0.2.0" + "v8-compile-cache" "^2.0.3" + +"espree@^7.3.0": + "integrity" "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==" + "resolved" "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz" + "version" "7.3.0" + dependencies: + "acorn" "^7.4.0" + "acorn-jsx" "^5.2.0" + "eslint-visitor-keys" "^1.3.0" + +"esprima@^2.7.1": + "integrity" "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "resolved" "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" + "version" "2.7.3" + +"esprima@^4.0.0": + "integrity" "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "resolved" "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + "version" "4.0.1" + +"esprima@2.7.x": + "integrity" "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "resolved" "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" + "version" "2.7.3" + +"esquery@^1.2.0": + "integrity" "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==" + "resolved" "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz" + "version" "1.3.1" + dependencies: + "estraverse" "^5.1.0" + +"esrecurse@^4.3.0": + "integrity" "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==" + "resolved" "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "estraverse" "^5.2.0" + +"estraverse@^1.9.1": + "integrity" "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz" + "version" "1.9.3" + +"estraverse@^4.1.1": + "integrity" "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + "version" "4.3.0" + +"estraverse@^5.1.0": + "integrity" "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz" + "version" "5.2.0" + +"estraverse@^5.2.0": + "integrity" "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz" + "version" "5.2.0" + +"esutils@^2.0.2": + "integrity" "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "resolved" "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + "version" "2.0.3" + +"eventemitter3@^3.1.0": + "integrity" "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + "resolved" "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz" + "version" "3.1.2" + +"execa@^1.0.0": + "integrity" "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==" + "resolved" "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "cross-spawn" "^6.0.0" + "get-stream" "^4.0.0" + "is-stream" "^1.1.0" + "npm-run-path" "^2.0.0" + "p-finally" "^1.0.0" + "signal-exit" "^3.0.0" + "strip-eof" "^1.0.0" + +"expand-brackets@^2.1.4": + "integrity" "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=" + "resolved" "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz" + "version" "2.1.4" + dependencies: + "debug" "^2.3.3" + "define-property" "^0.2.5" + "extend-shallow" "^2.0.1" + "posix-character-classes" "^0.1.0" + "regex-not" "^1.0.0" + "snapdragon" "^0.8.1" + "to-regex" "^3.0.1" + +"expect.js@0.3.1": + "integrity" "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=" + "resolved" "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz" + "version" "0.3.1" + +"extend-shallow@^2.0.1": + "integrity" "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=" + "resolved" "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "is-extendable" "^0.1.0" + +"extend-shallow@^3.0.0", "extend-shallow@^3.0.2": + "integrity" "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=" + "resolved" "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "assign-symbols" "^1.0.0" + "is-extendable" "^1.0.1" + +"extend@~3.0.2": + "integrity" "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "resolved" "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + "version" "3.0.2" + +"external-editor@^3.0.3": + "integrity" "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==" + "resolved" "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "chardet" "^0.7.0" + "iconv-lite" "^0.4.24" + "tmp" "^0.0.33" + +"extglob@^2.0.4": + "integrity" "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==" + "resolved" "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz" + "version" "2.0.4" + dependencies: + "array-unique" "^0.3.2" + "define-property" "^1.0.0" + "expand-brackets" "^2.1.4" + "extend-shallow" "^2.0.1" + "fragment-cache" "^0.2.1" + "regex-not" "^1.0.0" + "snapdragon" "^0.8.1" + "to-regex" "^3.0.1" + +"extsprintf@^1.2.0", "extsprintf@1.3.0": + "integrity" "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "resolved" "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + "version" "1.3.0" + +"fast-deep-equal@^3.1.1": + "integrity" "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + "version" "3.1.3" + +"fast-diff@^1.1.2": + "integrity" "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" + "resolved" "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz" + "version" "1.2.0" + +"fast-glob@^2.2.6": + "integrity" "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==" + "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz" + "version" "2.2.7" + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + "glob-parent" "^3.1.0" + "is-glob" "^4.0.0" + "merge2" "^1.2.3" + "micromatch" "^3.1.10" + +"fast-glob@^3.1.1": + "integrity" "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==" + "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz" + "version" "3.2.4" + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + "glob-parent" "^5.1.0" + "merge2" "^1.3.0" + "micromatch" "^4.0.2" + "picomatch" "^2.2.1" + +"fast-json-stable-stringify@^2.0.0": + "integrity" "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "resolved" "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + "version" "2.1.0" + +"fast-levenshtein@^2.0.6", "fast-levenshtein@~2.0.6": + "integrity" "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "resolved" "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + "version" "2.0.6" + +"fastq@^1.6.0": + "integrity" "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==" + "resolved" "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz" + "version" "1.8.0" + dependencies: + "reusify" "^1.0.4" + +"figgy-pudding@^3.4.1", "figgy-pudding@^3.5.1": + "integrity" "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" + "resolved" "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz" + "version" "3.5.2" + +"figures@^2.0.0": + "integrity" "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=" + "resolved" "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "escape-string-regexp" "^1.0.5" + +"file-entry-cache@^5.0.1": + "integrity" "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==" + "resolved" "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "flat-cache" "^2.0.1" + +"fill-range@^4.0.0": + "integrity" "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=" + "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "extend-shallow" "^2.0.1" + "is-number" "^3.0.0" + "repeat-string" "^1.6.1" + "to-regex-range" "^2.1.0" + +"fill-range@^7.0.1": + "integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" + "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + "version" "7.0.1" + dependencies: + "to-regex-range" "^5.0.1" + +"find-up@^1.0.0": + "integrity" "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz" + "version" "1.1.2" + dependencies: + "path-exists" "^2.0.0" + "pinkie-promise" "^2.0.0" + +"find-up@^2.0.0": + "integrity" "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "locate-path" "^2.0.0" + +"find-up@^3.0.0", "find-up@3.0.0": + "integrity" "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "locate-path" "^3.0.0" + +"find-up@^4.1.0": + "integrity" "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "locate-path" "^5.0.0" + "path-exists" "^4.0.0" + +"flat-cache@^2.0.1": + "integrity" "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==" + "resolved" "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "flatted" "^2.0.0" + "rimraf" "2.6.3" + "write" "1.0.3" + +"flat@^4.1.0": + "integrity" "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==" + "resolved" "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "is-buffer" "~2.0.3" + +"flatted@^2.0.0": + "integrity" "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" + "resolved" "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz" + "version" "2.0.2" + +"flush-write-stream@^1.0.0": + "integrity" "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==" + "resolved" "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "inherits" "^2.0.3" + "readable-stream" "^2.3.6" + +"fomatto@git://github.com/BonsaiDen/Fomatto.git#468666f600b46f9067e3da7200fd9df428923ea6": + "integrity" "sha512-2JeZCbUi12tmDJhd9F3mxZ6xL0+2Ym2MamXZHnHh5OVKKA1h/6svVAAzJ2tt5ycFAQ4EwseW3FoAFNUle/opbQ==" + "resolved" "git+ssh://git@github.com/BonsaiDen/Fomatto.git" + "version" "0.6.0" + +"for-in@^1.0.2": + "integrity" "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + "resolved" "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" + "version" "1.0.2" + +"forever-agent@~0.6.1": + "integrity" "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "resolved" "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + "version" "0.6.1" + +"form-data@~2.3.2": + "integrity" "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==" + "resolved" "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" + "version" "2.3.3" + dependencies: + "asynckit" "^0.4.0" + "combined-stream" "^1.0.6" + "mime-types" "^2.1.12" + +"fragment-cache@^0.2.1": + "integrity" "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=" + "resolved" "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz" + "version" "0.2.1" + dependencies: + "map-cache" "^0.2.2" + +"from@~0.0.2": + "integrity" "sha1-f/+sZHovmbINV7jig3lFXLtBidA=" + "resolved" "https://registry.npmjs.org/from/-/from-0.0.2.tgz" + "version" "0.0.2" + +"from2@^2.1.0": + "integrity" "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=" + "resolved" "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz" + "version" "2.3.0" + dependencies: + "inherits" "^2.0.1" + "readable-stream" "^2.0.0" + +"fs-extra@^8.1.0": + "integrity" "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==" + "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" + "version" "8.1.0" + dependencies: + "graceful-fs" "^4.2.0" + "jsonfile" "^4.0.0" + "universalify" "^0.1.0" + +"fs-minipass@^1.2.5": + "integrity" "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==" + "resolved" "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz" + "version" "1.2.7" + dependencies: + "minipass" "^2.6.0" + +"fs-write-stream-atomic@^1.0.8": + "integrity" "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=" + "resolved" "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz" + "version" "1.0.10" + dependencies: + "graceful-fs" "^4.1.2" + "iferr" "^0.1.5" + "imurmurhash" "^0.1.4" + "readable-stream" "1 || 2" + +"fs.realpath@^1.0.0": + "integrity" "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + "version" "1.0.0" + +"fsevents@~2.1.1": + "integrity" "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==" + "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz" + "version" "2.1.3" + +"function-bind@^1.1.1": + "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + "version" "1.1.1" + +"functional-red-black-tree@^1.0.1": + "integrity" "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "resolved" "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" + "version" "1.0.1" + +"gauge@~2.7.3": + "integrity" "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=" + "resolved" "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz" + "version" "2.7.4" + dependencies: + "aproba" "^1.0.3" + "console-control-strings" "^1.0.0" + "has-unicode" "^2.0.0" + "object-assign" "^4.1.0" + "signal-exit" "^3.0.0" + "string-width" "^1.0.1" + "strip-ansi" "^3.0.1" + "wide-align" "^1.1.0" + +"genfun@^5.0.0": + "integrity" "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==" + "resolved" "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz" + "version" "5.0.0" + +"get-caller-file@^2.0.1": + "integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + "version" "2.0.5" + +"get-func-name@^2.0.0": + "integrity" "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + "resolved" "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz" + "version" "2.0.0" + +"get-pkg-repo@^1.0.0": + "integrity" "sha1-xztInAbYDMVTbCyFP54FIyBWly0=" + "resolved" "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "hosted-git-info" "^2.1.4" + "meow" "^3.3.0" + "normalize-package-data" "^2.3.0" + "parse-github-repo-url" "^1.3.0" + "through2" "^2.0.0" + +"get-port@^4.2.0": + "integrity" "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==" + "resolved" "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz" + "version" "4.2.0" + +"get-stdin@^4.0.1": + "integrity" "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + "resolved" "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" + "version" "4.0.1" + +"get-stdin@^6.0.0": + "integrity" "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==" + "resolved" "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz" + "version" "6.0.0" + +"get-stream@^4.0.0", "get-stream@^4.1.0": + "integrity" "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==" + "resolved" "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "pump" "^3.0.0" + +"get-value@^2.0.3", "get-value@^2.0.6": + "integrity" "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "resolved" "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz" + "version" "2.0.6" + +"getpass@^0.1.1": + "integrity" "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=" + "resolved" "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" + "version" "0.1.7" + dependencies: + "assert-plus" "^1.0.0" + +"git-raw-commits@2.0.0": + "integrity" "sha512-w4jFEJFgKXMQJ0H0ikBk2S+4KP2VEjhCvLCNqbNRQC8BgGWgLKNCO7a9K9LI+TVT7Gfoloje502sEnctibffgg==" + "resolved" "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "dargs" "^4.0.1" + "lodash.template" "^4.0.2" + "meow" "^4.0.0" + "split2" "^2.0.0" + "through2" "^2.0.0" + +"git-remote-origin-url@^2.0.0": + "integrity" "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=" + "resolved" "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "gitconfiglocal" "^1.0.0" + "pify" "^2.3.0" + +"git-semver-tags@^2.0.3": + "integrity" "sha512-tj4FD4ww2RX2ae//jSrXZzrocla9db5h0V7ikPl1P/WwoZar9epdUhwR7XHXSgc+ZkNq72BEEerqQuicoEQfzA==" + "resolved" "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-2.0.3.tgz" + "version" "2.0.3" + dependencies: + "meow" "^4.0.0" + "semver" "^6.0.0" + +"git-up@^4.0.0": + "integrity" "sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ==" + "resolved" "https://registry.npmjs.org/git-up/-/git-up-4.0.2.tgz" + "version" "4.0.2" + dependencies: + "is-ssh" "^1.3.0" + "parse-url" "^5.0.0" + +"git-url-parse@^11.1.2": + "integrity" "sha512-i3XNa8IKmqnUqWBcdWBjOcnyZYfN3C1WRvnKI6ouFWwsXCZEnlgbwbm55ZpJ3OJMhfEP/ryFhqW8bBhej3C5Ug==" + "resolved" "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.3.0.tgz" + "version" "11.3.0" + dependencies: + "git-up" "^4.0.0" + +"gitconfiglocal@^1.0.0": + "integrity" "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=" + "resolved" "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "ini" "^1.3.2" + +"glob-parent@^3.1.0": + "integrity" "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "is-glob" "^3.1.0" + "path-dirname" "^1.0.0" + +"glob-parent@^5.0.0", "glob-parent@^5.1.0", "glob-parent@~5.1.0": + "integrity" "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz" + "version" "5.1.1" + dependencies: + "is-glob" "^4.0.1" + +"glob-to-regexp@^0.3.0": + "integrity" "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + "resolved" "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz" + "version" "0.3.0" + +"glob@^5.0.15": + "integrity" "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=" + "resolved" "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + "version" "5.0.15" + dependencies: + "inflight" "^1.0.4" + "inherits" "2" + "minimatch" "2 || 3" + "once" "^1.3.0" + "path-is-absolute" "^1.0.0" + +"glob@^7.1.1", "glob@^7.1.3", "glob@^7.1.4": + "integrity" "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==" + "resolved" "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" + "version" "7.1.6" + dependencies: + "fs.realpath" "^1.0.0" + "inflight" "^1.0.4" + "inherits" "2" + "minimatch" "^3.0.4" + "once" "^1.3.0" + "path-is-absolute" "^1.0.0" + +"glob@7.1.3": + "integrity" "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==" + "resolved" "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz" + "version" "7.1.3" + dependencies: + "fs.realpath" "^1.0.0" + "inflight" "^1.0.4" + "inherits" "2" + "minimatch" "^3.0.4" + "once" "^1.3.0" + "path-is-absolute" "^1.0.0" + +"globals@^12.1.0": + "integrity" "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==" + "resolved" "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz" + "version" "12.4.0" + dependencies: + "type-fest" "^0.8.1" + +"globby@^11.0.1": + "integrity" "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==" + "resolved" "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz" + "version" "11.0.1" + dependencies: + "array-union" "^2.1.0" + "dir-glob" "^3.0.1" + "fast-glob" "^3.1.1" + "ignore" "^5.1.4" + "merge2" "^1.3.0" + "slash" "^3.0.0" + +"globby@^9.2.0": + "integrity" "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==" + "resolved" "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz" + "version" "9.2.0" + dependencies: + "@types/glob" "^7.1.1" + "array-union" "^1.0.2" + "dir-glob" "^2.2.2" + "fast-glob" "^2.2.6" + "glob" "^7.1.3" + "ignore" "^4.0.3" + "pify" "^4.0.1" + "slash" "^2.0.0" + +"graceful-fs@^4.1.11", "graceful-fs@^4.1.15", "graceful-fs@^4.1.2", "graceful-fs@^4.1.6", "graceful-fs@^4.2.0", "graceful-fs@^4.2.2": + "integrity" "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "resolved" "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz" + "version" "4.2.4" + +"growl@1.10.5": + "integrity" "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + "resolved" "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz" + "version" "1.10.5" + +"handlebars@^4.0.1", "handlebars@^4.7.6": + "integrity" "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==" + "resolved" "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz" + "version" "4.7.6" + dependencies: + "minimist" "^1.2.5" + "neo-async" "^2.6.0" + "source-map" "^0.6.1" + "wordwrap" "^1.0.0" + optionalDependencies: + "uglify-js" "^3.1.4" + +"har-schema@^2.0.0": + "integrity" "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "resolved" "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" + "version" "2.0.0" + +"har-validator@~5.1.3": + "integrity" "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==" + "resolved" "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" + "version" "5.1.5" + dependencies: + "ajv" "^6.12.3" + "har-schema" "^2.0.0" + +"hard-rejection@^2.1.0": + "integrity" "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==" + "resolved" "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" + "version" "2.1.0" + +"has-flag@^1.0.0": + "integrity" "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" + "version" "1.0.0" + +"has-flag@^3.0.0": + "integrity" "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + "version" "3.0.0" + +"has-flag@^4.0.0": + "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + "version" "4.0.0" + +"has-symbols@^1.0.0", "has-symbols@^1.0.1": + "integrity" "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz" + "version" "1.0.1" + +"has-unicode@^2.0.0", "has-unicode@^2.0.1": + "integrity" "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "resolved" "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + "version" "2.0.1" + +"has-value@^0.3.1": + "integrity" "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=" + "resolved" "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz" + "version" "0.3.1" + dependencies: + "get-value" "^2.0.3" + "has-values" "^0.1.4" + "isobject" "^2.0.0" + +"has-value@^1.0.0": + "integrity" "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=" + "resolved" "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "get-value" "^2.0.6" + "has-values" "^1.0.0" + "isobject" "^3.0.0" + +"has-values@^0.1.4": + "integrity" "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + "resolved" "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz" + "version" "0.1.4" + +"has-values@^1.0.0": + "integrity" "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=" + "resolved" "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "is-number" "^3.0.0" + "kind-of" "^4.0.0" + +"has@^1.0.3": + "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" + "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "function-bind" "^1.1.1" + +"he@1.2.0": + "integrity" "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "resolved" "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + "version" "1.2.0" + +"hosted-git-info@^2.1.4", "hosted-git-info@^2.7.1": + "integrity" "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "resolved" "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz" + "version" "2.8.8" + +"http-cache-semantics@^3.8.1": + "integrity" "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" + "resolved" "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz" + "version" "3.8.1" + +"http-proxy-agent@^2.1.0": + "integrity" "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==" + "resolved" "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "agent-base" "4" + "debug" "3.1.0" + +"http-signature@~1.2.0": + "integrity" "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=" + "resolved" "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" + "version" "1.2.0" + dependencies: + "assert-plus" "^1.0.0" + "jsprim" "^1.2.2" + "sshpk" "^1.7.0" + +"https-proxy-agent@^2.2.3": + "integrity" "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==" + "resolved" "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz" + "version" "2.2.4" + dependencies: + "agent-base" "^4.3.0" + "debug" "^3.1.0" + +"humanize-ms@^1.2.1": + "integrity" "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=" + "resolved" "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" + "version" "1.2.1" + dependencies: + "ms" "^2.0.0" + +"iconv-lite@^0.4.24": + "integrity" "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==" + "resolved" "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + "version" "0.4.24" + dependencies: + "safer-buffer" ">= 2.1.2 < 3" + +"iconv-lite@^0.6.2": + "integrity" "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==" + "resolved" "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz" + "version" "0.6.2" + dependencies: + "safer-buffer" ">= 2.1.2 < 3.0.0" + +"iferr@^0.1.5": + "integrity" "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + "resolved" "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz" + "version" "0.1.5" + +"ignore-walk@^3.0.1": + "integrity" "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==" + "resolved" "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz" + "version" "3.0.3" + dependencies: + "minimatch" "^3.0.4" + +"ignore@^4.0.3", "ignore@^4.0.6": + "integrity" "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + "resolved" "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz" + "version" "4.0.6" + +"ignore@^5.1.1": + "integrity" "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + "resolved" "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz" + "version" "5.1.8" + +"ignore@^5.1.4": + "integrity" "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + "resolved" "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz" + "version" "5.1.8" + +"import-fresh@^2.0.0": + "integrity" "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=" + "resolved" "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "caller-path" "^2.0.0" + "resolve-from" "^3.0.0" + +"import-fresh@^3.0.0", "import-fresh@^3.2.1": + "integrity" "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==" + "resolved" "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz" + "version" "3.2.1" + dependencies: + "parent-module" "^1.0.0" + "resolve-from" "^4.0.0" + +"import-local@^2.0.0": + "integrity" "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==" + "resolved" "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "pkg-dir" "^3.0.0" + "resolve-cwd" "^2.0.0" + +"imurmurhash@^0.1.4": + "integrity" "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "resolved" "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + "version" "0.1.4" + +"indent-string@^2.1.0": + "integrity" "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=" + "resolved" "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "repeating" "^2.0.0" + +"indent-string@^3.0.0": + "integrity" "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + "resolved" "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz" + "version" "3.2.0" + +"indent-string@^4.0.0": + "integrity" "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + "resolved" "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + "version" "4.0.0" + +"infer-owner@^1.0.3", "infer-owner@^1.0.4": + "integrity" "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + "resolved" "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz" + "version" "1.0.4" + +"inflight@^1.0.4": + "integrity" "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + "version" "1.0.6" + dependencies: + "once" "^1.3.0" + "wrappy" "1" + +"inherits@^2.0.1", "inherits@^2.0.3", "inherits@^2.0.4", "inherits@~2.0.3", "inherits@2": + "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + "version" "2.0.4" + +"ini@^1.3.2", "ini@^1.3.4": + "integrity" "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "resolved" "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + "version" "1.3.8" + +"init-package-json@^1.10.3": + "integrity" "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==" + "resolved" "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz" + "version" "1.10.3" + dependencies: + "glob" "^7.1.1" + "npm-package-arg" "^4.0.0 || ^5.0.0 || ^6.0.0" + "promzard" "^0.3.0" + "read" "~1.0.1" + "read-package-json" "1 || 2" + "semver" "2.x || 3.x || 4 || 5" + "validate-npm-package-license" "^3.0.1" + "validate-npm-package-name" "^3.0.0" + +"inquirer@^6.2.0": + "integrity" "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==" + "resolved" "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz" + "version" "6.5.2" + dependencies: + "ansi-escapes" "^3.2.0" + "chalk" "^2.4.2" + "cli-cursor" "^2.1.0" + "cli-width" "^2.0.0" + "external-editor" "^3.0.3" + "figures" "^2.0.0" + "lodash" "^4.17.12" + "mute-stream" "0.0.7" + "run-async" "^2.2.0" + "rxjs" "^6.4.0" + "string-width" "^2.1.0" + "strip-ansi" "^5.1.0" + "through" "^2.3.6" + +"ip@1.1.5": + "integrity" "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "resolved" "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz" + "version" "1.1.5" + +"is-accessor-descriptor@^0.1.6": + "integrity" "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=" + "resolved" "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz" + "version" "0.1.6" + dependencies: + "kind-of" "^3.0.2" + +"is-accessor-descriptor@^1.0.0": + "integrity" "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==" + "resolved" "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "kind-of" "^6.0.0" + +"is-arrayish@^0.2.1": + "integrity" "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "resolved" "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + "version" "0.2.1" + +"is-binary-path@~2.1.0": + "integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==" + "resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "binary-extensions" "^2.0.0" + +"is-buffer@^1.1.5": + "integrity" "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "resolved" "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + "version" "1.1.6" + +"is-buffer@~2.0.3": + "integrity" "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + "resolved" "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz" + "version" "2.0.4" + +"is-callable@^1.1.4", "is-callable@^1.2.2": + "integrity" "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz" + "version" "1.2.2" + +"is-ci@^2.0.0": + "integrity" "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==" + "resolved" "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "ci-info" "^2.0.0" + +"is-data-descriptor@^0.1.4": + "integrity" "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=" + "resolved" "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz" + "version" "0.1.4" + dependencies: + "kind-of" "^3.0.2" + +"is-data-descriptor@^1.0.0": + "integrity" "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==" + "resolved" "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "kind-of" "^6.0.0" + +"is-date-object@^1.0.1": + "integrity" "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "resolved" "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz" + "version" "1.0.2" + +"is-descriptor@^0.1.0": + "integrity" "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==" + "resolved" "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz" + "version" "0.1.6" + dependencies: + "is-accessor-descriptor" "^0.1.6" + "is-data-descriptor" "^0.1.4" + "kind-of" "^5.0.0" + +"is-descriptor@^1.0.0", "is-descriptor@^1.0.2": + "integrity" "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==" + "resolved" "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "is-accessor-descriptor" "^1.0.0" + "is-data-descriptor" "^1.0.0" + "kind-of" "^6.0.2" + +"is-directory@^0.3.1": + "integrity" "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + "resolved" "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz" + "version" "0.3.1" + +"is-extendable@^0.1.0", "is-extendable@^0.1.1": + "integrity" "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "resolved" "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + "version" "0.1.1" + +"is-extendable@^0.1.1": + "integrity" "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "resolved" "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + "version" "0.1.1" + +"is-extendable@^1.0.1": + "integrity" "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==" + "resolved" "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "is-plain-object" "^2.0.4" + +"is-extglob@^2.1.0", "is-extglob@^2.1.1": + "integrity" "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + "version" "2.1.1" + +"is-finite@^1.0.0": + "integrity" "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + "resolved" "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz" + "version" "1.1.0" + +"is-fullwidth-code-point@^1.0.0": + "integrity" "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=" + "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "number-is-nan" "^1.0.0" + +"is-fullwidth-code-point@^2.0.0": + "integrity" "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + "version" "2.0.0" + +"is-glob@^3.1.0": + "integrity" "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=" + "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "is-extglob" "^2.1.0" + +"is-glob@^4.0.0", "is-glob@^4.0.1", "is-glob@~4.0.1": + "integrity" "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==" + "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" + "version" "4.0.1" + dependencies: + "is-extglob" "^2.1.1" + +"is-negative-zero@^2.0.0": + "integrity" "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + "resolved" "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz" + "version" "2.0.0" + +"is-number@^3.0.0": + "integrity" "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=" + "resolved" "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "kind-of" "^3.0.2" + +"is-number@^7.0.0": + "integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + "version" "7.0.0" + +"is-obj@^1.0.0": + "integrity" "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + "resolved" "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" + "version" "1.0.1" + +"is-obj@^2.0.0": + "integrity" "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + "resolved" "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" + "version" "2.0.0" + +"is-plain-obj@^1.0.0", "is-plain-obj@^1.1.0": + "integrity" "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "resolved" "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + "version" "1.1.0" + +"is-plain-object@^2.0.3": + "integrity" "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==" + "resolved" "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" + "version" "2.0.4" + dependencies: + "isobject" "^3.0.1" + +"is-plain-object@^2.0.4": + "integrity" "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==" + "resolved" "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" + "version" "2.0.4" + dependencies: + "isobject" "^3.0.1" + +"is-plain-object@^5.0.0": + "integrity" "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + "resolved" "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" + "version" "5.0.0" + +"is-regex@^1.1.1": + "integrity" "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==" + "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "has-symbols" "^1.0.1" + +"is-ssh@^1.3.0": + "integrity" "sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ==" + "resolved" "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "protocols" "^1.1.0" + +"is-stream@^1.1.0": + "integrity" "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "resolved" "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" + "version" "1.1.0" + +"is-symbol@^1.0.2": + "integrity" "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==" + "resolved" "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "has-symbols" "^1.0.1" + +"is-text-path@^1.0.1": + "integrity" "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=" + "resolved" "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "text-extensions" "^1.0.0" + +"is-typedarray@~1.0.0": + "integrity" "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "resolved" "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + "version" "1.0.0" + +"is-utf8@^0.2.0": + "integrity" "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + "resolved" "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" + "version" "0.2.1" + +"is-windows@^1.0.0", "is-windows@^1.0.2": + "integrity" "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + "resolved" "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" + "version" "1.0.2" + +"isarray@~1.0.0", "isarray@1.0.0": + "integrity" "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "resolved" "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + "version" "1.0.0" + +"isexe@^2.0.0": + "integrity" "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + "version" "2.0.0" + +"isobject@^2.0.0": + "integrity" "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=" + "resolved" "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "isarray" "1.0.0" + +"isobject@^3.0.0", "isobject@^3.0.1": + "integrity" "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "resolved" "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + "version" "3.0.1" + +"isstream@~0.1.2": + "integrity" "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "resolved" "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + "version" "0.1.2" + +"istanbul@^0.4.5": + "integrity" "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=" + "resolved" "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz" + "version" "0.4.5" + dependencies: + "abbrev" "1.0.x" + "async" "1.x" + "escodegen" "1.8.x" + "esprima" "2.7.x" + "glob" "^5.0.15" + "handlebars" "^4.0.1" + "js-yaml" "3.x" + "mkdirp" "0.5.x" + "nopt" "3.x" + "once" "1.x" + "resolve" "1.1.x" + "supports-color" "^3.1.0" + "which" "^1.1.1" + "wordwrap" "^1.0.0" + +"js-tokens@^4.0.0": + "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + "version" "4.0.0" + +"js-yaml@^3.13.1", "js-yaml@3.x": + "integrity" "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz" + "version" "3.14.0" + dependencies: + "argparse" "^1.0.7" + "esprima" "^4.0.0" + +"js-yaml@3.13.1": + "integrity" "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz" + "version" "3.13.1" + dependencies: + "argparse" "^1.0.7" + "esprima" "^4.0.0" + +"jsbn@~0.1.0": + "integrity" "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "resolved" "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + "version" "0.1.1" + +"json-parse-better-errors@^1.0.0", "json-parse-better-errors@^1.0.1": + "integrity" "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "resolved" "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" + "version" "1.0.2" + +"json-parse-even-better-errors@^2.3.0": + "integrity" "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "resolved" "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + "version" "2.3.1" + +"json-schema-traverse@^0.4.1": + "integrity" "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + "version" "0.4.1" + +"json-schema@0.2.3": + "integrity" "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "resolved" "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" + "version" "0.2.3" + +"json-stable-stringify-without-jsonify@^1.0.1": + "integrity" "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + "resolved" "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + "version" "1.0.1" + +"json-stringify-safe@^5.0.1", "json-stringify-safe@~5.0.1": + "integrity" "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "resolved" "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + "version" "5.0.1" + +"jsonfile@^4.0.0": + "integrity" "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=" + "resolved" "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" + "version" "4.0.0" + optionalDependencies: + "graceful-fs" "^4.1.6" + +"jsonparse@^1.2.0": + "integrity" "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + "resolved" "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" + "version" "1.3.1" + +"jsonparse@0.0.5": + "integrity" "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" + "resolved" "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz" + "version" "0.0.5" + +"JSONStream@^1.0.4", "JSONStream@^1.3.4": + "integrity" "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==" + "resolved" "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + "version" "1.3.5" + dependencies: + "jsonparse" "^1.2.0" + "through" ">=2.2.7 <3" + +"JSONStream@~0.7.1": + "integrity" "sha1-c0KQ5BUR7qfCz+FR+/mlY6l7l4Y=" + "resolved" "https://registry.npmjs.org/JSONStream/-/JSONStream-0.7.4.tgz" + "version" "0.7.4" + dependencies: + "jsonparse" "0.0.5" + "through" ">=2.2.7 <3" + +"jsprim@^1.2.2": + "integrity" "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=" + "resolved" "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz" + "version" "1.4.1" + dependencies: + "assert-plus" "1.0.0" + "extsprintf" "1.3.0" + "json-schema" "0.2.3" + "verror" "1.10.0" + +"kind-of@^3.0.2", "kind-of@^3.0.3": + "integrity" "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + "resolved" "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" + "version" "3.2.2" + dependencies: + "is-buffer" "^1.1.5" + +"kind-of@^3.2.0": + "integrity" "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + "resolved" "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" + "version" "3.2.2" + dependencies: + "is-buffer" "^1.1.5" + +"kind-of@^4.0.0": + "integrity" "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=" + "resolved" "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "is-buffer" "^1.1.5" + +"kind-of@^5.0.0": + "integrity" "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + "resolved" "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz" + "version" "5.1.0" + +"kind-of@^6.0.0", "kind-of@^6.0.2", "kind-of@^6.0.3": + "integrity" "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + "resolved" "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + "version" "6.0.3" + +"lcov-parse@^1.0.0": + "integrity" "sha1-6w1GtUER68VhrLTECO+TY73I9+A=" + "resolved" "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz" + "version" "1.0.0" + +"lerna@^3.19.0": + "integrity" "sha512-vk1lfVRFm+UuEFA7wkLKeSF7Iz13W+N/vFd48aW2yuS7Kv0RbNm2/qcDPV863056LMfkRlsEe+QYOw3palj5Lg==" + "resolved" "https://registry.npmjs.org/lerna/-/lerna-3.22.1.tgz" + "version" "3.22.1" + dependencies: + "@lerna/add" "3.21.0" + "@lerna/bootstrap" "3.21.0" + "@lerna/changed" "3.21.0" + "@lerna/clean" "3.21.0" + "@lerna/cli" "3.18.5" + "@lerna/create" "3.22.0" + "@lerna/diff" "3.21.0" + "@lerna/exec" "3.21.0" + "@lerna/import" "3.22.0" + "@lerna/info" "3.21.0" + "@lerna/init" "3.21.0" + "@lerna/link" "3.21.0" + "@lerna/list" "3.21.0" + "@lerna/publish" "3.22.1" + "@lerna/run" "3.21.0" + "@lerna/version" "3.22.1" + "import-local" "^2.0.0" + "npmlog" "^4.1.2" + +"levn@^0.4.1": + "integrity" "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==" + "resolved" "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + "version" "0.4.1" + dependencies: + "prelude-ls" "^1.2.1" + "type-check" "~0.4.0" + +"levn@~0.3.0": + "integrity" "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=" + "resolved" "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" + "version" "0.3.0" + dependencies: + "prelude-ls" "~1.1.2" + "type-check" "~0.3.2" + +"lines-and-columns@^1.1.6": + "integrity" "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + "resolved" "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz" + "version" "1.1.6" + +"load-json-file@^1.0.0": + "integrity" "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=" + "resolved" "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "graceful-fs" "^4.1.2" + "parse-json" "^2.2.0" + "pify" "^2.0.0" + "pinkie-promise" "^2.0.0" + "strip-bom" "^2.0.0" + +"load-json-file@^4.0.0": + "integrity" "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=" + "resolved" "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "graceful-fs" "^4.1.2" + "parse-json" "^4.0.0" + "pify" "^3.0.0" + "strip-bom" "^3.0.0" + +"load-json-file@^5.3.0": + "integrity" "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==" + "resolved" "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz" + "version" "5.3.0" + dependencies: + "graceful-fs" "^4.1.15" + "parse-json" "^4.0.0" + "pify" "^4.0.1" + "strip-bom" "^3.0.0" + "type-fest" "^0.3.0" + +"locate-path@^2.0.0": + "integrity" "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "p-locate" "^2.0.0" + "path-exists" "^3.0.0" + +"locate-path@^3.0.0": + "integrity" "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "p-locate" "^3.0.0" + "path-exists" "^3.0.0" + +"locate-path@^5.0.0": + "integrity" "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "p-locate" "^4.1.0" + +"lodash._reinterpolate@^3.0.0": + "integrity" "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + "resolved" "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" + "version" "3.0.0" + +"lodash.clonedeep@^4.5.0": + "integrity" "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "resolved" "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" + "version" "4.5.0" + +"lodash.get@^4.4.2": + "integrity" "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + "resolved" "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" + "version" "4.4.2" + +"lodash.ismatch@^4.4.0": + "integrity" "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=" + "resolved" "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz" + "version" "4.4.0" + +"lodash.set@^4.3.2": + "integrity" "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "resolved" "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz" + "version" "4.3.2" + +"lodash.sortby@^4.7.0": + "integrity" "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + "resolved" "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" + "version" "4.7.0" + +"lodash.template@^4.0.2", "lodash.template@^4.5.0": + "integrity" "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==" + "resolved" "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz" + "version" "4.5.0" + dependencies: + "lodash._reinterpolate" "^3.0.0" + "lodash.templatesettings" "^4.0.0" + +"lodash.templatesettings@^4.0.0": + "integrity" "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==" + "resolved" "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "lodash._reinterpolate" "^3.0.0" + +"lodash.uniq@^4.5.0": + "integrity" "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + "resolved" "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" + "version" "4.5.0" + +"lodash@^4.17.11", "lodash@^4.17.12", "lodash@^4.17.14", "lodash@^4.17.15", "lodash@^4.17.19", "lodash@^4.2.1": + "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + "version" "4.17.21" + +"log-driver@^1.2.7": + "integrity" "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" + "resolved" "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz" + "version" "1.2.7" + +"log-symbols@3.0.0": + "integrity" "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==" + "resolved" "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "chalk" "^2.4.2" + +"loud-rejection@^1.0.0": + "integrity" "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=" + "resolved" "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz" + "version" "1.6.0" + dependencies: + "currently-unhandled" "^0.4.1" + "signal-exit" "^3.0.0" + +"lru-cache@^5.1.1": + "integrity" "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==" + "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + "version" "5.1.1" + dependencies: + "yallist" "^3.0.2" + +"macgyver@~1.10": + "integrity" "sha1-sJ0VmdizbtWxb1lYlRXZ0UvC/Yg=" + "resolved" "https://registry.npmjs.org/macgyver/-/macgyver-1.10.1.tgz" + "version" "1.10.1" + +"macos-release@^2.2.0": + "integrity" "sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg==" + "resolved" "https://registry.npmjs.org/macos-release/-/macos-release-2.4.1.tgz" + "version" "2.4.1" + +"make-dir@^1.0.0": + "integrity" "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==" + "resolved" "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz" + "version" "1.3.0" + dependencies: + "pify" "^3.0.0" + +"make-dir@^2.1.0": + "integrity" "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==" + "resolved" "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "pify" "^4.0.1" + "semver" "^5.6.0" + +"make-error@^1.1.1": + "integrity" "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "resolved" "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + "version" "1.3.6" + +"make-fetch-happen@^5.0.0": + "integrity" "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==" + "resolved" "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz" + "version" "5.0.2" + dependencies: + "agentkeepalive" "^3.4.1" + "cacache" "^12.0.0" + "http-cache-semantics" "^3.8.1" + "http-proxy-agent" "^2.1.0" + "https-proxy-agent" "^2.2.3" + "lru-cache" "^5.1.1" + "mississippi" "^3.0.0" + "node-fetch-npm" "^2.0.2" + "promise-retry" "^1.1.1" + "socks-proxy-agent" "^4.0.0" + "ssri" "^6.0.0" + +"map-cache@^0.2.2": + "integrity" "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + "resolved" "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz" + "version" "0.2.2" + +"map-obj@^1.0.0", "map-obj@^1.0.1": + "integrity" "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + "resolved" "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + "version" "1.0.1" + +"map-obj@^2.0.0": + "integrity" "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + "resolved" "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz" + "version" "2.0.0" + +"map-obj@^4.0.0": + "integrity" "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==" + "resolved" "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz" + "version" "4.1.0" + +"map-visit@^1.0.0": + "integrity" "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=" + "resolved" "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "object-visit" "^1.0.0" + +"meow@^3.3.0": + "integrity" "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=" + "resolved" "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz" + "version" "3.7.0" + dependencies: + "camelcase-keys" "^2.0.0" + "decamelize" "^1.1.2" + "loud-rejection" "^1.0.0" + "map-obj" "^1.0.1" + "minimist" "^1.1.3" + "normalize-package-data" "^2.3.4" + "object-assign" "^4.0.1" + "read-pkg-up" "^1.0.1" + "redent" "^1.0.0" + "trim-newlines" "^1.0.0" + +"meow@^4.0.0": + "integrity" "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==" + "resolved" "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz" + "version" "4.0.1" + dependencies: + "camelcase-keys" "^4.0.0" + "decamelize-keys" "^1.0.0" + "loud-rejection" "^1.0.0" + "minimist" "^1.1.3" + "minimist-options" "^3.0.1" + "normalize-package-data" "^2.3.4" + "read-pkg-up" "^3.0.0" + "redent" "^2.0.0" + "trim-newlines" "^2.0.0" + +"meow@^7.0.0": + "integrity" "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==" + "resolved" "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz" + "version" "7.1.1" + dependencies: + "@types/minimist" "^1.2.0" + "camelcase-keys" "^6.2.2" + "decamelize-keys" "^1.1.0" + "hard-rejection" "^2.1.0" + "minimist-options" "4.1.0" + "normalize-package-data" "^2.5.0" + "read-pkg-up" "^7.0.1" + "redent" "^3.0.0" + "trim-newlines" "^3.0.0" + "type-fest" "^0.13.1" + "yargs-parser" "^18.1.3" + +"merge2@^1.2.3", "merge2@^1.3.0": + "integrity" "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + "resolved" "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + "version" "1.4.1" + +"micromatch@^3.1.10": + "integrity" "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==" + "resolved" "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz" + "version" "3.1.10" + dependencies: + "arr-diff" "^4.0.0" + "array-unique" "^0.3.2" + "braces" "^2.3.1" + "define-property" "^2.0.2" + "extend-shallow" "^3.0.2" + "extglob" "^2.0.4" + "fragment-cache" "^0.2.1" + "kind-of" "^6.0.2" + "nanomatch" "^1.2.9" + "object.pick" "^1.3.0" + "regex-not" "^1.0.0" + "snapdragon" "^0.8.1" + "to-regex" "^3.0.2" + +"micromatch@^4.0.2": + "integrity" "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==" + "resolved" "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz" + "version" "4.0.2" + dependencies: + "braces" "^3.0.1" + "picomatch" "^2.0.5" + +"mime-db@1.44.0": + "integrity" "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz" + "version" "1.44.0" + +"mime-types@^2.1.12", "mime-types@~2.1.19": + "integrity" "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==" + "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz" + "version" "2.1.27" + dependencies: + "mime-db" "1.44.0" + +"mimic-fn@^1.0.0": + "integrity" "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" + "version" "1.2.0" + +"min-indent@^1.0.0": + "integrity" "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + "resolved" "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" + "version" "1.0.1" + +"minimatch@^3.0.4", "minimatch@2 || 3", "minimatch@3.0.4": + "integrity" "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + "version" "3.0.4" + dependencies: + "brace-expansion" "^1.1.7" + +"minimist-options@^3.0.1": + "integrity" "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==" + "resolved" "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "arrify" "^1.0.1" + "is-plain-obj" "^1.1.0" + +"minimist-options@4.1.0": + "integrity" "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==" + "resolved" "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "arrify" "^1.0.1" + "is-plain-obj" "^1.1.0" + "kind-of" "^6.0.3" + +"minimist@^1.1.3", "minimist@^1.2.0", "minimist@^1.2.5": + "integrity" "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" + "version" "1.2.5" + +"minipass@^2.3.5", "minipass@^2.6.0", "minipass@^2.8.6", "minipass@^2.9.0": + "integrity" "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==" + "resolved" "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz" + "version" "2.9.0" + dependencies: + "safe-buffer" "^5.1.2" + "yallist" "^3.0.0" + +"minizlib@^1.2.1": + "integrity" "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==" + "resolved" "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz" + "version" "1.3.3" + dependencies: + "minipass" "^2.9.0" + +"mississippi@^3.0.0": + "integrity" "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==" + "resolved" "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "concat-stream" "^1.5.0" + "duplexify" "^3.4.2" + "end-of-stream" "^1.1.0" + "flush-write-stream" "^1.0.0" + "from2" "^2.1.0" + "parallel-transform" "^1.1.0" + "pump" "^3.0.0" + "pumpify" "^1.3.3" + "stream-each" "^1.1.0" + "through2" "^2.0.0" + +"mixin-deep@^1.2.0": + "integrity" "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==" + "resolved" "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "for-in" "^1.0.2" + "is-extendable" "^1.0.1" + +"mkdirp-promise@^5.0.1": + "integrity" "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=" + "resolved" "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "mkdirp" "*" + +"mkdirp@*", "mkdirp@^0.5.0", "mkdirp@^0.5.1", "mkdirp@0.5.5", "mkdirp@0.5.x": + "integrity" "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==" + "resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" + "version" "0.5.5" + dependencies: + "minimist" "^1.2.5" + +"mocha@^7.1.2": + "integrity" "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==" + "resolved" "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "ansi-colors" "3.2.3" + "browser-stdout" "1.3.1" + "chokidar" "3.3.0" + "debug" "3.2.6" + "diff" "3.5.0" + "escape-string-regexp" "1.0.5" + "find-up" "3.0.0" + "glob" "7.1.3" + "growl" "1.10.5" + "he" "1.2.0" + "js-yaml" "3.13.1" + "log-symbols" "3.0.0" + "minimatch" "3.0.4" + "mkdirp" "0.5.5" + "ms" "2.1.1" + "node-environment-flags" "1.0.6" + "object.assign" "4.1.0" + "strip-json-comments" "2.0.1" + "supports-color" "6.0.0" + "which" "1.3.1" + "wide-align" "1.1.3" + "yargs" "13.3.2" + "yargs-parser" "13.1.2" + "yargs-unparser" "1.6.0" + +"modify-values@^1.0.0": + "integrity" "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==" + "resolved" "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz" + "version" "1.0.1" + +"move-concurrently@^1.0.1": + "integrity" "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=" + "resolved" "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "aproba" "^1.1.1" + "copy-concurrently" "^1.0.0" + "fs-write-stream-atomic" "^1.0.8" + "mkdirp" "^0.5.1" + "rimraf" "^2.5.4" + "run-queue" "^1.0.3" + +"ms@^2.0.0", "ms@^2.1.1", "ms@2.1.2": + "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + "version" "2.1.2" + +"ms@2.0.0": + "integrity" "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + "version" "2.0.0" + +"ms@2.1.1": + "integrity" "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + "version" "2.1.1" + +"multimatch@^3.0.0": + "integrity" "sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==" + "resolved" "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "array-differ" "^2.0.3" + "array-union" "^1.0.2" + "arrify" "^1.0.1" + "minimatch" "^3.0.4" + +"mute-stream@~0.0.4", "mute-stream@0.0.7": + "integrity" "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + "resolved" "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz" + "version" "0.0.7" + +"mz@^2.5.0": + "integrity" "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==" + "resolved" "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + "version" "2.7.0" + dependencies: + "any-promise" "^1.0.0" + "object-assign" "^4.0.1" + "thenify-all" "^1.0.0" + +"nanomatch@^1.2.9": + "integrity" "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==" + "resolved" "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz" + "version" "1.2.13" + dependencies: + "arr-diff" "^4.0.0" + "array-unique" "^0.3.2" + "define-property" "^2.0.2" + "extend-shallow" "^3.0.2" + "fragment-cache" "^0.2.1" + "is-windows" "^1.0.2" + "kind-of" "^6.0.2" + "object.pick" "^1.3.0" + "regex-not" "^1.0.0" + "snapdragon" "^0.8.1" + "to-regex" "^3.0.1" + +"natural-compare@^1.4.0": + "integrity" "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + "resolved" "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + "version" "1.4.0" + +"neo-async@^2.6.0": + "integrity" "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + "resolved" "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + "version" "2.6.2" + +"nice-try@^1.0.4": + "integrity" "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "resolved" "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" + "version" "1.0.5" + +"node-environment-flags@1.0.6": + "integrity" "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==" + "resolved" "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz" + "version" "1.0.6" + dependencies: + "object.getownpropertydescriptors" "^2.0.3" + "semver" "^5.7.0" + +"node-fetch-npm@^2.0.2": + "integrity" "sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==" + "resolved" "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz" + "version" "2.0.4" + dependencies: + "encoding" "^0.1.11" + "json-parse-better-errors" "^1.0.0" + "safe-buffer" "^5.1.1" + +"node-fetch@^2.5.0", "node-fetch@^2.6.1": + "integrity" "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz" + "version" "2.6.1" + +"node-gyp@^5.0.2": + "integrity" "sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==" + "resolved" "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz" + "version" "5.1.1" + dependencies: + "env-paths" "^2.2.0" + "glob" "^7.1.4" + "graceful-fs" "^4.2.2" + "mkdirp" "^0.5.1" + "nopt" "^4.0.1" + "npmlog" "^4.1.2" + "request" "^2.88.0" + "rimraf" "^2.6.3" + "semver" "^5.7.1" + "tar" "^4.4.12" + "which" "^1.3.1" + +"nopt@^4.0.1": + "integrity" "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==" + "resolved" "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz" + "version" "4.0.3" + dependencies: + "abbrev" "1" + "osenv" "^0.1.4" + +"nopt@3.x": + "integrity" "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=" + "resolved" "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" + "version" "3.0.6" + dependencies: + "abbrev" "1" + +"normalize-package-data@^2.0.0", "normalize-package-data@^2.3.0", "normalize-package-data@^2.3.2", "normalize-package-data@^2.3.4", "normalize-package-data@^2.3.5", "normalize-package-data@^2.4.0", "normalize-package-data@^2.5.0": + "integrity" "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==" + "resolved" "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" + "version" "2.5.0" + dependencies: + "hosted-git-info" "^2.1.4" + "resolve" "^1.10.0" + "semver" "2 || 3 || 4 || 5" + "validate-npm-package-license" "^3.0.1" + +"normalize-path@^3.0.0", "normalize-path@~3.0.0": + "integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + "version" "3.0.0" + +"normalize-url@^3.3.0": + "integrity" "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + "resolved" "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz" + "version" "3.3.0" + +"npm-bundled@^1.0.1": + "integrity" "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==" + "resolved" "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "npm-normalize-package-bin" "^1.0.1" + +"npm-lifecycle@^3.1.2": + "integrity" "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==" + "resolved" "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz" + "version" "3.1.5" + dependencies: + "byline" "^5.0.0" + "graceful-fs" "^4.1.15" + "node-gyp" "^5.0.2" + "resolve-from" "^4.0.0" + "slide" "^1.1.6" + "uid-number" "0.0.6" + "umask" "^1.1.0" + "which" "^1.3.1" + +"npm-normalize-package-bin@^1.0.0", "npm-normalize-package-bin@^1.0.1": + "integrity" "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + "resolved" "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz" + "version" "1.0.1" + +"npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", "npm-package-arg@^6.0.0", "npm-package-arg@^6.1.0": + "integrity" "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==" + "resolved" "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz" + "version" "6.1.1" + dependencies: + "hosted-git-info" "^2.7.1" + "osenv" "^0.1.5" + "semver" "^5.6.0" + "validate-npm-package-name" "^3.0.0" + +"npm-packlist@^1.4.4": + "integrity" "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==" + "resolved" "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz" + "version" "1.4.8" + dependencies: + "ignore-walk" "^3.0.1" + "npm-bundled" "^1.0.1" + "npm-normalize-package-bin" "^1.0.1" + +"npm-pick-manifest@^3.0.0": + "integrity" "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==" + "resolved" "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "figgy-pudding" "^3.5.1" + "npm-package-arg" "^6.0.0" + "semver" "^5.4.1" + +"npm-run-path@^2.0.0": + "integrity" "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=" + "resolved" "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "path-key" "^2.0.0" + +"npmlog@^4.1.2": + "integrity" "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==" + "resolved" "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz" + "version" "4.1.2" + dependencies: + "are-we-there-yet" "~1.1.2" + "console-control-strings" "~1.1.0" + "gauge" "~2.7.3" + "set-blocking" "~2.0.0" + +"number-is-nan@^1.0.0": + "integrity" "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "resolved" "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + "version" "1.0.1" + +"oauth-sign@~0.9.0": + "integrity" "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "resolved" "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" + "version" "0.9.0" + +"object-assign@^4.0.1", "object-assign@^4.1.0": + "integrity" "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "resolved" "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + "version" "4.1.1" + +"object-copy@^0.1.0": + "integrity" "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=" + "resolved" "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz" + "version" "0.1.0" + dependencies: + "copy-descriptor" "^0.1.0" + "define-property" "^0.2.5" + "kind-of" "^3.0.3" + +"object-inspect@^1.8.0": + "integrity" "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz" + "version" "1.8.0" + +"object-keys@^1.0.11", "object-keys@^1.0.12", "object-keys@^1.1.1": + "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + "version" "1.1.1" + +"object-visit@^1.0.0": + "integrity" "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=" + "resolved" "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "isobject" "^3.0.0" + +"object.assign@^4.1.1": + "integrity" "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==" + "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz" + "version" "4.1.1" + dependencies: + "define-properties" "^1.1.3" + "es-abstract" "^1.18.0-next.0" + "has-symbols" "^1.0.1" + "object-keys" "^1.1.1" + +"object.assign@4.1.0": + "integrity" "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==" + "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "define-properties" "^1.1.2" + "function-bind" "^1.1.1" + "has-symbols" "^1.0.0" + "object-keys" "^1.0.11" + +"object.getownpropertydescriptors@^2.0.3": + "integrity" "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==" + "resolved" "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "define-properties" "^1.1.3" + "es-abstract" "^1.17.0-next.1" + +"object.pick@^1.3.0": + "integrity" "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=" + "resolved" "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz" + "version" "1.3.0" + dependencies: + "isobject" "^3.0.1" + +"octokit-pagination-methods@^1.1.0": + "integrity" "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==" + "resolved" "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz" + "version" "1.1.0" + +"once@^1.3.0", "once@^1.3.1", "once@^1.4.0", "once@1.x": + "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "wrappy" "1" + +"onetime@^2.0.0": + "integrity" "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=" + "resolved" "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "mimic-fn" "^1.0.0" + +"optionator@^0.8.1": + "integrity" "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==" + "resolved" "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" + "version" "0.8.3" + dependencies: + "deep-is" "~0.1.3" + "fast-levenshtein" "~2.0.6" + "levn" "~0.3.0" + "prelude-ls" "~1.1.2" + "type-check" "~0.3.2" + "word-wrap" "~1.2.3" + +"optionator@^0.9.1": + "integrity" "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==" + "resolved" "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + "version" "0.9.1" + dependencies: + "deep-is" "^0.1.3" + "fast-levenshtein" "^2.0.6" + "levn" "^0.4.1" + "prelude-ls" "^1.2.1" + "type-check" "^0.4.0" + "word-wrap" "^1.2.3" + +"os-homedir@^1.0.0": + "integrity" "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "resolved" "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" + "version" "1.0.2" + +"os-name@^3.1.0": + "integrity" "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==" + "resolved" "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "macos-release" "^2.2.0" + "windows-release" "^3.1.0" + +"os-tmpdir@^1.0.0", "os-tmpdir@~1.0.2": + "integrity" "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "resolved" "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + "version" "1.0.2" + +"osenv@^0.1.4", "osenv@^0.1.5": + "integrity" "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==" + "resolved" "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz" + "version" "0.1.5" + dependencies: + "os-homedir" "^1.0.0" + "os-tmpdir" "^1.0.0" + +"p-finally@^1.0.0": + "integrity" "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "resolved" "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" + "version" "1.0.0" + +"p-limit@^1.1.0": + "integrity" "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" + "version" "1.3.0" + dependencies: + "p-try" "^1.0.0" + +"p-limit@^2.0.0", "p-limit@^2.2.0": + "integrity" "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + "version" "2.3.0" + dependencies: + "p-try" "^2.0.0" + +"p-locate@^2.0.0": + "integrity" "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "p-limit" "^1.1.0" + +"p-locate@^3.0.0": + "integrity" "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "p-limit" "^2.0.0" + +"p-locate@^4.1.0": + "integrity" "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "p-limit" "^2.2.0" + +"p-map-series@^1.0.0": + "integrity" "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=" + "resolved" "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "p-reduce" "^1.0.0" + +"p-map@^2.1.0": + "integrity" "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + "resolved" "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz" + "version" "2.1.0" + +"p-pipe@^1.2.0": + "integrity" "sha1-SxoROZoRUgpneQ7loMHViB1r7+k=" + "resolved" "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz" + "version" "1.2.0" + +"p-queue@^4.0.0": + "integrity" "sha512-3cRXXn3/O0o3+eVmUroJPSj/esxoEFIm0ZOno/T+NzG/VZgPOqQ8WKmlNqubSEpZmCIngEy34unkHGg83ZIBmg==" + "resolved" "https://registry.npmjs.org/p-queue/-/p-queue-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "eventemitter3" "^3.1.0" + +"p-reduce@^1.0.0": + "integrity" "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + "resolved" "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz" + "version" "1.0.0" + +"p-try@^1.0.0": + "integrity" "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "resolved" "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz" + "version" "1.0.0" + +"p-try@^2.0.0": + "integrity" "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "resolved" "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + "version" "2.2.0" + +"p-waterfall@^1.0.0": + "integrity" "sha1-ftlLPOszMngjU69qrhGqn8I1uwA=" + "resolved" "https://registry.npmjs.org/p-waterfall/-/p-waterfall-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "p-reduce" "^1.0.0" + +"packet-reader@1.0.0": + "integrity" "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + "resolved" "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz" + "version" "1.0.0" + +"parallel-transform@^1.1.0": + "integrity" "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==" + "resolved" "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz" + "version" "1.2.0" + dependencies: + "cyclist" "^1.0.1" + "inherits" "^2.0.3" + "readable-stream" "^2.1.5" + +"parent-module@^1.0.0": + "integrity" "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==" + "resolved" "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "callsites" "^3.0.0" + +"parse-github-repo-url@^1.3.0": + "integrity" "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=" + "resolved" "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz" + "version" "1.4.1" + +"parse-json@^2.2.0": + "integrity" "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=" + "resolved" "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz" + "version" "2.2.0" + dependencies: + "error-ex" "^1.2.0" + +"parse-json@^4.0.0": + "integrity" "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=" + "resolved" "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "error-ex" "^1.3.1" + "json-parse-better-errors" "^1.0.1" + +"parse-json@^5.0.0": + "integrity" "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==" + "resolved" "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz" + "version" "5.1.0" + dependencies: + "@babel/code-frame" "^7.0.0" + "error-ex" "^1.3.1" + "json-parse-even-better-errors" "^2.3.0" + "lines-and-columns" "^1.1.6" + +"parse-path@^4.0.0": + "integrity" "sha512-HSqVz6iuXSiL8C1ku5Gl1Z5cwDd9Wo0q8CoffdAghP6bz8pJa1tcMC+m4N+z6VAS8QdksnIGq1TB6EgR4vPR6w==" + "resolved" "https://registry.npmjs.org/parse-path/-/parse-path-4.0.2.tgz" + "version" "4.0.2" + dependencies: + "is-ssh" "^1.3.0" + "protocols" "^1.4.0" + +"parse-url@^5.0.0": + "integrity" "sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA==" + "resolved" "https://registry.npmjs.org/parse-url/-/parse-url-5.0.2.tgz" + "version" "5.0.2" + dependencies: + "is-ssh" "^1.3.0" + "normalize-url" "^3.3.0" + "parse-path" "^4.0.0" + "protocols" "^1.4.0" + +"pascalcase@^0.1.1": + "integrity" "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + "resolved" "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz" + "version" "0.1.1" + +"path-dirname@^1.0.0": + "integrity" "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + "resolved" "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz" + "version" "1.0.2" + +"path-exists@^2.0.0": + "integrity" "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "pinkie-promise" "^2.0.0" + +"path-exists@^3.0.0": + "integrity" "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" + "version" "3.0.0" + +"path-exists@^4.0.0": + "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + "version" "4.0.0" + +"path-is-absolute@^1.0.0": + "integrity" "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + "version" "1.0.1" + +"path-key@^2.0.0": + "integrity" "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "resolved" "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" + "version" "2.0.1" + +"path-key@^2.0.1": + "integrity" "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "resolved" "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" + "version" "2.0.1" + +"path-key@^3.1.0": + "integrity" "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "resolved" "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + "version" "3.1.1" + +"path-parse@^1.0.6": + "integrity" "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "resolved" "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz" + "version" "1.0.6" + +"path-type@^1.0.0": + "integrity" "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=" + "resolved" "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "graceful-fs" "^4.1.2" + "pify" "^2.0.0" + "pinkie-promise" "^2.0.0" + +"path-type@^3.0.0": + "integrity" "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==" + "resolved" "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "pify" "^3.0.0" + +"path-type@^4.0.0": + "integrity" "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + "resolved" "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + "version" "4.0.0" + +"pathval@^1.1.0": + "integrity" "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + "resolved" "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz" + "version" "1.1.0" + +"performance-now@^2.1.0": + "integrity" "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "resolved" "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + "version" "2.1.0" + +"pg-connection-string@^2.5.0": + "version" "2.5.0" + +"pg-connection-string@file:/Users/chenxy/node-openGauss/packages/pg-connection-string": + "resolved" "file:packages/pg-connection-string" + "version" "2.5.0" + +"pg-copy-streams@0.3.0": + "integrity" "sha1-pPvCo7eI1Onab3fOs1Qi2NcEO38=" + "resolved" "https://registry.npmjs.org/pg-copy-streams/-/pg-copy-streams-0.3.0.tgz" + "version" "0.3.0" + +"pg-cursor@^1.3.0": + "integrity" "sha1-siDxkIl2t7QNqjc8etpfyoI6sNk=" + "resolved" "https://registry.npmjs.org/pg-cursor/-/pg-cursor-1.3.0.tgz" + "version" "1.3.0" + +"pg-cursor@^2.6.0", "pg-cursor@file:/Users/chenxy/node-openGauss/packages/pg-cursor": + "resolved" "file:packages/pg-cursor" + "version" "2.6.0" + +"pg-int8@1.0.1": + "integrity" "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + "resolved" "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" + "version" "1.0.1" + +"pg-pool@^3.3.0": + "version" "3.3.0" + +"pg-pool@file:/Users/chenxy/node-openGauss/packages/pg-pool": + "resolved" "file:packages/pg-pool" + "version" "3.3.0" + +"pg-protocol@^1.5.0": + "version" "1.5.0" + +"pg-protocol@file:/Users/chenxy/node-openGauss/packages/pg-protocol": + "resolved" "file:packages/pg-protocol" + "version" "1.5.0" + +"pg-query-stream@file:/Users/chenxy/node-openGauss/packages/pg-query-stream": + "resolved" "file:packages/pg-query-stream" + "version" "4.1.0" + dependencies: + "pg-cursor" "^2.6.0" + +"pg-types@^2.1.0", "pg-types@^2.2.0": + "integrity" "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==" + "resolved" "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" + "version" "2.2.0" + dependencies: + "pg-int8" "1.0.1" + "postgres-array" "~2.0.0" + "postgres-bytea" "~1.0.0" + "postgres-date" "~1.0.4" + "postgres-interval" "^1.1.0" + +"pg@^8.6.0", "pg@>=8.0", "pg@file:/Users/chenxy/node-openGauss/packages/pg": + "resolved" "file:packages/pg" + "version" "8.6.0" + dependencies: + "buffer-writer" "2.0.0" + "packet-reader" "1.0.0" + "pg-connection-string" "^2.5.0" + "pg-pool" "^3.3.0" + "pg-protocol" "^1.5.0" + "pg-types" "^2.1.0" + "pgpass" "1.x" + +"pgpass@1.x": + "version" "1.0.4" + dependencies: + "split2" "^3.1.1" + +"picomatch@^2.0.4", "picomatch@^2.0.5", "picomatch@^2.2.1": + "integrity" "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz" + "version" "2.2.2" + +"pify@^2.0.0": + "integrity" "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "resolved" "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + "version" "2.3.0" + +"pify@^2.3.0": + "integrity" "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "resolved" "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + "version" "2.3.0" + +"pify@^3.0.0": + "integrity" "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "resolved" "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" + "version" "3.0.0" + +"pify@^4.0.1": + "integrity" "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + "resolved" "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" + "version" "4.0.1" + +"pinkie-promise@^2.0.0": + "integrity" "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" + "resolved" "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "pinkie" "^2.0.0" + +"pinkie@^2.0.0": + "integrity" "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "resolved" "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + "version" "2.0.4" + +"pkg-dir@^3.0.0": + "integrity" "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==" + "resolved" "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "find-up" "^3.0.0" + +"posix-character-classes@^0.1.0": + "integrity" "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + "resolved" "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" + "version" "0.1.1" + +"postgres-array@~2.0.0": + "integrity" "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + "resolved" "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz" + "version" "2.0.0" + +"postgres-bytea@~1.0.0": + "integrity" "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + "resolved" "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz" + "version" "1.0.0" + +"postgres-date@~1.0.4": + "integrity" "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + "resolved" "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz" + "version" "1.0.7" + +"postgres-interval@^1.1.0": + "integrity" "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==" + "resolved" "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz" + "version" "1.2.0" + dependencies: + "xtend" "^4.0.0" + +"prelude-ls@^1.2.1": + "integrity" "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + "resolved" "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + "version" "1.2.1" + +"prelude-ls@~1.1.2": + "integrity" "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "resolved" "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + "version" "1.1.2" + +"prettier-linter-helpers@^1.0.0": + "integrity" "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==" + "resolved" "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "fast-diff" "^1.1.2" + +"prettier@>=1.13.0", "prettier@2.1.2": + "integrity" "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==" + "resolved" "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz" + "version" "2.1.2" + +"process-nextick-args@~2.0.0": + "integrity" "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "resolved" "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + "version" "2.0.1" + +"progress@^2.0.0": + "integrity" "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + "resolved" "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + "version" "2.0.3" + +"promise-inflight@^1.0.1": + "integrity" "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + "resolved" "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" + "version" "1.0.1" + +"promise-retry@^1.1.1": + "integrity" "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=" + "resolved" "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "err-code" "^1.0.0" + "retry" "^0.10.0" + +"promzard@^0.3.0": + "integrity" "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=" + "resolved" "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz" + "version" "0.3.0" + dependencies: + "read" "1" + +"proto-list@~1.2.1": + "integrity" "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + "resolved" "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz" + "version" "1.2.4" + +"protocols@^1.1.0", "protocols@^1.4.0": + "integrity" "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==" + "resolved" "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz" + "version" "1.4.8" + +"protoduck@^5.0.1": + "integrity" "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==" + "resolved" "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "genfun" "^5.0.0" + +"psl@^1.1.28": + "integrity" "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + "resolved" "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" + "version" "1.8.0" + +"pump@^2.0.0": + "integrity" "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==" + "resolved" "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "end-of-stream" "^1.1.0" + "once" "^1.3.1" + +"pump@^3.0.0": + "integrity" "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==" + "resolved" "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "end-of-stream" "^1.1.0" + "once" "^1.3.1" + +"pumpify@^1.3.3": + "integrity" "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==" + "resolved" "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz" + "version" "1.5.1" + dependencies: + "duplexify" "^3.6.0" + "inherits" "^2.0.3" + "pump" "^2.0.0" + +"punycode@^2.1.0", "punycode@^2.1.1": + "integrity" "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + "version" "2.1.1" + +"q@^1.5.1": + "integrity" "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "resolved" "https://registry.npmjs.org/q/-/q-1.5.1.tgz" + "version" "1.5.1" + +"qs@~6.5.2": + "integrity" "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "resolved" "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz" + "version" "6.5.2" + +"quick-lru@^1.0.0": + "integrity" "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + "resolved" "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz" + "version" "1.1.0" + +"quick-lru@^4.0.1": + "integrity" "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" + "resolved" "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" + "version" "4.0.1" + +"read-cmd-shim@^1.0.1": + "integrity" "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==" + "resolved" "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz" + "version" "1.0.5" + dependencies: + "graceful-fs" "^4.1.2" + +"read-package-json@^2.0.0", "read-package-json@^2.0.13", "read-package-json@1 || 2": + "integrity" "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==" + "resolved" "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz" + "version" "2.1.2" + dependencies: + "glob" "^7.1.1" + "json-parse-even-better-errors" "^2.3.0" + "normalize-package-data" "^2.0.0" + "npm-normalize-package-bin" "^1.0.0" + +"read-package-tree@^5.1.6": + "integrity" "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==" + "resolved" "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz" + "version" "5.3.1" + dependencies: + "read-package-json" "^2.0.0" + "readdir-scoped-modules" "^1.0.0" + "util-promisify" "^2.1.0" + +"read-pkg-up@^1.0.1": + "integrity" "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=" + "resolved" "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "find-up" "^1.0.0" + "read-pkg" "^1.0.0" + +"read-pkg-up@^3.0.0": + "integrity" "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=" + "resolved" "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "find-up" "^2.0.0" + "read-pkg" "^3.0.0" + +"read-pkg-up@^7.0.1": + "integrity" "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==" + "resolved" "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" + "version" "7.0.1" + dependencies: + "find-up" "^4.1.0" + "read-pkg" "^5.2.0" + "type-fest" "^0.8.1" + +"read-pkg@^1.0.0": + "integrity" "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=" + "resolved" "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "load-json-file" "^1.0.0" + "normalize-package-data" "^2.3.2" + "path-type" "^1.0.0" + +"read-pkg@^3.0.0": + "integrity" "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=" + "resolved" "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "load-json-file" "^4.0.0" + "normalize-package-data" "^2.3.2" + "path-type" "^3.0.0" + +"read-pkg@^5.2.0": + "integrity" "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==" + "resolved" "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" + "version" "5.2.0" + dependencies: + "@types/normalize-package-data" "^2.4.0" + "normalize-package-data" "^2.5.0" + "parse-json" "^5.0.0" + "type-fest" "^0.6.0" + +"read@~1.0.1", "read@1": + "integrity" "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=" + "resolved" "https://registry.npmjs.org/read/-/read-1.0.7.tgz" + "version" "1.0.7" + dependencies: + "mute-stream" "~0.0.4" + +"readable-stream@^2.0.0": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" + dependencies: + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" + +"readable-stream@^2.0.6": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" + dependencies: + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" + +"readable-stream@^2.1.5": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" + dependencies: + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" + +"readable-stream@^2.2.2", "readable-stream@~2.3.6": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" + dependencies: + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" + +"readable-stream@^2.3.6": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" + dependencies: + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" + +"readable-stream@^3.0.0": + "version" "3.6.0" + dependencies: + "inherits" "^2.0.3" + "string_decoder" "^1.1.1" + "util-deprecate" "^1.0.1" + +"readable-stream@^3.0.2", "readable-stream@2 || 3": + "integrity" "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + "version" "3.6.0" + dependencies: + "inherits" "^2.0.3" + "string_decoder" "^1.1.1" + "util-deprecate" "^1.0.1" + +"readable-stream@1 || 2": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" + dependencies: + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" + +"readdir-scoped-modules@^1.0.0": + "integrity" "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==" + "resolved" "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "debuglog" "^1.0.1" + "dezalgo" "^1.0.0" + "graceful-fs" "^4.1.2" + "once" "^1.3.0" + +"readdirp@~3.2.0": + "integrity" "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==" + "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz" + "version" "3.2.0" + dependencies: + "picomatch" "^2.0.4" + +"redent@^1.0.0": + "integrity" "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=" + "resolved" "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "indent-string" "^2.1.0" + "strip-indent" "^1.0.1" + +"redent@^2.0.0": + "integrity" "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=" + "resolved" "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "indent-string" "^3.0.0" + "strip-indent" "^2.0.0" + +"redent@^3.0.0": + "integrity" "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==" + "resolved" "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "indent-string" "^4.0.0" + "strip-indent" "^3.0.0" + +"regex-not@^1.0.0", "regex-not@^1.0.2": + "integrity" "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==" + "resolved" "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "extend-shallow" "^3.0.2" + "safe-regex" "^1.1.0" + +"regexpp@^3.0.0", "regexpp@^3.1.0": + "integrity" "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==" + "resolved" "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz" + "version" "3.1.0" + +"render@0.1": + "integrity" "sha1-z7M6NOJgaFkdQYRp4j2Mxc4c7/U=" + "resolved" "https://registry.npmjs.org/render/-/render-0.1.4.tgz" + "version" "0.1.4" + dependencies: + "traverser" "0.0.x" + +"repeat-element@^1.1.2": + "integrity" "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "resolved" "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz" + "version" "1.1.3" + +"repeat-string@^1.6.1": + "integrity" "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "resolved" "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + "version" "1.6.1" + +"repeating@^2.0.0": + "integrity" "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=" + "resolved" "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "is-finite" "^1.0.0" + +"request@^2.88.0", "request@^2.88.2": + "integrity" "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==" + "resolved" "https://registry.npmjs.org/request/-/request-2.88.2.tgz" + "version" "2.88.2" + dependencies: + "aws-sign2" "~0.7.0" + "aws4" "^1.8.0" + "caseless" "~0.12.0" + "combined-stream" "~1.0.6" + "extend" "~3.0.2" + "forever-agent" "~0.6.1" + "form-data" "~2.3.2" + "har-validator" "~5.1.3" + "http-signature" "~1.2.0" + "is-typedarray" "~1.0.0" + "isstream" "~0.1.2" + "json-stringify-safe" "~5.0.1" + "mime-types" "~2.1.19" + "oauth-sign" "~0.9.0" + "performance-now" "^2.1.0" + "qs" "~6.5.2" + "safe-buffer" "^5.1.2" + "tough-cookie" "~2.5.0" + "tunnel-agent" "^0.6.0" + "uuid" "^3.3.2" + +"require-directory@^2.1.1": + "integrity" "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + "version" "2.1.1" + +"require-main-filename@^2.0.0": + "integrity" "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "resolved" "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" + "version" "2.0.0" + +"resolve-cwd@^2.0.0": + "integrity" "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=" + "resolved" "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "resolve-from" "^3.0.0" + +"resolve-from@^3.0.0": + "integrity" "sha1-six699nWiBvItuZTM17rywoYh0g=" + "resolved" "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz" + "version" "3.0.0" + +"resolve-from@^4.0.0": + "integrity" "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "resolved" "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + "version" "4.0.0" + +"resolve-url@^0.2.1": + "integrity" "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "resolved" "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz" + "version" "0.2.1" + +"resolve@^1.10.0", "resolve@^1.10.1": + "integrity" "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==" + "resolved" "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz" + "version" "1.17.0" + dependencies: + "path-parse" "^1.0.6" + +"resolve@1.1.x": + "integrity" "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + "resolved" "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + "version" "1.1.7" + +"restore-cursor@^2.0.0": + "integrity" "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=" + "resolved" "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "onetime" "^2.0.0" + "signal-exit" "^3.0.2" + +"ret@~0.1.10": + "integrity" "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + "resolved" "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" + "version" "0.1.15" + +"retry@^0.10.0": + "integrity" "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + "resolved" "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz" + "version" "0.10.1" + +"reusify@^1.0.4": + "integrity" "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "resolved" "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + "version" "1.0.4" + +"rimraf@^2.5.4", "rimraf@^2.6.2", "rimraf@^2.6.3", "rimraf@2.6.3": + "integrity" "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==" + "resolved" "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz" + "version" "2.6.3" + dependencies: + "glob" "^7.1.3" + +"run-async@^2.2.0": + "integrity" "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + "resolved" "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" + "version" "2.4.1" + +"run-parallel@^1.1.9": + "integrity" "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + "resolved" "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz" + "version" "1.1.9" + +"run-queue@^1.0.0", "run-queue@^1.0.3": + "integrity" "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=" + "resolved" "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "aproba" "^1.1.1" + +"rxjs@^6.4.0": + "integrity" "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==" + "resolved" "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz" + "version" "6.6.3" + dependencies: + "tslib" "^1.9.0" + +"safe-buffer@^5.0.1", "safe-buffer@^5.1.1", "safe-buffer@^5.1.2", "safe-buffer@^5.2.0", "safe-buffer@~5.2.0": + "integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + "version" "5.2.1" + +"safe-buffer@~5.1.0", "safe-buffer@~5.1.1": + "integrity" "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + "version" "5.1.2" + +"safe-regex@^1.1.0": + "integrity" "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=" + "resolved" "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "ret" "~0.1.10" + +"safer-buffer@^2.0.2", "safer-buffer@^2.1.0", "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", "safer-buffer@~2.1.0": + "integrity" "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "resolved" "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + "version" "2.1.2" + +"semver@^5.4.1": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^5.5.0": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^5.5.1": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^5.6.0": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^5.7.0": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^5.7.1": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^6.0.0": + "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + "version" "6.3.0" + +"semver@^6.1.0": + "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + "version" "6.3.0" + +"semver@^6.2.0": + "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + "version" "6.3.0" + +"semver@^7.2.1", "semver@^7.3.2": + "integrity" "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz" + "version" "7.3.2" + +"semver@2 || 3 || 4 || 5": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@2.x || 3.x || 4 || 5": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"set-blocking@^2.0.0", "set-blocking@~2.0.0": + "integrity" "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "resolved" "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + "version" "2.0.0" + +"set-value@^2.0.0", "set-value@^2.0.1": + "integrity" "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==" + "resolved" "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "extend-shallow" "^2.0.1" + "is-extendable" "^0.1.1" + "is-plain-object" "^2.0.3" + "split-string" "^3.0.1" + +"shallow-clone@^3.0.0": + "integrity" "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==" + "resolved" "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "kind-of" "^6.0.2" + +"shebang-command@^1.2.0": + "integrity" "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=" + "resolved" "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" + "version" "1.2.0" + dependencies: + "shebang-regex" "^1.0.0" + +"shebang-command@^2.0.0": + "integrity" "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==" + "resolved" "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "shebang-regex" "^3.0.0" + +"shebang-regex@^1.0.0": + "integrity" "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "resolved" "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + "version" "1.0.0" + +"shebang-regex@^3.0.0": + "integrity" "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + "resolved" "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + "version" "3.0.0" + +"signal-exit@^3.0.0", "signal-exit@^3.0.2": + "integrity" "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz" + "version" "3.0.3" + +"slash@^2.0.0": + "integrity" "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "resolved" "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz" + "version" "2.0.0" + +"slash@^3.0.0": + "integrity" "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "resolved" "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + "version" "3.0.0" + +"slice-ansi@^2.1.0": + "integrity" "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==" + "resolved" "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "ansi-styles" "^3.2.0" + "astral-regex" "^1.0.0" + "is-fullwidth-code-point" "^2.0.0" + +"slide@^1.1.6": + "integrity" "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + "resolved" "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz" + "version" "1.1.6" + +"sm3@^1.0.3": + "integrity" "sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==" + "resolved" "https://registry.npmjs.org/sm3/-/sm3-1.0.3.tgz" + "version" "1.0.3" + +"smart-buffer@^4.1.0": + "integrity" "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" + "resolved" "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz" + "version" "4.1.0" + +"snapdragon-node@^2.0.1": + "integrity" "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==" + "resolved" "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "define-property" "^1.0.0" + "isobject" "^3.0.0" + "snapdragon-util" "^3.0.1" + +"snapdragon-util@^3.0.1": + "integrity" "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==" + "resolved" "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "kind-of" "^3.2.0" + +"snapdragon@^0.8.1": + "integrity" "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==" + "resolved" "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz" + "version" "0.8.2" + dependencies: + "base" "^0.11.1" + "debug" "^2.2.0" + "define-property" "^0.2.5" + "extend-shallow" "^2.0.1" + "map-cache" "^0.2.2" + "source-map" "^0.5.6" + "source-map-resolve" "^0.5.0" + "use" "^3.1.0" + +"socks-proxy-agent@^4.0.0": + "integrity" "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==" + "resolved" "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz" + "version" "4.0.2" + dependencies: + "agent-base" "~4.2.1" + "socks" "~2.3.2" + +"socks@~2.3.2": + "integrity" "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==" + "resolved" "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz" + "version" "2.3.3" + dependencies: + "ip" "1.1.5" + "smart-buffer" "^4.1.0" + +"sort-keys@^2.0.0": + "integrity" "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=" + "resolved" "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "is-plain-obj" "^1.0.0" + +"source-map-resolve@^0.5.0": + "integrity" "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==" + "resolved" "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" + "version" "0.5.3" + dependencies: + "atob" "^2.1.2" + "decode-uri-component" "^0.2.0" + "resolve-url" "^0.2.1" + "source-map-url" "^0.4.0" + "urix" "^0.1.0" + +"source-map-support@^0.5.17": + "integrity" "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==" + "resolved" "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz" + "version" "0.5.19" + dependencies: + "buffer-from" "^1.0.0" + "source-map" "^0.6.0" + +"source-map-url@^0.4.0": + "integrity" "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "resolved" "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz" + "version" "0.4.0" + +"source-map@^0.5.6": + "integrity" "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + "version" "0.5.7" + +"source-map@^0.6.0", "source-map@^0.6.1": + "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + "version" "0.6.1" + +"source-map@~0.2.0": + "integrity" "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz" + "version" "0.2.0" + dependencies: + "amdefine" ">=0.0.4" + +"spdx-correct@^3.0.0": + "integrity" "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==" + "resolved" "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" + "version" "3.1.1" + dependencies: + "spdx-expression-parse" "^3.0.0" + "spdx-license-ids" "^3.0.0" + +"spdx-exceptions@^2.1.0": + "integrity" "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "resolved" "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" + "version" "2.3.0" + +"spdx-expression-parse@^3.0.0": + "integrity" "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==" + "resolved" "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "spdx-exceptions" "^2.1.0" + "spdx-license-ids" "^3.0.0" + +"spdx-license-ids@^3.0.0": + "integrity" "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" + "resolved" "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz" + "version" "3.0.6" + +"split-string@^3.0.1", "split-string@^3.0.2": + "integrity" "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==" + "resolved" "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "extend-shallow" "^3.0.0" + +"split@^1.0.0": + "integrity" "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==" + "resolved" "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "through" "2" + +"split2@^2.0.0": + "integrity" "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==" + "resolved" "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz" + "version" "2.2.0" + dependencies: + "through2" "^2.0.2" + +"split2@^3.1.1": + "version" "3.2.2" + dependencies: + "readable-stream" "^3.0.0" + +"sprintf-js@~1.0.2": + "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + "version" "1.0.3" + +"sshpk@^1.7.0": + "integrity" "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==" + "resolved" "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz" + "version" "1.16.1" + dependencies: + "asn1" "~0.2.3" + "assert-plus" "^1.0.0" + "bcrypt-pbkdf" "^1.0.0" + "dashdash" "^1.12.0" + "ecc-jsbn" "~0.1.1" + "getpass" "^0.1.1" + "jsbn" "~0.1.0" + "safer-buffer" "^2.0.2" + "tweetnacl" "~0.14.0" + +"ssri@^6.0.0", "ssri@^6.0.1": + "integrity" "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==" + "resolved" "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz" + "version" "6.0.1" + dependencies: + "figgy-pudding" "^3.5.1" + +"static-extend@^0.1.1": + "integrity" "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=" + "resolved" "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz" + "version" "0.1.2" + dependencies: + "define-property" "^0.2.5" + "object-copy" "^0.1.0" + +"stream-each@^1.1.0": + "integrity" "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==" + "resolved" "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz" + "version" "1.2.3" + dependencies: + "end-of-stream" "^1.1.0" + "stream-shift" "^1.0.0" + +"stream-shift@^1.0.0": + "integrity" "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "resolved" "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz" + "version" "1.0.1" + +"stream-spec@~0.3.5": + "integrity" "sha1-L92sSge/Pp+JY8Z3prWmzCEVJV4=" + "resolved" "https://registry.npmjs.org/stream-spec/-/stream-spec-0.3.6.tgz" + "version" "0.3.6" + dependencies: + "macgyver" "~1.10" + +"stream-tester@0.0.5": + "integrity" "sha1-T4byUxFJra9t1LP/Ji7fZK6aFxo=" + "resolved" "https://registry.npmjs.org/stream-tester/-/stream-tester-0.0.5.tgz" + "version" "0.0.5" + dependencies: + "assertions" "~2.3.0" + "from" "~0.0.2" + "through" "~0.0.3" + +"string_decoder@^1.1.1": + "integrity" "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==" + "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + "version" "1.3.0" + dependencies: + "safe-buffer" "~5.2.0" + +"string_decoder@~1.1.1": + "integrity" "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==" + "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "safe-buffer" "~5.1.0" + +"string-width@^1.0.1": + "integrity" "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "code-point-at" "^1.0.0" + "is-fullwidth-code-point" "^1.0.0" + "strip-ansi" "^3.0.0" + +"string-width@^1.0.2 || 2", "string-width@^2.1.0": + "integrity" "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "is-fullwidth-code-point" "^2.0.0" + "strip-ansi" "^4.0.0" + +"string-width@^3.0.0": + "integrity" "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "emoji-regex" "^7.0.1" + "is-fullwidth-code-point" "^2.0.0" + "strip-ansi" "^5.1.0" + +"string-width@^3.1.0": + "integrity" "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "emoji-regex" "^7.0.1" + "is-fullwidth-code-point" "^2.0.0" + "strip-ansi" "^5.1.0" + +"string.prototype.trimend@^1.0.1": + "integrity" "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==" + "resolved" "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "define-properties" "^1.1.3" + "es-abstract" "^1.17.5" + +"string.prototype.trimstart@^1.0.1": + "integrity" "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==" + "resolved" "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "define-properties" "^1.1.3" + "es-abstract" "^1.17.5" + +"strip-ansi@^3.0.0", "strip-ansi@^3.0.1": + "integrity" "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "ansi-regex" "^2.0.0" + +"strip-ansi@^4.0.0": + "integrity" "sha1-qEeQIusaw2iocTibY1JixQXuNo8=" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "ansi-regex" "^3.0.0" + +"strip-ansi@^5.0.0", "strip-ansi@^5.1.0", "strip-ansi@^5.2.0": + "integrity" "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" + "version" "5.2.0" + dependencies: + "ansi-regex" "^4.1.0" + +"strip-ansi@^6.0.0": + "integrity" "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "ansi-regex" "^5.0.0" + +"strip-bom@^2.0.0": + "integrity" "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=" + "resolved" "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "is-utf8" "^0.2.0" + +"strip-bom@^3.0.0": + "integrity" "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "resolved" "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + "version" "3.0.0" + +"strip-eof@^1.0.0": + "integrity" "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "resolved" "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz" + "version" "1.0.0" + +"strip-indent@^1.0.1": + "integrity" "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=" + "resolved" "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "get-stdin" "^4.0.1" + +"strip-indent@^2.0.0": + "integrity" "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + "resolved" "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz" + "version" "2.0.0" + +"strip-indent@^3.0.0": + "integrity" "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==" + "resolved" "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "min-indent" "^1.0.0" + +"strip-json-comments@^3.1.0", "strip-json-comments@^3.1.1": + "integrity" "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + "version" "3.1.1" + +"strip-json-comments@2.0.1": + "integrity" "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + "version" "2.0.1" + +"strong-log-transformer@^2.0.0": + "integrity" "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==" + "resolved" "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "duplexer" "^0.1.1" + "minimist" "^1.2.0" + "through" "^2.3.4" + +"supports-color@^3.1.0": + "integrity" "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz" + "version" "3.2.3" + dependencies: + "has-flag" "^1.0.0" + +"supports-color@^5.3.0": + "integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + "version" "5.5.0" + dependencies: + "has-flag" "^3.0.0" + +"supports-color@^7.1.0": + "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "has-flag" "^4.0.0" + +"supports-color@6.0.0": + "integrity" "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "has-flag" "^3.0.0" + +"table@^5.2.3": + "integrity" "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==" + "resolved" "https://registry.npmjs.org/table/-/table-5.4.6.tgz" + "version" "5.4.6" + dependencies: + "ajv" "^6.10.2" + "lodash" "^4.17.14" + "slice-ansi" "^2.1.0" + "string-width" "^3.0.0" + +"tar@^4.4.10", "tar@^4.4.12", "tar@^4.4.8": + "integrity" "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==" + "resolved" "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz" + "version" "4.4.13" + dependencies: + "chownr" "^1.1.1" + "fs-minipass" "^1.2.5" + "minipass" "^2.8.6" + "minizlib" "^1.2.1" + "mkdirp" "^0.5.0" + "safe-buffer" "^5.1.2" + "yallist" "^3.0.3" + +"temp-dir@^1.0.0": + "integrity" "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" + "resolved" "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" + "version" "1.0.0" + +"temp-write@^3.4.0": + "integrity" "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=" + "resolved" "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz" + "version" "3.4.0" + dependencies: + "graceful-fs" "^4.1.2" + "is-stream" "^1.1.0" + "make-dir" "^1.0.0" + "pify" "^3.0.0" + "temp-dir" "^1.0.0" + "uuid" "^3.0.1" + +"text-extensions@^1.0.0": + "integrity" "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==" + "resolved" "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz" + "version" "1.9.0" + +"text-table@^0.2.0": + "integrity" "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + "resolved" "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + "version" "0.2.0" + +"thenify-all@^1.0.0": + "integrity" "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=" + "resolved" "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + "version" "1.6.0" + dependencies: + "thenify" ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + "integrity" "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==" + "resolved" "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + "version" "3.3.1" + dependencies: + "any-promise" "^1.0.0" + +"through@^2.3.4", "through@^2.3.6", "through@>=2.2.7 <3", "through@2": + "integrity" "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "resolved" "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + "version" "2.3.8" + +"through@~0.0.3": + "integrity" "sha1-C/Lw//r6rEusvFM2Z+mKrQC1iMg=" + "resolved" "https://registry.npmjs.org/through/-/through-0.0.4.tgz" + "version" "0.0.4" + +"through2@^2.0.0": + "integrity" "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==" + "resolved" "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + "version" "2.0.5" + dependencies: + "readable-stream" "~2.3.6" + "xtend" "~4.0.1" + +"through2@^2.0.2": + "integrity" "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==" + "resolved" "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + "version" "2.0.5" + dependencies: + "readable-stream" "~2.3.6" + "xtend" "~4.0.1" + +"through2@^3.0.0": + "integrity" "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==" + "resolved" "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "inherits" "^2.0.4" + "readable-stream" "2 || 3" + +"tmp@^0.0.33": + "integrity" "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==" + "resolved" "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" + "version" "0.0.33" + dependencies: + "os-tmpdir" "~1.0.2" + +"to-object-path@^0.3.0": + "integrity" "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=" + "resolved" "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz" + "version" "0.3.0" + dependencies: + "kind-of" "^3.0.2" + +"to-regex-range@^2.1.0": + "integrity" "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=" + "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "is-number" "^3.0.0" + "repeat-string" "^1.6.1" + +"to-regex-range@^5.0.1": + "integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" + "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "is-number" "^7.0.0" + +"to-regex@^3.0.1", "to-regex@^3.0.2": + "integrity" "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==" + "resolved" "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "define-property" "^2.0.2" + "extend-shallow" "^3.0.2" + "regex-not" "^1.0.2" + "safe-regex" "^1.1.0" + +"to-utf8@0.0.1": + "integrity" "sha1-0Xrqcv8vujm55DYBvns/9y4ImFI=" + "resolved" "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz" + "version" "0.0.1" + +"tough-cookie@~2.5.0": + "integrity" "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==" + "resolved" "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" + "version" "2.5.0" + dependencies: + "psl" "^1.1.28" + "punycode" "^2.1.1" + +"tr46@^1.0.1": + "integrity" "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=" + "resolved" "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "punycode" "^2.1.0" + +"traverser@0.0.x": + "integrity" "sha1-xm84xFagwhqIAUsSI1gMfr4GMes=" + "resolved" "https://registry.npmjs.org/traverser/-/traverser-0.0.5.tgz" + "version" "0.0.5" + dependencies: + "curry" "0.0.x" + +"traverser@1": + "integrity" "sha1-b1nlgTdZruqzZGuPRRP9SmLk/iA=" + "resolved" "https://registry.npmjs.org/traverser/-/traverser-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "curry" "0.0.x" + +"trim-newlines@^1.0.0": + "integrity" "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + "resolved" "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" + "version" "1.0.0" + +"trim-newlines@^2.0.0": + "integrity" "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + "resolved" "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz" + "version" "2.0.0" + +"trim-newlines@^3.0.0": + "integrity" "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==" + "resolved" "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz" + "version" "3.0.0" + +"trim-off-newlines@^1.0.0": + "integrity" "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=" + "resolved" "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz" + "version" "1.0.1" + +"ts-node@^8.5.4": + "integrity" "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==" + "resolved" "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz" + "version" "8.10.2" + dependencies: + "arg" "^4.1.0" + "diff" "^4.0.1" + "make-error" "^1.1.1" + "source-map-support" "^0.5.17" + "yn" "3.1.1" + +"tslib@^1.8.1", "tslib@^1.9.0": + "integrity" "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + "version" "1.14.1" + +"tsutils@^3.17.1": + "integrity" "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==" + "resolved" "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz" + "version" "3.17.1" + dependencies: + "tslib" "^1.8.1" + +"tunnel-agent@^0.6.0": + "integrity" "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" + "resolved" "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + "version" "0.6.0" + dependencies: + "safe-buffer" "^5.0.1" + +"tweetnacl@^0.14.3", "tweetnacl@~0.14.0": + "integrity" "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "resolved" "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + "version" "0.14.5" + +"type-check@^0.4.0", "type-check@~0.4.0": + "integrity" "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==" + "resolved" "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + "version" "0.4.0" + dependencies: + "prelude-ls" "^1.2.1" + +"type-check@~0.3.2": + "integrity" "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=" + "resolved" "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" + "version" "0.3.2" + dependencies: + "prelude-ls" "~1.1.2" + +"type-detect@^4.0.0", "type-detect@^4.0.5": + "integrity" "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + "resolved" "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + "version" "4.0.8" + +"type-fest@^0.13.1": + "integrity" "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz" + "version" "0.13.1" + +"type-fest@^0.3.0": + "integrity" "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz" + "version" "0.3.1" + +"type-fest@^0.6.0": + "integrity" "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" + "version" "0.6.0" + +"type-fest@^0.8.1": + "integrity" "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" + "version" "0.8.1" + +"typedarray@^0.0.6": + "integrity" "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "resolved" "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + "version" "0.0.6" + +"typescript@^4.0.3", "typescript@^4.3.5", "typescript@>=2.7", "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta": + "integrity" "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==" + "resolved" "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz" + "version" "4.3.5" + +"uglify-js@^3.1.4": + "integrity" "sha512-OApPSuJcxcnewwjSGGfWOjx3oix5XpmrK9Z2j0fTRlHGoZ49IU6kExfZTM0++fCArOOCet+vIfWwFHbvWqwp6g==" + "resolved" "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.1.tgz" + "version" "3.11.1" + +"uid-number@0.0.6": + "integrity" "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + "resolved" "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" + "version" "0.0.6" + +"umask@^1.1.0": + "integrity" "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=" + "resolved" "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz" + "version" "1.1.0" + +"union-value@^1.0.0": + "integrity" "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==" + "resolved" "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "arr-union" "^3.1.0" + "get-value" "^2.0.6" + "is-extendable" "^0.1.1" + "set-value" "^2.0.1" + +"unique-filename@^1.1.1": + "integrity" "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==" + "resolved" "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "unique-slug" "^2.0.0" + +"unique-slug@^2.0.0": + "integrity" "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==" + "resolved" "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "imurmurhash" "^0.1.4" + +"universal-user-agent@^4.0.0": + "integrity" "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==" + "resolved" "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz" + "version" "4.0.1" + dependencies: + "os-name" "^3.1.0" + +"universal-user-agent@^6.0.0": + "integrity" "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + "resolved" "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz" + "version" "6.0.0" + +"universalify@^0.1.0": + "integrity" "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "resolved" "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" + "version" "0.1.2" + +"unset-value@^1.0.0": + "integrity" "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=" + "resolved" "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "has-value" "^0.3.1" + "isobject" "^3.0.0" + +"upath@^1.2.0": + "integrity" "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + "resolved" "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz" + "version" "1.2.0" + +"uri-js@^4.2.2": + "integrity" "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==" + "resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz" + "version" "4.4.0" + dependencies: + "punycode" "^2.1.0" + +"urix@^0.1.0": + "integrity" "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "resolved" "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz" + "version" "0.1.0" + +"use@^3.1.0": + "integrity" "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + "resolved" "https://registry.npmjs.org/use/-/use-3.1.1.tgz" + "version" "3.1.1" + +"util-deprecate@^1.0.1", "util-deprecate@~1.0.1": + "integrity" "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + "version" "1.0.2" + +"util-promisify@^2.1.0": + "integrity" "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=" + "resolved" "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "object.getownpropertydescriptors" "^2.0.3" + +"uuid@^3.0.1", "uuid@^3.3.2": + "integrity" "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "resolved" "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" + "version" "3.4.0" + +"v8-compile-cache@^2.0.3": + "integrity" "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==" + "resolved" "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz" + "version" "2.1.1" + +"validate-npm-package-license@^3.0.1", "validate-npm-package-license@^3.0.3": + "integrity" "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==" + "resolved" "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" + "version" "3.0.4" + dependencies: + "spdx-correct" "^3.0.0" + "spdx-expression-parse" "^3.0.0" + +"validate-npm-package-name@^3.0.0": + "integrity" "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=" + "resolved" "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "builtins" "^1.0.3" + +"verror@1.10.0": + "integrity" "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=" + "resolved" "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" + "version" "1.10.0" + dependencies: + "assert-plus" "^1.0.0" + "core-util-is" "1.0.2" + "extsprintf" "^1.2.0" + +"wcwidth@^1.0.0": + "integrity" "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=" + "resolved" "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "defaults" "^1.0.3" + +"webidl-conversions@^4.0.2": + "integrity" "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" + "version" "4.0.2" + +"whatwg-url@^7.0.0": + "integrity" "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==" + "resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz" + "version" "7.1.0" + dependencies: + "lodash.sortby" "^4.7.0" + "tr46" "^1.0.1" + "webidl-conversions" "^4.0.2" + +"which-module@^2.0.0": + "integrity" "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "resolved" "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" + "version" "2.0.0" + +"which@^1.1.1": + "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" + "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + "version" "1.3.1" + dependencies: + "isexe" "^2.0.0" + +"which@^1.2.9": + "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" + "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + "version" "1.3.1" + dependencies: + "isexe" "^2.0.0" + +"which@^1.3.1": + "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" + "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + "version" "1.3.1" + dependencies: + "isexe" "^2.0.0" + +"which@^2.0.1": + "integrity" "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==" + "resolved" "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "isexe" "^2.0.0" + +"which@1.3.1": + "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" + "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + "version" "1.3.1" + dependencies: + "isexe" "^2.0.0" + +"wide-align@^1.1.0", "wide-align@1.1.3": + "integrity" "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==" + "resolved" "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" + "version" "1.1.3" + dependencies: + "string-width" "^1.0.2 || 2" + +"windows-release@^3.1.0": + "integrity" "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==" + "resolved" "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz" + "version" "3.3.3" + dependencies: + "execa" "^1.0.0" + +"word-wrap@^1.2.3", "word-wrap@~1.2.3": + "integrity" "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "resolved" "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + "version" "1.2.3" + +"wordwrap@^1.0.0": + "integrity" "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "resolved" "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + "version" "1.0.0" + +"wrap-ansi@^5.1.0": + "integrity" "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==" + "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz" + "version" "5.1.0" + dependencies: + "ansi-styles" "^3.2.0" + "string-width" "^3.0.0" + "strip-ansi" "^5.0.0" + +"wrappy@1": + "integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + "version" "1.0.2" + +"write-file-atomic@^2.0.0", "write-file-atomic@^2.3.0", "write-file-atomic@^2.4.2": + "integrity" "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==" + "resolved" "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz" + "version" "2.4.3" + dependencies: + "graceful-fs" "^4.1.11" + "imurmurhash" "^0.1.4" + "signal-exit" "^3.0.2" + +"write-json-file@^2.2.0": + "integrity" "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=" + "resolved" "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz" + "version" "2.3.0" + dependencies: + "detect-indent" "^5.0.0" + "graceful-fs" "^4.1.2" + "make-dir" "^1.0.0" + "pify" "^3.0.0" + "sort-keys" "^2.0.0" + "write-file-atomic" "^2.0.0" + +"write-json-file@^3.2.0": + "integrity" "sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==" + "resolved" "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz" + "version" "3.2.0" + dependencies: + "detect-indent" "^5.0.0" + "graceful-fs" "^4.1.15" + "make-dir" "^2.1.0" + "pify" "^4.0.1" + "sort-keys" "^2.0.0" + "write-file-atomic" "^2.4.2" + +"write-pkg@^3.1.0": + "integrity" "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==" + "resolved" "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz" + "version" "3.2.0" + dependencies: + "sort-keys" "^2.0.0" + "write-json-file" "^2.2.0" + +"write@1.0.3": + "integrity" "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==" + "resolved" "https://registry.npmjs.org/write/-/write-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "mkdirp" "^0.5.1" + +"xtend@^4.0.0": + "integrity" "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "resolved" "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + "version" "4.0.2" + +"xtend@~4.0.1": + "integrity" "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "resolved" "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + "version" "4.0.2" + +"y18n@^4.0.0": + "integrity" "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "resolved" "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz" + "version" "4.0.0" + +"yallist@^3.0.0", "yallist@^3.0.2", "yallist@^3.0.3": + "integrity" "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "resolved" "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + "version" "3.1.1" + +"yargs-parser@^13.1.2", "yargs-parser@13.1.2": + "integrity" "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==" + "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz" + "version" "13.1.2" + dependencies: + "camelcase" "^5.0.0" + "decamelize" "^1.2.0" + +"yargs-parser@^15.0.1": + "integrity" "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==" + "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz" + "version" "15.0.1" + dependencies: + "camelcase" "^5.0.0" + "decamelize" "^1.2.0" + +"yargs-parser@^18.1.3": + "integrity" "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==" + "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" + "version" "18.1.3" + dependencies: + "camelcase" "^5.0.0" + "decamelize" "^1.2.0" + +"yargs-unparser@1.6.0": + "integrity" "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==" + "resolved" "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz" + "version" "1.6.0" + dependencies: + "flat" "^4.1.0" + "lodash" "^4.17.15" + "yargs" "^13.3.0" + +"yargs@^13.3.0": + "integrity" "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==" + "resolved" "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz" + "version" "13.3.2" + dependencies: + "cliui" "^5.0.0" + "find-up" "^3.0.0" + "get-caller-file" "^2.0.1" + "require-directory" "^2.1.1" + "require-main-filename" "^2.0.0" + "set-blocking" "^2.0.0" + "string-width" "^3.0.0" + "which-module" "^2.0.0" + "y18n" "^4.0.0" + "yargs-parser" "^13.1.2" + +"yargs@^14.2.2": + "integrity" "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==" + "resolved" "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz" + "version" "14.2.3" + dependencies: + "cliui" "^5.0.0" + "decamelize" "^1.2.0" + "find-up" "^3.0.0" + "get-caller-file" "^2.0.1" + "require-directory" "^2.1.1" + "require-main-filename" "^2.0.0" + "set-blocking" "^2.0.0" + "string-width" "^3.0.0" + "which-module" "^2.0.0" + "y18n" "^4.0.0" + "yargs-parser" "^15.0.1" + +"yargs@13.3.2": + "integrity" "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==" + "resolved" "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz" + "version" "13.3.2" + dependencies: + "cliui" "^5.0.0" + "find-up" "^3.0.0" + "get-caller-file" "^2.0.1" + "require-directory" "^2.1.1" + "require-main-filename" "^2.0.0" + "set-blocking" "^2.0.0" + "string-width" "^3.0.0" + "which-module" "^2.0.0" + "y18n" "^4.0.0" + "yargs-parser" "^13.1.2" + +"yn@3.1.1": + "integrity" "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "resolved" "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + "version" "3.1.1" -- Gitee